UNPKG

hfs

Version:
138 lines (137 loc) 6.66 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DirentFromStats = void 0; exports.walkDir = walkDir; const makeQ_1 = require("./makeQ"); const promises_1 = require("fs/promises"); const const_1 = require("./const"); const path_1 = require("path"); const cross_1 = require("./cross"); const node_fs_1 = require("node:fs"); const events_1 = __importDefault(require("./events")); const lodash_1 = __importDefault(require("lodash")); const fswin_1 = __importDefault(require("fswin")); const util_files_1 = require("./util-files"); const dirQ = (0, makeQ_1.makeQ)(3); // cb returns void = just go on, null = stop, false = go on but don't recur (in case of depth) function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false, ctx }, cb) { let stopped = false; const closingQ = []; return new Promise(async (resolve, reject) => { if (!await (0, util_files_1.isDirectory)(path)) return reject(Error('ENOTDIR')); dirQ.add(() => readDir('', depth) .then(res => { Promise.resolve(res === null || res === void 0 ? void 0 : res.branchDone).then(resolve); }, reject)); }); async function readDir(relativePath, depth) { var _a, _b; if (stopped) return; const base = (0, path_1.join)(path, relativePath); const subDirsDone = []; let n = 0; let last; const res = (_a = (await events_1.default.emitAsync('listDiskFolder', { path: base, ctx }))) === null || _a === void 0 ? void 0 : _a[0]; // consider only first result const pluginReceiver = lodash_1.default.isFunction(res) && res || null; const pluginIterator = lodash_1.default.isFunction((res === null || res === void 0 ? void 0 : res[Symbol.asyncIterator]) || (res === null || res === void 0 ? void 0 : res[Symbol.iterator])) && res; if (const_1.IS_WINDOWS && !pluginIterator) { // use native apis to read the 'hidden' attribute const direntMethods = { isDir: false, isFile() { return !this.isDir; }, isDirectory() { return this.isDir; }, isBlockDevice() { return false; }, isCharacterDevice() { return false; }, }; await new Promise(res => fswin_1.default.find(base + '\\*', (event, f) => { if (event !== 'FOUND') return res(); if (stopped) return true; // stop signal if (!hidden && f.IS_HIDDEN) return; work(Object.assign(Object.create(direntMethods), { isDir: f.IS_DIRECTORY, name: f.LONG_NAME, stats: { size: f.SIZE, birthtime: f.CREATION_TIME, mtime: f.LAST_WRITE_TIME } })); }, true)); } else for await (let entry of (pluginIterator || await (0, promises_1.opendir)(base))) { if (stopped) break; if (!hidden && entry.name[0] === '.' && !const_1.IS_WINDOWS) continue; const stats = ((_b = entry.isSymbolicLink) === null || _b === void 0 ? void 0 : _b.call(entry)) && await (0, promises_1.stat)((0, path_1.join)(base, entry.name)).catch(() => null); if (stats === null) continue; if (stats) entry = new DirentFromStats(entry.name, stats); const expanded = entry; if (stats) expanded.stats = stats; await work(expanded); } pluginReceiver === null || pluginReceiver === void 0 ? void 0 : pluginReceiver(!stopped); const branchDone = Promise.allSettled(subDirsDone); if (last) // using streams, we don't know when the entries are received, so we need to notify on last item last.closingBranch = branchDone.then(() => relativePath); else closingQ.push(relativePath); // ok, we'll ask next one to carry this info // don't return the promise directly, as this job ends here, but communicate to caller the promise for the whole branch return { branchDone, n }; async function work(entry) { entry.path = (relativePath && relativePath + '/') + entry.name; pluginReceiver === null || pluginReceiver === void 0 ? void 0 : pluginReceiver(entry); if (last && closingQ.length) // pending entries last.closingBranch = Promise.resolve(closingQ.shift()); last = entry; const res = await cb(entry); if (res === null) return stopped = true; if (res === false) return; n++; if (!depth || !entry.isDirectory()) return; const branchDone = (0, cross_1.pendingPromise)(); // per-job subDirsDone.push(branchDone); const job = () => readDir(entry.path, depth - 1) // recur .then(x => x, () => { }) // mute errors .then(res => { if (!(res === null || res === void 0 ? void 0 : res.n)) closingQ.push(entry.path); // no children to tell i'm done Promise.resolve(res === null || res === void 0 ? void 0 : res.branchDone).then(() => branchDone.resolve()); }); if (parallelizeRecursion) dirQ.add(job); else await job(); } } } const kStats = Symbol('stats'); // Adapting an internal class in Node.js to mimic the behavior of `Dirent` when creating it manually from `Stats`. // https://github.com/nodejs/node/blob/a4cf6b204f0b160480153dc293ae748bf15225f9/lib/internal/fs/utils.js#L199C1-L213 class DirentFromStats extends node_fs_1.Dirent { constructor(name, stats) { // @ts-expect-error The constructor has parameters, but they are not represented in types. // https://github.com/nodejs/node/blob/a4cf6b204f0b160480153dc293ae748bf15225f9/lib/internal/fs/utils.js#L164 super(name, null); this[kStats] = stats; } } exports.DirentFromStats = DirentFromStats; for (const key of Reflect.ownKeys(node_fs_1.Dirent.prototype)) { const name = key; if (name === 'constructor') continue; DirentFromStats.prototype[name] = function () { return this[kStats][name](); }; }