UNPKG

hfs

Version:
169 lines (168 loc) 6.37 kB
"use strict"; // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseFileCache = void 0; exports.isDirectory = isDirectory; exports.readFileBusy = readFileBusy; exports.watchDir = watchDir; exports.dirTraversal = dirTraversal; exports.adjustStaticPathForGlob = adjustStaticPathForGlob; exports.unzip = unzip; exports.prepareFolder = prepareFolder; exports.createFileWithPath = createFileWithPath; exports.safeWriteStream = safeWriteStream; exports.isValidFileName = isValidFileName; exports.exists = exists; exports.parseFile = parseFile; exports.parseFileContent = parseFileContent; const promises_1 = require("fs/promises"); const misc_1 = require("./misc"); const fs_1 = require("fs"); const path_1 = require("path"); const fast_glob_1 = __importDefault(require("fast-glob")); const const_1 = require("./const"); const stream_1 = require("stream"); // @ts-ignore const unzip_stream_1 = __importDefault(require("unzip-stream")); async function isDirectory(path) { try { return (await (0, promises_1.stat)(path)).isDirectory(); } catch (_a) { } } async function readFileBusy(path) { return (0, promises_1.readFile)(path, 'utf8').catch(e => { if ((e === null || e === void 0 ? void 0 : e.code) !== 'EBUSY') throw e; console.debug('busy'); return (0, misc_1.wait)(100).then(() => readFileBusy(path)); }); } function watchDir(dir, cb, atStart = false) { let watcher; let paused = false; try { watcher = (0, fs_1.watch)(dir, controlledCb); } catch (_a) { // failing watching the content of the dir, we try to monitor its parent, but filtering events only for our target dir const base = (0, path_1.basename)(dir); try { watcher = (0, fs_1.watch)((0, path_1.dirname)(dir), (event, name) => { if (name !== base) return; try { watcher.close(); // if we succeed, we give up the parent watching watcher = (0, fs_1.watch)(dir, controlledCb); // attempt at passing to a more specific watching } catch (_a) { } controlledCb(); }); } catch (e) { console.debug(String(e)); } } if (atStart) controlledCb(); return { working() { return Boolean(watcher); }, stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); }, pause() { paused = true; }, unpause() { paused = false; }, }; function controlledCb() { if (!paused) cb(); } } function dirTraversal(s) { return s && /(^|[/\\])\.\.($|[/\\])/.test(s); } // apply this to paths that may contain \ as separator (not supported by fast-glob) and other special chars to be escaped (parenthesis) function adjustStaticPathForGlob(path) { return fast_glob_1.default.escapePath(path.replace(/\\/g, '/')); } async function unzip(stream, cb) { let pending = Promise.resolve(); return new Promise((resolve, reject) => stream.pipe(unzip_stream_1.default.Parse()) .on('end', () => pending.then(resolve)) .on('error', reject) .on('entry', (entry) => pending = pending.then(async () => { const { path, type } = entry; const dest = await (0, misc_1.try_)(() => cb(path), e => console.warn(String(e))); if (!dest || type !== 'File') return entry.autodrain(); console.debug('unzip', dest); const thisFile = entry.pipe(await safeWriteStream(dest)); await (0, stream_1.once)(thisFile, 'finish'); }))); } async function prepareFolder(path, dirnameIt = true) { if (dirnameIt) path = (0, path_1.dirname)(path); if ((0, misc_1.isWindowsDrive)(path)) return; try { await (0, promises_1.mkdir)(path, { recursive: true }); return true; } catch (_a) { return false; } } function createFileWithPath(path, options) { const folder = (0, path_1.dirname)(path); if (!(0, misc_1.isWindowsDrive)(folder)) // can't use prepareFolder because it's async try { (0, fs_1.mkdirSync)(folder, { recursive: true }); } catch (_a) { return; } return (0, fs_1.createWriteStream)(path, options); } async function safeWriteStream(path, options) { await prepareFolder(path); return new Promise((resolve, reject) => { const first = (0, fs_1.createWriteStream)(path, options) .on('open', () => resolve(first)) .on('error', (e) => { if (!const_1.IS_WINDOWS || e.code !== 'EPERM') // Windows throws EPERM for hidden files with flags 'w' and 'a' return reject(e); if (typeof options === 'string') options = { encoding: options }; else options || (options = {}); if (options.flags && options.flags !== 'w') // we only handle the 'w' case return reject(e); options.flags = 'r+'; const second = (0, fs_1.createWriteStream)(path, options) .on('open', fd => (0, fs_1.ftruncate)(fd, 0, () => resolve(second))) .on('error', reject); }); }); } function isValidFileName(name) { return !(const_1.IS_WINDOWS ? /[/:"*?<>|\\]/ : /\//).test(name) && !dirTraversal(name); } function exists(path) { return (0, promises_1.access)(path).then(() => true, () => false); } // parse a file, caching unless timestamp has changed exports.parseFileCache = new Map(); async function parseFile(path, parse) { const { mtime: ts } = await (0, promises_1.stat)(path); const cached = exports.parseFileCache.get(path); if (cached && Number(ts) === Number(cached.ts)) return cached.parsed; const parsed = parse(path); exports.parseFileCache.set(path, { ts, parsed }); return parsed; } async function parseFileContent(path, parse) { return parseFile(path, () => (0, promises_1.readFile)(path).then(parse)); }