@zenfs/core
Version:
A filesystem, anywhere
136 lines (135 loc) • 4.1 kB
JavaScript
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);
},
});
}
}