diff options
author | Jeremiah Senkpiel <fishrock123@rocketmail.com> | 2019-08-27 17:14:27 -0700 |
---|---|---|
committer | Jeremiah Senkpiel <fishrock123@rocketmail.com> | 2019-10-08 10:34:48 -0700 |
commit | cbd8d715b2286e5726e6988921f5c870cbf74127 (patch) | |
tree | 20c878fd9d7882f2a5b43718a0b3daad4a9a0255 /lib/internal/fs/dir.js | |
parent | 064e111515185529b6f943dc66929440557fd609 (diff) | |
download | android-node-v8-cbd8d715b2286e5726e6988921f5c870cbf74127.tar.gz android-node-v8-cbd8d715b2286e5726e6988921f5c870cbf74127.tar.bz2 android-node-v8-cbd8d715b2286e5726e6988921f5c870cbf74127.zip |
fs: introduce `opendir()` and `fs.Dir`
This adds long-requested methods for asynchronously interacting and
iterating through directory entries by using `uv_fs_opendir`,
`uv_fs_readdir`, and `uv_fs_closedir`.
`fs.opendir()` and friends return an `fs.Dir`, which contains methods
for doing reads and cleanup. `fs.Dir` also has the async iterator
symbol exposed.
The `read()` method and friends only return `fs.Dirent`s for this API.
Having a entry type or doing a `stat` call is deemed to be necessary in
the majority of cases, so just returning dirents seems like the logical
choice for a new api.
Reading when there are no more entries returns `null` instead of a
dirent. However the async iterator hides that (and does automatic
cleanup).
The code lives in separate files from the rest of fs, this is done
partially to prevent over-pollution of those (already very large)
files, but also in the case of js allows loading into `fsPromises`.
Due to async_hooks, this introduces a new handle type of `DIRHANDLE`.
This PR does not attempt to make complete optimization of
this feature. Notable future improvements include:
- Moving promise work into C++ land like FileHandle.
- Possibly adding `readv()` to do multi-entry directory reads.
- Aliasing `fs.readdir` to `fs.scandir` and doing a deprecation.
Refs: https://github.com/nodejs/node-v0.x-archive/issues/388
Refs: https://github.com/nodejs/node/issues/583
Refs: https://github.com/libuv/libuv/pull/2057
PR-URL: https://github.com/nodejs/node/pull/29349
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: David Carlier <devnexen@gmail.com>
Diffstat (limited to 'lib/internal/fs/dir.js')
-rw-r--r-- | lib/internal/fs/dir.js | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/lib/internal/fs/dir.js b/lib/internal/fs/dir.js new file mode 100644 index 0000000000..c1a25f0de3 --- /dev/null +++ b/lib/internal/fs/dir.js @@ -0,0 +1,201 @@ +'use strict'; + +const { Object } = primordials; + +const pathModule = require('path'); +const binding = internalBinding('fs'); +const dirBinding = internalBinding('fs_dir'); +const { + codes: { + ERR_DIR_CLOSED, + ERR_INVALID_CALLBACK, + ERR_MISSING_ARGS + } +} = require('internal/errors'); + +const { FSReqCallback } = binding; +const internalUtil = require('internal/util'); +const { + getDirent, + getOptions, + getValidatedPath, + handleErrorFromBinding +} = require('internal/fs/utils'); + +const kDirHandle = Symbol('kDirHandle'); +const kDirPath = Symbol('kDirPath'); +const kDirClosed = Symbol('kDirClosed'); +const kDirOptions = Symbol('kDirOptions'); +const kDirReadPromisified = Symbol('kDirReadPromisified'); +const kDirClosePromisified = Symbol('kDirClosePromisified'); + +class Dir { + constructor(handle, path, options) { + if (handle == null) throw new ERR_MISSING_ARGS('handle'); + this[kDirHandle] = handle; + this[kDirPath] = path; + this[kDirClosed] = false; + + this[kDirOptions] = getOptions(options, { + encoding: 'utf8' + }); + + this[kDirReadPromisified] = internalUtil.promisify(this.read).bind(this); + this[kDirClosePromisified] = internalUtil.promisify(this.close).bind(this); + } + + get path() { + return this[kDirPath]; + } + + read(options, callback) { + if (this[kDirClosed] === true) { + throw new ERR_DIR_CLOSED(); + } + + callback = typeof options === 'function' ? options : callback; + if (callback === undefined) { + return this[kDirReadPromisified](options); + } else if (typeof callback !== 'function') { + throw new ERR_INVALID_CALLBACK(callback); + } + options = getOptions(options, this[kDirOptions]); + + const req = new FSReqCallback(); + req.oncomplete = (err, result) => { + if (err || result === null) { + return callback(err, result); + } + getDirent(this[kDirPath], result[0], result[1], callback); + }; + + this[kDirHandle].read( + options.encoding, + req + ); + } + + readSync(options) { + if (this[kDirClosed] === true) { + throw new ERR_DIR_CLOSED(); + } + + options = getOptions(options, this[kDirOptions]); + + const ctx = { path: this[kDirPath] }; + const result = this[kDirHandle].read( + options.encoding, + undefined, + ctx + ); + handleErrorFromBinding(ctx); + + if (result === null) { + return result; + } + + return getDirent(this[kDirPath], result[0], result[1]); + } + + close(callback) { + if (this[kDirClosed] === true) { + throw new ERR_DIR_CLOSED(); + } + + if (callback === undefined) { + return this[kDirClosePromisified](); + } else if (typeof callback !== 'function') { + throw new ERR_INVALID_CALLBACK(callback); + } + + this[kDirClosed] = true; + const req = new FSReqCallback(); + req.oncomplete = callback; + this[kDirHandle].close(req); + } + + closeSync() { + if (this[kDirClosed] === true) { + throw new ERR_DIR_CLOSED(); + } + + this[kDirClosed] = true; + const ctx = { path: this[kDirPath] }; + const result = this[kDirHandle].close(undefined, ctx); + handleErrorFromBinding(ctx); + return result; + } + + async* entries() { + try { + while (true) { + const result = await this[kDirReadPromisified](); + if (result === null) { + break; + } + yield result; + } + } finally { + await this[kDirClosePromisified](); + } + } +} + +Object.defineProperty(Dir.prototype, Symbol.asyncIterator, { + value: Dir.prototype.entries, + enumerable: false, + writable: true, + configurable: true, +}); + +function opendir(path, options, callback) { + callback = typeof options === 'function' ? options : callback; + if (typeof callback !== 'function') { + throw new ERR_INVALID_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 ctx = { path }; + const handle = dirBinding.opendir( + pathModule.toNamespacedPath(path), + options.encoding, + undefined, + ctx + ); + handleErrorFromBinding(ctx); + + return new Dir(handle, path, options); +} + +module.exports = { + Dir, + opendir, + opendirSync +}; |