UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

295 lines (242 loc) 7 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/fs/dir.js import * as pathModule from "nstdlib/lib/path"; import * as binding from "nstdlib/stub/binding/fs"; import * as dirBinding from "nstdlib/stub/binding/fs_dir"; import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import * as internalUtil from "nstdlib/lib/internal/util"; import { getDirent, getOptions, getValidatedPath, } from "nstdlib/lib/internal/fs/utils"; import { validateFunction, validateUint32, } from "nstdlib/lib/internal/validators"; const { ERR_DIR_CLOSED, ERR_DIR_CONCURRENT_OPERATION, ERR_MISSING_ARGS } = __codes__; const { FSReqCallback } = binding; 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 = Function.prototype.bind.call( internalUtil.promisify(this.#readImpl), this, false, ); this.#closePromisified = Function.prototype.bind.call( 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) { Array.prototype.push.call(this.#operationQueue, () => { this.#readImpl(maybeSync, callback); }); return; } if (this.#bufferedEntries.length > 0) { try { const dirent = Array.prototype.shift.call(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 = Array.prototype.shift.call(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) { Array.prototype.push.call( this.#bufferedEntries, getDirent(path, result[i], result[i + 1]), ); } } readSyncRecursive(dirent) { const path = pathModule.join(dirent.parentPath, dirent.name); const handle = dirBinding.opendir(path, this.#options.encoding); // Terminate early, since it's already thrown. if (handle === undefined) { return; } const result = handle.read( this.#options.encoding, this.#options.bufferSize, ); if (result) { this.processReadResult(path, result); } handle.close(); } 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 = Array.prototype.shift.call(this.#bufferedEntries); if (this.#options.recursive && dirent.isDirectory()) { this.readSyncRecursive(dirent); } return dirent; } const result = this.#handle.read( this.#options.encoding, this.#options.bufferSize, ); if (result === null) { return result; } this.processReadResult(this.#path, result); const dirent = Array.prototype.shift.call(this.#bufferedEntries); if (this.#options.recursive && dirent.isDirectory()) { this.readSyncRecursive(dirent); } return dirent; } close(callback) { // Promise if (callback === undefined) { if (this.#closed === true) { return Promise.reject(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) { Array.prototype.push.call(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; this.#handle.close(); } async *entries() { try { while (true) { const result = await this.#readPromisified(); if (result === null) { break; } yield result; } } finally { await this.#closePromisified(); } } } Object.defineProperty(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(path, options.encoding, req); } function opendirSync(path, options) { path = getValidatedPath(path); options = getOptions(options, { encoding: "utf8" }); const handle = dirBinding.opendirSync(path); return new Dir(handle, path, options); } export { Dir }; export { opendir }; export { opendirSync };