Skip to content

Commit

Permalink
fs: add c++ binding for readdirSyncRecursive
Browse files Browse the repository at this point in the history
  • Loading branch information
gurgunday committed Nov 5, 2024
1 parent 32ff100 commit 1f7f80f
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 44 deletions.
53 changes: 9 additions & 44 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1380,52 +1380,17 @@ function readdirSyncRecursive(basePath, options) {
const withFileTypes = Boolean(options.withFileTypes);
const encoding = options.encoding;

const readdirResults = [];
const pathsQueue = [basePath];

function read(path) {
const readdirResult = binding.readdir(
path,
encoding,
withFileTypes,
);

if (readdirResult === undefined) {
return;
}

if (withFileTypes) {
// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
// The first array is the names, and the second array is the types.
// They are guaranteed to be the same length; hence, setting `length` to the length
// of the first array within the result.
const length = readdirResult[0].length;
for (let i = 0; i < length; i++) {
const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
ArrayPrototypePush(readdirResults, dirent);
if (dirent.isDirectory()) {
ArrayPrototypePush(pathsQueue, pathModule.join(dirent.parentPath, dirent.name));
}
}
} else {
for (let i = 0; i < readdirResult.length; i++) {
const resultPath = pathModule.join(path, readdirResult[i]);
const relativeResultPath = pathModule.relative(basePath, resultPath);
const stat = binding.internalModuleStat(binding, resultPath);
ArrayPrototypePush(readdirResults, relativeResultPath);
// 1 indicates directory
if (stat === 1) {
ArrayPrototypePush(pathsQueue, resultPath);
}
}
}
}
const results = binding.readdirRecursiveSync(
basePath,
encoding,
withFileTypes,
);

for (let i = 0; i < pathsQueue.length; i++) {
read(pathsQueue[i]);
if (withFileTypes) {
return results.map(({ 0: path, 1: name, 2: type }) => getDirent(path, name, type));
}

return readdirResults;
return results;
}

/**
Expand Down Expand Up @@ -2204,7 +2169,7 @@ function lutimesSync(path, atime, mtime) {

function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback) {
if (signal?.aborted) {
const abortError = new AbortError(undefined, { cause: signal?.reason });
const abortError = new AbortError(undefined, { cause: signal.reason });
if (isUserFd) {
callback(abortError);
} else {
Expand Down
127 changes: 127 additions & 0 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2072,6 +2072,131 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
}
}

static void ReaddirRecursiveSync(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

CHECK_GE(args.Length(), 3);

BufferValue path(isolate, args[0]);
CHECK_NOT_NULL(*path);
ToNamespacedPath(env, &path);

const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
bool with_file_types = args[2]->IsTrue();

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());

std::vector<std::string> paths;
std::vector<std::string> names;
std::vector<int> types;
std::vector<std::string> relative_paths;

std::vector<std::string> dir_queue;
dir_queue.push_back(*path);

FSReqWrapSync req_wrap_sync("readdir_recursive");

const std::string base_path(*path);

while (!dir_queue.empty()) {
std::string current_path = std::move(dir_queue.back());
dir_queue.pop_back();

int err = uv_fs_scandir(nullptr, &req_wrap_sync.req,
current_path.c_str(), 0, nullptr);
if (err < 0) {
if (err == UV_ENOENT || err == UV_ENOTDIR) {
continue;
}
uv_fs_req_cleanup(&req_wrap_sync.req);
args.GetReturnValue().Set(v8::Undefined(isolate));
return;
}

uv_dirent_t ent;
while (uv_fs_scandir_next(&req_wrap_sync.req, &ent) != UV_EOF) {
if (strcmp(ent.name, ".") == 0 || strcmp(ent.name, "..") == 0) {
continue;
}

std::string full_path = current_path;
if (full_path.back() != kPathSeparator) {
full_path += kPathSeparator;
}
full_path += ent.name;

if (with_file_types) {
paths.push_back(current_path);
names.emplace_back(ent.name);
types.push_back(static_cast<int>(ent.type));
} else {
if (full_path.length() > base_path.length()) {
size_t start_pos = base_path.length();
if (full_path[start_pos] == kPathSeparator) {
start_pos++;
}
if (start_pos < full_path.length()) {
relative_paths.emplace_back(full_path.substr(start_pos));
}
}
}

if (ent.type == UV_DIRENT_DIR) {
dir_queue.push_back(std::move(full_path));
}
}

uv_fs_req_cleanup(&req_wrap_sync.req);
}

Local<Array> result_array = Array::New(isolate,
with_file_types ? paths.size() : relative_paths.size());

if (with_file_types) {
for (size_t i = 0; i < paths.size(); i++) {
Local<Array> entry_info = Array::New(isolate, 3);
Local<Value> error;

MaybeLocal<Value> path_value = StringBytes::Encode(isolate,
paths[i].c_str(), encoding, &error);
if (!error.IsEmpty()) {
isolate->ThrowException(error);
return;
}

MaybeLocal<Value> name_value = StringBytes::Encode(isolate,
names[i].c_str(), encoding, &error);
if (!error.IsEmpty()) {
isolate->ThrowException(error);
return;
}

entry_info->Set(env->context(), 0, path_value.ToLocalChecked()).Check();
entry_info->Set(env->context(), 1, name_value.ToLocalChecked()).Check();
entry_info->Set(env->context(), 2,
Integer::New(isolate, types[i])).Check();

result_array->Set(env->context(), i, entry_info).Check();
}
} else {
for (size_t i = 0; i < relative_paths.size(); i++) {
Local<Value> error;
MaybeLocal<Value> path_value = StringBytes::Encode(isolate,
relative_paths[i].c_str(), encoding, &error);
if (!error.IsEmpty()) {
isolate->ThrowException(error);
return;
}

result_array->Set(env->context(), i, path_value.ToLocalChecked()).Check();
}
}

args.GetReturnValue().Set(result_array);
}

static inline Maybe<void> AsyncCheckOpenPermissions(Environment* env,
FSReqBase* req_wrap,
const BufferValue& path,
Expand Down Expand Up @@ -3614,6 +3739,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "rmSync", RmSync);
SetMethod(isolate, target, "mkdir", MKDir);
SetMethod(isolate, target, "readdir", ReadDir);
SetMethod(isolate, target, "readdirRecursiveSync", ReaddirRecursiveSync);
SetFastMethod(isolate,
target,
"internalModuleStat",
Expand Down Expand Up @@ -3741,6 +3867,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(RmSync);
registry->Register(MKDir);
registry->Register(ReadDir);
registry->Register(ReaddirRecursiveSync);
registry->Register(InternalModuleStat);
registry->Register(FastInternalModuleStat);
registry->Register(fast_internal_module_stat_.GetTypeInfo());
Expand Down

0 comments on commit 1f7f80f

Please sign in to comment.