UNPKG

fs-zoo

Version:

File system abstractions and implementations

283 lines (282 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CrudExtended = void 0; const util_1 = require("./util"); const fromStream_1 = require("@jsonjoy.com/util/lib/streams/fromStream"); const toUint8Array_1 = require("@jsonjoy.com/buffers/lib/toUint8Array"); const CborEncoder_1 = require("@jsonjoy.com/json-pack/lib/cbor/CborEncoder"); const CborDecoder_1 = require("@jsonjoy.com/json-pack/lib/cbor/CborDecoder"); const Writer_1 = require("@jsonjoy.com/buffers/lib/Writer"); const StreamingReader_1 = require("@jsonjoy.com/buffers/lib/StreamingReader"); class CrudExtended { static from(fs) { return new CrudExtended(fs); } constructor(_fs) { this._fs = _fs; } async exists(path) { try { return await this._fs.info(path); } catch { return void 0; } } async dir(path, options) { return this._fs.dir(path, options); } write(path, options) { return this._fs.write(path, options); } put(path, data, options) { if (data === void 0) return this.dir(path, options); return typeof data === 'string' ? this.putUtf8(path, data, options) : this._fs.put(path, data, options); } async putUtf8(path, text, options) { const data = new TextEncoder().encode(text); return this.put(path, data, options); } read(path) { return this._fs.read(path); } async get(path) { const stream = await this.read(path); const buf = await (0, fromStream_1.fromStream)(stream); return (0, toUint8Array_1.toUint8Array)(buf); } async getUtf8(path) { const data = await this.get(path); return new TextDecoder().decode(data); } async file(path) { const fs = this._fs; if (fs.file) return fs.file(path); const [, id] = (0, util_1.parseId)(path); const data = await this.get(path); return new File([new Blob([data])], id); } del(path, silent) { return this._fs.del(path, silent); } info(path) { return this._fs.info(path); } drop(path, silent) { return this._fs.drop(path, silent); } scan(path) { return this._fs.scan(path); } list(path) { return this._fs.list(path); } async from(path) { const child = await this._fs.from(path); return new CrudExtended(child); } async *deepScan(path, prefix = '') { let dirpath = (0, util_1.parseParts)(path).join('/'); if (dirpath) dirpath += '/'; for await (const entry0 of this._fs.scan(path)) { const entry = entry0; const id = entry.id; entry.path = prefix + id; yield entry; if (entry.type === 'collection') yield* this.deepScan(dirpath + id, prefix ? prefix + id + '/' : id + '/'); } } async cp(path, target) { // const snapshot = await this.toNested(path, true); // await this.fromNested(snapshot, target); await this.fromBytes(await this.toBytes(path), target); } async toNested(path = '', fast = false) { const info = await this.info(path); if (info.type === 'resource') { const data = await this.get(path); const data2 = fast ? data : await (0, util_1.tryUtf8Decode)(data); return [1 /* SnapshotNodeType.File */, {}, data2]; } const entries = []; const node = [0, {}, entries]; const list = await this.list(path); for (const entry of list) entries.push([entry.id, await this.toNested(path ? path + '/' + entry.id : entry.id, fast)]); if (!fast) entries.sort(([a], [b]) => a.localeCompare(b)); return node; } async fromNested(snapshot, path = '') { if (!snapshot) return; if (snapshot[0] === 1 /* SnapshotNodeType.File */) return await this.put(path, snapshot[2]); const [, , entries] = snapshot; await this.put(path, undefined); for (const [id, child] of entries) await this.fromNested(child, path ? path + '/' + id : id); } async toFlat(path = '', fast = false, folders, verbose) { const snapshot = await this.toNested(path, fast); const flat = {}; const addToFlat = (node, currentPath) => { if (node[0] === 1 /* SnapshotNodeType.File */) flat[currentPath] = verbose ? [node[0], node[1], node[2]] : node[2]; else { if (folders && (verbose || !node[2].length)) flat[currentPath] = verbose ? [0 /* SnapshotNodeType.Folder */, node[1]] : null; for (const [name, child] of node[2]) addToFlat(child, currentPath ? currentPath + '/' + name : name); } }; addToFlat(snapshot, ''); return flat; } async fromFlat(snapshot, path) { path = (0, util_1.parseParts)(path ?? '').join('/'); if (path) path += '/'; for (const [currentPath, node] of Object.entries(snapshot)) { const fullPath = path + currentPath; if (!Array.isArray(node)) await this.put(fullPath, node ?? void 0); else await this.put(fullPath, node[0] === 1 /* SnapshotNodeType.File */ ? node[2] : void 0); } } async toBytes(path = '') { let dirpath = (0, util_1.parseParts)(path).join('/'); if (dirpath) dirpath += '/'; const entries = []; let i = 0; let fileReader; const encoder = new CborEncoder_1.CborEncoder(new Writer_1.Writer(1024 * 1024)); return new ReadableStream({ start: async () => { const info = await this.info(path); if (info.type === 'resource') { entries.push({ type: 'resource', id: info.id, path: '', }); return; } // We retrieve all locations before we start the actual byte stream // so that all locations are constant, in case the stream is used // to copy files into a subdirectory. for await (const entry of this.deepScan(path)) entries.push(entry); }, pull: async (controller) => { if (fileReader) { const { done, value } = await fileReader.read(); if (!done) { const u8 = encoder.encode(value); controller.enqueue(u8); return; } try { fileReader.releaseLock(); } catch { } fileReader = void 0; // Fall through to possibly emit next entry on the same pull } if (i < entries.length) { const entry = entries[i++]; if (entry.type === 'collection') { const dto = [0 /* SnapshotNodeType.Folder */, entry.path]; controller.enqueue(encoder.encode(dto)); } else if (entry.type === 'resource') { const dto = [1 /* SnapshotNodeType.File */, entry.path]; controller.enqueue(encoder.encode(dto)); const filepath = dirpath + entry.path; fileReader = (await this.read(filepath)).getReader(); } return; } controller.close(); }, cancel: async (reason) => { try { await fileReader?.cancel?.(reason); } catch { } try { fileReader?.releaseLock?.(); } catch { } fileReader = void 0; }, }); } async fromBytes(stream, path) { path = (0, util_1.parseParts)(path ?? '').join('/'); if (path) path += '/'; const reader = new StreamingReader_1.StreamingReader(); const decoder = new CborDecoder_1.CborDecoder(reader); let writer; try { const parse = async () => { while (reader.size() > 0) { const x = reader.x; try { const value = decoder.readAny(); reader.consume(); if (Array.isArray(value)) { writer?.close(); writer = undefined; if (value[0] === 0 /* SnapshotNodeType.Folder */) { const [, subPath] = value; if (typeof subPath !== 'string') throw new TypeError('INV_PATH'); const fullPath = path + (0, util_1.parseParts)(subPath).join('/'); await this.dir(fullPath); } else if (value[0] === 1 /* SnapshotNodeType.File */) { const [, subPath] = value; if (typeof subPath !== 'string') throw new TypeError('INV_PATH'); const fullPath = path + (0, util_1.parseParts)(subPath).join('/'); const writable = await this.write(fullPath); writer = writable.getWriter(); } } else if (value instanceof Uint8Array) { writer?.write(value); } } catch (err) { if (err instanceof RangeError) { reader.x = x; break; } else throw err; } } }; const r = stream.getReader(); while (true) { const { done, value } = await r.read(); if (done) break; reader.push(value); await parse(); } } finally { writer?.close(); } } } exports.CrudExtended = CrudExtended;