fs: readdir optionally returning type information
readdir and readdirSync now have a "withFileTypes" option, which, when enabled, provides an array of DirectoryEntry objects, similar to Stats objects, which have the filename and the type information. Refs: https://github.com/nodejs/node/issues/15699 PR-URL: https://github.com/nodejs/node/pull/22020 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
This commit is contained in:
parent
78584b64d8
commit
c7944a7a7b
101
doc/api/fs.md
101
doc/api/fs.md
@ -283,6 +283,92 @@ synchronous use libuv's threadpool, which can have surprising and negative
|
|||||||
performance implications for some applications. See the
|
performance implications for some applications. See the
|
||||||
[`UV_THREADPOOL_SIZE`][] documentation for more information.
|
[`UV_THREADPOOL_SIZE`][] documentation for more information.
|
||||||
|
|
||||||
|
## Class: fs.Dirent
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
When [`fs.readdir()`][] or [`fs.readdirSync()`][] is called with the
|
||||||
|
`withFileTypes` option set to `true`, the resulting array is filled with
|
||||||
|
`fs.Dirent` objects, rather than strings or `Buffers`.
|
||||||
|
|
||||||
|
### dirent.isBlockDevice()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a block device.
|
||||||
|
|
||||||
|
### dirent.isCharacterDevice()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a character device.
|
||||||
|
|
||||||
|
### dirent.isDirectory()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a file system
|
||||||
|
directory.
|
||||||
|
|
||||||
|
### dirent.isFIFO()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a first-in-first-out
|
||||||
|
(FIFO) pipe.
|
||||||
|
|
||||||
|
### dirent.isFile()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a regular file.
|
||||||
|
|
||||||
|
### dirent.isSocket()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a socket.
|
||||||
|
|
||||||
|
### dirent.isSymbolicLink()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Returns `true` if the `fs.Dirent` object describes a symbolic link.
|
||||||
|
|
||||||
|
|
||||||
|
### dirent.name
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* {string|Buffer}
|
||||||
|
|
||||||
|
The file name that this `fs.Dirent` object refers to. The type of this
|
||||||
|
value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
|
||||||
|
[`fs.readdirSync()`][].
|
||||||
|
|
||||||
## Class: fs.FSWatcher
|
## Class: fs.FSWatcher
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.5.8
|
added: v0.5.8
|
||||||
@ -2319,9 +2405,10 @@ changes:
|
|||||||
* `path` {string|Buffer|URL}
|
* `path` {string|Buffer|URL}
|
||||||
* `options` {string|Object}
|
* `options` {string|Object}
|
||||||
* `encoding` {string} **Default:** `'utf8'`
|
* `encoding` {string} **Default:** `'utf8'`
|
||||||
|
* `withFileTypes` {boolean} **Default:** `false`
|
||||||
* `callback` {Function}
|
* `callback` {Function}
|
||||||
* `err` {Error}
|
* `err` {Error}
|
||||||
* `files` {string[]|Buffer[]}
|
* `files` {string[]|Buffer[]|fs.Dirent[]}
|
||||||
|
|
||||||
Asynchronous readdir(3). Reads the contents of a directory.
|
Asynchronous readdir(3). Reads the contents of a directory.
|
||||||
The callback gets two arguments `(err, files)` where `files` is an array of
|
The callback gets two arguments `(err, files)` where `files` is an array of
|
||||||
@ -2332,6 +2419,9 @@ object with an `encoding` property specifying the character encoding to use for
|
|||||||
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
|
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
|
||||||
the filenames returned will be passed as `Buffer` objects.
|
the filenames returned will be passed as `Buffer` objects.
|
||||||
|
|
||||||
|
If `options.withFileTypes` is set to `true`, the `files` array will contain
|
||||||
|
[`fs.Dirent`][] objects.
|
||||||
|
|
||||||
## fs.readdirSync(path[, options])
|
## fs.readdirSync(path[, options])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.21
|
added: v0.1.21
|
||||||
@ -2345,7 +2435,8 @@ changes:
|
|||||||
* `path` {string|Buffer|URL}
|
* `path` {string|Buffer|URL}
|
||||||
* `options` {string|Object}
|
* `options` {string|Object}
|
||||||
* `encoding` {string} **Default:** `'utf8'`
|
* `encoding` {string} **Default:** `'utf8'`
|
||||||
* Returns: {string[]} An array of filenames excluding `'.'` and `'..'`.
|
* `withFileTypes` {boolean} **Default:** `false`
|
||||||
|
* Returns: {string[]|Buffer[]|fs.Dirent[]}
|
||||||
|
|
||||||
Synchronous readdir(3).
|
Synchronous readdir(3).
|
||||||
|
|
||||||
@ -2354,6 +2445,9 @@ object with an `encoding` property specifying the character encoding to use for
|
|||||||
the filenames returned. If the `encoding` is set to `'buffer'`,
|
the filenames returned. If the `encoding` is set to `'buffer'`,
|
||||||
the filenames returned will be passed as `Buffer` objects.
|
the filenames returned will be passed as `Buffer` objects.
|
||||||
|
|
||||||
|
If `options.withFileTypes` is set to `true`, the result will contain
|
||||||
|
[`fs.Dirent`][] objects.
|
||||||
|
|
||||||
## fs.readFile(path[, options], callback)
|
## fs.readFile(path[, options], callback)
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.29
|
added: v0.1.29
|
||||||
@ -4637,6 +4731,7 @@ the file contents.
|
|||||||
[`WriteStream`]: #fs_class_fs_writestream
|
[`WriteStream`]: #fs_class_fs_writestream
|
||||||
[`EventEmitter`]: events.html
|
[`EventEmitter`]: events.html
|
||||||
[`event ports`]: http://illumos.org/man/port_create
|
[`event ports`]: http://illumos.org/man/port_create
|
||||||
|
[`fs.Dirent`]: #fs_class_fs_dirent
|
||||||
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
|
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
|
||||||
[`fs.Stats`]: #fs_class_fs_stats
|
[`fs.Stats`]: #fs_class_fs_stats
|
||||||
[`fs.access()`]: #fs_fs_access_path_mode_callback
|
[`fs.access()`]: #fs_fs_access_path_mode_callback
|
||||||
@ -4652,6 +4747,8 @@ the file contents.
|
|||||||
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
|
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
|
||||||
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
|
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
|
||||||
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
|
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
|
||||||
|
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
|
||||||
|
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
|
||||||
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
|
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
|
||||||
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
|
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
|
||||||
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
|
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
|
||||||
|
23
lib/fs.js
23
lib/fs.js
@ -58,6 +58,8 @@ const { getPathFromURL } = require('internal/url');
|
|||||||
const internalUtil = require('internal/util');
|
const internalUtil = require('internal/util');
|
||||||
const {
|
const {
|
||||||
copyObject,
|
copyObject,
|
||||||
|
Dirent,
|
||||||
|
getDirents,
|
||||||
getOptions,
|
getOptions,
|
||||||
nullCheck,
|
nullCheck,
|
||||||
preprocessSymlinkDestination,
|
preprocessSymlinkDestination,
|
||||||
@ -773,8 +775,19 @@ function readdir(path, options, callback) {
|
|||||||
validatePath(path);
|
validatePath(path);
|
||||||
|
|
||||||
const req = new FSReqCallback();
|
const req = new FSReqCallback();
|
||||||
req.oncomplete = callback;
|
if (!options.withFileTypes) {
|
||||||
binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
|
req.oncomplete = callback;
|
||||||
|
} else {
|
||||||
|
req.oncomplete = (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDirents(path, result, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
|
||||||
|
!!options.withFileTypes, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readdirSync(path, options) {
|
function readdirSync(path, options) {
|
||||||
@ -783,9 +796,10 @@ function readdirSync(path, options) {
|
|||||||
validatePath(path);
|
validatePath(path);
|
||||||
const ctx = { path };
|
const ctx = { path };
|
||||||
const result = binding.readdir(pathModule.toNamespacedPath(path),
|
const result = binding.readdir(pathModule.toNamespacedPath(path),
|
||||||
options.encoding, undefined, ctx);
|
options.encoding, !!options.withFileTypes,
|
||||||
|
undefined, ctx);
|
||||||
handleErrorFromBinding(ctx);
|
handleErrorFromBinding(ctx);
|
||||||
return result;
|
return options.withFileTypes ? getDirents(path, result) : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fstat(fd, options, callback) {
|
function fstat(fd, options, callback) {
|
||||||
@ -1819,6 +1833,7 @@ module.exports = fs = {
|
|||||||
writeFileSync,
|
writeFileSync,
|
||||||
write,
|
write,
|
||||||
writeSync,
|
writeSync,
|
||||||
|
Dirent,
|
||||||
Stats,
|
Stats,
|
||||||
|
|
||||||
get ReadStream() {
|
get ReadStream() {
|
||||||
|
@ -19,6 +19,7 @@ const { getPathFromURL } = require('internal/url');
|
|||||||
const { isUint8Array } = require('internal/util/types');
|
const { isUint8Array } = require('internal/util/types');
|
||||||
const {
|
const {
|
||||||
copyObject,
|
copyObject,
|
||||||
|
getDirents,
|
||||||
getOptions,
|
getOptions,
|
||||||
getStatsFromBinding,
|
getStatsFromBinding,
|
||||||
nullCheck,
|
nullCheck,
|
||||||
@ -37,10 +38,13 @@ const {
|
|||||||
validateUint32
|
validateUint32
|
||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
const pathModule = require('path');
|
const pathModule = require('path');
|
||||||
|
const { promisify } = require('internal/util');
|
||||||
|
|
||||||
const kHandle = Symbol('handle');
|
const kHandle = Symbol('handle');
|
||||||
const { kUsePromises } = binding;
|
const { kUsePromises } = binding;
|
||||||
|
|
||||||
|
const getDirectoryEntriesPromise = promisify(getDirents);
|
||||||
|
|
||||||
class FileHandle {
|
class FileHandle {
|
||||||
constructor(filehandle) {
|
constructor(filehandle) {
|
||||||
this[kHandle] = filehandle;
|
this[kHandle] = filehandle;
|
||||||
@ -312,8 +316,12 @@ async function readdir(path, options) {
|
|||||||
options = getOptions(options, {});
|
options = getOptions(options, {});
|
||||||
path = getPathFromURL(path);
|
path = getPathFromURL(path);
|
||||||
validatePath(path);
|
validatePath(path);
|
||||||
return binding.readdir(pathModule.toNamespacedPath(path),
|
const result = await binding.readdir(pathModule.toNamespacedPath(path),
|
||||||
options.encoding, kUsePromises);
|
options.encoding, !!options.withTypes,
|
||||||
|
kUsePromises);
|
||||||
|
return options.withFileTypes ?
|
||||||
|
getDirectoryEntriesPromise(path, result) :
|
||||||
|
result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readlink(path, options) {
|
async function readlink(path, options) {
|
||||||
|
@ -12,6 +12,8 @@ const {
|
|||||||
const { isUint8Array } = require('internal/util/types');
|
const { isUint8Array } = require('internal/util/types');
|
||||||
const pathModule = require('path');
|
const pathModule = require('path');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
const kType = Symbol('type');
|
||||||
|
const kStats = Symbol('stats');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
O_APPEND,
|
O_APPEND,
|
||||||
@ -31,17 +33,84 @@ const {
|
|||||||
S_IFREG,
|
S_IFREG,
|
||||||
S_IFSOCK,
|
S_IFSOCK,
|
||||||
UV_FS_SYMLINK_DIR,
|
UV_FS_SYMLINK_DIR,
|
||||||
UV_FS_SYMLINK_JUNCTION
|
UV_FS_SYMLINK_JUNCTION,
|
||||||
|
UV_DIRENT_UNKNOWN,
|
||||||
|
UV_DIRENT_FILE,
|
||||||
|
UV_DIRENT_DIR,
|
||||||
|
UV_DIRENT_LINK,
|
||||||
|
UV_DIRENT_FIFO,
|
||||||
|
UV_DIRENT_SOCKET,
|
||||||
|
UV_DIRENT_CHAR,
|
||||||
|
UV_DIRENT_BLOCK
|
||||||
} = process.binding('constants').fs;
|
} = process.binding('constants').fs;
|
||||||
|
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
|
let fs;
|
||||||
|
function lazyLoadFs() {
|
||||||
|
if (!fs) {
|
||||||
|
fs = require('fs');
|
||||||
|
}
|
||||||
|
return fs;
|
||||||
|
}
|
||||||
|
|
||||||
function assertEncoding(encoding) {
|
function assertEncoding(encoding) {
|
||||||
if (encoding && !Buffer.isEncoding(encoding)) {
|
if (encoding && !Buffer.isEncoding(encoding)) {
|
||||||
throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
|
throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Dirent {
|
||||||
|
constructor(name, type) {
|
||||||
|
this.name = name;
|
||||||
|
this[kType] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirectory() {
|
||||||
|
return this[kType] === UV_DIRENT_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFile() {
|
||||||
|
return this[kType] === UV_DIRENT_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
isBlockDevice() {
|
||||||
|
return this[kType] === UV_DIRENT_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCharacterDevice() {
|
||||||
|
return this[kType] === UV_DIRENT_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSymbolicLink() {
|
||||||
|
return this[kType] === UV_DIRENT_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFIFO() {
|
||||||
|
return this[kType] === UV_DIRENT_FIFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSocket() {
|
||||||
|
return this[kType] === UV_DIRENT_SOCKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirentFromStats extends Dirent {
|
||||||
|
constructor(name, stats) {
|
||||||
|
super(name, null);
|
||||||
|
this[kStats] = stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of Reflect.ownKeys(Dirent.prototype)) {
|
||||||
|
if (name === 'constructor') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DirentFromStats.prototype[name] = function() {
|
||||||
|
return this[kStats][name]();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function copyObject(source) {
|
function copyObject(source) {
|
||||||
var target = {};
|
var target = {};
|
||||||
for (var key in source)
|
for (var key in source)
|
||||||
@ -49,6 +118,50 @@ function copyObject(source) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDirents(path, [names, types], callback) {
|
||||||
|
var i;
|
||||||
|
if (typeof callback == 'function') {
|
||||||
|
const len = names.length;
|
||||||
|
let toFinish = 0;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
const type = types[i];
|
||||||
|
if (type === UV_DIRENT_UNKNOWN) {
|
||||||
|
const name = names[i];
|
||||||
|
const idx = i;
|
||||||
|
toFinish++;
|
||||||
|
lazyLoadFs().stat(pathModule.resolve(path, name), (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
names[idx] = new DirentFromStats(name, stats);
|
||||||
|
if (--toFinish === 0) {
|
||||||
|
callback(null, names);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
names[i] = new Dirent(names[i], types[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toFinish === 0) {
|
||||||
|
callback(null, names);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const len = names.length;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
const type = types[i];
|
||||||
|
if (type === UV_DIRENT_UNKNOWN) {
|
||||||
|
const name = names[i];
|
||||||
|
const stats = lazyLoadFs().statSync(pathModule.resolve(path, name));
|
||||||
|
names[i] = new DirentFromStats(name, stats);
|
||||||
|
} else {
|
||||||
|
names[i] = new Dirent(names[i], types[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getOptions(options, defaultOptions) {
|
function getOptions(options, defaultOptions) {
|
||||||
if (options === null || options === undefined ||
|
if (options === null || options === undefined ||
|
||||||
typeof options === 'function') {
|
typeof options === 'function') {
|
||||||
@ -342,6 +455,8 @@ function validatePath(path, propName = 'path') {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
assertEncoding,
|
assertEncoding,
|
||||||
copyObject,
|
copyObject,
|
||||||
|
Dirent,
|
||||||
|
getDirents,
|
||||||
getOptions,
|
getOptions,
|
||||||
nullCheck,
|
nullCheck,
|
||||||
preprocessSymlinkDestination,
|
preprocessSymlinkDestination,
|
||||||
|
@ -1022,6 +1022,16 @@ void DefineSystemConstants(Local<Object> target) {
|
|||||||
NODE_DEFINE_CONSTANT(target, O_WRONLY);
|
NODE_DEFINE_CONSTANT(target, O_WRONLY);
|
||||||
NODE_DEFINE_CONSTANT(target, O_RDWR);
|
NODE_DEFINE_CONSTANT(target, O_RDWR);
|
||||||
|
|
||||||
|
// file types from readdir
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_UNKNOWN);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FILE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_DIR);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_LINK);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FIFO);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_SOCKET);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_CHAR);
|
||||||
|
NODE_DEFINE_CONSTANT(target, UV_DIRENT_BLOCK);
|
||||||
|
|
||||||
NODE_DEFINE_CONSTANT(target, S_IFMT);
|
NODE_DEFINE_CONSTANT(target, S_IFMT);
|
||||||
NODE_DEFINE_CONSTANT(target, S_IFREG);
|
NODE_DEFINE_CONSTANT(target, S_IFREG);
|
||||||
NODE_DEFINE_CONSTANT(target, S_IFDIR);
|
NODE_DEFINE_CONSTANT(target, S_IFDIR);
|
||||||
|
239
src/node_file.cc
239
src/node_file.cc
@ -561,51 +561,139 @@ void AfterScanDir(uv_fs_t* req) {
|
|||||||
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||||||
FSReqAfterScope after(req_wrap, req);
|
FSReqAfterScope after(req_wrap, req);
|
||||||
|
|
||||||
if (after.Proceed()) {
|
if (!after.Proceed()) {
|
||||||
Environment* env = req_wrap->env();
|
return;
|
||||||
Local<Value> error;
|
|
||||||
int r;
|
|
||||||
Local<Array> names = Array::New(env->isolate(), 0);
|
|
||||||
Local<Function> fn = env->push_values_to_array_function();
|
|
||||||
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
|
||||||
size_t name_idx = 0;
|
|
||||||
|
|
||||||
for (int i = 0; ; i++) {
|
|
||||||
uv_dirent_t ent;
|
|
||||||
|
|
||||||
r = uv_fs_scandir_next(req, &ent);
|
|
||||||
if (r == UV_EOF)
|
|
||||||
break;
|
|
||||||
if (r != 0) {
|
|
||||||
return req_wrap->Reject(
|
|
||||||
UVException(r, nullptr, req_wrap->syscall(),
|
|
||||||
static_cast<const char*>(req->path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
MaybeLocal<Value> filename =
|
|
||||||
StringBytes::Encode(env->isolate(),
|
|
||||||
ent.name,
|
|
||||||
req_wrap->encoding(),
|
|
||||||
&error);
|
|
||||||
if (filename.IsEmpty())
|
|
||||||
return req_wrap->Reject(error);
|
|
||||||
|
|
||||||
name_argv[name_idx++] = filename.ToLocalChecked();
|
|
||||||
|
|
||||||
if (name_idx >= arraysize(name_argv)) {
|
|
||||||
fn->Call(env->context(), names, name_idx, name_argv)
|
|
||||||
.ToLocalChecked();
|
|
||||||
name_idx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name_idx > 0) {
|
|
||||||
fn->Call(env->context(), names, name_idx, name_argv)
|
|
||||||
.ToLocalChecked();
|
|
||||||
}
|
|
||||||
|
|
||||||
req_wrap->Resolve(names);
|
|
||||||
}
|
}
|
||||||
|
Environment* env = req_wrap->env();
|
||||||
|
Local<Value> error;
|
||||||
|
int r;
|
||||||
|
Local<Array> names = Array::New(env->isolate(), 0);
|
||||||
|
Local<Function> fn = env->push_values_to_array_function();
|
||||||
|
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
||||||
|
size_t name_idx = 0;
|
||||||
|
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
uv_dirent_t ent;
|
||||||
|
|
||||||
|
r = uv_fs_scandir_next(req, &ent);
|
||||||
|
if (r == UV_EOF)
|
||||||
|
break;
|
||||||
|
if (r != 0) {
|
||||||
|
return req_wrap->Reject(
|
||||||
|
UVException(r, nullptr, req_wrap->syscall(),
|
||||||
|
static_cast<const char*>(req->path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeLocal<Value> filename =
|
||||||
|
StringBytes::Encode(env->isolate(),
|
||||||
|
ent.name,
|
||||||
|
req_wrap->encoding(),
|
||||||
|
&error);
|
||||||
|
if (filename.IsEmpty())
|
||||||
|
return req_wrap->Reject(error);
|
||||||
|
|
||||||
|
name_argv[name_idx++] = filename.ToLocalChecked();
|
||||||
|
|
||||||
|
if (name_idx >= arraysize(name_argv)) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
|
||||||
|
name_argv);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
name_idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name_idx > 0) {
|
||||||
|
fn->Call(env->context(), names, name_idx, name_argv)
|
||||||
|
.ToLocalChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
req_wrap->Resolve(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AfterScanDirWithTypes(uv_fs_t* req) {
|
||||||
|
FSReqBase* req_wrap = FSReqBase::from_req(req);
|
||||||
|
FSReqAfterScope after(req_wrap, req);
|
||||||
|
|
||||||
|
if (!after.Proceed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment* env = req_wrap->env();
|
||||||
|
Local<Value> error;
|
||||||
|
int r;
|
||||||
|
Local<Array> names = Array::New(env->isolate(), 0);
|
||||||
|
Local<Function> fn = env->push_values_to_array_function();
|
||||||
|
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
||||||
|
size_t name_idx = 0;
|
||||||
|
Local<Value> types = Array::New(env->isolate(), 0);
|
||||||
|
Local<Value> type_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
||||||
|
size_t type_idx = 0;
|
||||||
|
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
uv_dirent_t ent;
|
||||||
|
|
||||||
|
r = uv_fs_scandir_next(req, &ent);
|
||||||
|
if (r == UV_EOF)
|
||||||
|
break;
|
||||||
|
if (r != 0) {
|
||||||
|
return req_wrap->Reject(
|
||||||
|
UVException(r, nullptr, req_wrap->syscall(),
|
||||||
|
static_cast<const char*>(req->path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeLocal<Value> filename =
|
||||||
|
StringBytes::Encode(env->isolate(),
|
||||||
|
ent.name,
|
||||||
|
req_wrap->encoding(),
|
||||||
|
&error);
|
||||||
|
if (filename.IsEmpty())
|
||||||
|
return req_wrap->Reject(error);
|
||||||
|
|
||||||
|
name_argv[name_idx++] = filename.ToLocalChecked();
|
||||||
|
|
||||||
|
if (name_idx >= arraysize(name_argv)) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
|
||||||
|
name_argv);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
name_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
type_argv[type_idx++] = Integer::New(env->isolate(), ent.type);
|
||||||
|
|
||||||
|
if (type_idx >= arraysize(type_argv)) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
|
||||||
|
type_argv);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type_idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name_idx > 0) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
|
||||||
|
name_argv);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type_idx > 0) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
|
||||||
|
type_argv);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Array> result = Array::New(env->isolate(), 2);
|
||||||
|
result->Set(0, names);
|
||||||
|
result->Set(1, types);
|
||||||
|
req_wrap->Resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1372,15 +1460,22 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
|
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
|
||||||
|
|
||||||
FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
|
bool with_types = args[2]->BooleanValue();
|
||||||
if (req_wrap_async != nullptr) { // readdir(path, encoding, req)
|
|
||||||
AsyncCall(env, req_wrap_async, args, "scandir", encoding, AfterScanDir,
|
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
|
||||||
uv_fs_scandir, *path, 0 /*flags*/);
|
if (req_wrap_async != nullptr) { // readdir(path, encoding, withTypes, req)
|
||||||
} else { // readdir(path, encoding, undefined, ctx)
|
if (with_types) {
|
||||||
CHECK_EQ(argc, 4);
|
AsyncCall(env, req_wrap_async, args, "scandir", encoding,
|
||||||
|
AfterScanDirWithTypes, uv_fs_scandir, *path, 0 /*flags*/);
|
||||||
|
} else {
|
||||||
|
AsyncCall(env, req_wrap_async, args, "scandir", encoding,
|
||||||
|
AfterScanDir, uv_fs_scandir, *path, 0 /*flags*/);
|
||||||
|
}
|
||||||
|
} else { // readdir(path, encoding, withTypes, undefined, ctx)
|
||||||
|
CHECK_EQ(argc, 5);
|
||||||
FSReqWrapSync req_wrap_sync;
|
FSReqWrapSync req_wrap_sync;
|
||||||
FS_SYNC_TRACE_BEGIN(readdir);
|
FS_SYNC_TRACE_BEGIN(readdir);
|
||||||
int err = SyncCall(env, args[3], &req_wrap_sync, "scandir",
|
int err = SyncCall(env, args[4], &req_wrap_sync, "scandir",
|
||||||
uv_fs_scandir, *path, 0 /*flags*/);
|
uv_fs_scandir, *path, 0 /*flags*/);
|
||||||
FS_SYNC_TRACE_END(readdir);
|
FS_SYNC_TRACE_END(readdir);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
@ -1394,6 +1489,14 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
||||||
size_t name_idx = 0;
|
size_t name_idx = 0;
|
||||||
|
|
||||||
|
Local<Value> types;
|
||||||
|
Local<Value> type_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
|
||||||
|
size_t type_idx;
|
||||||
|
if (with_types) {
|
||||||
|
types = Array::New(env->isolate(), 0);
|
||||||
|
type_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; ; i++) {
|
for (int i = 0; ; i++) {
|
||||||
uv_dirent_t ent;
|
uv_dirent_t ent;
|
||||||
|
|
||||||
@ -1401,7 +1504,7 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
if (r == UV_EOF)
|
if (r == UV_EOF)
|
||||||
break;
|
break;
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
Local<Object> ctx = args[3].As<Object>();
|
Local<Object> ctx = args[4].As<Object>();
|
||||||
ctx->Set(env->context(), env->errno_string(),
|
ctx->Set(env->context(), env->errno_string(),
|
||||||
Integer::New(env->isolate(), r)).FromJust();
|
Integer::New(env->isolate(), r)).FromJust();
|
||||||
ctx->Set(env->context(), env->syscall_string(),
|
ctx->Set(env->context(), env->syscall_string(),
|
||||||
@ -1414,8 +1517,9 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
ent.name,
|
ent.name,
|
||||||
encoding,
|
encoding,
|
||||||
&error);
|
&error);
|
||||||
|
|
||||||
if (filename.IsEmpty()) {
|
if (filename.IsEmpty()) {
|
||||||
Local<Object> ctx = args[3].As<Object>();
|
Local<Object> ctx = args[4].As<Object>();
|
||||||
ctx->Set(env->context(), env->error_string(), error).FromJust();
|
ctx->Set(env->context(), env->error_string(), error).FromJust();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1430,6 +1534,19 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
name_idx = 0;
|
name_idx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (with_types) {
|
||||||
|
type_v[type_idx++] = Integer::New(env->isolate(), ent.type);
|
||||||
|
|
||||||
|
if (type_idx >= arraysize(type_v)) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
|
||||||
|
type_v);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type_idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name_idx > 0) {
|
if (name_idx > 0) {
|
||||||
@ -1439,7 +1556,21 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.GetReturnValue().Set(names);
|
if (with_types && type_idx > 0) {
|
||||||
|
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx, type_v);
|
||||||
|
if (ret.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (with_types) {
|
||||||
|
Local<Array> result = Array::New(env->isolate(), 2);
|
||||||
|
result->Set(0, names);
|
||||||
|
result->Set(1, types);
|
||||||
|
args.GetReturnValue().Set(result);
|
||||||
|
} else {
|
||||||
|
args.GetReturnValue().Set(names);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
95
test/parallel/test-fs-readdir-types.js
Normal file
95
test/parallel/test-fs-readdir-types.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const tmpdir = require('../common/tmpdir');
|
||||||
|
|
||||||
|
const binding = process.binding('fs');
|
||||||
|
|
||||||
|
const readdirDir = tmpdir.path;
|
||||||
|
const files = ['empty', 'files', 'for', 'just', 'testing'];
|
||||||
|
const constants = process.binding('constants').fs;
|
||||||
|
const types = {
|
||||||
|
isDirectory: constants.UV_DIRENT_DIR,
|
||||||
|
isFile: constants.UV_DIRENT_FILE,
|
||||||
|
isBlockDevice: constants.UV_DIRENT_BLOCK,
|
||||||
|
isCharacterDevice: constants.UV_DIRENT_CHAR,
|
||||||
|
isSymbolicLink: constants.UV_DIRENT_LINK,
|
||||||
|
isFIFO: constants.UV_DIRENT_FIFO,
|
||||||
|
isSocket: constants.UV_DIRENT_SOCKET
|
||||||
|
};
|
||||||
|
const typeMethods = Object.keys(types);
|
||||||
|
|
||||||
|
// Make sure tmp directory is clean
|
||||||
|
tmpdir.refresh();
|
||||||
|
|
||||||
|
// Create the necessary files
|
||||||
|
files.forEach(function(currentFile) {
|
||||||
|
fs.closeSync(fs.openSync(`${readdirDir}/${currentFile}`, 'w'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function assertDirents(dirents) {
|
||||||
|
assert.strictEqual(files.length, dirents.length);
|
||||||
|
for (const [i, dirent] of dirents.entries()) {
|
||||||
|
assert(dirent instanceof fs.Dirent);
|
||||||
|
assert.strictEqual(dirent.name, files[i]);
|
||||||
|
assert.strictEqual(dirent.isFile(), true);
|
||||||
|
assert.strictEqual(dirent.isDirectory(), false);
|
||||||
|
assert.strictEqual(dirent.isSocket(), false);
|
||||||
|
assert.strictEqual(dirent.isBlockDevice(), false);
|
||||||
|
assert.strictEqual(dirent.isCharacterDevice(), false);
|
||||||
|
assert.strictEqual(dirent.isFIFO(), false);
|
||||||
|
assert.strictEqual(dirent.isSymbolicLink(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the readdir Sync version
|
||||||
|
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
|
||||||
|
|
||||||
|
// Check the readdir async version
|
||||||
|
fs.readdir(readdirDir, {
|
||||||
|
withFileTypes: true
|
||||||
|
}, common.mustCall((err, dirents) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assertDirents(dirents);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Check for correct types when the binding returns unknowns
|
||||||
|
const UNKNOWN = constants.UV_DIRENT_UNKNOWN;
|
||||||
|
const oldReaddir = binding.readdir;
|
||||||
|
binding.readdir = common.mustCall((path, encoding, types, req, ctx) => {
|
||||||
|
if (req) {
|
||||||
|
const oldCb = req.oncomplete;
|
||||||
|
req.oncomplete = (err, results) => {
|
||||||
|
if (err) {
|
||||||
|
oldCb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results[1] = results[1].map(() => UNKNOWN);
|
||||||
|
oldCb(null, results);
|
||||||
|
};
|
||||||
|
oldReaddir(path, encoding, types, req);
|
||||||
|
} else {
|
||||||
|
const results = oldReaddir(path, encoding, types, req, ctx);
|
||||||
|
results[1] = results[1].map(() => UNKNOWN);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}, 2);
|
||||||
|
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
|
||||||
|
fs.readdir(readdirDir, {
|
||||||
|
withFileTypes: true
|
||||||
|
}, common.mustCall((err, dirents) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assertDirents(dirents);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Dirent types
|
||||||
|
for (const method of typeMethods) {
|
||||||
|
const dirent = new fs.Dirent('foo', types[method]);
|
||||||
|
for (const testMethod of typeMethods) {
|
||||||
|
assert.strictEqual(dirent[testMethod](), testMethod === method);
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,7 @@ const customTypesMap = {
|
|||||||
'EventEmitter': 'events.html#events_class_eventemitter',
|
'EventEmitter': 'events.html#events_class_eventemitter',
|
||||||
|
|
||||||
'FileHandle': 'fs.html#fs_class_filehandle',
|
'FileHandle': 'fs.html#fs_class_filehandle',
|
||||||
|
'fs.Dirent': 'fs.html#fs_class_fs_dirent',
|
||||||
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
|
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
|
||||||
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
|
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
|
||||||
'fs.Stats': 'fs.html#fs_class_fs_stats',
|
'fs.Stats': 'fs.html#fs_class_fs_stats',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user