hfs
Version:
HTTP File Server
138 lines (137 loc) • 6.66 kB
JavaScript
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]();
};
}
;