fs-zoo
Version:
File system abstractions and implementations
283 lines (282 loc) • 10.8 kB
JavaScript
"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;