UNPKG

static-fs

Version:

A static filesystem to bundle files and read them using NodeJS

277 lines (206 loc) 8.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReadableStaticVolume = void 0; var filesystem = _interopRequireWildcard(require("fs")); var _path = require("path"); var _common = require("../../common"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } const fs = { ...filesystem }; class ReadableStaticVolume { constructor(sourcePath) { this.sourcePath = sourcePath; this.moutingRoot = (0, _path.resolve)((0, _path.dirname)(this.sourcePath), '../'); this.runtimePath = (0, _path.resolve)((0, _path.dirname)(this.sourcePath), 'static_fs_runtime.js'); this.reset(); } reset() { this.buf = Buffer.alloc(1024 * 16); this.directoriesIndex = {}; this.fd = -1; this.hash = ''; this.intBuffer = Buffer.alloc(_common.INT_SIZE); this.index = {}; this.statData = {}; this.filesBeingRead = {}; this.pathVolumeIndex = {}; } load() { if (this.fd >= 0) { return; } // clone the original static fs values and set some defaults this.statData = { isDirectory: () => false, isSymbolicLink: () => false, isBlockDevice: () => false, isCharacterDevice: () => false, isFile: () => false, isFIFO: () => false, isSocket: () => false, size: 0, ...fs.statSync(this.sourcePath) }; // read the index this.fd = fs.openSync(this.sourcePath, 'r'); // close on process exit. let dataOffset = this.readInt(); // read hash let hashSize = this.readInt(); if (hashSize > this.buf.length) { this.buf = Buffer.alloc(hashSize); } this.readBuffer(this.buf, hashSize); this.hash = this.buf.toString('utf8', 0, hashSize); const hashCheckIndex = {}; do { const nameSz = this.readInt(); if (nameSz === 0) { break; } const dataSz = this.readInt(); if (nameSz > this.buf.length) { this.buf = Buffer.alloc(nameSz); } this.readBuffer(this.buf, nameSz); const name = this.buf.toString('utf8', 0, nameSz); const mountedName = this._resolveMountedPath(name); hashCheckIndex[name] = true; // add entry for file into index this.index[mountedName] = Object.assign({}, this.statData, { ino: dataOffset, // the location in the static fs size: dataSz, // the size of the file blocks: 1, // one block blksize: dataSz, // of file size size. isFile: () => true // it's a file! }); // this creates an index (every_path) -> (sourcePath) // it also needs to assign inside addParentFolders this.pathVolumeIndex[mountedName] = this.sourcePath; // ensure parent path has a directory entry this.addParentFolders(mountedName); // build our directories index this.updateDirectoriesIndex(mountedName); dataOffset += dataSz; } while (true); const hashCheck = (0, _common.calculateHash)(Object.keys(hashCheckIndex).sort()); if (hashCheck !== this.hash) { throw new Error(`Something went wrong loading the volume ${this.sourcePath}. Check hash after loading is different from the one stored in the volume.`); } return this.pathVolumeIndex; } readBuffer(buffer, length) { return fs.readSync(this.fd, buffer, 0, length || buffer.length, null); } readInt() { fs.readSync(this.fd, this.intBuffer, 0, _common.INT_SIZE, null); return this.intBuffer.readIntBE(0, 6); } shutdown() { fs.closeSync(this.fd); this.reset(); } getFromDirectoriesIndex(filePath) { return this.directoriesIndex[(0, _common.sanitizePath)(filePath)]; } getFromIndex(filePath) { return this.index[(0, _common.sanitizePath)(filePath)]; } addParentFolders(name) { const parent = (0, _path.dirname)(name); if (parent && !this.index[parent] && parent.includes((0, _common.unixifyPath)(this.moutingRoot))) { this.index[parent] = Object.assign({}, this.statData, { isDirectory: () => true }); this.pathVolumeIndex[parent] = this.sourcePath; return this.addParentFolders(parent); } } updateDirectoriesIndex(name) { if (!this.index[name] || (0, _common.unixifyPath)(this.moutingRoot) === name) { return; } const directoryAlreadyExists = this.directoriesIndex[name]; if (!directoryAlreadyExists) { this.directoriesIndex[name] = {}; } const isFile = this.index[name].isFile(); const isDirectory = this.index[name].isDirectory(); const parent = (0, _path.dirname)(name); if (isFile || isDirectory) { const fileName = (0, _path.basename)(name); if (!this.directoriesIndex[parent]) { this.directoriesIndex[parent] = {}; } this.directoriesIndex[parent][fileName] = true; } this.updateDirectoriesIndex(parent); } _resolveMountedPath(unmountedPath) { if (unmountedPath.includes(this.moutingRoot)) { return unmountedPath; } return (0, _common.sanitizePath)(this.moutingRoot, unmountedPath); } readFileSync(filePath, options) { const sanitizedFilePath = (0, _common.sanitizePath)(filePath); const item = this.index[sanitizedFilePath]; if (!item || !item.isFile()) { return undefined; } const encoding = options ? typeof options === 'string' ? options : typeof options === 'object' ? options.encoding : null : null; // re-alloc if necessary if (this.buf.length < item.size) { this.buf = Buffer.alloc(item.size); } // read the content and return a string fs.readSync(this.fd, this.buf, 0, item.size, item.ino); if (!encoding) { const buf = Buffer.alloc(item.size); this.buf.copy(buf); return buf; } return this.buf.toString(encoding, 0, item.size); } _deleteReadFileFromCache(filePath, length, position) { const cachedBuffer = this.filesBeingRead[filePath].buffer; if (position >= cachedBuffer.length || position + length >= cachedBuffer.length) { this.filesBeingRead[filePath].consumers -= 1; } if (this.filesBeingRead[filePath].consumers <= 0) { delete this.filesBeingRead[filePath]; } } _readFromCache(filePath, buffer, offset, length, position, callback) { const cachedBuffer = this.filesBeingRead[filePath].buffer; if (position >= cachedBuffer.length) { this._deleteReadFileFromCache(filePath, length, position); callback(null, 0, buffer); return; } const copiedBytes = cachedBuffer.copy(buffer, offset, position, Math.min(position + length, cachedBuffer.length)); this._deleteReadFileFromCache(filePath, length, position); callback(null, copiedBytes, buffer); } read(filePath, buffer, offset, length, position, callback) { const sanitizedFilePath = (0, _common.sanitizePath)(filePath); const item = this.index[sanitizedFilePath]; if (item && item.isFile()) { // read the content and return a string if (this.filesBeingRead[filePath]) { this.filesBeingRead[filePath].consumers += 1; this._readFromCache(filePath, buffer, offset, length, position, callback); } else { const cachedFile = this.filesBeingRead[filePath] = { buffer: Buffer.alloc(item.size), consumers: 1 }; fs.read(this.fd, cachedFile.buffer, 0, item.size, item.ino, err => { if (err) { callback(err); } this._readFromCache(filePath, buffer, offset, length, position, callback); }); } } else { callback(new Error()); } } } exports.ReadableStaticVolume = ReadableStaticVolume;