From f5545cd3f45016c271f6b6c43e06a61d21091823 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 14 Jan 2024 17:51:26 -0600 Subject: [PATCH] Add:Scanner extracts cover from comic files #1837 and ComicInfo.xml parser --- server/libs/libarchive/LICENSE | 21 ++ server/libs/libarchive/archive.js | 262 ++++++++++++++++++ server/libs/libarchive/libarchiveWorker.js | 72 +++++ server/libs/libarchive/wasm-libarchive.js | 18 ++ server/libs/libarchive/wasm-module.js | 235 ++++++++++++++++ server/scanner/BookScanner.js | 2 +- .../utils/parsers/parseComicInfoMetadata.js | 35 +++ server/utils/parsers/parseComicMetadata.js | 109 ++++++++ server/utils/parsers/parseEbookMetadata.js | 7 +- server/utils/parsers/parseEpubMetadata.js | 5 +- 10 files changed, 762 insertions(+), 4 deletions(-) create mode 100644 server/libs/libarchive/LICENSE create mode 100644 server/libs/libarchive/archive.js create mode 100644 server/libs/libarchive/libarchiveWorker.js create mode 100644 server/libs/libarchive/wasm-libarchive.js create mode 100644 server/libs/libarchive/wasm-module.js create mode 100644 server/utils/parsers/parseComicInfoMetadata.js create mode 100644 server/utils/parsers/parseComicMetadata.js diff --git a/server/libs/libarchive/LICENSE b/server/libs/libarchive/LICENSE new file mode 100644 index 00000000..e9ec54b0 --- /dev/null +++ b/server/libs/libarchive/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 ნიკა + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/server/libs/libarchive/archive.js b/server/libs/libarchive/archive.js new file mode 100644 index 00000000..1f6b962b --- /dev/null +++ b/server/libs/libarchive/archive.js @@ -0,0 +1,262 @@ +/** + * Modified from https://github.com/nika-begiashvili/libarchivejs + */ + +const Path = require('path') +const { Worker } = require('worker_threads') + +/** + * Represents compressed file before extraction + */ +class CompressedFile { + + constructor(name, size, path, archiveRef) { + this._name = name + this._size = size + this._path = path + this._archiveRef = archiveRef + } + + /** + * file name + */ + get name() { + return this._name + } + /** + * file size + */ + get size() { + return this._size + } + + /** + * Extract file from archive + * @returns {Promise} extracted file + */ + extract() { + return this._archiveRef.extractSingleFile(this._path) + } + +} + +class Archive { + /** + * Creates new archive instance from browser native File object + * @param {Buffer} fileBuffer + * @param {object} options + * @returns {Archive} + */ + static open(fileBuffer) { + const arch = new Archive(fileBuffer, { workerUrl: Path.join(__dirname, 'libarchiveWorker.js') }) + return arch.open() + } + + /** + * Create new archive + * @param {File} file + * @param {Object} options + */ + constructor(file, options) { + this._worker = new Worker(options.workerUrl) + this._worker.on('message', this._workerMsg.bind(this)) + + this._callbacks = [] + this._content = {} + this._processed = 0 + this._file = file + } + + /** + * Prepares file for reading + * @returns {Promise} archive instance + */ + async open() { + await this._postMessage({ type: 'HELLO' }, (resolve, reject, msg) => { + if (msg.type === 'READY') { + resolve() + } + }) + return await this._postMessage({ type: 'OPEN', file: this._file }, (resolve, reject, msg) => { + if (msg.type === 'OPENED') { + resolve(this) + } + }) + } + + /** + * Terminate worker to free up memory + */ + close() { + this._worker.terminate() + this._worker = null + } + + /** + * detect if archive has encrypted data + * @returns {boolean|null} null if could not be determined + */ + hasEncryptedData() { + return this._postMessage({ type: 'CHECK_ENCRYPTION' }, + (resolve, reject, msg) => { + if (msg.type === 'ENCRYPTION_STATUS') { + resolve(msg.status) + } + } + ) + } + + /** + * set password to be used when reading archive + */ + usePassword(archivePassword) { + return this._postMessage({ type: 'SET_PASSPHRASE', passphrase: archivePassword }, + (resolve, reject, msg) => { + if (msg.type === 'PASSPHRASE_STATUS') { + resolve(msg.status) + } + } + ) + } + + /** + * Returns object containing directory structure and file information + * @returns {Promise} + */ + getFilesObject() { + if (this._processed > 0) { + return Promise.resolve().then(() => this._content) + } + return this._postMessage({ type: 'LIST_FILES' }, (resolve, reject, msg) => { + if (msg.type === 'ENTRY') { + const entry = msg.entry + const [target, prop] = this._getProp(this._content, entry.path) + if (entry.type === 'FILE') { + target[prop] = new CompressedFile(entry.fileName, entry.size, entry.path, this) + } + return true + } else if (msg.type === 'END') { + this._processed = 1 + resolve(this._cloneContent(this._content)) + } + }) + } + + getFilesArray() { + return this.getFilesObject().then((obj) => { + return this._objectToArray(obj) + }) + } + + extractSingleFile(target) { + // Prevent extraction if worker already terminated + if (this._worker === null) { + throw new Error("Archive already closed") + } + + return this._postMessage({ type: 'EXTRACT_SINGLE_FILE', target: target }, + (resolve, reject, msg) => { + if (msg.type === 'FILE') { + resolve(msg.entry) + } + } + ) + } + + /** + * Returns object containing directory structure and extracted File objects + * @param {Function} extractCallback + * + */ + extractFiles(extractCallback) { + if (this._processed > 1) { + return Promise.resolve().then(() => this._content) + } + return this._postMessage({ type: 'EXTRACT_FILES' }, (resolve, reject, msg) => { + if (msg.type === 'ENTRY') { + const [target, prop] = this._getProp(this._content, msg.entry.path) + if (msg.entry.type === 'FILE') { + target[prop] = msg.entry + if (extractCallback !== undefined) { + setTimeout(extractCallback.bind(null, { + file: target[prop], + path: msg.entry.path, + })) + } + } + return true + } else if (msg.type === 'END') { + this._processed = 2 + this._worker.terminate() + resolve(this._cloneContent(this._content)) + } + }) + } + + _cloneContent(obj) { + if (obj instanceof CompressedFile || obj === null) return obj + const o = {} + for (const prop of Object.keys(obj)) { + o[prop] = this._cloneContent(obj[prop]) + } + return o + } + + _objectToArray(obj, path = '') { + const files = [] + for (const key of Object.keys(obj)) { + if (obj[key] instanceof CompressedFile || obj[key] === null) { + files.push({ + file: obj[key] || key, + path: path + }) + } else { + files.push(...this._objectToArray(obj[key], `${path}${key}/`)) + } + } + return files + } + + _getProp(obj, path) { + const parts = path.split('/') + if (parts[parts.length - 1] === '') parts.pop() + let cur = obj, prev = null + for (const part of parts) { + cur[part] = cur[part] || {} + prev = cur + cur = cur[part] + } + return [prev, parts[parts.length - 1]] + } + + _postMessage(msg, callback) { + this._worker.postMessage(msg) + return new Promise((resolve, reject) => { + this._callbacks.push(this._msgHandler.bind(this, callback, resolve, reject)) + }) + } + + _msgHandler(callback, resolve, reject, msg) { + if (!msg) { + reject('invalid msg') + return + } + if (msg.type === 'BUSY') { + reject('worker is busy') + } else if (msg.type === 'ERROR') { + reject(msg.error) + } else { + return callback(resolve, reject, msg) + } + } + + _workerMsg(msg) { + const callback = this._callbacks[this._callbacks.length - 1] + const next = callback(msg) + if (!next) { + this._callbacks.pop() + } + } + +} +module.exports = Archive \ No newline at end of file diff --git a/server/libs/libarchive/libarchiveWorker.js b/server/libs/libarchive/libarchiveWorker.js new file mode 100644 index 00000000..e768a8c2 --- /dev/null +++ b/server/libs/libarchive/libarchiveWorker.js @@ -0,0 +1,72 @@ +/** + * Modified from https://github.com/nika-begiashvili/libarchivejs + */ + +const { parentPort } = require('worker_threads') +const { getArchiveReader } = require('./wasm-module') + +let reader = null +let busy = false + +getArchiveReader((_reader) => { + reader = _reader + busy = false + parentPort.postMessage({ type: 'READY' }) +}) + +parentPort.on('message', async msg => { + if (busy) { + parentPort.postMessage({ type: 'BUSY' }) + return + } + + let skipExtraction = false + busy = true + try { + switch (msg.type) { + case 'HELLO': // module will respond READY when it's ready + break + case 'OPEN': + await reader.open(msg.file) + parentPort.postMessage({ type: 'OPENED' }) + break + case 'LIST_FILES': + skipExtraction = true + // eslint-disable-next-line no-fallthrough + case 'EXTRACT_FILES': + for (const entry of reader.entries(skipExtraction)) { + parentPort.postMessage({ type: 'ENTRY', entry }) + } + parentPort.postMessage({ type: 'END' }) + break + case 'EXTRACT_SINGLE_FILE': + for (const entry of reader.entries(true, msg.target)) { + if (entry.fileData) { + parentPort.postMessage({ type: 'FILE', entry }) + } + } + break + case 'CHECK_ENCRYPTION': + parentPort.postMessage({ type: 'ENCRYPTION_STATUS', status: reader.hasEncryptedData() }) + break + case 'SET_PASSPHRASE': + reader.setPassphrase(msg.passphrase) + parentPort.postMessage({ type: 'PASSPHRASE_STATUS', status: true }) + break + default: + throw new Error('Invalid Command') + } + } catch (err) { + parentPort.postMessage({ + type: 'ERROR', + error: { + message: err.message, + name: err.name, + stack: err.stack + } + }) + } finally { + // eslint-disable-next-line require-atomic-updates + busy = false + } +}) diff --git a/server/libs/libarchive/wasm-libarchive.js b/server/libs/libarchive/wasm-libarchive.js new file mode 100644 index 00000000..6e40d6c9 --- /dev/null +++ b/server/libs/libarchive/wasm-libarchive.js @@ -0,0 +1,18 @@ +/** + * Modified from https://github.com/nika-begiashvili/libarchivejs + */ + +var libarchive = (function () { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined + return ( + function (libarchive) { + libarchive = libarchive || {} + + var Module = typeof libarchive !== "undefined" ? libarchive : {}; var moduleOverrides = {}; var key; for (key in Module) { if (Module.hasOwnProperty(key)) { moduleOverrides[key] = Module[key] } } Module["arguments"] = []; Module["thisProgram"] = "./this.program"; Module["quit"] = function (status, toThrow) { throw toThrow }; Module["preRun"] = []; Module["postRun"] = []; var ENVIRONMENT_IS_WEB = false; var ENVIRONMENT_IS_WORKER = false; var ENVIRONMENT_IS_NODE = false; var ENVIRONMENT_IS_SHELL = false; ENVIRONMENT_IS_WEB = typeof window === "object"; ENVIRONMENT_IS_WORKER = typeof importScripts === "function"; ENVIRONMENT_IS_NODE = typeof process === "object" && typeof require === "function" && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER; ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; var scriptDirectory = ""; function locateFile(path) { if (Module["locateFile"]) { return Module["locateFile"](path, scriptDirectory) } else { return scriptDirectory + path } } if (ENVIRONMENT_IS_NODE) { scriptDirectory = __dirname + "/"; var nodeFS; var nodePath; Module["read"] = function shell_read(filename, binary) { var ret; if (!nodeFS) nodeFS = require("fs"); if (!nodePath) nodePath = require("path"); filename = nodePath["normalize"](filename); ret = nodeFS["readFileSync"](filename); return binary ? ret : ret.toString() }; Module["readBinary"] = function readBinary(filename) { var ret = Module["read"](filename, true); if (!ret.buffer) { ret = new Uint8Array(ret) } assert(ret.buffer); return ret }; if (process["argv"].length > 1) { Module["thisProgram"] = process["argv"][1].replace(/\\/g, "/") } Module["arguments"] = process["argv"].slice(2); process["on"]("uncaughtException", function (ex) { if (!(ex instanceof ExitStatus)) { throw ex } }); process["on"]("unhandledRejection", abort); Module["quit"] = function (status) { process["exit"](status) }; Module["inspect"] = function () { return "[Emscripten Module object]" } } else if (ENVIRONMENT_IS_SHELL) { if (typeof read != "undefined") { Module["read"] = function shell_read(f) { return read(f) } } Module["readBinary"] = function readBinary(f) { var data; if (typeof readbuffer === "function") { return new Uint8Array(readbuffer(f)) } data = read(f, "binary"); assert(typeof data === "object"); return data }; if (typeof scriptArgs != "undefined") { Module["arguments"] = scriptArgs } else if (typeof arguments != "undefined") { Module["arguments"] = arguments } if (typeof quit === "function") { Module["quit"] = function (status) { quit(status) } } } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { if (ENVIRONMENT_IS_WORKER) { scriptDirectory = self.location.href } else if (document.currentScript) { scriptDirectory = document.currentScript.src } if (_scriptDir) { scriptDirectory = _scriptDir } if (scriptDirectory.indexOf("blob:") !== 0) { scriptDirectory = scriptDirectory.substr(0, scriptDirectory.lastIndexOf("/") + 1) } else { scriptDirectory = "" } Module["read"] = function shell_read(url) { var xhr = new XMLHttpRequest; xhr.open("GET", url, false); xhr.send(null); return xhr.responseText }; if (ENVIRONMENT_IS_WORKER) { Module["readBinary"] = function readBinary(url) { var xhr = new XMLHttpRequest; xhr.open("GET", url, false); xhr.responseType = "arraybuffer"; xhr.send(null); return new Uint8Array(xhr.response) } } Module["readAsync"] = function readAsync(url, onload, onerror) { var xhr = new XMLHttpRequest; xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; xhr.onload = function xhr_onload() { if (xhr.status == 200 || xhr.status == 0 && xhr.response) { onload(xhr.response); return } onerror() }; xhr.onerror = onerror; xhr.send(null) }; Module["setWindowTitle"] = function (title) { document.title = title } } else { } var out = Module["print"] || (typeof console !== "undefined" ? console.log.bind(console) : typeof print !== "undefined" ? print : null); var err = Module["printErr"] || (typeof printErr !== "undefined" ? printErr : typeof console !== "undefined" && console.warn.bind(console) || out); for (key in moduleOverrides) { if (moduleOverrides.hasOwnProperty(key)) { Module[key] = moduleOverrides[key] } } moduleOverrides = undefined; function dynamicAlloc(size) { var ret = HEAP32[DYNAMICTOP_PTR >> 2]; var end = ret + size + 15 & -16; if (end <= _emscripten_get_heap_size()) { HEAP32[DYNAMICTOP_PTR >> 2] = end } else { var success = _emscripten_resize_heap(end); if (!success) return 0 } return ret } function getNativeTypeSize(type) { switch (type) { case "i1": case "i8": return 1; case "i16": return 2; case "i32": return 4; case "i64": return 8; case "float": return 4; case "double": return 8; default: { if (type[type.length - 1] === "*") { return 4 } else if (type[0] === "i") { var bits = parseInt(type.substr(1)); assert(bits % 8 === 0, "getNativeTypeSize invalid bits " + bits + ", type " + type); return bits / 8 } else { return 0 } } } } var asm2wasmImports = { "f64-rem": function (x, y) { return x % y }, "debugger": function () { debugger } }; var functionPointers = new Array(0); var tempRet0 = 0; var setTempRet0 = function (value) { tempRet0 = value }; if (typeof WebAssembly !== "object") { err("no native wasm support detected") } var wasmMemory; var wasmTable; var ABORT = false; var EXITSTATUS = 0; function assert(condition, text) { if (!condition) { abort("Assertion failed: " + text) } } function getCFunc(ident) { var func = Module["_" + ident]; assert(func, "Cannot call unknown function " + ident + ", make sure it is exported"); return func } function ccall(ident, returnType, argTypes, args, opts) { var toC = { "string": function (str) { var ret = 0; if (str !== null && str !== undefined && str !== 0) { var len = (str.length << 2) + 1; ret = stackAlloc(len); stringToUTF8(str, ret, len) } return ret }, "array": function (arr) { var ret = stackAlloc(arr.length); writeArrayToMemory(arr, ret); return ret } }; function convertReturnValue(ret) { if (returnType === "string") return UTF8ToString(ret); if (returnType === "boolean") return Boolean(ret); return ret } var func = getCFunc(ident); var cArgs = []; var stack = 0; if (args) { for (var i = 0; i < args.length; i++) { var converter = toC[argTypes[i]]; if (converter) { if (stack === 0) stack = stackSave(); cArgs[i] = converter(args[i]) } else { cArgs[i] = args[i] } } } var ret = func.apply(null, cArgs); ret = convertReturnValue(ret); if (stack !== 0) stackRestore(stack); return ret } function cwrap(ident, returnType, argTypes, opts) { argTypes = argTypes || []; var numericArgs = argTypes.every(function (type) { return type === "number" }); var numericRet = returnType !== "string"; if (numericRet && numericArgs && !opts) { return getCFunc(ident) } return function () { return ccall(ident, returnType, argTypes, arguments, opts) } } function setValue(ptr, value, type, noSafe) { type = type || "i8"; if (type.charAt(type.length - 1) === "*") type = "i32"; switch (type) { case "i1": HEAP8[ptr >> 0] = value; break; case "i8": HEAP8[ptr >> 0] = value; break; case "i16": HEAP16[ptr >> 1] = value; break; case "i32": HEAP32[ptr >> 2] = value; break; case "i64": tempI64 = [value >>> 0, (tempDouble = value, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1]; break; case "float": HEAPF32[ptr >> 2] = value; break; case "double": HEAPF64[ptr >> 3] = value; break; default: abort("invalid type for setValue: " + type) } } var ALLOC_NORMAL = 0; var ALLOC_NONE = 3; function allocate(slab, types, allocator, ptr) { var zeroinit, size; if (typeof slab === "number") { zeroinit = true; size = slab } else { zeroinit = false; size = slab.length } var singleType = typeof types === "string" ? types : null; var ret; if (allocator == ALLOC_NONE) { ret = ptr } else { ret = [_malloc, stackAlloc, dynamicAlloc][allocator](Math.max(size, singleType ? 1 : types.length)) } if (zeroinit) { var stop; ptr = ret; assert((ret & 3) == 0); stop = ret + (size & ~3); for (; ptr < stop; ptr += 4) { HEAP32[ptr >> 2] = 0 } stop = ret + size; while (ptr < stop) { HEAP8[ptr++ >> 0] = 0 } return ret } if (singleType === "i8") { if (slab.subarray || slab.slice) { HEAPU8.set(slab, ret) } else { HEAPU8.set(new Uint8Array(slab), ret) } return ret } var i = 0, type, typeSize, previousType; while (i < size) { var curr = slab[i]; type = singleType || types[i]; if (type === 0) { i++; continue } if (type == "i64") type = "i32"; setValue(ret + i, curr, type); if (previousType !== type) { typeSize = getNativeTypeSize(type); previousType = type } i += typeSize } return ret } function getMemory(size) { if (!runtimeInitialized) return dynamicAlloc(size); return _malloc(size) } var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined; function UTF8ArrayToString(u8Array, idx, maxBytesToRead) { var endIdx = idx + maxBytesToRead; var endPtr = idx; while (u8Array[endPtr] && !(endPtr >= endIdx)) ++endPtr; if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) { return UTF8Decoder.decode(u8Array.subarray(idx, endPtr)) } else { var str = ""; while (idx < endPtr) { var u0 = u8Array[idx++]; if (!(u0 & 128)) { str += String.fromCharCode(u0); continue } var u1 = u8Array[idx++] & 63; if ((u0 & 224) == 192) { str += String.fromCharCode((u0 & 31) << 6 | u1); continue } var u2 = u8Array[idx++] & 63; if ((u0 & 240) == 224) { u0 = (u0 & 15) << 12 | u1 << 6 | u2 } else { u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | u8Array[idx++] & 63 } if (u0 < 65536) { str += String.fromCharCode(u0) } else { var ch = u0 - 65536; str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023) } } } return str } function UTF8ToString(ptr, maxBytesToRead) { return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "" } function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) { if (!(maxBytesToWrite > 0)) return 0; var startIdx = outIdx; var endIdx = outIdx + maxBytesToWrite - 1; for (var i = 0; i < str.length; ++i) { var u = str.charCodeAt(i); if (u >= 55296 && u <= 57343) { var u1 = str.charCodeAt(++i); u = 65536 + ((u & 1023) << 10) | u1 & 1023 } if (u <= 127) { if (outIdx >= endIdx) break; outU8Array[outIdx++] = u } else if (u <= 2047) { if (outIdx + 1 >= endIdx) break; outU8Array[outIdx++] = 192 | u >> 6; outU8Array[outIdx++] = 128 | u & 63 } else if (u <= 65535) { if (outIdx + 2 >= endIdx) break; outU8Array[outIdx++] = 224 | u >> 12; outU8Array[outIdx++] = 128 | u >> 6 & 63; outU8Array[outIdx++] = 128 | u & 63 } else { if (outIdx + 3 >= endIdx) break; outU8Array[outIdx++] = 240 | u >> 18; outU8Array[outIdx++] = 128 | u >> 12 & 63; outU8Array[outIdx++] = 128 | u >> 6 & 63; outU8Array[outIdx++] = 128 | u & 63 } } outU8Array[outIdx] = 0; return outIdx - startIdx } function stringToUTF8(str, outPtr, maxBytesToWrite) { return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite) } function lengthBytesUTF8(str) { var len = 0; for (var i = 0; i < str.length; ++i) { var u = str.charCodeAt(i); if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023; if (u <= 127) ++len; else if (u <= 2047) len += 2; else if (u <= 65535) len += 3; else len += 4 } return len } var UTF16Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-16le") : undefined; function writeArrayToMemory(array, buffer) { HEAP8.set(array, buffer) } function writeAsciiToMemory(str, buffer, dontAddNull) { for (var i = 0; i < str.length; ++i) { HEAP8[buffer++ >> 0] = str.charCodeAt(i) } if (!dontAddNull) HEAP8[buffer >> 0] = 0 } function demangle(func) { return func } function demangleAll(text) { var regex = /__Z[\w\d_]+/g; return text.replace(regex, function (x) { var y = demangle(x); return x === y ? x : y + " [" + x + "]" }) } function jsStackTrace() { var err = new Error; if (!err.stack) { try { throw new Error(0) } catch (e) { err = e } if (!err.stack) { return "(no stack trace available)" } } return err.stack.toString() } function stackTrace() { var js = jsStackTrace(); if (Module["extraStackTrace"]) js += "\n" + Module["extraStackTrace"](); return demangleAll(js) } var WASM_PAGE_SIZE = 65536; function alignUp(x, multiple) { if (x % multiple > 0) { x += multiple - x % multiple } return x } var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; function updateGlobalBufferViews() { Module["HEAP8"] = HEAP8 = new Int8Array(buffer); Module["HEAP16"] = HEAP16 = new Int16Array(buffer); Module["HEAP32"] = HEAP32 = new Int32Array(buffer); Module["HEAPU8"] = HEAPU8 = new Uint8Array(buffer); Module["HEAPU16"] = HEAPU16 = new Uint16Array(buffer); Module["HEAPU32"] = HEAPU32 = new Uint32Array(buffer); Module["HEAPF32"] = HEAPF32 = new Float32Array(buffer); Module["HEAPF64"] = HEAPF64 = new Float64Array(buffer) } var DYNAMIC_BASE = 5520464, DYNAMICTOP_PTR = 277552; var TOTAL_STACK = 5242880; var INITIAL_TOTAL_MEMORY = Module["TOTAL_MEMORY"] || 16777216; if (INITIAL_TOTAL_MEMORY < TOTAL_STACK) err("TOTAL_MEMORY should be larger than TOTAL_STACK, was " + INITIAL_TOTAL_MEMORY + "! (TOTAL_STACK=" + TOTAL_STACK + ")"); if (Module["buffer"]) { buffer = Module["buffer"] } else { if (typeof WebAssembly === "object" && typeof WebAssembly.Memory === "function") { wasmMemory = new WebAssembly.Memory({ "initial": INITIAL_TOTAL_MEMORY / WASM_PAGE_SIZE }); buffer = wasmMemory.buffer } else { buffer = new ArrayBuffer(INITIAL_TOTAL_MEMORY) } } updateGlobalBufferViews(); HEAP32[DYNAMICTOP_PTR >> 2] = DYNAMIC_BASE; function callRuntimeCallbacks(callbacks) { while (callbacks.length > 0) { var callback = callbacks.shift(); if (typeof callback == "function") { callback(); continue } var func = callback.func; if (typeof func === "number") { if (callback.arg === undefined) { Module["dynCall_v"](func) } else { Module["dynCall_vi"](func, callback.arg) } } else { func(callback.arg === undefined ? null : callback.arg) } } } var __ATPRERUN__ = []; var __ATINIT__ = []; var __ATMAIN__ = []; var __ATPOSTRUN__ = []; var runtimeInitialized = false; var runtimeExited = false; function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]]; while (Module["preRun"].length) { addOnPreRun(Module["preRun"].shift()) } } callRuntimeCallbacks(__ATPRERUN__) } function ensureInitRuntime() { if (runtimeInitialized) return; runtimeInitialized = true; if (!Module["noFSInit"] && !FS.init.initialized) FS.init(); TTY.init(); PIPEFS.root = FS.mount(PIPEFS, {}, null); callRuntimeCallbacks(__ATINIT__) } function preMain() { FS.ignorePermissions = false; callRuntimeCallbacks(__ATMAIN__) } function exitRuntime() { runtimeExited = true } function postRun() { if (Module["postRun"]) { if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]]; while (Module["postRun"].length) { addOnPostRun(Module["postRun"].shift()) } } callRuntimeCallbacks(__ATPOSTRUN__) } function addOnPreRun(cb) { __ATPRERUN__.unshift(cb) } function addOnPostRun(cb) { __ATPOSTRUN__.unshift(cb) } var Math_abs = Math.abs; var Math_ceil = Math.ceil; var Math_floor = Math.floor; var Math_min = Math.min; var runDependencies = 0; var runDependencyWatcher = null; var dependenciesFulfilled = null; function getUniqueRunDependency(id) { return id } function addRunDependency(id) { runDependencies++; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies) } } function removeRunDependency(id) { runDependencies--; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies) } if (runDependencies == 0) { if (runDependencyWatcher !== null) { clearInterval(runDependencyWatcher); runDependencyWatcher = null } if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; callback() } } } Module["preloadedImages"] = {}; Module["preloadedAudios"] = {}; var dataURIPrefix = "data:application/octet-stream;base64,"; function isDataURI(filename) { return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0 } var wasmBinaryFile = "libarchive.wasm"; if (!isDataURI(wasmBinaryFile)) { wasmBinaryFile = locateFile(wasmBinaryFile) } function getBinary() { try { if (Module["wasmBinary"]) { return new Uint8Array(Module["wasmBinary"]) } if (Module["readBinary"]) { return Module["readBinary"](wasmBinaryFile) } else { throw "both async and sync fetching of the wasm failed" } } catch (err) { abort(err) } } function getBinaryPromise() { if (!Module["wasmBinary"] && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && typeof fetch === "function") { return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function (response) { if (!response["ok"]) { throw "failed to load wasm binary file at '" + wasmBinaryFile + "'" } return response["arrayBuffer"]() }).catch(function () { return getBinary() }) } return new Promise(function (resolve, reject) { resolve(getBinary()) }) } function createWasm(env) { var info = { "env": env, "global": { "NaN": NaN, Infinity: Infinity }, "global.Math": Math, "asm2wasm": asm2wasmImports }; function receiveInstance(instance, module) { var exports = instance.exports; Module["asm"] = exports; removeRunDependency("wasm-instantiate") } addRunDependency("wasm-instantiate"); if (Module["instantiateWasm"]) { try { return Module["instantiateWasm"](info, receiveInstance) } catch (e) { err("Module.instantiateWasm callback failed with error: " + e); return false } } function receiveInstantiatedSource(output) { receiveInstance(output["instance"]) } function instantiateArrayBuffer(receiver) { getBinaryPromise().then(function (binary) { return WebAssembly.instantiate(binary, info) }).then(receiver, function (reason) { err("failed to asynchronously prepare wasm: " + reason); abort(reason) }) } if (!Module["wasmBinary"] && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function") { WebAssembly.instantiateStreaming(fetch(wasmBinaryFile, { credentials: "same-origin" }), info).then(receiveInstantiatedSource, function (reason) { err("wasm streaming compile failed: " + reason); err("falling back to ArrayBuffer instantiation"); instantiateArrayBuffer(receiveInstantiatedSource) }) } else { instantiateArrayBuffer(receiveInstantiatedSource) } return {} } Module["asm"] = function (global, env, providedBuffer) { env["memory"] = wasmMemory; env["table"] = wasmTable = new WebAssembly.Table({ "initial": 507, "maximum": 507, "element": "anyfunc" }); env["__memory_base"] = 1024; env["__table_base"] = 0; var exports = createWasm(env); return exports }; __ATINIT__.push({ func: function () { ___emscripten_environ_constructor() } }); var ENV = {}; function ___buildEnvironment(environ) { var MAX_ENV_VALUES = 64; var TOTAL_ENV_SIZE = 1024; var poolPtr; var envPtr; if (!___buildEnvironment.called) { ___buildEnvironment.called = true; ENV["USER"] = ENV["LOGNAME"] = "web_user"; ENV["PATH"] = "/"; ENV["PWD"] = "/"; ENV["HOME"] = "/home/web_user"; ENV["LANG"] = "C.UTF-8"; ENV["_"] = Module["thisProgram"]; poolPtr = getMemory(TOTAL_ENV_SIZE); envPtr = getMemory(MAX_ENV_VALUES * 4); HEAP32[envPtr >> 2] = poolPtr; HEAP32[environ >> 2] = envPtr } else { envPtr = HEAP32[environ >> 2]; poolPtr = HEAP32[envPtr >> 2] } var strings = []; var totalSize = 0; for (var key in ENV) { if (typeof ENV[key] === "string") { var line = key + "=" + ENV[key]; strings.push(line); totalSize += line.length } } if (totalSize > TOTAL_ENV_SIZE) { throw new Error("Environment size exceeded TOTAL_ENV_SIZE!") } var ptrSize = 4; for (var i = 0; i < strings.length; i++) { var line = strings[i]; writeAsciiToMemory(line, poolPtr); HEAP32[envPtr + i * ptrSize >> 2] = poolPtr; poolPtr += line.length + 1 } HEAP32[envPtr + strings.length * ptrSize >> 2] = 0 } var PATH = { splitPath: function (filename) { var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; return splitPathRe.exec(filename).slice(1) }, normalizeArray: function (parts, allowAboveRoot) { var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === ".") { parts.splice(i, 1) } else if (last === "..") { parts.splice(i, 1); up++ } else if (up) { parts.splice(i, 1); up-- } } if (allowAboveRoot) { for (; up; up--) { parts.unshift("..") } } return parts }, normalize: function (path) { var isAbsolute = path.charAt(0) === "/", trailingSlash = path.substr(-1) === "/"; path = PATH.normalizeArray(path.split("/").filter(function (p) { return !!p }), !isAbsolute).join("/"); if (!path && !isAbsolute) { path = "." } if (path && trailingSlash) { path += "/" } return (isAbsolute ? "/" : "") + path }, dirname: function (path) { var result = PATH.splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { return "." } if (dir) { dir = dir.substr(0, dir.length - 1) } return root + dir }, basename: function (path) { if (path === "/") return "/"; var lastSlash = path.lastIndexOf("/"); if (lastSlash === -1) return path; return path.substr(lastSlash + 1) }, extname: function (path) { return PATH.splitPath(path)[3] }, join: function () { var paths = Array.prototype.slice.call(arguments, 0); return PATH.normalize(paths.join("/")) }, join2: function (l, r) { return PATH.normalize(l + "/" + r) } }; function ___setErrNo(value) { if (Module["___errno_location"]) HEAP32[Module["___errno_location"]() >> 2] = value; return value } var PATH_FS = { resolve: function () { var resolvedPath = "", resolvedAbsolute = false; for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = i >= 0 ? arguments[i] : FS.cwd(); if (typeof path !== "string") { throw new TypeError("Arguments to path.resolve must be strings") } else if (!path) { return "" } resolvedPath = path + "/" + resolvedPath; resolvedAbsolute = path.charAt(0) === "/" } resolvedPath = PATH.normalizeArray(resolvedPath.split("/").filter(function (p) { return !!p }), !resolvedAbsolute).join("/"); return (resolvedAbsolute ? "/" : "") + resolvedPath || "." }, relative: function (from, to) { from = PATH_FS.resolve(from).substr(1); to = PATH_FS.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== "") break } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== "") break } if (start > end) return []; return arr.slice(start, end - start + 1) } var fromParts = trim(from.split("/")); var toParts = trim(to.split("/")); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push("..") } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join("/") } }; var TTY = { ttys: [], init: function () { }, shutdown: function () { }, register: function (dev, ops) { TTY.ttys[dev] = { input: [], output: [], ops: ops }; FS.registerDevice(dev, TTY.stream_ops) }, stream_ops: { open: function (stream) { var tty = TTY.ttys[stream.node.rdev]; if (!tty) { throw new FS.ErrnoError(19) } stream.tty = tty; stream.seekable = false }, close: function (stream) { stream.tty.ops.flush(stream.tty) }, flush: function (stream) { stream.tty.ops.flush(stream.tty) }, read: function (stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.get_char) { throw new FS.ErrnoError(6) } var bytesRead = 0; for (var i = 0; i < length; i++) { var result; try { result = stream.tty.ops.get_char(stream.tty) } catch (e) { throw new FS.ErrnoError(5) } if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError(11) } if (result === null || result === undefined) break; bytesRead++; buffer[offset + i] = result } if (bytesRead) { stream.node.timestamp = Date.now() } return bytesRead }, write: function (stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.put_char) { throw new FS.ErrnoError(6) } try { for (var i = 0; i < length; i++) { stream.tty.ops.put_char(stream.tty, buffer[offset + i]) } } catch (e) { throw new FS.ErrnoError(5) } if (length) { stream.node.timestamp = Date.now() } return i } }, default_tty_ops: { get_char: function (tty) { if (!tty.input.length) { var result = null; if (ENVIRONMENT_IS_NODE) { var BUFSIZE = 256; var buf = new Buffer(BUFSIZE); var bytesRead = 0; var isPosixPlatform = process.platform != "win32"; var fd = process.stdin.fd; if (isPosixPlatform) { var usingDevice = false; try { fd = fs.openSync("/dev/stdin", "r"); usingDevice = true } catch (e) { } } try { bytesRead = fs.readSync(fd, buf, 0, BUFSIZE, null) } catch (e) { if (e.toString().indexOf("EOF") != -1) bytesRead = 0; else throw e } if (usingDevice) { fs.closeSync(fd) } if (bytesRead > 0) { result = buf.slice(0, bytesRead).toString("utf-8") } else { result = null } } else if (typeof window != "undefined" && typeof window.prompt == "function") { result = window.prompt("Input: "); if (result !== null) { result += "\n" } } else if (typeof readline == "function") { result = readline(); if (result !== null) { result += "\n" } } if (!result) { return null } tty.input = intArrayFromString(result, true) } return tty.input.shift() }, put_char: function (tty, val) { if (val === null || val === 10) { out(UTF8ArrayToString(tty.output, 0)); tty.output = [] } else { if (val != 0) tty.output.push(val) } }, flush: function (tty) { if (tty.output && tty.output.length > 0) { out(UTF8ArrayToString(tty.output, 0)); tty.output = [] } } }, default_tty1_ops: { put_char: function (tty, val) { if (val === null || val === 10) { err(UTF8ArrayToString(tty.output, 0)); tty.output = [] } else { if (val != 0) tty.output.push(val) } }, flush: function (tty) { if (tty.output && tty.output.length > 0) { err(UTF8ArrayToString(tty.output, 0)); tty.output = [] } } } }; var MEMFS = { ops_table: null, mount: function (mount) { return MEMFS.createNode(null, "/", 16384 | 511, 0) }, createNode: function (parent, name, mode, dev) { if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { throw new FS.ErrnoError(1) } if (!MEMFS.ops_table) { MEMFS.ops_table = { dir: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr, lookup: MEMFS.node_ops.lookup, mknod: MEMFS.node_ops.mknod, rename: MEMFS.node_ops.rename, unlink: MEMFS.node_ops.unlink, rmdir: MEMFS.node_ops.rmdir, readdir: MEMFS.node_ops.readdir, symlink: MEMFS.node_ops.symlink }, stream: { llseek: MEMFS.stream_ops.llseek } }, file: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, stream: { llseek: MEMFS.stream_ops.llseek, read: MEMFS.stream_ops.read, write: MEMFS.stream_ops.write, allocate: MEMFS.stream_ops.allocate, mmap: MEMFS.stream_ops.mmap, msync: MEMFS.stream_ops.msync } }, link: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr, readlink: MEMFS.node_ops.readlink }, stream: {} }, chrdev: { node: { getattr: MEMFS.node_ops.getattr, setattr: MEMFS.node_ops.setattr }, stream: FS.chrdev_stream_ops } } } var node = FS.createNode(parent, name, mode, dev); if (FS.isDir(node.mode)) { node.node_ops = MEMFS.ops_table.dir.node; node.stream_ops = MEMFS.ops_table.dir.stream; node.contents = {} } else if (FS.isFile(node.mode)) { node.node_ops = MEMFS.ops_table.file.node; node.stream_ops = MEMFS.ops_table.file.stream; node.usedBytes = 0; node.contents = null } else if (FS.isLink(node.mode)) { node.node_ops = MEMFS.ops_table.link.node; node.stream_ops = MEMFS.ops_table.link.stream } else if (FS.isChrdev(node.mode)) { node.node_ops = MEMFS.ops_table.chrdev.node; node.stream_ops = MEMFS.ops_table.chrdev.stream } node.timestamp = Date.now(); if (parent) { parent.contents[name] = node } return node }, getFileDataAsRegularArray: function (node) { if (node.contents && node.contents.subarray) { var arr = []; for (var i = 0; i < node.usedBytes; ++i)arr.push(node.contents[i]); return arr } return node.contents }, getFileDataAsTypedArray: function (node) { if (!node.contents) return new Uint8Array; if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); return new Uint8Array(node.contents) }, expandFileStorage: function (node, newCapacity) { var prevCapacity = node.contents ? node.contents.length : 0; if (prevCapacity >= newCapacity) return; var CAPACITY_DOUBLING_MAX = 1024 * 1024; newCapacity = Math.max(newCapacity, prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125) | 0); if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); var oldContents = node.contents; node.contents = new Uint8Array(newCapacity); if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); return }, resizeFileStorage: function (node, newSize) { if (node.usedBytes == newSize) return; if (newSize == 0) { node.contents = null; node.usedBytes = 0; return } if (!node.contents || node.contents.subarray) { var oldContents = node.contents; node.contents = new Uint8Array(new ArrayBuffer(newSize)); if (oldContents) { node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))) } node.usedBytes = newSize; return } if (!node.contents) node.contents = []; if (node.contents.length > newSize) node.contents.length = newSize; else while (node.contents.length < newSize) node.contents.push(0); node.usedBytes = newSize }, node_ops: { getattr: function (node) { var attr = {}; attr.dev = FS.isChrdev(node.mode) ? node.id : 1; attr.ino = node.id; attr.mode = node.mode; attr.nlink = 1; attr.uid = 0; attr.gid = 0; attr.rdev = node.rdev; if (FS.isDir(node.mode)) { attr.size = 4096 } else if (FS.isFile(node.mode)) { attr.size = node.usedBytes } else if (FS.isLink(node.mode)) { attr.size = node.link.length } else { attr.size = 0 } attr.atime = new Date(node.timestamp); attr.mtime = new Date(node.timestamp); attr.ctime = new Date(node.timestamp); attr.blksize = 4096; attr.blocks = Math.ceil(attr.size / attr.blksize); return attr }, setattr: function (node, attr) { if (attr.mode !== undefined) { node.mode = attr.mode } if (attr.timestamp !== undefined) { node.timestamp = attr.timestamp } if (attr.size !== undefined) { MEMFS.resizeFileStorage(node, attr.size) } }, lookup: function (parent, name) { throw FS.genericErrors[2] }, mknod: function (parent, name, mode, dev) { return MEMFS.createNode(parent, name, mode, dev) }, rename: function (old_node, new_dir, new_name) { if (FS.isDir(old_node.mode)) { var new_node; try { new_node = FS.lookupNode(new_dir, new_name) } catch (e) { } if (new_node) { for (var i in new_node.contents) { throw new FS.ErrnoError(39) } } } delete old_node.parent.contents[old_node.name]; old_node.name = new_name; new_dir.contents[new_name] = old_node; old_node.parent = new_dir }, unlink: function (parent, name) { delete parent.contents[name] }, rmdir: function (parent, name) { var node = FS.lookupNode(parent, name); for (var i in node.contents) { throw new FS.ErrnoError(39) } delete parent.contents[name] }, readdir: function (node) { var entries = [".", ".."]; for (var key in node.contents) { if (!node.contents.hasOwnProperty(key)) { continue } entries.push(key) } return entries }, symlink: function (parent, newname, oldpath) { var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); node.link = oldpath; return node }, readlink: function (node) { if (!FS.isLink(node.mode)) { throw new FS.ErrnoError(22) } return node.link } }, stream_ops: { read: function (stream, buffer, offset, length, position) { var contents = stream.node.contents; if (position >= stream.node.usedBytes) return 0; var size = Math.min(stream.node.usedBytes - position, length); if (size > 8 && contents.subarray) { buffer.set(contents.subarray(position, position + size), offset) } else { for (var i = 0; i < size; i++)buffer[offset + i] = contents[position + i] } return size }, write: function (stream, buffer, offset, length, position, canOwn) { canOwn = false; if (!length) return 0; var node = stream.node; node.timestamp = Date.now(); if (buffer.subarray && (!node.contents || node.contents.subarray)) { if (canOwn) { node.contents = buffer.subarray(offset, offset + length); node.usedBytes = length; return length } else if (node.usedBytes === 0 && position === 0) { node.contents = new Uint8Array(buffer.subarray(offset, offset + length)); node.usedBytes = length; return length } else if (position + length <= node.usedBytes) { node.contents.set(buffer.subarray(offset, offset + length), position); return length } } MEMFS.expandFileStorage(node, position + length); if (node.contents.subarray && buffer.subarray) node.contents.set(buffer.subarray(offset, offset + length), position); else { for (var i = 0; i < length; i++) { node.contents[position + i] = buffer[offset + i] } } node.usedBytes = Math.max(node.usedBytes, position + length); return length }, llseek: function (stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { position += stream.node.usedBytes } } if (position < 0) { throw new FS.ErrnoError(22) } return position }, allocate: function (stream, offset, length) { MEMFS.expandFileStorage(stream.node, offset + length); stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length) }, mmap: function (stream, buffer, offset, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError(19) } var ptr; var allocated; var contents = stream.node.contents; if (!(flags & 2) && (contents.buffer === buffer || contents.buffer === buffer.buffer)) { allocated = false; ptr = contents.byteOffset } else { if (position > 0 || position + length < stream.node.usedBytes) { if (contents.subarray) { contents = contents.subarray(position, position + length) } else { contents = Array.prototype.slice.call(contents, position, position + length) } } allocated = true; ptr = _malloc(length); if (!ptr) { throw new FS.ErrnoError(12) } buffer.set(contents, ptr) } return { ptr: ptr, allocated: allocated } }, msync: function (stream, buffer, offset, length, mmapFlags) { if (!FS.isFile(stream.node.mode)) { throw new FS.ErrnoError(19) } if (mmapFlags & 2) { return 0 } var bytesWritten = MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); return 0 } } }; var IDBFS = { dbs: {}, indexedDB: function () { if (typeof indexedDB !== "undefined") return indexedDB; var ret = null; if (typeof window === "object") ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; assert(ret, "IDBFS used, but indexedDB not supported"); return ret }, DB_VERSION: 21, DB_STORE_NAME: "FILE_DATA", mount: function (mount) { return MEMFS.mount.apply(null, arguments) }, syncfs: function (mount, populate, callback) { IDBFS.getLocalSet(mount, function (err, local) { if (err) return callback(err); IDBFS.getRemoteSet(mount, function (err, remote) { if (err) return callback(err); var src = populate ? remote : local; var dst = populate ? local : remote; IDBFS.reconcile(src, dst, callback) }) }) }, getDB: function (name, callback) { var db = IDBFS.dbs[name]; if (db) { return callback(null, db) } var req; try { req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION) } catch (e) { return callback(e) } if (!req) { return callback("Unable to connect to IndexedDB") } req.onupgradeneeded = function (e) { var db = e.target.result; var transaction = e.target.transaction; var fileStore; if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME) } else { fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME) } if (!fileStore.indexNames.contains("timestamp")) { fileStore.createIndex("timestamp", "timestamp", { unique: false }) } }; req.onsuccess = function () { db = req.result; IDBFS.dbs[name] = db; callback(null, db) }; req.onerror = function (e) { callback(this.error); e.preventDefault() } }, getLocalSet: function (mount, callback) { var entries = {}; function isRealDir(p) { return p !== "." && p !== ".." } function toAbsolute(root) { return function (p) { return PATH.join2(root, p) } } var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); while (check.length) { var path = check.pop(); var stat; try { stat = FS.stat(path) } catch (e) { return callback(e) } if (FS.isDir(stat.mode)) { check.push.apply(check, FS.readdir(path).filter(isRealDir).map(toAbsolute(path))) } entries[path] = { timestamp: stat.mtime } } return callback(null, { type: "local", entries: entries }) }, getRemoteSet: function (mount, callback) { var entries = {}; IDBFS.getDB(mount.mountpoint, function (err, db) { if (err) return callback(err); try { var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readonly"); transaction.onerror = function (e) { callback(this.error); e.preventDefault() }; var store = transaction.objectStore(IDBFS.DB_STORE_NAME); var index = store.index("timestamp"); index.openKeyCursor().onsuccess = function (event) { var cursor = event.target.result; if (!cursor) { return callback(null, { type: "remote", db: db, entries: entries }) } entries[cursor.primaryKey] = { timestamp: cursor.key }; cursor.continue() } } catch (e) { return callback(e) } }) }, loadLocalEntry: function (path, callback) { var stat, node; try { var lookup = FS.lookupPath(path); node = lookup.node; stat = FS.stat(path) } catch (e) { return callback(e) } if (FS.isDir(stat.mode)) { return callback(null, { timestamp: stat.mtime, mode: stat.mode }) } else if (FS.isFile(stat.mode)) { node.contents = MEMFS.getFileDataAsTypedArray(node); return callback(null, { timestamp: stat.mtime, mode: stat.mode, contents: node.contents }) } else { return callback(new Error("node type not supported")) } }, storeLocalEntry: function (path, entry, callback) { try { if (FS.isDir(entry.mode)) { FS.mkdir(path, entry.mode) } else if (FS.isFile(entry.mode)) { FS.writeFile(path, entry.contents, { canOwn: true }) } else { return callback(new Error("node type not supported")) } FS.chmod(path, entry.mode); FS.utime(path, entry.timestamp, entry.timestamp) } catch (e) { return callback(e) } callback(null) }, removeLocalEntry: function (path, callback) { try { var lookup = FS.lookupPath(path); var stat = FS.stat(path); if (FS.isDir(stat.mode)) { FS.rmdir(path) } else if (FS.isFile(stat.mode)) { FS.unlink(path) } } catch (e) { return callback(e) } callback(null) }, loadRemoteEntry: function (store, path, callback) { var req = store.get(path); req.onsuccess = function (event) { callback(null, event.target.result) }; req.onerror = function (e) { callback(this.error); e.preventDefault() } }, storeRemoteEntry: function (store, path, entry, callback) { var req = store.put(entry, path); req.onsuccess = function () { callback(null) }; req.onerror = function (e) { callback(this.error); e.preventDefault() } }, removeRemoteEntry: function (store, path, callback) { var req = store.delete(path); req.onsuccess = function () { callback(null) }; req.onerror = function (e) { callback(this.error); e.preventDefault() } }, reconcile: function (src, dst, callback) { var total = 0; var create = []; Object.keys(src.entries).forEach(function (key) { var e = src.entries[key]; var e2 = dst.entries[key]; if (!e2 || e.timestamp > e2.timestamp) { create.push(key); total++ } }); var remove = []; Object.keys(dst.entries).forEach(function (key) { var e = dst.entries[key]; var e2 = src.entries[key]; if (!e2) { remove.push(key); total++ } }); if (!total) { return callback(null) } var errored = false; var completed = 0; var db = src.type === "remote" ? src.db : dst.db; var transaction = db.transaction([IDBFS.DB_STORE_NAME], "readwrite"); var store = transaction.objectStore(IDBFS.DB_STORE_NAME); function done(err) { if (err) { if (!done.errored) { done.errored = true; return callback(err) } return } if (++completed >= total) { return callback(null) } } transaction.onerror = function (e) { done(this.error); e.preventDefault() }; create.sort().forEach(function (path) { if (dst.type === "local") { IDBFS.loadRemoteEntry(store, path, function (err, entry) { if (err) return done(err); IDBFS.storeLocalEntry(path, entry, done) }) } else { IDBFS.loadLocalEntry(path, function (err, entry) { if (err) return done(err); IDBFS.storeRemoteEntry(store, path, entry, done) }) } }); remove.sort().reverse().forEach(function (path) { if (dst.type === "local") { IDBFS.removeLocalEntry(path, done) } else { IDBFS.removeRemoteEntry(store, path, done) } }) } }; var NODEFS = { isWindows: false, staticInit: function () { NODEFS.isWindows = !!process.platform.match(/^win/); var flags = process["binding"]("constants"); if (flags["fs"]) { flags = flags["fs"] } NODEFS.flagsForNodeMap = { 1024: flags["O_APPEND"], 64: flags["O_CREAT"], 128: flags["O_EXCL"], 0: flags["O_RDONLY"], 2: flags["O_RDWR"], 4096: flags["O_SYNC"], 512: flags["O_TRUNC"], 1: flags["O_WRONLY"] } }, bufferFrom: function (arrayBuffer) { return Buffer.alloc ? Buffer.from(arrayBuffer) : new Buffer(arrayBuffer) }, mount: function (mount) { assert(ENVIRONMENT_IS_NODE); return NODEFS.createNode(null, "/", NODEFS.getMode(mount.opts.root), 0) }, createNode: function (parent, name, mode, dev) { if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { throw new FS.ErrnoError(22) } var node = FS.createNode(parent, name, mode); node.node_ops = NODEFS.node_ops; node.stream_ops = NODEFS.stream_ops; return node }, getMode: function (path) { var stat; try { stat = fs.lstatSync(path); if (NODEFS.isWindows) { stat.mode = stat.mode | (stat.mode & 292) >> 2 } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } return stat.mode }, realPath: function (node) { var parts = []; while (node.parent !== node) { parts.push(node.name); node = node.parent } parts.push(node.mount.opts.root); parts.reverse(); return PATH.join.apply(null, parts) }, flagsForNode: function (flags) { flags &= ~2097152; flags &= ~2048; flags &= ~32768; flags &= ~524288; var newFlags = 0; for (var k in NODEFS.flagsForNodeMap) { if (flags & k) { newFlags |= NODEFS.flagsForNodeMap[k]; flags ^= k } } if (!flags) { return newFlags } else { throw new FS.ErrnoError(22) } }, node_ops: { getattr: function (node) { var path = NODEFS.realPath(node); var stat; try { stat = fs.lstatSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } if (NODEFS.isWindows && !stat.blksize) { stat.blksize = 4096 } if (NODEFS.isWindows && !stat.blocks) { stat.blocks = (stat.size + stat.blksize - 1) / stat.blksize | 0 } return { dev: stat.dev, ino: stat.ino, mode: stat.mode, nlink: stat.nlink, uid: stat.uid, gid: stat.gid, rdev: stat.rdev, size: stat.size, atime: stat.atime, mtime: stat.mtime, ctime: stat.ctime, blksize: stat.blksize, blocks: stat.blocks } }, setattr: function (node, attr) { var path = NODEFS.realPath(node); try { if (attr.mode !== undefined) { fs.chmodSync(path, attr.mode); node.mode = attr.mode } if (attr.timestamp !== undefined) { var date = new Date(attr.timestamp); fs.utimesSync(path, date, date) } if (attr.size !== undefined) { fs.truncateSync(path, attr.size) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, lookup: function (parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); var mode = NODEFS.getMode(path); return NODEFS.createNode(parent, name, mode) }, mknod: function (parent, name, mode, dev) { var node = NODEFS.createNode(parent, name, mode, dev); var path = NODEFS.realPath(node); try { if (FS.isDir(node.mode)) { fs.mkdirSync(path, node.mode) } else { fs.writeFileSync(path, "", { mode: node.mode }) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } return node }, rename: function (oldNode, newDir, newName) { var oldPath = NODEFS.realPath(oldNode); var newPath = PATH.join2(NODEFS.realPath(newDir), newName); try { fs.renameSync(oldPath, newPath) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, unlink: function (parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); try { fs.unlinkSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, rmdir: function (parent, name) { var path = PATH.join2(NODEFS.realPath(parent), name); try { fs.rmdirSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, readdir: function (node) { var path = NODEFS.realPath(node); try { return fs.readdirSync(path) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, symlink: function (parent, newName, oldPath) { var newPath = PATH.join2(NODEFS.realPath(parent), newName); try { fs.symlinkSync(oldPath, newPath) } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, readlink: function (node) { var path = NODEFS.realPath(node); try { path = fs.readlinkSync(path); path = NODEJS_PATH.relative(NODEJS_PATH.resolve(node.mount.opts.root), path); return path } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } } }, stream_ops: { open: function (stream) { var path = NODEFS.realPath(stream.node); try { if (FS.isFile(stream.node.mode)) { stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, close: function (stream) { try { if (FS.isFile(stream.node.mode) && stream.nfd) { fs.closeSync(stream.nfd) } } catch (e) { if (!e.code) throw e; throw new FS.ErrnoError(-e.errno) } }, read: function (stream, buffer, offset, length, position) { if (length === 0) return 0; try { return fs.readSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position) } catch (e) { throw new FS.ErrnoError(-e.errno) } }, write: function (stream, buffer, offset, length, position) { try { return fs.writeSync(stream.nfd, NODEFS.bufferFrom(buffer.buffer), offset, length, position) } catch (e) { throw new FS.ErrnoError(-e.errno) } }, llseek: function (stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { try { var stat = fs.fstatSync(stream.nfd); position += stat.size } catch (e) { throw new FS.ErrnoError(-e.errno) } } } if (position < 0) { throw new FS.ErrnoError(22) } return position } } }; var WORKERFS = { DIR_MODE: 16895, FILE_MODE: 33279, reader: null, mount: function (mount) { assert(ENVIRONMENT_IS_WORKER); if (!WORKERFS.reader) WORKERFS.reader = new FileReaderSync; var root = WORKERFS.createNode(null, "/", WORKERFS.DIR_MODE, 0); var createdParents = {}; function ensureParent(path) { var parts = path.split("/"); var parent = root; for (var i = 0; i < parts.length - 1; i++) { var curr = parts.slice(0, i + 1).join("/"); if (!createdParents[curr]) { createdParents[curr] = WORKERFS.createNode(parent, parts[i], WORKERFS.DIR_MODE, 0) } parent = createdParents[curr] } return parent } function base(path) { var parts = path.split("/"); return parts[parts.length - 1] } Array.prototype.forEach.call(mount.opts["files"] || [], function (file) { WORKERFS.createNode(ensureParent(file.name), base(file.name), WORKERFS.FILE_MODE, 0, file, file.lastModifiedDate) }); (mount.opts["blobs"] || []).forEach(function (obj) { WORKERFS.createNode(ensureParent(obj["name"]), base(obj["name"]), WORKERFS.FILE_MODE, 0, obj["data"]) }); (mount.opts["packages"] || []).forEach(function (pack) { pack["metadata"].files.forEach(function (file) { var name = file.filename.substr(1); WORKERFS.createNode(ensureParent(name), base(name), WORKERFS.FILE_MODE, 0, pack["blob"].slice(file.start, file.end)) }) }); return root }, createNode: function (parent, name, mode, dev, contents, mtime) { var node = FS.createNode(parent, name, mode); node.mode = mode; node.node_ops = WORKERFS.node_ops; node.stream_ops = WORKERFS.stream_ops; node.timestamp = (mtime || new Date).getTime(); assert(WORKERFS.FILE_MODE !== WORKERFS.DIR_MODE); if (mode === WORKERFS.FILE_MODE) { node.size = contents.size; node.contents = contents } else { node.size = 4096; node.contents = {} } if (parent) { parent.contents[name] = node } return node }, node_ops: { getattr: function (node) { return { dev: 1, ino: undefined, mode: node.mode, nlink: 1, uid: 0, gid: 0, rdev: undefined, size: node.size, atime: new Date(node.timestamp), mtime: new Date(node.timestamp), ctime: new Date(node.timestamp), blksize: 4096, blocks: Math.ceil(node.size / 4096) } }, setattr: function (node, attr) { if (attr.mode !== undefined) { node.mode = attr.mode } if (attr.timestamp !== undefined) { node.timestamp = attr.timestamp } }, lookup: function (parent, name) { throw new FS.ErrnoError(2) }, mknod: function (parent, name, mode, dev) { throw new FS.ErrnoError(1) }, rename: function (oldNode, newDir, newName) { throw new FS.ErrnoError(1) }, unlink: function (parent, name) { throw new FS.ErrnoError(1) }, rmdir: function (parent, name) { throw new FS.ErrnoError(1) }, readdir: function (node) { var entries = [".", ".."]; for (var key in node.contents) { if (!node.contents.hasOwnProperty(key)) { continue } entries.push(key) } return entries }, symlink: function (parent, newName, oldPath) { throw new FS.ErrnoError(1) }, readlink: function (node) { throw new FS.ErrnoError(1) } }, stream_ops: { read: function (stream, buffer, offset, length, position) { if (position >= stream.node.size) return 0; var chunk = stream.node.contents.slice(position, position + length); var ab = WORKERFS.reader.readAsArrayBuffer(chunk); buffer.set(new Uint8Array(ab), offset); return chunk.size }, write: function (stream, buffer, offset, length, position) { throw new FS.ErrnoError(5) }, llseek: function (stream, offset, whence) { var position = offset; if (whence === 1) { position += stream.position } else if (whence === 2) { if (FS.isFile(stream.node.mode)) { position += stream.node.size } } if (position < 0) { throw new FS.ErrnoError(22) } return position } } }; var FS = { root: null, mounts: [], devices: {}, streams: [], nextInode: 1, nameTable: null, currentPath: "/", initialized: false, ignorePermissions: true, trackingDelegate: {}, tracking: { openFlags: { READ: 1, WRITE: 2 } }, ErrnoError: null, genericErrors: {}, filesystems: null, syncFSRequests: 0, handleFSError: function (e) { if (!(e instanceof FS.ErrnoError)) throw e + " : " + stackTrace(); return ___setErrNo(e.errno) }, lookupPath: function (path, opts) { path = PATH_FS.resolve(FS.cwd(), path); opts = opts || {}; if (!path) return { path: "", node: null }; var defaults = { follow_mount: true, recurse_count: 0 }; for (var key in defaults) { if (opts[key] === undefined) { opts[key] = defaults[key] } } if (opts.recurse_count > 8) { throw new FS.ErrnoError(40) } var parts = PATH.normalizeArray(path.split("/").filter(function (p) { return !!p }), false); var current = FS.root; var current_path = "/"; for (var i = 0; i < parts.length; i++) { var islast = i === parts.length - 1; if (islast && opts.parent) { break } current = FS.lookupNode(current, parts[i]); current_path = PATH.join2(current_path, parts[i]); if (FS.isMountpoint(current)) { if (!islast || islast && opts.follow_mount) { current = current.mounted.root } } if (!islast || opts.follow) { var count = 0; while (FS.isLink(current.mode)) { var link = FS.readlink(current_path); current_path = PATH_FS.resolve(PATH.dirname(current_path), link); var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); current = lookup.node; if (count++ > 40) { throw new FS.ErrnoError(40) } } } } return { path: current_path, node: current } }, getPath: function (node) { var path; while (true) { if (FS.isRoot(node)) { var mount = node.mount.mountpoint; if (!path) return mount; return mount[mount.length - 1] !== "/" ? mount + "/" + path : mount + path } path = path ? node.name + "/" + path : node.name; node = node.parent } }, hashName: function (parentid, name) { var hash = 0; for (var i = 0; i < name.length; i++) { hash = (hash << 5) - hash + name.charCodeAt(i) | 0 } return (parentid + hash >>> 0) % FS.nameTable.length }, hashAddNode: function (node) { var hash = FS.hashName(node.parent.id, node.name); node.name_next = FS.nameTable[hash]; FS.nameTable[hash] = node }, hashRemoveNode: function (node) { var hash = FS.hashName(node.parent.id, node.name); if (FS.nameTable[hash] === node) { FS.nameTable[hash] = node.name_next } else { var current = FS.nameTable[hash]; while (current) { if (current.name_next === node) { current.name_next = node.name_next; break } current = current.name_next } } }, lookupNode: function (parent, name) { var err = FS.mayLookup(parent); if (err) { throw new FS.ErrnoError(err, parent) } var hash = FS.hashName(parent.id, name); for (var node = FS.nameTable[hash]; node; node = node.name_next) { var nodeName = node.name; if (node.parent.id === parent.id && nodeName === name) { return node } } return FS.lookup(parent, name) }, createNode: function (parent, name, mode, rdev) { if (!FS.FSNode) { FS.FSNode = function (parent, name, mode, rdev) { if (!parent) { parent = this } this.parent = parent; this.mount = parent.mount; this.mounted = null; this.id = FS.nextInode++; this.name = name; this.mode = mode; this.node_ops = {}; this.stream_ops = {}; this.rdev = rdev }; FS.FSNode.prototype = {}; var readMode = 292 | 73; var writeMode = 146; Object.defineProperties(FS.FSNode.prototype, { read: { get: function () { return (this.mode & readMode) === readMode }, set: function (val) { val ? this.mode |= readMode : this.mode &= ~readMode } }, write: { get: function () { return (this.mode & writeMode) === writeMode }, set: function (val) { val ? this.mode |= writeMode : this.mode &= ~writeMode } }, isFolder: { get: function () { return FS.isDir(this.mode) } }, isDevice: { get: function () { return FS.isChrdev(this.mode) } } }) } var node = new FS.FSNode(parent, name, mode, rdev); FS.hashAddNode(node); return node }, destroyNode: function (node) { FS.hashRemoveNode(node) }, isRoot: function (node) { return node === node.parent }, isMountpoint: function (node) { return !!node.mounted }, isFile: function (mode) { return (mode & 61440) === 32768 }, isDir: function (mode) { return (mode & 61440) === 16384 }, isLink: function (mode) { return (mode & 61440) === 40960 }, isChrdev: function (mode) { return (mode & 61440) === 8192 }, isBlkdev: function (mode) { return (mode & 61440) === 24576 }, isFIFO: function (mode) { return (mode & 61440) === 4096 }, isSocket: function (mode) { return (mode & 49152) === 49152 }, flagModes: { "r": 0, "rs": 1052672, "r+": 2, "w": 577, "wx": 705, "xw": 705, "w+": 578, "wx+": 706, "xw+": 706, "a": 1089, "ax": 1217, "xa": 1217, "a+": 1090, "ax+": 1218, "xa+": 1218 }, modeStringToFlags: function (str) { var flags = FS.flagModes[str]; if (typeof flags === "undefined") { throw new Error("Unknown file open mode: " + str) } return flags }, flagsToPermissionString: function (flag) { var perms = ["r", "w", "rw"][flag & 3]; if (flag & 512) { perms += "w" } return perms }, nodePermissions: function (node, perms) { if (FS.ignorePermissions) { return 0 } if (perms.indexOf("r") !== -1 && !(node.mode & 292)) { return 13 } else if (perms.indexOf("w") !== -1 && !(node.mode & 146)) { return 13 } else if (perms.indexOf("x") !== -1 && !(node.mode & 73)) { return 13 } return 0 }, mayLookup: function (dir) { var err = FS.nodePermissions(dir, "x"); if (err) return err; if (!dir.node_ops.lookup) return 13; return 0 }, mayCreate: function (dir, name) { try { var node = FS.lookupNode(dir, name); return 17 } catch (e) { } return FS.nodePermissions(dir, "wx") }, mayDelete: function (dir, name, isdir) { var node; try { node = FS.lookupNode(dir, name) } catch (e) { return e.errno } var err = FS.nodePermissions(dir, "wx"); if (err) { return err } if (isdir) { if (!FS.isDir(node.mode)) { return 20 } if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { return 16 } } else { if (FS.isDir(node.mode)) { return 21 } } return 0 }, mayOpen: function (node, flags) { if (!node) { return 2 } if (FS.isLink(node.mode)) { return 40 } else if (FS.isDir(node.mode)) { if (FS.flagsToPermissionString(flags) !== "r" || flags & 512) { return 21 } } return FS.nodePermissions(node, FS.flagsToPermissionString(flags)) }, MAX_OPEN_FDS: 4096, nextfd: function (fd_start, fd_end) { fd_start = fd_start || 0; fd_end = fd_end || FS.MAX_OPEN_FDS; for (var fd = fd_start; fd <= fd_end; fd++) { if (!FS.streams[fd]) { return fd } } throw new FS.ErrnoError(24) }, getStream: function (fd) { return FS.streams[fd] }, createStream: function (stream, fd_start, fd_end) { if (!FS.FSStream) { FS.FSStream = function () { }; FS.FSStream.prototype = {}; Object.defineProperties(FS.FSStream.prototype, { object: { get: function () { return this.node }, set: function (val) { this.node = val } }, isRead: { get: function () { return (this.flags & 2097155) !== 1 } }, isWrite: { get: function () { return (this.flags & 2097155) !== 0 } }, isAppend: { get: function () { return this.flags & 1024 } } }) } var newStream = new FS.FSStream; for (var p in stream) { newStream[p] = stream[p] } stream = newStream; var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; FS.streams[fd] = stream; return stream }, closeStream: function (fd) { FS.streams[fd] = null }, chrdev_stream_ops: { open: function (stream) { var device = FS.getDevice(stream.node.rdev); stream.stream_ops = device.stream_ops; if (stream.stream_ops.open) { stream.stream_ops.open(stream) } }, llseek: function () { throw new FS.ErrnoError(29) } }, major: function (dev) { return dev >> 8 }, minor: function (dev) { return dev & 255 }, makedev: function (ma, mi) { return ma << 8 | mi }, registerDevice: function (dev, ops) { FS.devices[dev] = { stream_ops: ops } }, getDevice: function (dev) { return FS.devices[dev] }, getMounts: function (mount) { var mounts = []; var check = [mount]; while (check.length) { var m = check.pop(); mounts.push(m); check.push.apply(check, m.mounts) } return mounts }, syncfs: function (populate, callback) { if (typeof populate === "function") { callback = populate; populate = false } FS.syncFSRequests++; if (FS.syncFSRequests > 1) { console.log("warning: " + FS.syncFSRequests + " FS.syncfs operations in flight at once, probably just doing extra work") } var mounts = FS.getMounts(FS.root.mount); var completed = 0; function doCallback(err) { FS.syncFSRequests--; return callback(err) } function done(err) { if (err) { if (!done.errored) { done.errored = true; return doCallback(err) } return } if (++completed >= mounts.length) { doCallback(null) } } mounts.forEach(function (mount) { if (!mount.type.syncfs) { return done(null) } mount.type.syncfs(mount, populate, done) }) }, mount: function (type, opts, mountpoint) { var root = mountpoint === "/"; var pseudo = !mountpoint; var node; if (root && FS.root) { throw new FS.ErrnoError(16) } else if (!root && !pseudo) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); mountpoint = lookup.path; node = lookup.node; if (FS.isMountpoint(node)) { throw new FS.ErrnoError(16) } if (!FS.isDir(node.mode)) { throw new FS.ErrnoError(20) } } var mount = { type: type, opts: opts, mountpoint: mountpoint, mounts: [] }; var mountRoot = type.mount(mount); mountRoot.mount = mount; mount.root = mountRoot; if (root) { FS.root = mountRoot } else if (node) { node.mounted = mount; if (node.mount) { node.mount.mounts.push(mount) } } return mountRoot }, unmount: function (mountpoint) { var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); if (!FS.isMountpoint(lookup.node)) { throw new FS.ErrnoError(22) } var node = lookup.node; var mount = node.mounted; var mounts = FS.getMounts(mount); Object.keys(FS.nameTable).forEach(function (hash) { var current = FS.nameTable[hash]; while (current) { var next = current.name_next; if (mounts.indexOf(current.mount) !== -1) { FS.destroyNode(current) } current = next } }); node.mounted = null; var idx = node.mount.mounts.indexOf(mount); node.mount.mounts.splice(idx, 1) }, lookup: function (parent, name) { return parent.node_ops.lookup(parent, name) }, mknod: function (path, mode, dev) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); if (!name || name === "." || name === "..") { throw new FS.ErrnoError(22) } var err = FS.mayCreate(parent, name); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.mknod) { throw new FS.ErrnoError(1) } return parent.node_ops.mknod(parent, name, mode, dev) }, create: function (path, mode) { mode = mode !== undefined ? mode : 438; mode &= 4095; mode |= 32768; return FS.mknod(path, mode, 0) }, mkdir: function (path, mode) { mode = mode !== undefined ? mode : 511; mode &= 511 | 512; mode |= 16384; return FS.mknod(path, mode, 0) }, mkdirTree: function (path, mode) { var dirs = path.split("/"); var d = ""; for (var i = 0; i < dirs.length; ++i) { if (!dirs[i]) continue; d += "/" + dirs[i]; try { FS.mkdir(d, mode) } catch (e) { if (e.errno != 17) throw e } } }, mkdev: function (path, mode, dev) { if (typeof dev === "undefined") { dev = mode; mode = 438 } mode |= 8192; return FS.mknod(path, mode, dev) }, symlink: function (oldpath, newpath) { if (!PATH_FS.resolve(oldpath)) { throw new FS.ErrnoError(2) } var lookup = FS.lookupPath(newpath, { parent: true }); var parent = lookup.node; if (!parent) { throw new FS.ErrnoError(2) } var newname = PATH.basename(newpath); var err = FS.mayCreate(parent, newname); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.symlink) { throw new FS.ErrnoError(1) } return parent.node_ops.symlink(parent, newname, oldpath) }, rename: function (old_path, new_path) { var old_dirname = PATH.dirname(old_path); var new_dirname = PATH.dirname(new_path); var old_name = PATH.basename(old_path); var new_name = PATH.basename(new_path); var lookup, old_dir, new_dir; try { lookup = FS.lookupPath(old_path, { parent: true }); old_dir = lookup.node; lookup = FS.lookupPath(new_path, { parent: true }); new_dir = lookup.node } catch (e) { throw new FS.ErrnoError(16) } if (!old_dir || !new_dir) throw new FS.ErrnoError(2); if (old_dir.mount !== new_dir.mount) { throw new FS.ErrnoError(18) } var old_node = FS.lookupNode(old_dir, old_name); var relative = PATH_FS.relative(old_path, new_dirname); if (relative.charAt(0) !== ".") { throw new FS.ErrnoError(22) } relative = PATH_FS.relative(new_path, old_dirname); if (relative.charAt(0) !== ".") { throw new FS.ErrnoError(39) } var new_node; try { new_node = FS.lookupNode(new_dir, new_name) } catch (e) { } if (old_node === new_node) { return } var isdir = FS.isDir(old_node.mode); var err = FS.mayDelete(old_dir, old_name, isdir); if (err) { throw new FS.ErrnoError(err) } err = new_node ? FS.mayDelete(new_dir, new_name, isdir) : FS.mayCreate(new_dir, new_name); if (err) { throw new FS.ErrnoError(err) } if (!old_dir.node_ops.rename) { throw new FS.ErrnoError(1) } if (FS.isMountpoint(old_node) || new_node && FS.isMountpoint(new_node)) { throw new FS.ErrnoError(16) } if (new_dir !== old_dir) { err = FS.nodePermissions(old_dir, "w"); if (err) { throw new FS.ErrnoError(err) } } try { if (FS.trackingDelegate["willMovePath"]) { FS.trackingDelegate["willMovePath"](old_path, new_path) } } catch (e) { console.log("FS.trackingDelegate['willMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message) } FS.hashRemoveNode(old_node); try { old_dir.node_ops.rename(old_node, new_dir, new_name) } catch (e) { throw e } finally { FS.hashAddNode(old_node) } try { if (FS.trackingDelegate["onMovePath"]) FS.trackingDelegate["onMovePath"](old_path, new_path) } catch (e) { console.log("FS.trackingDelegate['onMovePath']('" + old_path + "', '" + new_path + "') threw an exception: " + e.message) } }, rmdir: function (path) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); var node = FS.lookupNode(parent, name); var err = FS.mayDelete(parent, name, true); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.rmdir) { throw new FS.ErrnoError(1) } if (FS.isMountpoint(node)) { throw new FS.ErrnoError(16) } try { if (FS.trackingDelegate["willDeletePath"]) { FS.trackingDelegate["willDeletePath"](path) } } catch (e) { console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message) } parent.node_ops.rmdir(parent, name); FS.destroyNode(node); try { if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path) } catch (e) { console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message) } }, readdir: function (path) { var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; if (!node.node_ops.readdir) { throw new FS.ErrnoError(20) } return node.node_ops.readdir(node) }, unlink: function (path) { var lookup = FS.lookupPath(path, { parent: true }); var parent = lookup.node; var name = PATH.basename(path); var node = FS.lookupNode(parent, name); var err = FS.mayDelete(parent, name, false); if (err) { throw new FS.ErrnoError(err) } if (!parent.node_ops.unlink) { throw new FS.ErrnoError(1) } if (FS.isMountpoint(node)) { throw new FS.ErrnoError(16) } try { if (FS.trackingDelegate["willDeletePath"]) { FS.trackingDelegate["willDeletePath"](path) } } catch (e) { console.log("FS.trackingDelegate['willDeletePath']('" + path + "') threw an exception: " + e.message) } parent.node_ops.unlink(parent, name); FS.destroyNode(node); try { if (FS.trackingDelegate["onDeletePath"]) FS.trackingDelegate["onDeletePath"](path) } catch (e) { console.log("FS.trackingDelegate['onDeletePath']('" + path + "') threw an exception: " + e.message) } }, readlink: function (path) { var lookup = FS.lookupPath(path); var link = lookup.node; if (!link) { throw new FS.ErrnoError(2) } if (!link.node_ops.readlink) { throw new FS.ErrnoError(22) } return PATH_FS.resolve(FS.getPath(link.parent), link.node_ops.readlink(link)) }, stat: function (path, dontFollow) { var lookup = FS.lookupPath(path, { follow: !dontFollow }); var node = lookup.node; if (!node) { throw new FS.ErrnoError(2) } if (!node.node_ops.getattr) { throw new FS.ErrnoError(1) } return node.node_ops.getattr(node) }, lstat: function (path) { return FS.stat(path, true) }, chmod: function (path, mode, dontFollow) { var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(1) } node.node_ops.setattr(node, { mode: mode & 4095 | node.mode & ~4095, timestamp: Date.now() }) }, lchmod: function (path, mode) { FS.chmod(path, mode, true) }, fchmod: function (fd, mode) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(9) } FS.chmod(stream.node, mode) }, chown: function (path, uid, gid, dontFollow) { var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: !dontFollow }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(1) } node.node_ops.setattr(node, { timestamp: Date.now() }) }, lchown: function (path, uid, gid) { FS.chown(path, uid, gid, true) }, fchown: function (fd, uid, gid) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(9) } FS.chown(stream.node, uid, gid) }, truncate: function (path, len) { if (len < 0) { throw new FS.ErrnoError(22) } var node; if (typeof path === "string") { var lookup = FS.lookupPath(path, { follow: true }); node = lookup.node } else { node = path } if (!node.node_ops.setattr) { throw new FS.ErrnoError(1) } if (FS.isDir(node.mode)) { throw new FS.ErrnoError(21) } if (!FS.isFile(node.mode)) { throw new FS.ErrnoError(22) } var err = FS.nodePermissions(node, "w"); if (err) { throw new FS.ErrnoError(err) } node.node_ops.setattr(node, { size: len, timestamp: Date.now() }) }, ftruncate: function (fd, len) { var stream = FS.getStream(fd); if (!stream) { throw new FS.ErrnoError(9) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(22) } FS.truncate(stream.node, len) }, utime: function (path, atime, mtime) { var lookup = FS.lookupPath(path, { follow: true }); var node = lookup.node; node.node_ops.setattr(node, { timestamp: Math.max(atime, mtime) }) }, open: function (path, flags, mode, fd_start, fd_end) { if (path === "") { throw new FS.ErrnoError(2) } flags = typeof flags === "string" ? FS.modeStringToFlags(flags) : flags; mode = typeof mode === "undefined" ? 438 : mode; if (flags & 64) { mode = mode & 4095 | 32768 } else { mode = 0 } var node; if (typeof path === "object") { node = path } else { path = PATH.normalize(path); try { var lookup = FS.lookupPath(path, { follow: !(flags & 131072) }); node = lookup.node } catch (e) { } } var created = false; if (flags & 64) { if (node) { if (flags & 128) { throw new FS.ErrnoError(17) } } else { node = FS.mknod(path, mode, 0); created = true } } if (!node) { throw new FS.ErrnoError(2) } if (FS.isChrdev(node.mode)) { flags &= ~512 } if (flags & 65536 && !FS.isDir(node.mode)) { throw new FS.ErrnoError(20) } if (!created) { var err = FS.mayOpen(node, flags); if (err) { throw new FS.ErrnoError(err) } } if (flags & 512) { FS.truncate(node, 0) } flags &= ~(128 | 512); var stream = FS.createStream({ node: node, path: FS.getPath(node), flags: flags, seekable: true, position: 0, stream_ops: node.stream_ops, ungotten: [], error: false }, fd_start, fd_end); if (stream.stream_ops.open) { stream.stream_ops.open(stream) } if (Module["logReadFiles"] && !(flags & 1)) { if (!FS.readFiles) FS.readFiles = {}; if (!(path in FS.readFiles)) { FS.readFiles[path] = 1; console.log("FS.trackingDelegate error on read file: " + path) } } try { if (FS.trackingDelegate["onOpenFile"]) { var trackingFlags = 0; if ((flags & 2097155) !== 1) { trackingFlags |= FS.tracking.openFlags.READ } if ((flags & 2097155) !== 0) { trackingFlags |= FS.tracking.openFlags.WRITE } FS.trackingDelegate["onOpenFile"](path, trackingFlags) } } catch (e) { console.log("FS.trackingDelegate['onOpenFile']('" + path + "', flags) threw an exception: " + e.message) } return stream }, close: function (stream) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(9) } if (stream.getdents) stream.getdents = null; try { if (stream.stream_ops.close) { stream.stream_ops.close(stream) } } catch (e) { throw e } finally { FS.closeStream(stream.fd) } stream.fd = null }, isClosed: function (stream) { return stream.fd === null }, llseek: function (stream, offset, whence) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(9) } if (!stream.seekable || !stream.stream_ops.llseek) { throw new FS.ErrnoError(29) } if (whence != 0 && whence != 1 && whence != 2) { throw new FS.ErrnoError(22) } stream.position = stream.stream_ops.llseek(stream, offset, whence); stream.ungotten = []; return stream.position }, read: function (stream, buffer, offset, length, position) { if (length < 0 || position < 0) { throw new FS.ErrnoError(22) } if (FS.isClosed(stream)) { throw new FS.ErrnoError(9) } if ((stream.flags & 2097155) === 1) { throw new FS.ErrnoError(9) } if (FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(21) } if (!stream.stream_ops.read) { throw new FS.ErrnoError(22) } var seeking = typeof position !== "undefined"; if (!seeking) { position = stream.position } else if (!stream.seekable) { throw new FS.ErrnoError(29) } var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); if (!seeking) stream.position += bytesRead; return bytesRead }, write: function (stream, buffer, offset, length, position, canOwn) { if (length < 0 || position < 0) { throw new FS.ErrnoError(22) } if (FS.isClosed(stream)) { throw new FS.ErrnoError(9) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(9) } if (FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(21) } if (!stream.stream_ops.write) { throw new FS.ErrnoError(22) } if (stream.flags & 1024) { FS.llseek(stream, 0, 2) } var seeking = typeof position !== "undefined"; if (!seeking) { position = stream.position } else if (!stream.seekable) { throw new FS.ErrnoError(29) } var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); if (!seeking) stream.position += bytesWritten; try { if (stream.path && FS.trackingDelegate["onWriteToFile"]) FS.trackingDelegate["onWriteToFile"](stream.path) } catch (e) { console.log("FS.trackingDelegate['onWriteToFile']('" + stream.path + "') threw an exception: " + e.message) } return bytesWritten }, allocate: function (stream, offset, length) { if (FS.isClosed(stream)) { throw new FS.ErrnoError(9) } if (offset < 0 || length <= 0) { throw new FS.ErrnoError(22) } if ((stream.flags & 2097155) === 0) { throw new FS.ErrnoError(9) } if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) { throw new FS.ErrnoError(19) } if (!stream.stream_ops.allocate) { throw new FS.ErrnoError(95) } stream.stream_ops.allocate(stream, offset, length) }, mmap: function (stream, buffer, offset, length, position, prot, flags) { if ((stream.flags & 2097155) === 1) { throw new FS.ErrnoError(13) } if (!stream.stream_ops.mmap) { throw new FS.ErrnoError(19) } return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags) }, msync: function (stream, buffer, offset, length, mmapFlags) { if (!stream || !stream.stream_ops.msync) { return 0 } return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags) }, munmap: function (stream) { return 0 }, ioctl: function (stream, cmd, arg) { if (!stream.stream_ops.ioctl) { throw new FS.ErrnoError(25) } return stream.stream_ops.ioctl(stream, cmd, arg) }, readFile: function (path, opts) { opts = opts || {}; opts.flags = opts.flags || "r"; opts.encoding = opts.encoding || "binary"; if (opts.encoding !== "utf8" && opts.encoding !== "binary") { throw new Error('Invalid encoding type "' + opts.encoding + '"') } var ret; var stream = FS.open(path, opts.flags); var stat = FS.stat(path); var length = stat.size; var buf = new Uint8Array(length); FS.read(stream, buf, 0, length, 0); if (opts.encoding === "utf8") { ret = UTF8ArrayToString(buf, 0) } else if (opts.encoding === "binary") { ret = buf } FS.close(stream); return ret }, writeFile: function (path, data, opts) { opts = opts || {}; opts.flags = opts.flags || "w"; var stream = FS.open(path, opts.flags, opts.mode); if (typeof data === "string") { var buf = new Uint8Array(lengthBytesUTF8(data) + 1); var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn) } else if (ArrayBuffer.isView(data)) { FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn) } else { throw new Error("Unsupported data type") } FS.close(stream) }, cwd: function () { return FS.currentPath }, chdir: function (path) { var lookup = FS.lookupPath(path, { follow: true }); if (lookup.node === null) { throw new FS.ErrnoError(2) } if (!FS.isDir(lookup.node.mode)) { throw new FS.ErrnoError(20) } var err = FS.nodePermissions(lookup.node, "x"); if (err) { throw new FS.ErrnoError(err) } FS.currentPath = lookup.path }, createDefaultDirectories: function () { FS.mkdir("/tmp"); FS.mkdir("/home"); FS.mkdir("/home/web_user") }, createDefaultDevices: function () { FS.mkdir("/dev"); FS.registerDevice(FS.makedev(1, 3), { read: function () { return 0 }, write: function (stream, buffer, offset, length, pos) { return length } }); FS.mkdev("/dev/null", FS.makedev(1, 3)); TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); FS.mkdev("/dev/tty", FS.makedev(5, 0)); FS.mkdev("/dev/tty1", FS.makedev(6, 0)); var random_device; if (typeof crypto === "object" && typeof crypto["getRandomValues"] === "function") { var randomBuffer = new Uint8Array(1); random_device = function () { crypto.getRandomValues(randomBuffer); return randomBuffer[0] } } else if (ENVIRONMENT_IS_NODE) { try { var crypto_module = require("crypto"); random_device = function () { return crypto_module["randomBytes"](1)[0] } } catch (e) { } } else { } if (!random_device) { random_device = function () { abort("random_device") } } FS.createDevice("/dev", "random", random_device); FS.createDevice("/dev", "urandom", random_device); FS.mkdir("/dev/shm"); FS.mkdir("/dev/shm/tmp") }, createSpecialDirectories: function () { FS.mkdir("/proc"); FS.mkdir("/proc/self"); FS.mkdir("/proc/self/fd"); FS.mount({ mount: function () { var node = FS.createNode("/proc/self", "fd", 16384 | 511, 73); node.node_ops = { lookup: function (parent, name) { var fd = +name; var stream = FS.getStream(fd); if (!stream) throw new FS.ErrnoError(9); var ret = { parent: null, mount: { mountpoint: "fake" }, node_ops: { readlink: function () { return stream.path } } }; ret.parent = ret; return ret } }; return node } }, {}, "/proc/self/fd") }, createStandardStreams: function () { if (Module["stdin"]) { FS.createDevice("/dev", "stdin", Module["stdin"]) } else { FS.symlink("/dev/tty", "/dev/stdin") } if (Module["stdout"]) { FS.createDevice("/dev", "stdout", null, Module["stdout"]) } else { FS.symlink("/dev/tty", "/dev/stdout") } if (Module["stderr"]) { FS.createDevice("/dev", "stderr", null, Module["stderr"]) } else { FS.symlink("/dev/tty1", "/dev/stderr") } var stdin = FS.open("/dev/stdin", "r"); var stdout = FS.open("/dev/stdout", "w"); var stderr = FS.open("/dev/stderr", "w") }, ensureErrnoError: function () { if (FS.ErrnoError) return; FS.ErrnoError = function ErrnoError(errno, node) { this.node = node; this.setErrno = function (errno) { this.errno = errno }; this.setErrno(errno); this.message = "FS error"; if (this.stack) Object.defineProperty(this, "stack", { value: (new Error).stack, writable: true }) }; FS.ErrnoError.prototype = new Error; FS.ErrnoError.prototype.constructor = FS.ErrnoError;[2].forEach(function (code) { FS.genericErrors[code] = new FS.ErrnoError(code); FS.genericErrors[code].stack = "" }) }, staticInit: function () { FS.ensureErrnoError(); FS.nameTable = new Array(4096); FS.mount(MEMFS, {}, "/"); FS.createDefaultDirectories(); FS.createDefaultDevices(); FS.createSpecialDirectories(); FS.filesystems = { "MEMFS": MEMFS, "IDBFS": IDBFS, "NODEFS": NODEFS, "WORKERFS": WORKERFS } }, init: function (input, output, error) { FS.init.initialized = true; FS.ensureErrnoError(); Module["stdin"] = input || Module["stdin"]; Module["stdout"] = output || Module["stdout"]; Module["stderr"] = error || Module["stderr"]; FS.createStandardStreams() }, quit: function () { FS.init.initialized = false; var fflush = Module["_fflush"]; if (fflush) fflush(0); for (var i = 0; i < FS.streams.length; i++) { var stream = FS.streams[i]; if (!stream) { continue } FS.close(stream) } }, getMode: function (canRead, canWrite) { var mode = 0; if (canRead) mode |= 292 | 73; if (canWrite) mode |= 146; return mode }, joinPath: function (parts, forceRelative) { var path = PATH.join.apply(null, parts); if (forceRelative && path[0] == "/") path = path.substr(1); return path }, absolutePath: function (relative, base) { return PATH_FS.resolve(base, relative) }, standardizePath: function (path) { return PATH.normalize(path) }, findObject: function (path, dontResolveLastLink) { var ret = FS.analyzePath(path, dontResolveLastLink); if (ret.exists) { return ret.object } else { ___setErrNo(ret.error); return null } }, analyzePath: function (path, dontResolveLastLink) { try { var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); path = lookup.path } catch (e) { } var ret = { isRoot: false, exists: false, error: 0, name: null, path: null, object: null, parentExists: false, parentPath: null, parentObject: null }; try { var lookup = FS.lookupPath(path, { parent: true }); ret.parentExists = true; ret.parentPath = lookup.path; ret.parentObject = lookup.node; ret.name = PATH.basename(path); lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); ret.exists = true; ret.path = lookup.path; ret.object = lookup.node; ret.name = lookup.node.name; ret.isRoot = lookup.path === "/" } catch (e) { ret.error = e.errno } return ret }, createFolder: function (parent, name, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(canRead, canWrite); return FS.mkdir(path, mode) }, createPath: function (parent, path, canRead, canWrite) { parent = typeof parent === "string" ? parent : FS.getPath(parent); var parts = path.split("/").reverse(); while (parts.length) { var part = parts.pop(); if (!part) continue; var current = PATH.join2(parent, part); try { FS.mkdir(current) } catch (e) { } parent = current } return current }, createFile: function (parent, name, properties, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(canRead, canWrite); return FS.create(path, mode) }, createDataFile: function (parent, name, data, canRead, canWrite, canOwn) { var path = name ? PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name) : parent; var mode = FS.getMode(canRead, canWrite); var node = FS.create(path, mode); if (data) { if (typeof data === "string") { var arr = new Array(data.length); for (var i = 0, len = data.length; i < len; ++i)arr[i] = data.charCodeAt(i); data = arr } FS.chmod(node, mode | 146); var stream = FS.open(node, "w"); FS.write(stream, data, 0, data.length, 0, canOwn); FS.close(stream); FS.chmod(node, mode) } return node }, createDevice: function (parent, name, input, output) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); var mode = FS.getMode(!!input, !!output); if (!FS.createDevice.major) FS.createDevice.major = 64; var dev = FS.makedev(FS.createDevice.major++, 0); FS.registerDevice(dev, { open: function (stream) { stream.seekable = false }, close: function (stream) { if (output && output.buffer && output.buffer.length) { output(10) } }, read: function (stream, buffer, offset, length, pos) { var bytesRead = 0; for (var i = 0; i < length; i++) { var result; try { result = input() } catch (e) { throw new FS.ErrnoError(5) } if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError(11) } if (result === null || result === undefined) break; bytesRead++; buffer[offset + i] = result } if (bytesRead) { stream.node.timestamp = Date.now() } return bytesRead }, write: function (stream, buffer, offset, length, pos) { for (var i = 0; i < length; i++) { try { output(buffer[offset + i]) } catch (e) { throw new FS.ErrnoError(5) } } if (length) { stream.node.timestamp = Date.now() } return i } }); return FS.mkdev(path, mode, dev) }, createLink: function (parent, name, target, canRead, canWrite) { var path = PATH.join2(typeof parent === "string" ? parent : FS.getPath(parent), name); return FS.symlink(target, path) }, forceLoadFile: function (obj) { if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; var success = true; if (typeof XMLHttpRequest !== "undefined") { throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.") } else if (Module["read"]) { try { obj.contents = intArrayFromString(Module["read"](obj.url), true); obj.usedBytes = obj.contents.length } catch (e) { success = false } } else { throw new Error("Cannot load without read() or XMLHttpRequest.") } if (!success) ___setErrNo(5); return success }, createLazyFile: function (parent, name, url, canRead, canWrite) { function LazyUint8Array() { this.lengthKnown = false; this.chunks = [] } LazyUint8Array.prototype.get = function LazyUint8Array_get(idx) { if (idx > this.length - 1 || idx < 0) { return undefined } var chunkOffset = idx % this.chunkSize; var chunkNum = idx / this.chunkSize | 0; return this.getter(chunkNum)[chunkOffset] }; LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) { this.getter = getter }; LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { var xhr = new XMLHttpRequest; xhr.open("HEAD", url, false); xhr.send(null); if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); var datalength = Number(xhr.getResponseHeader("Content-length")); var header; var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; var chunkSize = 1024 * 1024; if (!hasByteServing) chunkSize = datalength; var doXHR = function (from, to) { if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); if (to > datalength - 1) throw new Error("only " + datalength + " bytes available! programmer error!"); var xhr = new XMLHttpRequest; xhr.open("GET", url, false); if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); if (typeof Uint8Array != "undefined") xhr.responseType = "arraybuffer"; if (xhr.overrideMimeType) { xhr.overrideMimeType("text/plain; charset=x-user-defined") } xhr.send(null); if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); if (xhr.response !== undefined) { return new Uint8Array(xhr.response || []) } else { return intArrayFromString(xhr.responseText || "", true) } }; var lazyArray = this; lazyArray.setDataGetter(function (chunkNum) { var start = chunkNum * chunkSize; var end = (chunkNum + 1) * chunkSize - 1; end = Math.min(end, datalength - 1); if (typeof lazyArray.chunks[chunkNum] === "undefined") { lazyArray.chunks[chunkNum] = doXHR(start, end) } if (typeof lazyArray.chunks[chunkNum] === "undefined") throw new Error("doXHR failed!"); return lazyArray.chunks[chunkNum] }); if (usesGzip || !datalength) { chunkSize = datalength = 1; datalength = this.getter(0).length; chunkSize = datalength; console.log("LazyFiles on gzip forces download of the whole file when length is accessed") } this._length = datalength; this._chunkSize = chunkSize; this.lengthKnown = true }; if (typeof XMLHttpRequest !== "undefined") { if (!ENVIRONMENT_IS_WORKER) throw "Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc"; var lazyArray = new LazyUint8Array; Object.defineProperties(lazyArray, { length: { get: function () { if (!this.lengthKnown) { this.cacheLength() } return this._length } }, chunkSize: { get: function () { if (!this.lengthKnown) { this.cacheLength() } return this._chunkSize } } }); var properties = { isDevice: false, contents: lazyArray } } else { var properties = { isDevice: false, url: url } } var node = FS.createFile(parent, name, properties, canRead, canWrite); if (properties.contents) { node.contents = properties.contents } else if (properties.url) { node.contents = null; node.url = properties.url } Object.defineProperties(node, { usedBytes: { get: function () { return this.contents.length } } }); var stream_ops = {}; var keys = Object.keys(node.stream_ops); keys.forEach(function (key) { var fn = node.stream_ops[key]; stream_ops[key] = function forceLoadLazyFile() { if (!FS.forceLoadFile(node)) { throw new FS.ErrnoError(5) } return fn.apply(null, arguments) } }); stream_ops.read = function stream_ops_read(stream, buffer, offset, length, position) { if (!FS.forceLoadFile(node)) { throw new FS.ErrnoError(5) } var contents = stream.node.contents; if (position >= contents.length) return 0; var size = Math.min(contents.length - position, length); if (contents.slice) { for (var i = 0; i < size; i++) { buffer[offset + i] = contents[position + i] } } else { for (var i = 0; i < size; i++) { buffer[offset + i] = contents.get(position + i) } } return size }; node.stream_ops = stream_ops; return node }, createPreloadedFile: function (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) { Browser.init(); var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; var dep = getUniqueRunDependency("cp " + fullname); function processData(byteArray) { function finish(byteArray) { if (preFinish) preFinish(); if (!dontCreateFile) { FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn) } if (onload) onload(); removeRunDependency(dep) } var handled = false; Module["preloadPlugins"].forEach(function (plugin) { if (handled) return; if (plugin["canHandle"](fullname)) { plugin["handle"](byteArray, fullname, finish, function () { if (onerror) onerror(); removeRunDependency(dep) }); handled = true } }); if (!handled) finish(byteArray) } addRunDependency(dep); if (typeof url == "string") { Browser.asyncLoad(url, function (byteArray) { processData(byteArray) }, onerror) } else { processData(url) } }, indexedDB: function () { return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB }, DB_NAME: function () { return "EM_FS_" + window.location.pathname }, DB_VERSION: 20, DB_STORE_NAME: "FILE_DATA", saveFilesToDB: function (paths, onload, onerror) { onload = onload || function () { }; onerror = onerror || function () { }; var indexedDB = FS.indexedDB(); try { var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION) } catch (e) { return onerror(e) } openRequest.onupgradeneeded = function openRequest_onupgradeneeded() { console.log("creating db"); var db = openRequest.result; db.createObjectStore(FS.DB_STORE_NAME) }; openRequest.onsuccess = function openRequest_onsuccess() { var db = openRequest.result; var transaction = db.transaction([FS.DB_STORE_NAME], "readwrite"); var files = transaction.objectStore(FS.DB_STORE_NAME); var ok = 0, fail = 0, total = paths.length; function finish() { if (fail == 0) onload(); else onerror() } paths.forEach(function (path) { var putRequest = files.put(FS.analyzePath(path).object.contents, path); putRequest.onsuccess = function putRequest_onsuccess() { ok++; if (ok + fail == total) finish() }; putRequest.onerror = function putRequest_onerror() { fail++; if (ok + fail == total) finish() } }); transaction.onerror = onerror }; openRequest.onerror = onerror }, loadFilesFromDB: function (paths, onload, onerror) { onload = onload || function () { }; onerror = onerror || function () { }; var indexedDB = FS.indexedDB(); try { var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION) } catch (e) { return onerror(e) } openRequest.onupgradeneeded = onerror; openRequest.onsuccess = function openRequest_onsuccess() { var db = openRequest.result; try { var transaction = db.transaction([FS.DB_STORE_NAME], "readonly") } catch (e) { onerror(e); return } var files = transaction.objectStore(FS.DB_STORE_NAME); var ok = 0, fail = 0, total = paths.length; function finish() { if (fail == 0) onload(); else onerror() } paths.forEach(function (path) { var getRequest = files.get(path); getRequest.onsuccess = function getRequest_onsuccess() { if (FS.analyzePath(path).exists) { FS.unlink(path) } FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); ok++; if (ok + fail == total) finish() }; getRequest.onerror = function getRequest_onerror() { fail++; if (ok + fail == total) finish() } }); transaction.onerror = onerror }; openRequest.onerror = onerror } }; var SYSCALLS = { DEFAULT_POLLMASK: 5, mappings: {}, umask: 511, calculateAt: function (dirfd, path) { if (path[0] !== "/") { var dir; if (dirfd === -100) { dir = FS.cwd() } else { var dirstream = FS.getStream(dirfd); if (!dirstream) throw new FS.ErrnoError(9); dir = dirstream.path } path = PATH.join2(dir, path) } return path }, doStat: function (func, path, buf) { try { var stat = func(path) } catch (e) { if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { return -20 } throw e } HEAP32[buf >> 2] = stat.dev; HEAP32[buf + 4 >> 2] = 0; HEAP32[buf + 8 >> 2] = stat.ino; HEAP32[buf + 12 >> 2] = stat.mode; HEAP32[buf + 16 >> 2] = stat.nlink; HEAP32[buf + 20 >> 2] = stat.uid; HEAP32[buf + 24 >> 2] = stat.gid; HEAP32[buf + 28 >> 2] = stat.rdev; HEAP32[buf + 32 >> 2] = 0; tempI64 = [stat.size >>> 0, (tempDouble = stat.size, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 40 >> 2] = tempI64[0], HEAP32[buf + 44 >> 2] = tempI64[1]; HEAP32[buf + 48 >> 2] = 4096; HEAP32[buf + 52 >> 2] = stat.blocks; HEAP32[buf + 56 >> 2] = stat.atime.getTime() / 1e3 | 0; HEAP32[buf + 60 >> 2] = 0; HEAP32[buf + 64 >> 2] = stat.mtime.getTime() / 1e3 | 0; HEAP32[buf + 68 >> 2] = 0; HEAP32[buf + 72 >> 2] = stat.ctime.getTime() / 1e3 | 0; HEAP32[buf + 76 >> 2] = 0; tempI64 = [stat.ino >>> 0, (tempDouble = stat.ino, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 80 >> 2] = tempI64[0], HEAP32[buf + 84 >> 2] = tempI64[1]; return 0 }, doMsync: function (addr, stream, len, flags) { var buffer = new Uint8Array(HEAPU8.subarray(addr, addr + len)); FS.msync(stream, buffer, 0, len, flags) }, doMkdir: function (path, mode) { path = PATH.normalize(path); if (path[path.length - 1] === "/") path = path.substr(0, path.length - 1); FS.mkdir(path, mode, 0); return 0 }, doMknod: function (path, mode, dev) { switch (mode & 61440) { case 32768: case 8192: case 24576: case 4096: case 49152: break; default: return -22 }FS.mknod(path, mode, dev); return 0 }, doReadlink: function (path, buf, bufsize) { if (bufsize <= 0) return -22; var ret = FS.readlink(path); var len = Math.min(bufsize, lengthBytesUTF8(ret)); var endChar = HEAP8[buf + len]; stringToUTF8(ret, buf, bufsize + 1); HEAP8[buf + len] = endChar; return len }, doAccess: function (path, amode) { if (amode & ~7) { return -22 } var node; var lookup = FS.lookupPath(path, { follow: true }); node = lookup.node; var perms = ""; if (amode & 4) perms += "r"; if (amode & 2) perms += "w"; if (amode & 1) perms += "x"; if (perms && FS.nodePermissions(node, perms)) { return -13 } return 0 }, doDup: function (path, flags, suggestFD) { var suggest = FS.getStream(suggestFD); if (suggest) FS.close(suggest); return FS.open(path, flags, 0, suggestFD, suggestFD).fd }, doReadv: function (stream, iov, iovcnt, offset) { var ret = 0; for (var i = 0; i < iovcnt; i++) { var ptr = HEAP32[iov + i * 8 >> 2]; var len = HEAP32[iov + (i * 8 + 4) >> 2]; var curr = FS.read(stream, HEAP8, ptr, len, offset); if (curr < 0) return -1; ret += curr; if (curr < len) break } return ret }, doWritev: function (stream, iov, iovcnt, offset) { var ret = 0; for (var i = 0; i < iovcnt; i++) { var ptr = HEAP32[iov + i * 8 >> 2]; var len = HEAP32[iov + (i * 8 + 4) >> 2]; var curr = FS.write(stream, HEAP8, ptr, len, offset); if (curr < 0) return -1; ret += curr } return ret }, varargs: 0, get: function (varargs) { SYSCALLS.varargs += 4; var ret = HEAP32[SYSCALLS.varargs - 4 >> 2]; return ret }, getStr: function () { var ret = UTF8ToString(SYSCALLS.get()); return ret }, getStreamFromFD: function () { var stream = FS.getStream(SYSCALLS.get()); if (!stream) throw new FS.ErrnoError(9); return stream }, get64: function () { var low = SYSCALLS.get(), high = SYSCALLS.get(); return low }, getZero: function () { SYSCALLS.get() } }; function ___syscall140(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), offset_high = SYSCALLS.get(), offset_low = SYSCALLS.get(), result = SYSCALLS.get(), whence = SYSCALLS.get(); if (!(offset_high == -1 && offset_low < 0) && !(offset_high == 0 && offset_low >= 0)) { return -75 } var offset = offset_low; FS.llseek(stream, offset, whence); tempI64 = [stream.position >>> 0, (tempDouble = stream.position, +Math_abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[result >> 2] = tempI64[0], HEAP32[result + 4 >> 2] = tempI64[1]; if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall146(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), iov = SYSCALLS.get(), iovcnt = SYSCALLS.get(); return SYSCALLS.doWritev(stream, iov, iovcnt) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall168(which, varargs) { SYSCALLS.varargs = varargs; try { var fds = SYSCALLS.get(), nfds = SYSCALLS.get(), timeout = SYSCALLS.get(); var nonzero = 0; for (var i = 0; i < nfds; i++) { var pollfd = fds + 8 * i; var fd = HEAP32[pollfd >> 2]; var events = HEAP16[pollfd + 4 >> 1]; var mask = 32; var stream = FS.getStream(fd); if (stream) { mask = SYSCALLS.DEFAULT_POLLMASK; if (stream.stream_ops.poll) { mask = stream.stream_ops.poll(stream) } } mask &= events | 8 | 16; if (mask) nonzero++; HEAP16[pollfd + 6 >> 1] = mask } return nonzero } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall195(which, varargs) { SYSCALLS.varargs = varargs; try { var path = SYSCALLS.getStr(), buf = SYSCALLS.get(); return SYSCALLS.doStat(FS.stat, path, buf) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall196(which, varargs) { SYSCALLS.varargs = varargs; try { var path = SYSCALLS.getStr(), buf = SYSCALLS.get(); return SYSCALLS.doStat(FS.lstat, path, buf) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall197(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), buf = SYSCALLS.get(); return SYSCALLS.doStat(FS.stat, stream.path, buf) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall221(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), cmd = SYSCALLS.get(); switch (cmd) { case 0: { var arg = SYSCALLS.get(); if (arg < 0) { return -22 } var newStream; newStream = FS.open(stream.path, stream.flags, 0, arg); return newStream.fd } case 1: case 2: return 0; case 3: return stream.flags; case 4: { var arg = SYSCALLS.get(); stream.flags |= arg; return 0 } case 12: { var arg = SYSCALLS.get(); var offset = 0; HEAP16[arg + offset >> 1] = 2; return 0 } case 13: case 14: return 0; case 16: case 8: return -22; case 9: ___setErrNo(22); return -1; default: { return -22 } } } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall3(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), buf = SYSCALLS.get(), count = SYSCALLS.get(); return FS.read(stream, HEAP8, buf, count) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall4(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(), buf = SYSCALLS.get(), count = SYSCALLS.get(); return FS.write(stream, HEAP8, buf, count) } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall41(which, varargs) { SYSCALLS.varargs = varargs; try { var old = SYSCALLS.getStreamFromFD(); return FS.open(old.path, old.flags, 0).fd } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } var ERRNO_CODES = { EPERM: 1, ENOENT: 2, ESRCH: 3, EINTR: 4, EIO: 5, ENXIO: 6, E2BIG: 7, ENOEXEC: 8, EBADF: 9, ECHILD: 10, EAGAIN: 11, EWOULDBLOCK: 11, ENOMEM: 12, EACCES: 13, EFAULT: 14, ENOTBLK: 15, EBUSY: 16, EEXIST: 17, EXDEV: 18, ENODEV: 19, ENOTDIR: 20, EISDIR: 21, EINVAL: 22, ENFILE: 23, EMFILE: 24, ENOTTY: 25, ETXTBSY: 26, EFBIG: 27, ENOSPC: 28, ESPIPE: 29, EROFS: 30, EMLINK: 31, EPIPE: 32, EDOM: 33, ERANGE: 34, ENOMSG: 42, EIDRM: 43, ECHRNG: 44, EL2NSYNC: 45, EL3HLT: 46, EL3RST: 47, ELNRNG: 48, EUNATCH: 49, ENOCSI: 50, EL2HLT: 51, EDEADLK: 35, ENOLCK: 37, EBADE: 52, EBADR: 53, EXFULL: 54, ENOANO: 55, EBADRQC: 56, EBADSLT: 57, EDEADLOCK: 35, EBFONT: 59, ENOSTR: 60, ENODATA: 61, ETIME: 62, ENOSR: 63, ENONET: 64, ENOPKG: 65, EREMOTE: 66, ENOLINK: 67, EADV: 68, ESRMNT: 69, ECOMM: 70, EPROTO: 71, EMULTIHOP: 72, EDOTDOT: 73, EBADMSG: 74, ENOTUNIQ: 76, EBADFD: 77, EREMCHG: 78, ELIBACC: 79, ELIBBAD: 80, ELIBSCN: 81, ELIBMAX: 82, ELIBEXEC: 83, ENOSYS: 38, ENOTEMPTY: 39, ENAMETOOLONG: 36, ELOOP: 40, EOPNOTSUPP: 95, EPFNOSUPPORT: 96, ECONNRESET: 104, ENOBUFS: 105, EAFNOSUPPORT: 97, EPROTOTYPE: 91, ENOTSOCK: 88, ENOPROTOOPT: 92, ESHUTDOWN: 108, ECONNREFUSED: 111, EADDRINUSE: 98, ECONNABORTED: 103, ENETUNREACH: 101, ENETDOWN: 100, ETIMEDOUT: 110, EHOSTDOWN: 112, EHOSTUNREACH: 113, EINPROGRESS: 115, EALREADY: 114, EDESTADDRREQ: 89, EMSGSIZE: 90, EPROTONOSUPPORT: 93, ESOCKTNOSUPPORT: 94, EADDRNOTAVAIL: 99, ENETRESET: 102, EISCONN: 106, ENOTCONN: 107, ETOOMANYREFS: 109, EUSERS: 87, EDQUOT: 122, ESTALE: 116, ENOTSUP: 95, ENOMEDIUM: 123, EILSEQ: 84, EOVERFLOW: 75, ECANCELED: 125, ENOTRECOVERABLE: 131, EOWNERDEAD: 130, ESTRPIPE: 86 }; var PIPEFS = { BUCKET_BUFFER_SIZE: 8192, mount: function (mount) { return FS.createNode(null, "/", 16384 | 511, 0) }, createPipe: function () { var pipe = { buckets: [] }; pipe.buckets.push({ buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), offset: 0, roffset: 0 }); var rName = PIPEFS.nextname(); var wName = PIPEFS.nextname(); var rNode = FS.createNode(PIPEFS.root, rName, 4096, 0); var wNode = FS.createNode(PIPEFS.root, wName, 4096, 0); rNode.pipe = pipe; wNode.pipe = pipe; var readableStream = FS.createStream({ path: rName, node: rNode, flags: FS.modeStringToFlags("r"), seekable: false, stream_ops: PIPEFS.stream_ops }); rNode.stream = readableStream; var writableStream = FS.createStream({ path: wName, node: wNode, flags: FS.modeStringToFlags("w"), seekable: false, stream_ops: PIPEFS.stream_ops }); wNode.stream = writableStream; return { readable_fd: readableStream.fd, writable_fd: writableStream.fd } }, stream_ops: { poll: function (stream) { var pipe = stream.node.pipe; if ((stream.flags & 2097155) === 1) { return 256 | 4 } else { if (pipe.buckets.length > 0) { for (var i = 0; i < pipe.buckets.length; i++) { var bucket = pipe.buckets[i]; if (bucket.offset - bucket.roffset > 0) { return 64 | 1 } } } } return 0 }, ioctl: function (stream, request, varargs) { return ERRNO_CODES.EINVAL }, read: function (stream, buffer, offset, length, position) { var pipe = stream.node.pipe; var currentLength = 0; for (var i = 0; i < pipe.buckets.length; i++) { var bucket = pipe.buckets[i]; currentLength += bucket.offset - bucket.roffset } assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer)); var data = buffer.subarray(offset, offset + length); if (length <= 0) { return 0 } if (currentLength == 0) { throw new FS.ErrnoError(ERRNO_CODES.EAGAIN) } var toRead = Math.min(currentLength, length); var totalRead = toRead; var toRemove = 0; for (var i = 0; i < pipe.buckets.length; i++) { var currBucket = pipe.buckets[i]; var bucketSize = currBucket.offset - currBucket.roffset; if (toRead <= bucketSize) { var tmpSlice = currBucket.buffer.subarray(currBucket.roffset, currBucket.offset); if (toRead < bucketSize) { tmpSlice = tmpSlice.subarray(0, toRead); currBucket.roffset += toRead } else { toRemove++ } data.set(tmpSlice); break } else { var tmpSlice = currBucket.buffer.subarray(currBucket.roffset, currBucket.offset); data.set(tmpSlice); data = data.subarray(tmpSlice.byteLength); toRead -= tmpSlice.byteLength; toRemove++ } } if (toRemove && toRemove == pipe.buckets.length) { toRemove--; pipe.buckets[toRemove].offset = 0; pipe.buckets[toRemove].roffset = 0 } pipe.buckets.splice(0, toRemove); return totalRead }, write: function (stream, buffer, offset, length, position) { var pipe = stream.node.pipe; assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer)); var data = buffer.subarray(offset, offset + length); var dataLen = data.byteLength; if (dataLen <= 0) { return 0 } var currBucket = null; if (pipe.buckets.length == 0) { currBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), offset: 0, roffset: 0 }; pipe.buckets.push(currBucket) } else { currBucket = pipe.buckets[pipe.buckets.length - 1] } assert(currBucket.offset <= PIPEFS.BUCKET_BUFFER_SIZE); var freeBytesInCurrBuffer = PIPEFS.BUCKET_BUFFER_SIZE - currBucket.offset; if (freeBytesInCurrBuffer >= dataLen) { currBucket.buffer.set(data, currBucket.offset); currBucket.offset += dataLen; return dataLen } else if (freeBytesInCurrBuffer > 0) { currBucket.buffer.set(data.subarray(0, freeBytesInCurrBuffer), currBucket.offset); currBucket.offset += freeBytesInCurrBuffer; data = data.subarray(freeBytesInCurrBuffer, data.byteLength) } var numBuckets = data.byteLength / PIPEFS.BUCKET_BUFFER_SIZE | 0; var remElements = data.byteLength % PIPEFS.BUCKET_BUFFER_SIZE; for (var i = 0; i < numBuckets; i++) { var newBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), offset: PIPEFS.BUCKET_BUFFER_SIZE, roffset: 0 }; pipe.buckets.push(newBucket); newBucket.buffer.set(data.subarray(0, PIPEFS.BUCKET_BUFFER_SIZE)); data = data.subarray(PIPEFS.BUCKET_BUFFER_SIZE, data.byteLength) } if (remElements > 0) { var newBucket = { buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), offset: data.byteLength, roffset: 0 }; pipe.buckets.push(newBucket); newBucket.buffer.set(data) } return dataLen }, close: function (stream) { var pipe = stream.node.pipe; pipe.buckets = null } }, nextname: function () { if (!PIPEFS.nextname.current) { PIPEFS.nextname.current = 0 } return "pipe[" + PIPEFS.nextname.current++ + "]" } }; function ___syscall42(which, varargs) { SYSCALLS.varargs = varargs; try { var fdPtr = SYSCALLS.get(); if (fdPtr == 0) { throw new FS.ErrnoError(14) } var res = PIPEFS.createPipe(); HEAP32[fdPtr >> 2] = res.readable_fd; HEAP32[fdPtr + 4 >> 2] = res.writable_fd; return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall5(which, varargs) { SYSCALLS.varargs = varargs; try { var pathname = SYSCALLS.getStr(), flags = SYSCALLS.get(), mode = SYSCALLS.get(); var stream = FS.open(pathname, flags, mode); return stream.fd } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function ___syscall6(which, varargs) { SYSCALLS.varargs = varargs; try { var stream = SYSCALLS.getStreamFromFD(); FS.close(stream); return 0 } catch (e) { if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e); return -e.errno } } function _abort() { Module["abort"]() } function _emscripten_get_heap_size() { return HEAP8.length } function abortOnCannotGrowMemory(requestedSize) { abort("OOM") } function emscripten_realloc_buffer(size) { var PAGE_MULTIPLE = 65536; size = alignUp(size, PAGE_MULTIPLE); var oldSize = buffer.byteLength; try { var result = wasmMemory.grow((size - oldSize) / 65536); if (result !== (-1 | 0)) { buffer = wasmMemory.buffer; return true } else { return false } } catch (e) { return false } } function _emscripten_resize_heap(requestedSize) { var oldSize = _emscripten_get_heap_size(); var PAGE_MULTIPLE = 65536; var LIMIT = 2147483648 - PAGE_MULTIPLE; if (requestedSize > LIMIT) { return false } var MIN_TOTAL_MEMORY = 16777216; var newSize = Math.max(oldSize, MIN_TOTAL_MEMORY); while (newSize < requestedSize) { if (newSize <= 536870912) { newSize = alignUp(2 * newSize, PAGE_MULTIPLE) } else { newSize = Math.min(alignUp((3 * newSize + 2147483648) / 4, PAGE_MULTIPLE), LIMIT) } } if (!emscripten_realloc_buffer(newSize)) { return false } updateGlobalBufferViews(); return true } function _exit(status) { exit(status) } var ___tm_current = 277408; var ___tm_timezone = (stringToUTF8("GMT", 277456, 4), 277456); function _tzset() { if (_tzset.called) return; _tzset.called = true; HEAP32[__get_timezone() >> 2] = (new Date).getTimezoneOffset() * 60; var winter = new Date(2e3, 0, 1); var summer = new Date(2e3, 6, 1); HEAP32[__get_daylight() >> 2] = Number(winter.getTimezoneOffset() != summer.getTimezoneOffset()); function extractZone(date) { var match = date.toTimeString().match(/\(([A-Za-z ]+)\)$/); return match ? match[1] : "GMT" } var winterName = extractZone(winter); var summerName = extractZone(summer); var winterNamePtr = allocate(intArrayFromString(winterName), "i8", ALLOC_NORMAL); var summerNamePtr = allocate(intArrayFromString(summerName), "i8", ALLOC_NORMAL); if (summer.getTimezoneOffset() < winter.getTimezoneOffset()) { HEAP32[__get_tzname() >> 2] = winterNamePtr; HEAP32[__get_tzname() + 4 >> 2] = summerNamePtr } else { HEAP32[__get_tzname() >> 2] = summerNamePtr; HEAP32[__get_tzname() + 4 >> 2] = winterNamePtr } } function _localtime_r(time, tmPtr) { _tzset(); var date = new Date(HEAP32[time >> 2] * 1e3); HEAP32[tmPtr >> 2] = date.getSeconds(); HEAP32[tmPtr + 4 >> 2] = date.getMinutes(); HEAP32[tmPtr + 8 >> 2] = date.getHours(); HEAP32[tmPtr + 12 >> 2] = date.getDate(); HEAP32[tmPtr + 16 >> 2] = date.getMonth(); HEAP32[tmPtr + 20 >> 2] = date.getFullYear() - 1900; HEAP32[tmPtr + 24 >> 2] = date.getDay(); var start = new Date(date.getFullYear(), 0, 1); var yday = (date.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24) | 0; HEAP32[tmPtr + 28 >> 2] = yday; HEAP32[tmPtr + 36 >> 2] = -(date.getTimezoneOffset() * 60); var summerOffset = new Date(2e3, 6, 1).getTimezoneOffset(); var winterOffset = start.getTimezoneOffset(); var dst = (summerOffset != winterOffset && date.getTimezoneOffset() == Math.min(winterOffset, summerOffset)) | 0; HEAP32[tmPtr + 32 >> 2] = dst; var zonePtr = HEAP32[__get_tzname() + (dst ? 4 : 0) >> 2]; HEAP32[tmPtr + 40 >> 2] = zonePtr; return tmPtr } function _localtime(time) { return _localtime_r(time, ___tm_current) } function _emscripten_memcpy_big(dest, src, num) { HEAPU8.set(HEAPU8.subarray(src, src + num), dest) } function _mktime(tmPtr) { _tzset(); var date = new Date(HEAP32[tmPtr + 20 >> 2] + 1900, HEAP32[tmPtr + 16 >> 2], HEAP32[tmPtr + 12 >> 2], HEAP32[tmPtr + 8 >> 2], HEAP32[tmPtr + 4 >> 2], HEAP32[tmPtr >> 2], 0); var dst = HEAP32[tmPtr + 32 >> 2]; var guessedOffset = date.getTimezoneOffset(); var start = new Date(date.getFullYear(), 0, 1); var summerOffset = new Date(2e3, 6, 1).getTimezoneOffset(); var winterOffset = start.getTimezoneOffset(); var dstOffset = Math.min(winterOffset, summerOffset); if (dst < 0) { HEAP32[tmPtr + 32 >> 2] = Number(summerOffset != winterOffset && dstOffset == guessedOffset) } else if (dst > 0 != (dstOffset == guessedOffset)) { var nonDstOffset = Math.max(winterOffset, summerOffset); var trueOffset = dst > 0 ? dstOffset : nonDstOffset; date.setTime(date.getTime() + (trueOffset - guessedOffset) * 6e4) } HEAP32[tmPtr + 24 >> 2] = date.getDay(); var yday = (date.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24) | 0; HEAP32[tmPtr + 28 >> 2] = yday; return date.getTime() / 1e3 | 0 } function _posix_spawn_file_actions_addclose() { err("missing function: posix_spawn_file_actions_addclose"); abort(-1) } function _posix_spawn_file_actions_adddup2() { err("missing function: posix_spawn_file_actions_adddup2"); abort(-1) } function _posix_spawn_file_actions_destroy() { err("missing function: posix_spawn_file_actions_destroy"); abort(-1) } function _posix_spawn_file_actions_init() { err("missing function: posix_spawn_file_actions_init"); abort(-1) } function _fork() { ___setErrNo(11); return -1 } function _posix_spawnp() { return _fork.apply(null, arguments) } function _timegm(tmPtr) { _tzset(); var time = Date.UTC(HEAP32[tmPtr + 20 >> 2] + 1900, HEAP32[tmPtr + 16 >> 2], HEAP32[tmPtr + 12 >> 2], HEAP32[tmPtr + 8 >> 2], HEAP32[tmPtr + 4 >> 2], HEAP32[tmPtr >> 2], 0); var date = new Date(time); HEAP32[tmPtr + 24 >> 2] = date.getUTCDay(); var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); var yday = (date.getTime() - start) / (1e3 * 60 * 60 * 24) | 0; HEAP32[tmPtr + 28 >> 2] = yday; return date.getTime() / 1e3 | 0 } function _wait(stat_loc) { ___setErrNo(10); return -1 } function _waitpid() { return _wait.apply(null, arguments) } FS.staticInit(); if (ENVIRONMENT_IS_NODE) { var fs = require("fs"); var NODEJS_PATH = require("path"); NODEFS.staticInit() } function intArrayFromString(stringy, dontAddNull, length) { var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; var u8array = new Array(len); var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); if (dontAddNull) u8array.length = numBytesWritten; return u8array } var asmGlobalArg = {}; var asmLibraryArg = { "b": abort, "q": setTempRet0, "G": ___buildEnvironment, "l": ___setErrNo, "s": ___syscall140, "i": ___syscall146, "p": ___syscall168, "o": ___syscall195, "n": ___syscall196, "m": ___syscall197, "c": ___syscall221, "F": ___syscall3, "E": ___syscall4, "D": ___syscall41, "C": ___syscall42, "B": ___syscall5, "h": ___syscall6, "g": _abort, "A": _emscripten_get_heap_size, "z": _emscripten_memcpy_big, "y": _emscripten_resize_heap, "f": _exit, "x": _localtime, "d": _mktime, "e": _posix_spawn_file_actions_addclose, "k": _posix_spawn_file_actions_adddup2, "j": _posix_spawn_file_actions_destroy, "w": _posix_spawn_file_actions_init, "v": _posix_spawnp, "u": _timegm, "t": _waitpid, "r": abortOnCannotGrowMemory, "a": DYNAMICTOP_PTR }; var asm = Module["asm"](asmGlobalArg, asmLibraryArg, buffer); Module["asm"] = asm; var ___emscripten_environ_constructor = Module["___emscripten_environ_constructor"] = function () { return Module["asm"]["H"].apply(null, arguments) }; var ___errno_location = Module["___errno_location"] = function () { return Module["asm"]["I"].apply(null, arguments) }; var __get_daylight = Module["__get_daylight"] = function () { return Module["asm"]["J"].apply(null, arguments) }; var __get_timezone = Module["__get_timezone"] = function () { return Module["asm"]["K"].apply(null, arguments) }; var __get_tzname = Module["__get_tzname"] = function () { return Module["asm"]["L"].apply(null, arguments) }; var _archive_close = Module["_archive_close"] = function () { return Module["asm"]["M"].apply(null, arguments) }; var _archive_entry_filetype = Module["_archive_entry_filetype"] = function () { return Module["asm"]["N"].apply(null, arguments) }; var _archive_entry_is_encrypted = Module["_archive_entry_is_encrypted"] = function () { return Module["asm"]["O"].apply(null, arguments) }; var _archive_entry_pathname = Module["_archive_entry_pathname"] = function () { return Module["asm"]["P"].apply(null, arguments) }; var _archive_entry_pathname_utf8 = Module["_archive_entry_pathname_utf8"] = function () { return Module["asm"]["Q"].apply(null, arguments) }; var _archive_entry_size = Module["_archive_entry_size"] = function () { return Module["asm"]["R"].apply(null, arguments) }; var _archive_error_string = Module["_archive_error_string"] = function () { return Module["asm"]["S"].apply(null, arguments) }; var _archive_open = Module["_archive_open"] = function () { return Module["asm"]["T"].apply(null, arguments) }; var _archive_read_add_passphrase = Module["_archive_read_add_passphrase"] = function () { return Module["asm"]["U"].apply(null, arguments) }; var _archive_read_data_skip = Module["_archive_read_data_skip"] = function () { return Module["asm"]["V"].apply(null, arguments) }; var _archive_read_has_encrypted_entries = Module["_archive_read_has_encrypted_entries"] = function () { return Module["asm"]["W"].apply(null, arguments) }; var _free = Module["_free"] = function () { return Module["asm"]["X"].apply(null, arguments) }; var _get_filedata = Module["_get_filedata"] = function () { return Module["asm"]["Y"].apply(null, arguments) }; var _get_next_entry = Module["_get_next_entry"] = function () { return Module["asm"]["Z"].apply(null, arguments) }; var _get_version = Module["_get_version"] = function () { return Module["asm"]["_"].apply(null, arguments) }; var _malloc = Module["_malloc"] = function () { return Module["asm"]["$"].apply(null, arguments) }; var stackAlloc = Module["stackAlloc"] = function () { return Module["asm"]["ca"].apply(null, arguments) }; var stackRestore = Module["stackRestore"] = function () { return Module["asm"]["da"].apply(null, arguments) }; var stackSave = Module["stackSave"] = function () { return Module["asm"]["ea"].apply(null, arguments) }; var dynCall_v = Module["dynCall_v"] = function () { return Module["asm"]["aa"].apply(null, arguments) }; var dynCall_vi = Module["dynCall_vi"] = function () { return Module["asm"]["ba"].apply(null, arguments) }; Module["asm"] = asm; Module["intArrayFromString"] = intArrayFromString; Module["cwrap"] = cwrap; Module["allocate"] = allocate; Module["then"] = function (func) { if (Module["calledRun"]) { func(Module) } else { var old = Module["onRuntimeInitialized"]; Module["onRuntimeInitialized"] = function () { if (old) old(); func(Module) } } return Module }; function ExitStatus(status) { this.name = "ExitStatus"; this.message = "Program terminated with exit(" + status + ")"; this.status = status } ExitStatus.prototype = new Error; ExitStatus.prototype.constructor = ExitStatus; dependenciesFulfilled = function runCaller() { if (!Module["calledRun"]) run(); if (!Module["calledRun"]) dependenciesFulfilled = runCaller }; function run(args) { args = args || Module["arguments"]; if (runDependencies > 0) { return } preRun(); if (runDependencies > 0) return; if (Module["calledRun"]) return; function doRun() { if (Module["calledRun"]) return; Module["calledRun"] = true; if (ABORT) return; ensureInitRuntime(); preMain(); if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); postRun() } if (Module["setStatus"]) { Module["setStatus"]("Running..."); setTimeout(function () { setTimeout(function () { Module["setStatus"]("") }, 1); doRun() }, 1) } else { doRun() } } Module["run"] = run; function exit(status, implicit) { if (implicit && Module["noExitRuntime"] && status === 0) { return } if (Module["noExitRuntime"]) { } else { ABORT = true; EXITSTATUS = status; exitRuntime(); if (Module["onExit"]) Module["onExit"](status) } Module["quit"](status, new ExitStatus(status)) } function abort(what) { if (Module["onAbort"]) { Module["onAbort"](what) } if (what !== undefined) { out(what); err(what); what = JSON.stringify(what) } else { what = "" } ABORT = true; EXITSTATUS = 1; throw "abort(" + what + "). Build with -s ASSERTIONS=1 for more info." } Module["abort"] = abort; if (Module["preInit"]) { if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]]; while (Module["preInit"].length > 0) { Module["preInit"].pop()() } } Module["noExitRuntime"] = true; run() + + + return libarchive + } + ) +})() +module.exports = libarchive \ No newline at end of file diff --git a/server/libs/libarchive/wasm-module.js b/server/libs/libarchive/wasm-module.js new file mode 100644 index 00000000..2e3a4ec0 --- /dev/null +++ b/server/libs/libarchive/wasm-module.js @@ -0,0 +1,235 @@ +/** + * Modified from https://github.com/nika-begiashvili/libarchivejs + */ + +const Path = require('path') +const libarchive = require('./wasm-libarchive') + +const TYPE_MAP = { + 32768: 'FILE', + 16384: 'DIR', + 40960: 'SYMBOLIC_LINK', + 49152: 'SOCKET', + 8192: 'CHARACTER_DEVICE', + 24576: 'BLOCK_DEVICE', + 4096: 'NAMED_PIPE', +} + +class ArchiveReader { + /** + * archive reader + * @param {WasmModule} wasmModule emscripten module + */ + constructor(wasmModule) { + this._wasmModule = wasmModule + this._runCode = wasmModule.runCode + this._file = null + this._passphrase = null + } + + /** + * open archive, needs to closed manually + * @param {File} file + */ + open(file) { + if (this._file !== null) { + console.warn('Closing previous file') + this.close() + } + const { promise, resolve, reject } = this._promiseHandles() + this._file = file + this._loadFile(file, resolve, reject) + return promise + } + + /** + * close archive + */ + close() { + this._runCode.closeArchive(this._archive) + this._wasmModule._free(this._filePtr) + this._file = null + this._filePtr = null + this._archive = null + } + + /** + * detect if archive has encrypted data + * @returns {boolean|null} null if could not be determined + */ + hasEncryptedData() { + this._archive = this._runCode.openArchive(this._filePtr, this._fileLength, this._passphrase) + this._runCode.getNextEntry(this._archive) + const status = this._runCode.hasEncryptedEntries(this._archive) + if (status === 0) { + return false + } else if (status > 0) { + return true + } else { + return null + } + } + + /** + * set passphrase to be used with archive + * @param {*} passphrase + */ + setPassphrase(passphrase) { + this._passphrase = passphrase + } + + /** + * get archive entries + * @param {boolean} skipExtraction + * @param {string} except don't skip this entry + */ + *entries(skipExtraction = false, except = null) { + this._archive = this._runCode.openArchive(this._filePtr, this._fileLength, this._passphrase) + let entry + while (true) { + entry = this._runCode.getNextEntry(this._archive) + if (entry === 0) break + + const entryData = { + size: this._runCode.getEntrySize(entry), + path: this._runCode.getEntryName(entry), + type: TYPE_MAP[this._runCode.getEntryType(entry)], + ref: entry, + } + + if (entryData.type === 'FILE') { + let fileName = entryData.path.split('/') + entryData.fileName = fileName[fileName.length - 1] + } + + if (skipExtraction && except !== entryData.path) { + this._runCode.skipEntry(this._archive) + } else { + const ptr = this._runCode.getFileData(this._archive, entryData.size) + if (ptr < 0) { + throw new Error(this._runCode.getError(this._archive)) + } + entryData.fileData = this._wasmModule.HEAP8.slice(ptr, ptr + entryData.size) + this._wasmModule._free(ptr) + } + yield entryData + } + } + + _loadFile(fileBuffer, resolve, reject) { + try { + const array = new Uint8Array(fileBuffer) + this._fileLength = array.length + this._filePtr = this._runCode.malloc(this._fileLength) + this._wasmModule.HEAP8.set(array, this._filePtr) + resolve() + } catch (error) { + reject(error) + } + } + + _promiseHandles() { + let resolve = null, reject = null + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + return { promise, resolve, reject } + } + +} + +class WasmModule { + constructor() { + this.preRun = [] + this.postRun = [] + this.totalDependencies = 0 + } + + print(...text) { + console.log(text) + } + + printErr(...text) { + console.error(text) + } + + initFunctions() { + this.runCode = { + // const char * get_version() + getVersion: this.cwrap('get_version', 'string', []), + // void * archive_open( const void * buffer, size_t buffer_size) + // retuns archive pointer + openArchive: this.cwrap('archive_open', 'number', ['number', 'number', 'string']), + // void * get_entry(void * archive) + // return archive entry pointer + getNextEntry: this.cwrap('get_next_entry', 'number', ['number']), + // void * get_filedata( void * archive, size_t bufferSize ) + getFileData: this.cwrap('get_filedata', 'number', ['number', 'number']), + // int archive_read_data_skip(struct archive *_a) + skipEntry: this.cwrap('archive_read_data_skip', 'number', ['number']), + // void archive_close( void * archive ) + closeArchive: this.cwrap('archive_close', null, ['number']), + // la_int64_t archive_entry_size( struct archive_entry * ) + getEntrySize: this.cwrap('archive_entry_size', 'number', ['number']), + // const char * archive_entry_pathname( struct archive_entry * ) + getEntryName: this.cwrap('archive_entry_pathname', 'string', ['number']), + // __LA_MODE_T archive_entry_filetype( struct archive_entry * ) + /* + #define AE_IFMT ((__LA_MODE_T)0170000) + #define AE_IFREG ((__LA_MODE_T)0100000) // Regular file + #define AE_IFLNK ((__LA_MODE_T)0120000) // Sybolic link + #define AE_IFSOCK ((__LA_MODE_T)0140000) // Socket + #define AE_IFCHR ((__LA_MODE_T)0020000) // Character device + #define AE_IFBLK ((__LA_MODE_T)0060000) // Block device + #define AE_IFDIR ((__LA_MODE_T)0040000) // Directory + #define AE_IFIFO ((__LA_MODE_T)0010000) // Named pipe + */ + getEntryType: this.cwrap('archive_entry_filetype', 'number', ['number']), + // const char * archive_error_string(struct archive *); + getError: this.cwrap('archive_error_string', 'string', ['number']), + + /* + * Returns 1 if the archive contains at least one encrypted entry. + * If the archive format not support encryption at all + * ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED is returned. + * If for any other reason (e.g. not enough data read so far) + * we cannot say whether there are encrypted entries, then + * ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW is returned. + * In general, this function will return values below zero when the + * reader is uncertain or totally incapable of encryption support. + * When this function returns 0 you can be sure that the reader + * supports encryption detection but no encrypted entries have + * been found yet. + * + * NOTE: If the metadata/header of an archive is also encrypted, you + * cannot rely on the number of encrypted entries. That is why this + * function does not return the number of encrypted entries but# + * just shows that there are some. + */ + // __LA_DECL int archive_read_has_encrypted_entries(struct archive *); + entryIsEncrypted: this.cwrap('archive_entry_is_encrypted', 'number', ['number']), + hasEncryptedEntries: this.cwrap('archive_read_has_encrypted_entries', 'number', ['number']), + // __LA_DECL int archive_read_add_passphrase(struct archive *, const char *); + addPassphrase: this.cwrap('archive_read_add_passphrase', 'number', ['number', 'string']), + //this.stringToUTF(str), // + string: (str) => this.allocate(this.intArrayFromString(str), 'i8', 0), + malloc: this.cwrap('malloc', 'number', ['number']), + free: this.cwrap('free', null, ['number']), + } + } + + monitorRunDependencies() { } + + locateFile(path /* ,prefix */) { + const wasmFilepath = Path.join(__dirname, `../../../client/dist/libarchive/wasm-gen/${path}`) + return wasmFilepath + } +} + +module.exports.getArchiveReader = (cb) => { + libarchive(new WasmModule()).then((module) => { + module.initFunctions() + cb(new ArchiveReader(module)) + }) +} \ No newline at end of file diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index b40e9323..85bf8146 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -681,7 +681,7 @@ class BookScanner { const bookTitle = this.bookMetadata.title || this.libraryItemData.mediaMetadata.title AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan) } else if (this.ebookFileScanData) { - const ebookMetdataObject = this.ebookFileScanData.metadata + const ebookMetdataObject = this.ebookFileScanData.metadata || {} for (const key in ebookMetdataObject) { if (key === 'tags') { if (ebookMetdataObject.tags.length) { diff --git a/server/utils/parsers/parseComicInfoMetadata.js b/server/utils/parsers/parseComicInfoMetadata.js new file mode 100644 index 00000000..e2bad4ec --- /dev/null +++ b/server/utils/parsers/parseComicInfoMetadata.js @@ -0,0 +1,35 @@ + +/** + * TODO: Add more fields + * @see https://anansi-project.github.io/docs/comicinfo/intro + * + * @param {Object} comicInfoJson + * @returns {import('../../scanner/BookScanner').BookMetadataObject} + */ +module.exports.parse = (comicInfoJson) => { + if (!comicInfoJson?.ComicInfo) return null + + const ComicSeries = comicInfoJson.ComicInfo.Series?.[0]?.trim() || null + const ComicNumber = comicInfoJson.ComicInfo.Number?.[0]?.trim() || null + const ComicSummary = comicInfoJson.ComicInfo.Summary?.[0]?.trim() || null + + let title = null + const series = [] + if (ComicSeries) { + series.push({ + name: ComicSeries, + sequence: ComicNumber + }) + + title = ComicSeries + if (ComicNumber) { + title += ` ${ComicNumber}` + } + } + + return { + title, + series, + description: ComicSummary + } +} \ No newline at end of file diff --git a/server/utils/parsers/parseComicMetadata.js b/server/utils/parsers/parseComicMetadata.js new file mode 100644 index 00000000..8d78b126 --- /dev/null +++ b/server/utils/parsers/parseComicMetadata.js @@ -0,0 +1,109 @@ +const Path = require('path') +const globals = require('../globals') +const fs = require('../../libs/fsExtra') +const Logger = require('../../Logger') +const Archive = require('../../libs/libarchive/archive') +const { xmlToJSON } = require('../index') +const parseComicInfoMetadata = require('./parseComicInfoMetadata') + +/** + * + * @param {string} filepath + * @returns {Promise} + */ +async function getComicFileBuffer(filepath) { + if (!await fs.pathExists(filepath)) { + Logger.error(`Comic path does not exist "${filepath}"`) + return null + } + try { + return fs.readFile(filepath) + } catch (error) { + Logger.error(`Failed to read comic at "${filepath}"`, error) + return null + } +} + +/** + * Extract cover image from comic return true if success + * + * @param {string} comicPath + * @param {string} comicImageFilepath + * @param {string} outputCoverPath + * @returns {Promise} + */ +async function extractCoverImage(comicPath, comicImageFilepath, outputCoverPath) { + const comicFileBuffer = await getComicFileBuffer(comicPath) + if (!comicFileBuffer) return null + + const archive = await Archive.open(comicFileBuffer) + const fileEntry = await archive.extractSingleFile(comicImageFilepath) + + if (!fileEntry?.fileData) { + Logger.error(`[parseComicMetadata] Invalid file entry data for comicPath "${comicPath}"/${comicImageFilepath}`) + return false + } + + try { + await fs.writeFile(outputCoverPath, fileEntry.fileData) + return true + } catch (error) { + Logger.error(`[parseComicMetadata] Failed to extract image from comicPath "${comicPath}"`, error) + return false + } +} +module.exports.extractCoverImage = extractCoverImage + +/** + * Parse metadata from comic + * + * @param {import('../../models/Book').EBookFileObject} ebookFile + * @returns {Promise} + */ +async function parse(ebookFile) { + const comicPath = ebookFile.metadata.path + Logger.debug(`Parsing metadata from comic at "${comicPath}"`) + + const comicFileBuffer = await getComicFileBuffer(comicPath) + if (!comicFileBuffer) return null + + const archive = await Archive.open(comicFileBuffer) + + const fileObjects = await archive.getFilesArray() + + fileObjects.sort((a, b) => { + return a.file.name.localeCompare(b.file.name, undefined, { + numeric: true, + sensitivity: 'base' + }) + }) + + let metadata = null + const comicInfo = fileObjects.find(fo => fo.file.name === 'ComicInfo.xml') + if (comicInfo) { + const comicInfoEntry = await comicInfo.file.extract() + if (comicInfoEntry?.fileData) { + const comicInfoStr = new TextDecoder().decode(comicInfoEntry.fileData) + const comicInfoJson = await xmlToJSON(comicInfoStr) + if (comicInfoJson) { + metadata = parseComicInfoMetadata.parse(comicInfoJson) + } + } + } + + const payload = { + path: comicPath, + ebookFormat: ebookFile.ebookFormat, + metadata + } + + const firstImage = fileObjects.find(fo => globals.SupportedImageTypes.includes(Path.extname(fo.file.name).toLowerCase().slice(1))) + if (firstImage?.file?._path) { + payload.ebookCoverPath = firstImage.file._path + } else { + Logger.warn(`Cover image not found in comic at "${comicPath}"`) + } + + return payload +} +module.exports.parse = parse \ No newline at end of file diff --git a/server/utils/parsers/parseEbookMetadata.js b/server/utils/parsers/parseEbookMetadata.js index 6e97c1da..9ef7861c 100644 --- a/server/utils/parsers/parseEbookMetadata.js +++ b/server/utils/parsers/parseEbookMetadata.js @@ -1,4 +1,5 @@ const parseEpubMetadata = require('./parseEpubMetadata') +const parseComicMetadata = require('./parseComicMetadata') /** * @typedef EBookFileScanData @@ -18,7 +19,9 @@ async function parse(ebookFile) { if (!ebookFile) return null if (ebookFile.ebookFormat === 'epub') { - return parseEpubMetadata.parse(ebookFile.metadata.path) + return parseEpubMetadata.parse(ebookFile) + } else if (['cbz', 'cbr'].includes(ebookFile.ebookFormat)) { + return parseComicMetadata.parse(ebookFile) } return null } @@ -36,6 +39,8 @@ async function extractCoverImage(ebookFileScanData, outputCoverPath) { if (ebookFileScanData.ebookFormat === 'epub') { return parseEpubMetadata.extractCoverImage(ebookFileScanData.path, ebookFileScanData.ebookCoverPath, outputCoverPath) + } else if (['cbz', 'cbr'].includes(ebookFileScanData.ebookFormat)) { + return parseComicMetadata.extractCoverImage(ebookFileScanData.path, ebookFileScanData.ebookCoverPath, outputCoverPath) } return false } diff --git a/server/utils/parsers/parseEpubMetadata.js b/server/utils/parsers/parseEpubMetadata.js index 7238b0bf..47330046 100644 --- a/server/utils/parsers/parseEpubMetadata.js +++ b/server/utils/parsers/parseEpubMetadata.js @@ -60,10 +60,11 @@ module.exports.extractCoverImage = extractCoverImage /** * Parse metadata from epub * - * @param {string} epubPath + * @param {import('../../models/Book').EBookFileObject} ebookFile * @returns {Promise} */ -async function parse(epubPath) { +async function parse(ebookFile) { + const epubPath = ebookFile.metadata.path Logger.debug(`Parsing metadata from epub at "${epubPath}"`) // Entrypoint of the epub that contains the filepath to the package document (opf file) const containerJson = await extractXmlToJson(epubPath, 'META-INF/container.xml')