UNPKG

kura

Version:

The FileSystem API abstraction library.

713 lines 24.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractAccessor = void 0; const BinaryConverter_1 = require("./BinaryConverter"); const ContentsCache_1 = require("./ContentsCache"); const FileError_1 = require("./FileError"); const FileSystemConstants_1 = require("./FileSystemConstants"); const FileSystemUtil_1 = require("./FileSystemUtil"); const ObjectUtil_1 = require("./ObjectUtil"); const TextConverter_1 = require("./TextConverter"); class AbstractAccessor { constructor(options) { this.options = options; this.recordCache = {}; this.initialize(options); } clearContentsCache(fullPath) { if (this.contentsCache == null) { return; } this.contentsCache.remove(fullPath); } async createIndexPath(fullPath, createDirectory) { const name = (0, FileSystemUtil_1.getName)(fullPath); const parentPath = (0, FileSystemUtil_1.getParentPath)(fullPath); const indexName = FileSystemConstants_1.INDEX_PREFIX + name; let indexDir = FileSystemConstants_1.INDEX_DIR_PATH + parentPath; if (!indexDir.endsWith(FileSystemConstants_1.DIR_SEPARATOR)) { indexDir += FileSystemConstants_1.DIR_SEPARATOR; } const indexPath = indexDir + indexName; if (!createDirectory) { return indexPath; } await this.makeDirectory(indexDir); return indexPath; } async createRecord(obj) { const fullPath = obj.fullPath; const lastModified = obj.lastModified ?? Date.now(); const size = obj.size; let record; try { record = await this.getRecord(fullPath); if (record.modified === lastModified) { return null; } record.size = size; record.modified = lastModified; delete record.deleted; } catch (e) { if (e instanceof FileError_1.NotFoundError) { record = { modified: lastModified, size }; } else if (e instanceof FileError_1.AbstractFileError) { throw e; } else { throw new FileError_1.NotReadableError(this.name, fullPath, e); } } return record; } async delete(fullPath, isFile, truncate) { if (fullPath.startsWith(FileSystemConstants_1.INDEX_DIR_PATH + "/")) { try { await this.doDelete(fullPath, isFile); } catch (e) { await this.handleWriteError(e, fullPath, isFile); } return; } if (this.options.index) { if (truncate) { await this.truncateRecord(fullPath); } else { await this.deleteRecord(fullPath, isFile); } } if (!this.options.indexOptions?.logicalDelete) { try { await this.doDelete(fullPath, isFile); } catch (e) { await this.handleWriteError(e, fullPath, isFile); } } if (isFile && this.contentsCache) { this.contentsCache.remove(fullPath); } } async deleteRecord(fullPath, _isFile) { if (!this.options.index) { return; } if (fullPath === FileSystemConstants_1.INDEX_DIR_PATH) { return; } let record; try { record = await this.getRecord(fullPath); } catch (e) { if (e instanceof FileError_1.NotFoundError) { return; } else if (e instanceof FileError_1.AbstractFileError) { throw e; } throw new FileError_1.NotReadableError(this.name, fullPath, e); } if (record.deleted == null) { record.deleted = Date.now(); } const indexPath = await this.createIndexPath(fullPath, false); await this.doSaveRecord(indexPath, record); const indexObj = await this.doGetObject(indexPath, true); this.recordCache[indexPath] = { record, lastModified: indexObj.lastModified, }; } async deleteRecursively(fullPath, truncate) { let children; try { children = await this.doGetObjects(fullPath); } catch (e) { await this.handleReadError(e, fullPath, false); } for (const child of children) { if (child.size == null) { await this.deleteRecursively(child.fullPath, truncate); } else { await this.delete(child.fullPath, true, truncate); } } if (fullPath !== FileSystemConstants_1.DIR_SEPARATOR && !(this.options?.index && fullPath === FileSystemConstants_1.INDEX_DIR_PATH)) { await this.delete(fullPath, false, truncate); } } async doPutObject(obj, content) { const fullPath = obj.fullPath; let record; try { this.debug("putObject", fullPath); if (content == null) { await this.makeDirectory(obj.fullPath); record = { modified: Date.now() }; } else { await this.doWriteContent(fullPath, content); obj = await this.doGetObject(fullPath, true); if (this.contentsCache) { this.contentsCache.put(obj, content); } record = await this.createRecord(obj); } } catch (e) { await this.handleWriteError(e, fullPath, obj.size != null); } if (record) { await this.saveRecord(fullPath, record); } return obj; } async doWriteContent(fullPath, content) { try { if (typeof content === "string") { await this.doWriteBase64(fullPath, content); } else if ((0, BinaryConverter_1.isBlob)(content)) { await this.doWriteBlob(fullPath, content); } else if ((0, BinaryConverter_1.isBuffer)(content)) { await this.doWriteBuffer(fullPath, content); } else if (ArrayBuffer.isView(content)) { await this.doWriteUint8Array(fullPath, content); } else { await this.doWriteArrayBuffer(fullPath, content); } } catch (e) { await this.handleWriteError(e, fullPath, true); } } async getFileNameIndex(dirPath) { const fileNameIndex = {}; if (dirPath === FileSystemConstants_1.INDEX_DIR_PATH) { return fileNameIndex; } const indexDir = FileSystemConstants_1.INDEX_DIR_PATH + (dirPath === FileSystemConstants_1.DIR_SEPARATOR ? "" : dirPath); let objects; try { objects = await this.doGetObjects(indexDir); } catch (e) { if (e instanceof FileError_1.NotFoundError) { return fileNameIndex; } await this.handleReadError(e, indexDir, false); } if (!dirPath.endsWith("/")) { dirPath += "/"; } for (const obj of objects) { if (obj.size == null) { continue; } let fullPath; try { const name = obj.name.substring(FileSystemConstants_1.INDEX_PREFIX_LEN); fullPath = dirPath + name; const record = await this.getRecord(fullPath); fileNameIndex[name] = { ...record, fullPath, name }; } catch (e) { console.warn("getFileNameIndex", obj, e); } } return fileNameIndex; } async getObject(fullPath, isFile) { this.debug("getObject", fullPath); let obj; if (this.options.index) { let record; try { record = await this.getRecord(fullPath); } catch (e) { if (e instanceof FileError_1.NotFoundError) { try { obj = await this.doGetObject(fullPath, isFile); } catch (e) { await this.handleReadError(e, fullPath, isFile); } await this.saveRecord(fullPath, { modified: obj.lastModified, size: obj.size, }); } else { throw e; } } if (record?.deleted != null) { throw new FileError_1.NotFoundError(this.name, fullPath, "getObject"); } } if (!obj) { try { obj = await this.doGetObject(fullPath, isFile); } catch (e) { await this.handleReadError(e, fullPath, isFile); } } if (!(await this.beforeHead(obj))) { throw new FileError_1.NotFoundError(this.name, obj.fullPath, "beforeHead"); } this.afterHead(obj); return obj; } async getObjects(dirPath) { try { const index = this.options.index; const objects = await this.doGetObjects(dirPath); if (index) { const newObjects = []; for (const obj of objects) { if (obj.fullPath === FileSystemConstants_1.INDEX_DIR_PATH) { continue; } if (this.options.indexOptions.logicalDelete) { try { const record = await this.getRecord(obj.fullPath); if (record.deleted) { continue; } } catch (e) { if (e instanceof FileError_1.NotFoundError) { const record = await this.createRecord(obj); if (record) { await this.saveRecord(obj.fullPath, record); } } else { console.warn("getObjects", obj, e); } continue; } } if (!(await this.beforeHead(obj))) { continue; } this.afterHead(obj); newObjects.push(obj); } return newObjects; } else { return objects; } } catch (e) { await this.handleReadError(e, dirPath, false); } } async getRecord(fullPath) { const indexPath = await this.createIndexPath(fullPath, false); let indexObj; try { indexObj = await this.doGetObject(indexPath, true); } catch (e) { if (e instanceof FileError_1.NotFoundError) { delete this.recordCache[fullPath]; } throw e; } const entry = this.recordCache[indexPath]; if (entry && indexObj.lastModified === entry.lastModified) { return entry.record; } const content = await this.doReadContent(indexPath); const text = await (0, TextConverter_1.toText)(content); const record = (0, ObjectUtil_1.textToObject)(text); this.recordCache[indexPath] = { record, lastModified: indexObj.lastModified, }; return record; } getURL(_fullPath, _method) { throw new Error("Not implemented"); } async makeDirectory(fullPath) { try { await this.doGetObject(fullPath, false); } catch (e) { if (e instanceof FileError_1.NotFoundError) { await this.doMakeDirectory(fullPath); } else { throw new FileError_1.NotReadableError(this.name, fullPath, e); } } } async purge() { await this.deleteRecursively(FileSystemConstants_1.DIR_SEPARATOR, true); this.recordCache = {}; if (this.contentsCache) { this.contentsCache.clear(); } } async putObject(obj, content) { if ((0, FileSystemUtil_1.isIllegalObject)(obj, this.options.index)) { const fullPath = obj.fullPath; throw new FileError_1.InvalidModificationError(this.name, fullPath, `illegal object ${fullPath}`); } const fullPath = obj.fullPath; let create = false; try { obj = await this.doGetObject(fullPath, content != null); } catch (e) { if (e instanceof FileError_1.NotFoundError) { create = true; } else if (e instanceof FileError_1.AbstractFileError) { throw e; } else { throw new FileError_1.NotReadableError(this.name, fullPath, e); } } if (create) { await this.beforePost(obj); } else { await this.beforePut(obj); } obj = await this.doPutObject(obj, content); if (create) { this.afterPost(obj); } else { this.afterPut(obj); } return obj; } async putText(obj, text) { const u8 = (0, TextConverter_1.textToUint8Array)(text); return this.putObject(obj, u8); } async readContent(obj, type) { if ((0, FileSystemUtil_1.isIllegalObject)(obj, this.options.index)) { const fullPath = obj.fullPath; throw new FileError_1.InvalidModificationError(this.name, fullPath, `illegal object ${fullPath}`); } const fullPath = obj.fullPath; this.debug("readContent", fullPath); if (!(await this.beforeGet(obj))) { throw new FileError_1.NotFoundError(this.name, obj.fullPath, "beforeGet"); } const content = await this.readContentInternal(obj, type); this.afterGet(obj); return content; } async readContentInternal(obj, type) { const fullPath = obj.fullPath; let content; if (this.contentsCache) { content = this.contentsCache.get(fullPath); } let read = false; if (!content) { try { content = await this.doReadContent(fullPath); } catch (e) { await this.handleReadError(e, fullPath, true); } read = true; } if (type === "blob") { content = (0, BinaryConverter_1.toBlob)(content); } else if (type === "buffer") { content = await (0, BinaryConverter_1.toBuffer)(content); } else if (type === "arraybuffer") { content = await (0, BinaryConverter_1.toArrayBuffer)(content); } else if (type === "base64") { content = await (0, BinaryConverter_1.toBase64)(content); } if (this.contentsCache && read) { this.contentsCache.put(obj, content); } return content; } async readText(obj) { const content = await this.readContent(obj); const text = await (0, TextConverter_1.toText)(content); return text; } async remove(obj) { const fullPath = obj.fullPath; if (fullPath === FileSystemConstants_1.DIR_SEPARATOR) { throw new FileError_1.InvalidModificationError(this.name, fullPath, "cannot remove root dir"); } const index = this.options.index; if (index && fullPath.startsWith(FileSystemConstants_1.INDEX_DIR_PATH + "/")) { throw new FileError_1.InvalidModificationError(this.name, fullPath, `cannot remove index dir`); } const isFile = obj.size != null; if (!isFile) { try { const objects = await this.getObjects(fullPath); if (0 < objects.length) { throw new FileError_1.InvalidModificationError(this.name, fullPath, `directory is not empty - ${objects .map((obj) => obj.fullPath) .toString()}`); } } catch (e) { if (e instanceof FileError_1.NotFoundError) { await this.handleNotFoundError(fullPath, false); return; } await this.handleReadError(e, fullPath, false); } } this.debug("remove", fullPath); await this.beforeDelete(obj); await this.delete(fullPath, isFile, false); this.afterDelete(obj); } async removeRecursively(obj) { const fullPath = obj.fullPath; let children; try { children = await this.getObjects(fullPath); } catch (e) { if (e instanceof FileError_1.NotFoundError) { await this.handleNotFoundError(fullPath, false); return; } await this.handleReadError(e, fullPath, false); } for (const child of children) { if (child.size == null) { await this.removeRecursively(child); } else { await this.remove(child); } } if (fullPath !== FileSystemConstants_1.DIR_SEPARATOR) { await this.remove(obj); } } async saveRecord(fullPath, record) { if (!this.options.index) { return; } try { const indexPath = await this.createIndexPath(fullPath, true); await this.doSaveRecord(indexPath, record); const indexObj = await this.doGetObject(indexPath, true); this.recordCache[indexPath] = { record, lastModified: indexObj.lastModified, }; return record; } catch (e) { await this.handleWriteError(e, fullPath, true); } } async truncateRecord(fullPath) { if (!this.options.index) { return; } if (fullPath === FileSystemConstants_1.INDEX_DIR_PATH) { return; } const indexPath = await this.createIndexPath(fullPath, false); try { await this.doDelete(indexPath, true); } catch (e) { (0, FileSystemUtil_1.onError)(e); } delete this.recordCache[indexPath]; } debug(title, value) { if (!this.options.verbose) { return; } if (typeof value === "string") { console.debug(`${this.name} - ${title}: fullPath=${value}`); } else { console.debug(`${this.name} - ${title}: fullPath=${value.fullPath}, lastModified=${value.lastModified}, size=${value.size}`); } } async doSaveRecord(indexPath, record) { const text = (0, ObjectUtil_1.objectToText)(record); const u8 = (0, TextConverter_1.textToUint8Array)(text); await this.doWriteContent(indexPath, u8); } async doWriteUint8Array(fullPath, view) { const buffer = await (0, BinaryConverter_1.toArrayBuffer)(view); await this.doWriteArrayBuffer(fullPath, buffer); } async handleNotFoundError(fullPath, isFile) { if (fullPath.startsWith(FileSystemConstants_1.INDEX_DIR_PATH + "/")) { return; } await this.deleteRecord(fullPath, isFile); if (isFile) { this.clearContentsCache(fullPath); } } async handleReadError(e, fullPath, isFile) { if (e instanceof FileError_1.NotFoundError) { await this.handleNotFoundError(fullPath, isFile); throw e; } else if (e instanceof FileError_1.AbstractFileError) { throw e; } throw new FileError_1.NotReadableError(this.name, fullPath, e); } async handleWriteError(e, fullPath, isFile) { if (e instanceof FileError_1.NotFoundError) { await this.handleNotFoundError(fullPath, isFile); throw e; } else if (e instanceof FileError_1.AbstractFileError) { throw e; } throw new FileError_1.InvalidModificationError(this.name, fullPath, e); } initialize(options) { this.initializeIndexOptions(options); if (options.contentsCache == null) { options.contentsCache = true; } this.initializeContentsCacheOptions(options); this.debug("AbstractAccessor#initialize", JSON.stringify(options)); } initializeContentsCacheOptions(options) { if (!options.contentsCache) { return; } if (options.contentsCacheOptions == null) { options.contentsCacheOptions = {}; } const contentsCacheOptions = options.contentsCacheOptions; if (!(0 < contentsCacheOptions.capacity)) { contentsCacheOptions.capacity = 100 * 1024 * 1024; } if (!(0 < contentsCacheOptions.limitSize)) { contentsCacheOptions.limitSize = 256 * 1024; } if (contentsCacheOptions.capacity < contentsCacheOptions.limitSize) { contentsCacheOptions.limitSize = contentsCacheOptions.capacity; } this.contentsCache = new ContentsCache_1.ContentsCache(this); } initializeIndexOptions(options) { if (!options.index) { return; } if (options.indexOptions == null) { options.indexOptions = {}; } const indexOptions = options.indexOptions; if (indexOptions.noCache == null) { indexOptions.noCache = false; } if (indexOptions.logicalDelete == null) { indexOptions.logicalDelete = false; } } afterDelete(obj) { if (!this.options.event.postDelete) { return; } this.options.event.postDelete(obj); } afterGet(obj) { if (!this.options.event.postGet) { return; } this.options.event.postHead(obj); } afterHead(obj) { if (!this.options.event.postHead) { return; } this.options.event.postHead(obj); } afterPost(obj) { if (!this.options.event.postPost) { return; } this.options.event.postPost(obj); } afterPut(obj) { if (!this.options.event.postPut) { return; } this.options.event.postPut(obj); } async beforeDelete(obj) { if (!this.options.event.preDelete) { return; } const result = await this.options.event.preDelete(obj); if (!result) { throw new FileError_1.NoModificationAllowedError(this.name, obj.fullPath, "beforeDelete"); } } async beforeGet(obj) { if (!this.options.event.preGet) { return true; } return this.options.event.preGet(obj); } async beforeHead(obj) { if (!this.options.event.preHead) { return true; } return this.options.event.preHead(obj); } async beforePost(obj) { if (!this.options.event.prePost) { return; } const result = await this.options.event.prePost(obj); if (!result) { throw new FileError_1.NoModificationAllowedError(this.name, obj.fullPath, "beforePost"); } } async beforePut(obj) { if (!this.options.event.prePut) { return; } const result = await this.options.event.prePut(obj); if (!result) { throw new FileError_1.NoModificationAllowedError(this.name, obj.fullPath, "beforePut"); } } } exports.AbstractAccessor = AbstractAccessor; //# sourceMappingURL=AbstractAccessor.js.map