UNPKG

@shockpkg/dir-projector

Version:

Package for creating Shockwave Director projectors

540 lines (431 loc) 13 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.Bundle = void 0; var _stream = require("stream"); var _path = require("path"); var _util = require("util"); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _archiveFiles = require("@shockpkg/archive-files"); var _util2 = require("./util"); var _queue = require("./queue"); const pipelineP = (0, _util.promisify)(_stream.pipeline); const userExec = 0b001000000; /** * Options for adding resources. */ /** * Bundle constructor. * * @param path Output path for the main executable. */ class Bundle extends Object { /** * File and directory names to exclude when adding a directory. */ /** * Bundle main executable path. */ /** * Projector instance. */ /** * Open flag. */ /** * Close callbacks priority queue. */ constructor(path) { super(); this.excludes = [/^\./, /^ehthumbs\.db$/, /^Thumbs\.db$/]; this.path = void 0; this.projector = void 0; this._isOpen = false; this._closeQueue = new _queue.Queue(); this.path = path; } /** * Check if output open. * * @returns Returns true if open, else false. */ get isOpen() { return this._isOpen; } /** * Check if name is excluded file. * * @param name File name. * @returns Returns true if excluded, else false. */ isExcludedFile(name) { for (const exclude of this.excludes) { if (exclude.test(name)) { return true; } } return false; } /** * Open output with file. * * @param player Player path. * @param configFile Config file. */ async openFile(player, configFile) { const configData = configFile ? await _fsExtra.default.readFile(configFile) : null; await this.openData(player, configData); } /** * Open output with data. * * @param player Player path. * @param configData Config data. */ async openData(player, configData) { if (this._isOpen) { throw new Error('Already open'); } await this._checkOutput(); this._closeQueue.clear(); await this._openData(player, configData); this._isOpen = true; } /** * Close output. */ async close() { this._assertIsOpen(); try { await this._close(); } finally { this._closeQueue.clear(); } this._isOpen = false; } /** * Write out projector with player and file. * Has a callback to write out the resources. * * @param player Player path. * @param configFile Config file. * @param func Async function. * @returns Return value of the async function. */ async withFile(player, configFile, func = null) { const configData = configFile ? await _fsExtra.default.readFile(configFile) : null; return this.withData(player, configData, func); } /** * Write out projector with player and data. * Has a callback to write out the resources. * * @param player Player path. * @param configData Config data. * @param func Async function. * @returns Return value of the async function. */ async withData(player, configData, func = null) { await this.openData(player, configData); try { return func ? await func.call(this, this) : null; } finally { await this.close(); } } /** * Get path for resource. * * @param destination Resource destination. * @returns Destination path. */ resourcePath(destination) { return (0, _path.join)((0, _path.dirname)(this.projector.path), destination); } /** * Check if path for resource exists. * * @param destination Resource destination. * @returns True if destination exists, else false. */ async resourceExists(destination) { return !!(0, _archiveFiles.fsLstatExists)(this.resourcePath(destination)); } /** * Copy resource, detecting source type automatically. * * @param destination Resource destination. * @param source Source directory. * @param options Resource options. */ async copyResource(destination, source, options = null) { this._assertIsOpen(); const stat = await _fsExtra.default.lstat(source); switch (true) { case stat.isSymbolicLink(): { await this.copyResourceSymlink(destination, source, options); break; } case stat.isFile(): { await this.copyResourceFile(destination, source, options); break; } case stat.isDirectory(): { await this.copyResourceDirectory(destination, source, options); break; } default: { throw new Error(`Unsupported resource type: ${source}`); } } } /** * Copy directory as resource, recursive copy. * * @param destination Resource destination. * @param source Source directory. * @param options Resource options. */ async copyResourceDirectory(destination, source, options = null) { this._assertIsOpen(); // Create directory. await this.createResourceDirectory(destination, options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.stat(source)) : options); // If not recursive do not walk contents. if (options && options.noRecurse) { return; } // Any directories we add should not be recursive. const opts = { ...(options || {}), noRecurse: true }; await (0, _archiveFiles.fsWalk)(source, async (path, stat) => { // If this name is excluded, skip without descending. if (this.isExcludedFile((0, _path.basename)(path))) { return false; } await this.copyResource((0, _path.join)(destination, path), (0, _path.join)(source, path), opts); return true; }); } /** * Copy file as resource. * * @param destination Resource destination. * @param source Source file. * @param options Resource options. */ async copyResourceFile(destination, source, options = null) { this._assertIsOpen(); await this.streamResourceFile(destination, _fsExtra.default.createReadStream(source), options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.stat(source)) : options); } /** * Copy symlink as resource. * * @param destination Resource destination. * @param source Source symlink. * @param options Resource options. */ async copyResourceSymlink(destination, source, options = null) { this._assertIsOpen(); await this.createResourceSymlink(destination, await _fsExtra.default.readlink(source), options ? await this._expandResourceOptionsCopy(options, async () => _fsExtra.default.lstat(source)) : options); } /** * Create a resource directory. * * @param destination Resource destination. * @param options Resource options. */ async createResourceDirectory(destination, options = null) { this._assertIsOpen(); const dest = await this._assertNotResourceExists(destination, !!(options && options.merge)); await _fsExtra.default.ensureDir(dest); // If either is set, queue up change times when closing. if (options && (options.atime || options.mtime)) { // Get absolute path, use length for the priority. // Also copy the options object which the owner could change. const abs = (0, _path.resolve)(dest); this._closeQueue.push(this._setResourceAttributes.bind(this, abs, { ...options }), abs.length); } } /** * Create a resource file. * * @param destination Resource destination. * @param data Resource data. * @param options Resource options. */ async createResourceFile(destination, data, options = null) { this._assertIsOpen(); await this.streamResourceFile(destination, new _stream.Readable({ read() { this.push(data); this.push(null); } }), options); } /** * Create a resource symlink. * * @param destination Resource destination. * @param target Symlink target. * @param options Resource options. */ async createResourceSymlink(destination, target, options = null) { this._assertIsOpen(); const dest = await this._assertNotResourceExists(destination); await _fsExtra.default.ensureDir((0, _path.dirname)(dest)); await _fsExtra.default.symlink(target, dest); if (options) { await this._setResourceAttributes(dest, options); } } /** * Stream readable source to resource file. * * @param destination Resource destination. * @param data Resource stream. * @param options Resource options. */ async streamResourceFile(destination, data, options = null) { this._assertIsOpen(); const dest = await this._assertNotResourceExists(destination); await _fsExtra.default.ensureDir((0, _path.dirname)(dest)); await pipelineP(data, _fsExtra.default.createWriteStream(dest)); if (options) { await this._setResourceAttributes(dest, options); } } /** * Check that output path is valid, else throws. */ async _checkOutput() { for (const p of [this.path, this.resourcePath('')]) { // eslint-disable-next-line no-await-in-loop if (await _fsExtra.default.pathExists(p)) { throw new Error(`Output path already exists: ${p}`); } } } /** * Expand resource options copy properties with stat object from source. * * @param options Options object. * @param stat Stat function. * @returns Options copy with any values populated. */ async _expandResourceOptionsCopy(options, stat) { const r = { ...options }; const st = (0, _util2.once)(stat); if (!r.atime && r.atimeCopy) { r.atime = (await st()).atime; } if (!r.mtime && r.mtimeCopy) { r.mtime = (await st()).mtime; } if (typeof r.executable !== 'boolean' && r.executableCopy) { r.executable = this._getResourceModeExecutable((await st()).mode); } return r; } /** * Set resource attributes from options object. * * @param path File path. * @param options Options object. */ async _setResourceAttributes(path, options) { const { atime, mtime, executable } = options; const st = await _fsExtra.default.lstat(path); // Maybe set executable if not a directory and supported. if (typeof executable === 'boolean' && !st.isDirectory()) { if (!st.isSymbolicLink()) { await _fsExtra.default.chmod(path, this._setResourceModeExecutable(st.mode, executable)); } else if (_archiveFiles.fsLchmodSupported) { await (0, _archiveFiles.fsLchmod)(path, this._setResourceModeExecutable( // Workaround for a legacy Node issue. // eslint-disable-next-line no-bitwise st.mode & 0b111111111, executable)); } } // Maybe change times if either is set and supported. if (atime || mtime) { if (!st.isSymbolicLink()) { await _fsExtra.default.utimes(path, atime || st.atime, mtime || st.mtime); } else if (_archiveFiles.fsLutimesSupported) { await (0, _archiveFiles.fsLutimes)(path, atime || st.atime, mtime || st.mtime); } } } /** * Get file mode executable. * * @param mode Current mode. * @returns Is executable. */ _getResourceModeExecutable(mode) { // eslint-disable-next-line no-bitwise return !!(mode & userExec); } /** * Set file mode executable. * * @param mode Current mode. * @param executable Is executable. * @returns File mode. */ _setResourceModeExecutable(mode, executable) { // eslint-disable-next-line no-bitwise return (executable ? mode | userExec : mode & ~userExec) >>> 0; } /** * Open output with data. * * @param player Player path. * @param configData Config data. */ async _openData(player, configData) { await this.projector.withData(player, configData); } /** * Close output. */ async _close() { await this._writeLauncher(); await this._closeQueue.run(); } /** * Assert bundle is open. */ _assertIsOpen() { if (!this._isOpen) { throw new Error('Not open'); } } /** * Assert resource does not exist, returning destination path. * * @param destination Resource destination. * @param ignoreDirectory Ignore directories. * @returns Destination path. */ async _assertNotResourceExists(destination, ignoreDirectory = false) { const dest = this.resourcePath(destination); const st = await (0, _archiveFiles.fsLstatExists)(dest); if (st && (!ignoreDirectory || !st.isDirectory())) { throw new Error(`Resource path exists: ${dest}`); } return dest; } /** * Main application file extension. * * @returns File extension. */ } exports.Bundle = Bundle; //# sourceMappingURL=bundle.js.map