kura
Version:
The FileSystem API abstraction library.
713 lines • 24.5 kB
JavaScript
"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