mirror of
https://github.com/nodejs/node.git
synced 2025-05-03 02:06:12 +00:00

PR-URL: https://github.com/nodejs/node/pull/51037 Refs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
332 lines
7.3 KiB
JavaScript
332 lines
7.3 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayPrototypePush,
|
|
ArrayPrototypeShift,
|
|
FunctionPrototypeBind,
|
|
ObjectDefineProperty,
|
|
PromiseReject,
|
|
SymbolAsyncIterator,
|
|
} = primordials;
|
|
|
|
const pathModule = require('path');
|
|
const binding = internalBinding('fs');
|
|
const dirBinding = internalBinding('fs_dir');
|
|
const {
|
|
codes: {
|
|
ERR_DIR_CLOSED,
|
|
ERR_DIR_CONCURRENT_OPERATION,
|
|
ERR_MISSING_ARGS,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const { FSReqCallback } = binding;
|
|
const internalUtil = require('internal/util');
|
|
const {
|
|
getDirent,
|
|
getOptions,
|
|
getValidatedPath,
|
|
handleErrorFromBinding,
|
|
} = require('internal/fs/utils');
|
|
const {
|
|
validateFunction,
|
|
validateUint32,
|
|
} = require('internal/validators');
|
|
|
|
class Dir {
|
|
#handle;
|
|
#path;
|
|
#bufferedEntries = [];
|
|
#closed = false;
|
|
#options;
|
|
#readPromisified;
|
|
#closePromisified;
|
|
// Either `null` or an Array of pending operations (= functions to be called
|
|
// once the current operation is done).
|
|
#operationQueue = null;
|
|
|
|
constructor(handle, path, options) {
|
|
if (handle == null) throw new ERR_MISSING_ARGS('handle');
|
|
this.#handle = handle;
|
|
this.#path = path;
|
|
this.#options = {
|
|
bufferSize: 32,
|
|
...getOptions(options, {
|
|
encoding: 'utf8',
|
|
}),
|
|
};
|
|
|
|
validateUint32(this.#options.bufferSize, 'options.bufferSize', true);
|
|
|
|
this.#readPromisified = FunctionPrototypeBind(
|
|
internalUtil.promisify(this.#readImpl), this, false);
|
|
this.#closePromisified = FunctionPrototypeBind(
|
|
internalUtil.promisify(this.close), this);
|
|
}
|
|
|
|
get path() {
|
|
return this.#path;
|
|
}
|
|
|
|
read(callback) {
|
|
return this.#readImpl(true, callback);
|
|
}
|
|
|
|
#readImpl(maybeSync, callback) {
|
|
if (this.#closed === true) {
|
|
throw new ERR_DIR_CLOSED();
|
|
}
|
|
|
|
if (callback === undefined) {
|
|
return this.#readPromisified();
|
|
}
|
|
|
|
validateFunction(callback, 'callback');
|
|
|
|
if (this.#operationQueue !== null) {
|
|
ArrayPrototypePush(this.#operationQueue, () => {
|
|
this.#readImpl(maybeSync, callback);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (this.#bufferedEntries.length > 0) {
|
|
try {
|
|
const dirent = ArrayPrototypeShift(this.#bufferedEntries);
|
|
|
|
if (this.#options.recursive && dirent.isDirectory()) {
|
|
this.readSyncRecursive(dirent);
|
|
}
|
|
|
|
if (maybeSync)
|
|
process.nextTick(callback, null, dirent);
|
|
else
|
|
callback(null, dirent);
|
|
return;
|
|
} catch (error) {
|
|
return callback(error);
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = (err, result) => {
|
|
process.nextTick(() => {
|
|
const queue = this.#operationQueue;
|
|
this.#operationQueue = null;
|
|
for (const op of queue) op();
|
|
});
|
|
|
|
if (err || result === null) {
|
|
return callback(err, result);
|
|
}
|
|
|
|
try {
|
|
this.processReadResult(this.#path, result);
|
|
const dirent = ArrayPrototypeShift(this.#bufferedEntries);
|
|
if (this.#options.recursive && dirent.isDirectory()) {
|
|
this.readSyncRecursive(dirent);
|
|
}
|
|
callback(null, dirent);
|
|
} catch (error) {
|
|
callback(error);
|
|
}
|
|
};
|
|
|
|
this.#operationQueue = [];
|
|
this.#handle.read(
|
|
this.#options.encoding,
|
|
this.#options.bufferSize,
|
|
req,
|
|
);
|
|
}
|
|
|
|
processReadResult(path, result) {
|
|
for (let i = 0; i < result.length; i += 2) {
|
|
ArrayPrototypePush(
|
|
this.#bufferedEntries,
|
|
getDirent(
|
|
path,
|
|
result[i],
|
|
result[i + 1],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
readSyncRecursive(dirent) {
|
|
const path = pathModule.join(dirent.parentPath, dirent.name);
|
|
const ctx = { path };
|
|
const handle = dirBinding.opendir(
|
|
pathModule.toNamespacedPath(path),
|
|
this.#options.encoding,
|
|
undefined,
|
|
ctx,
|
|
);
|
|
handleErrorFromBinding(ctx);
|
|
const result = handle.read(
|
|
this.#options.encoding,
|
|
this.#options.bufferSize,
|
|
undefined,
|
|
ctx,
|
|
);
|
|
|
|
if (result) {
|
|
this.processReadResult(path, result);
|
|
}
|
|
|
|
handle.close(undefined, ctx);
|
|
handleErrorFromBinding(ctx);
|
|
}
|
|
|
|
readSync() {
|
|
if (this.#closed === true) {
|
|
throw new ERR_DIR_CLOSED();
|
|
}
|
|
|
|
if (this.#operationQueue !== null) {
|
|
throw new ERR_DIR_CONCURRENT_OPERATION();
|
|
}
|
|
|
|
if (this.#bufferedEntries.length > 0) {
|
|
const dirent = ArrayPrototypeShift(this.#bufferedEntries);
|
|
if (this.#options.recursive && dirent.isDirectory()) {
|
|
this.readSyncRecursive(dirent);
|
|
}
|
|
return dirent;
|
|
}
|
|
|
|
const ctx = { path: this.#path };
|
|
const result = this.#handle.read(
|
|
this.#options.encoding,
|
|
this.#options.bufferSize,
|
|
undefined,
|
|
ctx,
|
|
);
|
|
handleErrorFromBinding(ctx);
|
|
|
|
if (result === null) {
|
|
return result;
|
|
}
|
|
|
|
this.processReadResult(this.#path, result);
|
|
|
|
const dirent = ArrayPrototypeShift(this.#bufferedEntries);
|
|
if (this.#options.recursive && dirent.isDirectory()) {
|
|
this.readSyncRecursive(dirent);
|
|
}
|
|
return dirent;
|
|
}
|
|
|
|
close(callback) {
|
|
// Promise
|
|
if (callback === undefined) {
|
|
if (this.#closed === true) {
|
|
return PromiseReject(new ERR_DIR_CLOSED());
|
|
}
|
|
return this.#closePromisified();
|
|
}
|
|
|
|
// callback
|
|
validateFunction(callback, 'callback');
|
|
|
|
if (this.#closed === true) {
|
|
process.nextTick(callback, new ERR_DIR_CLOSED());
|
|
return;
|
|
}
|
|
|
|
if (this.#operationQueue !== null) {
|
|
ArrayPrototypePush(this.#operationQueue, () => {
|
|
this.close(callback);
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.#closed = true;
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
this.#handle.close(req);
|
|
}
|
|
|
|
closeSync() {
|
|
if (this.#closed === true) {
|
|
throw new ERR_DIR_CLOSED();
|
|
}
|
|
|
|
if (this.#operationQueue !== null) {
|
|
throw new ERR_DIR_CONCURRENT_OPERATION();
|
|
}
|
|
|
|
this.#closed = true;
|
|
const ctx = { path: this.#path };
|
|
const result = this.#handle.close(undefined, ctx);
|
|
handleErrorFromBinding(ctx);
|
|
return result;
|
|
}
|
|
|
|
async* entries() {
|
|
try {
|
|
while (true) {
|
|
const result = await this.#readPromisified();
|
|
if (result === null) {
|
|
break;
|
|
}
|
|
yield result;
|
|
}
|
|
} finally {
|
|
await this.#closePromisified();
|
|
}
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperty(Dir.prototype, SymbolAsyncIterator, {
|
|
__proto__: null,
|
|
value: Dir.prototype.entries,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
function opendir(path, options, callback) {
|
|
callback = typeof options === 'function' ? options : callback;
|
|
validateFunction(callback, 'callback');
|
|
|
|
path = getValidatedPath(path);
|
|
options = getOptions(options, {
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
function opendirCallback(error, handle) {
|
|
if (error) {
|
|
callback(error);
|
|
} else {
|
|
callback(null, new Dir(handle, path, options));
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = opendirCallback;
|
|
|
|
dirBinding.opendir(
|
|
pathModule.toNamespacedPath(path),
|
|
options.encoding,
|
|
req,
|
|
);
|
|
}
|
|
|
|
function opendirSync(path, options) {
|
|
path = getValidatedPath(path);
|
|
options = getOptions(options, { encoding: 'utf8' });
|
|
|
|
const handle = dirBinding.opendirSync(
|
|
pathModule.toNamespacedPath(path),
|
|
);
|
|
|
|
return new Dir(handle, path, options);
|
|
}
|
|
|
|
module.exports = {
|
|
Dir,
|
|
opendir,
|
|
opendirSync,
|
|
};
|