-
-
Notifications
You must be signed in to change notification settings - Fork 139
Closed
Labels
Description
- Implemen
FileHandle
API- Cleanup
- Maybe pass in
FsPromisesApi
instead of (in addition to)FsCallbackApi
- Maybe pass in
- Missing functionality
- FileHandle store current file position #734
-
FileHandle#sync
-
FileHandle#read
overload -
FileHandle#write
overload -
FileHandle[Symbol.asyncDispose]
-
FileHandle#writev
-
FileHandle#readv
- etc.
- etc.
- Cleanup
Below is Node.js implementation of FileHandle
, for reference. We need to implement a similar functionality for in-memory memfs
library.
class FileHandle extends EventEmitter {
/**
* @param {InternalFSBinding.FileHandle | undefined} filehandle
*/
constructor(filehandle) {
super();
markTransferMode(this, false, true);
this[kHandle] = filehandle;
this[kFd] = filehandle ? filehandle.fd : -1;
this[kRefs] = 1;
this[kClosePromise] = null;
}
getAsyncId() {
return this[kHandle].getAsyncId();
}
get fd() {
return this[kFd];
}
appendFile(data, options) {
return fsCall(writeFile, this, data, options);
}
chmod(mode) {
return fsCall(fchmod, this, mode);
}
chown(uid, gid) {
return fsCall(fchown, this, uid, gid);
}
datasync() {
return fsCall(fdatasync, this);
}
sync() {
return fsCall(fsync, this);
}
read(buffer, offset, length, position) {
return fsCall(read, this, buffer, offset, length, position);
}
readv(buffers, position) {
return fsCall(readv, this, buffers, position);
}
readFile(options) {
return fsCall(readFile, this, options);
}
readLines(options = undefined) {
return new Interface({
input: this.createReadStream(options),
crlfDelay: Infinity,
});
}
stat(options) {
return fsCall(fstat, this, options);
}
truncate(len = 0) {
return fsCall(ftruncate, this, len);
}
utimes(atime, mtime) {
return fsCall(futimes, this, atime, mtime);
}
write(buffer, offset, length, position) {
return fsCall(write, this, buffer, offset, length, position);
}
writev(buffers, position) {
return fsCall(writev, this, buffers, position);
}
writeFile(data, options) {
return fsCall(writeFile, this, data, options);
}
close = () => {
if (this[kFd] === -1) {
return PromiseResolve();
}
if (this[kClosePromise]) {
return this[kClosePromise];
}
this[kRefs]--;
if (this[kRefs] === 0) {
this[kFd] = -1;
this[kClosePromise] = SafePromisePrototypeFinally(
this[kHandle].close(),
() => { this[kClosePromise] = undefined; },
);
} else {
this[kClosePromise] = SafePromisePrototypeFinally(
new Promise((resolve, reject) => {
this[kCloseResolve] = resolve;
this[kCloseReject] = reject;
}), () => {
this[kClosePromise] = undefined;
this[kCloseReject] = undefined;
this[kCloseResolve] = undefined;
},
);
}
this.emit('close');
return this[kClosePromise];
};
async [SymbolAsyncDispose]() {
await this.close();
}
/**
* @typedef {import('../webstreams/readablestream').ReadableStream
* } ReadableStream
* @param {{ type?: 'bytes', autoClose?: boolean }} [options]
* @returns {ReadableStream}
*/
readableWebStream(options = kEmptyObject) {
if (this[kFd] === -1)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (this[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (this[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
this[kLocked] = true;
validateObject(options, 'options');
const {
type = 'bytes',
autoClose = false,
} = options;
validateBoolean(autoClose, 'options.autoClose');
if (type !== 'bytes') {
process.emitWarning(
'A non-"bytes" options.type has no effect. A byte-oriented steam is ' +
'always created.',
'ExperimentalWarning',
);
}
const readFn = FunctionPrototypeBind(this.read, this);
const ondone = async () => {
this[kUnref]();
if (autoClose) await this.close();
};
const ReadableStream = lazyReadableStream();
const readable = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 16384,
async pull(controller) {
const view = controller.byobRequest.view;
const { bytesRead } = await readFn(view, view.byteOffset, view.byteLength);
if (bytesRead === 0) {
controller.close();
await ondone();
}
controller.byobRequest.respond(bytesRead);
},
async cancel() {
await ondone();
},
});
const {
readableStreamCancel,
} = require('internal/webstreams/readablestream');
this[kRef]();
this.once('close', () => {
readableStreamCancel(readable);
});
return readable;
}
/**
* @typedef {import('./streams').ReadStream
* } ReadStream
* @param {{
* encoding?: string;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* end?: number;
* highWaterMark?: number;
* }} [options]
* @returns {ReadStream}
*/
createReadStream(options = undefined) {
const { ReadStream } = lazyFsStreams();
return new ReadStream(undefined, { ...options, fd: this });
}
/**
* @typedef {import('./streams').WriteStream
* } WriteStream
* @param {{
* encoding?: string;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* highWaterMark?: number;
* flush?: boolean;
* }} [options]
* @returns {WriteStream}
*/
createWriteStream(options = undefined) {
const { WriteStream } = lazyFsStreams();
return new WriteStream(undefined, { ...options, fd: this });
}
[kTransfer]() {
if (this[kClosePromise] || this[kRefs] > 1) {
throw lazyDOMException('Cannot transfer FileHandle while in use',
'DataCloneError');
}
const handle = this[kHandle];
this[kFd] = -1;
this[kHandle] = null;
this[kRefs] = 0;
return {
data: { handle },
deserializeInfo: 'internal/fs/promises:FileHandle',
};
}
[kTransferList]() {
return [ this[kHandle] ];
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
this[kFd] = handle.fd;
}
[kRef]() {
this[kRefs]++;
}
[kUnref]() {
this[kRefs]--;
if (this[kRefs] === 0) {
this[kFd] = -1;
PromisePrototypeThen(
this[kHandle].close(),
this[kCloseResolve],
this[kCloseReject],
);
}
}
}
Copilot