UNPKG

file-system-access

Version:

File System Access API implementation (ponyfill) with pluggable storage adapters via IndexedDB, Cache API, in-memory etc.

203 lines 6.94 kB
import { errors, isChunkObject } from '../util.js'; let File = globalThis.File; let Blob = globalThis.Blob; /** @internal */ export const setFileImpl = (fileCtor) => { File = fileCtor; }; /** @internal */ export const setBlobImpl = (blobCtor) => { Blob = blobCtor; }; const { INVALID, GONE, MISMATCH, MOD_ERR, SYNTAX, DISALLOWED } = errors; class Sink { constructor(fileHandle, keepExistingData) { this.fileHandle = fileHandle; this.file = keepExistingData ? fileHandle.file : new File([], fileHandle.file.name, fileHandle.file); this.size = keepExistingData ? fileHandle.file.size : 0; this.position = 0; } async write(chunk) { if (!this.fileHandle.file) throw new DOMException(...GONE); let file = this.file; if (isChunkObject(chunk)) { if (chunk.type === 'write') { if (typeof chunk.position === 'number' && chunk.position >= 0) { this.position = chunk.position; if (this.size < chunk.position) { this.file = new File([this.file, new ArrayBuffer(chunk.position - this.size)], this.file.name, this.file); } } if (!('data' in chunk)) { throw new DOMException(...SYNTAX('write requires a data argument')); } chunk = chunk.data; } else if (chunk.type === 'seek') { if (Number.isInteger(chunk.position) && chunk.position >= 0) { if (this.size < chunk.position) { throw new DOMException(...INVALID); } this.position = chunk.position; return; } else { throw new DOMException(...SYNTAX('seek requires a position argument')); } } else if (chunk.type === 'truncate') { if (Number.isInteger(chunk.size) && chunk.size >= 0) { file = chunk.size < this.size ? new File([file.slice(0, chunk.size)], file.name, file) : new File([file, new Uint8Array(chunk.size - this.size)], file.name, file); this.size = file.size; if (this.position > file.size) { this.position = file.size; } this.file = file; return; } else { throw new DOMException(...SYNTAX('truncate requires a size argument')); } } } chunk = new Blob([chunk]); let blob = this.file; // Calc the head and tail fragments const head = blob.slice(0, this.position); const tail = blob.slice(this.position + chunk.size); // Calc the padding let padding = this.position - head.size; if (padding < 0) { padding = 0; } blob = new File([ head, new Uint8Array(padding), chunk, tail ], blob.name); this.size = blob.size; this.position += chunk.size; this.file = blob; } async close() { if (!this.fileHandle.file) throw new DOMException(...GONE); this.fileHandle.file = this.file; this.file = this.position = this.size = null; if (this.fileHandle.onclose) { this.fileHandle.onclose(this.fileHandle); } } } export class FileHandle { constructor(name = '', file = new File([], name), writable = true) { this.kind = 'file'; // TODO: check if we need this, b/c we can check file for null instead this.deleted = false; this.file = file; this.name = name; this.writable = writable; } async getFile() { if (this.deleted || this.file === null) throw new DOMException(...GONE); return this.file; } async createWritable(opts) { if (!this.writable) throw new DOMException(...DISALLOWED); if (this.deleted) throw new DOMException(...GONE); return new Sink(this, !!(opts === null || opts === void 0 ? void 0 : opts.keepExistingData)); } async isSameEntry(other) { return this === other; } destroy() { this.deleted = true; this.file = null; } } export class FolderHandle { constructor(name, writable = true) { this.kind = 'directory'; this.deleted = false; this._entries = {}; this.name = name; this.writable = writable; } async *entries() { if (this.deleted) throw new DOMException(...GONE); yield* Object.entries(this._entries); } async isSameEntry(other) { return this === other; } async getDirectoryHandle(name, opts = {}) { if (this.deleted) throw new DOMException(...GONE); const entry = this._entries[name]; if (entry) { // entry exist if (entry instanceof FileHandle) { throw new DOMException(...MISMATCH); } else { return entry; } } else { if (opts.create) { return (this._entries[name] = new FolderHandle(name)); } else { throw new DOMException(...GONE); } } } async getFileHandle(name, opts = {}) { const entry = this._entries[name]; if (entry) { if (entry instanceof FileHandle) { return entry; } else { throw new DOMException(...MISMATCH); } } else { if (!opts.create) { throw new DOMException(...GONE); } else { return (this._entries[name] = new FileHandle(name)); } } } async removeEntry(name, opts = {}) { const entry = this._entries[name]; if (!entry) throw new DOMException(...GONE); entry.destroy(opts.recursive); delete this._entries[name]; } destroy(recursive) { for (let x of Object.values(this._entries)) { if (!recursive) throw new DOMException(...MOD_ERR); x.destroy(recursive); } this._entries = {}; this.deleted = true; } } const fs = new FolderHandle(''); const adapter = () => fs; export default adapter; //# sourceMappingURL=memory.js.map