UNPKG

@zenfs/core

Version:

A filesystem, anywhere

136 lines (135 loc) 4.1 kB
import { withErrno } from 'kerium'; const _chunkSize = 0x1000; /** * Provides a consistent and easy to use internal API. * Default implementations for `exists` and `existsSync` are included. * If you are extending this class, note that every path is an absolute path and all arguments are present. * @category Internals * @internal */ export class FileSystem { type; name; label; /** * The last place this file system was mounted * @internal @protected */ _mountPoint; /** * The UUID of the file system. * @privateRemarks This is only used by `ioctl` * @internal @protected */ _uuid = crypto.randomUUID(); get uuid() { return this._uuid; } /** * @see FileSystemAttributes */ attributes = new Map(); constructor( /** * A unique ID for this kind of file system. * Currently unused internally, but could be used for partition tables or something */ type, /** * The name for this file system. * For example, tmpfs for an in memory one */ name) { this.type = type; this.name = name; if (this.streamRead === FileSystem.prototype.streamRead) this.attributes.set('default_stream_read'); if (this.streamWrite === FileSystem.prototype.streamWrite) this.attributes.set('default_stream_write'); } toString() { return `${this.name} ${this.label ? JSON.stringify(this.label) : ''} (${this._mountPoint ? 'mounted on ' + this._mountPoint : 'unmounted'})`; } /** * Default implementation. * @todo Implement * @experimental */ usage() { return { totalSpace: 0, freeSpace: 0, }; } async ready() { } readySync() { if (this.ready !== FileSystem.prototype.ready) throw withErrno('EAGAIN'); } /** * Test whether or not `path` exists. */ async exists(path) { try { await this.stat(path); return true; } catch (e) { return e.code != 'ENOENT'; } } /** * Test whether or not `path` exists. */ existsSync(path) { try { this.statSync(path); return true; } catch (e) { return e.code != 'ENOENT'; } } /** * Read a file using a stream. * @privateRemarks The default implementation of `streamRead` uses "chunked" `read`s */ streamRead(path, options) { return new ReadableStream({ start: async (controller) => { const { size } = await this.stat(path); const { start = 0, end = size } = options; for (let offset = start; offset < end; offset += _chunkSize) { const bytesRead = offset + _chunkSize > end ? end - offset : _chunkSize; const buffer = new Uint8Array(bytesRead); await this.read(path, buffer, offset, offset + bytesRead).catch(controller.error.bind(controller)); controller.enqueue(buffer); } controller.close(); }, type: 'bytes', }); } /** * Write a file using stream. * @privateRemarks The default implementation of `streamWrite` uses "chunked" `write`s */ streamWrite(path, options) { let position = options.start ?? 0; return new WritableStream({ write: async (chunk, controller) => { let err = false; const _err = (ex) => { err = true; controller.error(ex); }; const { size } = await this.stat(path); await this.write(path, chunk, position).catch(_err); if (err) return; position += chunk.byteLength; await this.touch(path, { mtimeMs: Date.now(), size: Math.max(size, position) }).catch(_err); }, }); } }