11cb0ef41Sopenharmony_ci'use strict'; 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciconst { 41cb0ef41Sopenharmony_ci ArrayPrototypePush, 51cb0ef41Sopenharmony_ci SafePromiseAllReturnVoid, 61cb0ef41Sopenharmony_ci Promise, 71cb0ef41Sopenharmony_ci PromisePrototypeThen, 81cb0ef41Sopenharmony_ci SafeMap, 91cb0ef41Sopenharmony_ci SafeSet, 101cb0ef41Sopenharmony_ci StringPrototypeStartsWith, 111cb0ef41Sopenharmony_ci SymbolAsyncIterator, 121cb0ef41Sopenharmony_ci} = primordials; 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst { EventEmitter } = require('events'); 151cb0ef41Sopenharmony_ciconst assert = require('internal/assert'); 161cb0ef41Sopenharmony_ciconst { 171cb0ef41Sopenharmony_ci AbortError, 181cb0ef41Sopenharmony_ci codes: { 191cb0ef41Sopenharmony_ci ERR_INVALID_ARG_VALUE, 201cb0ef41Sopenharmony_ci }, 211cb0ef41Sopenharmony_ci} = require('internal/errors'); 221cb0ef41Sopenharmony_ciconst { getValidatedPath } = require('internal/fs/utils'); 231cb0ef41Sopenharmony_ciconst { kFSWatchStart, StatWatcher } = require('internal/fs/watchers'); 241cb0ef41Sopenharmony_ciconst { kEmptyObject } = require('internal/util'); 251cb0ef41Sopenharmony_ciconst { validateBoolean, validateAbortSignal } = require('internal/validators'); 261cb0ef41Sopenharmony_ciconst { 271cb0ef41Sopenharmony_ci basename: pathBasename, 281cb0ef41Sopenharmony_ci join: pathJoin, 291cb0ef41Sopenharmony_ci relative: pathRelative, 301cb0ef41Sopenharmony_ci resolve: pathResolve, 311cb0ef41Sopenharmony_ci} = require('path'); 321cb0ef41Sopenharmony_ci 331cb0ef41Sopenharmony_cilet internalSync; 341cb0ef41Sopenharmony_cilet internalPromises; 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_cifunction lazyLoadFsPromises() { 371cb0ef41Sopenharmony_ci internalPromises ??= require('fs/promises'); 381cb0ef41Sopenharmony_ci return internalPromises; 391cb0ef41Sopenharmony_ci} 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_cifunction lazyLoadFsSync() { 421cb0ef41Sopenharmony_ci internalSync ??= require('fs'); 431cb0ef41Sopenharmony_ci return internalSync; 441cb0ef41Sopenharmony_ci} 451cb0ef41Sopenharmony_cilet kResistStopPropagation; 461cb0ef41Sopenharmony_ci 471cb0ef41Sopenharmony_ciasync function traverse(dir, files = new SafeMap(), symbolicLinks = new SafeSet()) { 481cb0ef41Sopenharmony_ci const { opendir } = lazyLoadFsPromises(); 491cb0ef41Sopenharmony_ci 501cb0ef41Sopenharmony_ci const filenames = await opendir(dir); 511cb0ef41Sopenharmony_ci const subdirectories = []; 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci for await (const file of filenames) { 541cb0ef41Sopenharmony_ci const f = pathJoin(dir, file.name); 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci files.set(f, file); 571cb0ef41Sopenharmony_ci 581cb0ef41Sopenharmony_ci // Do not follow symbolic links 591cb0ef41Sopenharmony_ci if (file.isSymbolicLink()) { 601cb0ef41Sopenharmony_ci symbolicLinks.add(f); 611cb0ef41Sopenharmony_ci } else if (file.isDirectory()) { 621cb0ef41Sopenharmony_ci ArrayPrototypePush(subdirectories, traverse(f, files)); 631cb0ef41Sopenharmony_ci } 641cb0ef41Sopenharmony_ci } 651cb0ef41Sopenharmony_ci 661cb0ef41Sopenharmony_ci await SafePromiseAllReturnVoid(subdirectories); 671cb0ef41Sopenharmony_ci 681cb0ef41Sopenharmony_ci return files; 691cb0ef41Sopenharmony_ci} 701cb0ef41Sopenharmony_ci 711cb0ef41Sopenharmony_ciclass FSWatcher extends EventEmitter { 721cb0ef41Sopenharmony_ci #options = null; 731cb0ef41Sopenharmony_ci #closed = false; 741cb0ef41Sopenharmony_ci #files = new SafeMap(); 751cb0ef41Sopenharmony_ci #symbolicFiles = new SafeSet(); 761cb0ef41Sopenharmony_ci #rootPath = pathResolve(); 771cb0ef41Sopenharmony_ci #watchingFile = false; 781cb0ef41Sopenharmony_ci 791cb0ef41Sopenharmony_ci constructor(options = kEmptyObject) { 801cb0ef41Sopenharmony_ci super(); 811cb0ef41Sopenharmony_ci 821cb0ef41Sopenharmony_ci assert(typeof options === 'object'); 831cb0ef41Sopenharmony_ci 841cb0ef41Sopenharmony_ci const { persistent, recursive, signal, encoding } = options; 851cb0ef41Sopenharmony_ci 861cb0ef41Sopenharmony_ci // TODO(anonrig): Add non-recursive support to non-native-watcher for IBMi & AIX support. 871cb0ef41Sopenharmony_ci if (recursive != null) { 881cb0ef41Sopenharmony_ci validateBoolean(recursive, 'options.recursive'); 891cb0ef41Sopenharmony_ci } 901cb0ef41Sopenharmony_ci 911cb0ef41Sopenharmony_ci if (persistent != null) { 921cb0ef41Sopenharmony_ci validateBoolean(persistent, 'options.persistent'); 931cb0ef41Sopenharmony_ci } 941cb0ef41Sopenharmony_ci 951cb0ef41Sopenharmony_ci if (signal != null) { 961cb0ef41Sopenharmony_ci validateAbortSignal(signal, 'options.signal'); 971cb0ef41Sopenharmony_ci } 981cb0ef41Sopenharmony_ci 991cb0ef41Sopenharmony_ci if (encoding != null) { 1001cb0ef41Sopenharmony_ci // This is required since on macOS and Windows it throws ERR_INVALID_ARG_VALUE 1011cb0ef41Sopenharmony_ci if (typeof encoding !== 'string') { 1021cb0ef41Sopenharmony_ci throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding'); 1031cb0ef41Sopenharmony_ci } 1041cb0ef41Sopenharmony_ci } 1051cb0ef41Sopenharmony_ci 1061cb0ef41Sopenharmony_ci this.#options = { persistent, recursive, signal, encoding }; 1071cb0ef41Sopenharmony_ci } 1081cb0ef41Sopenharmony_ci 1091cb0ef41Sopenharmony_ci close() { 1101cb0ef41Sopenharmony_ci if (this.#closed) { 1111cb0ef41Sopenharmony_ci return; 1121cb0ef41Sopenharmony_ci } 1131cb0ef41Sopenharmony_ci 1141cb0ef41Sopenharmony_ci const { unwatchFile } = lazyLoadFsSync(); 1151cb0ef41Sopenharmony_ci this.#closed = true; 1161cb0ef41Sopenharmony_ci 1171cb0ef41Sopenharmony_ci for (const file of this.#files.keys()) { 1181cb0ef41Sopenharmony_ci unwatchFile(file); 1191cb0ef41Sopenharmony_ci } 1201cb0ef41Sopenharmony_ci 1211cb0ef41Sopenharmony_ci this.#files.clear(); 1221cb0ef41Sopenharmony_ci this.#symbolicFiles.clear(); 1231cb0ef41Sopenharmony_ci this.emit('close'); 1241cb0ef41Sopenharmony_ci } 1251cb0ef41Sopenharmony_ci 1261cb0ef41Sopenharmony_ci #unwatchFiles(file) { 1271cb0ef41Sopenharmony_ci const { unwatchFile } = lazyLoadFsSync(); 1281cb0ef41Sopenharmony_ci 1291cb0ef41Sopenharmony_ci this.#symbolicFiles.delete(file); 1301cb0ef41Sopenharmony_ci 1311cb0ef41Sopenharmony_ci for (const filename of this.#files.keys()) { 1321cb0ef41Sopenharmony_ci if (StringPrototypeStartsWith(filename, file)) { 1331cb0ef41Sopenharmony_ci unwatchFile(filename); 1341cb0ef41Sopenharmony_ci } 1351cb0ef41Sopenharmony_ci } 1361cb0ef41Sopenharmony_ci } 1371cb0ef41Sopenharmony_ci 1381cb0ef41Sopenharmony_ci async #watchFolder(folder) { 1391cb0ef41Sopenharmony_ci const { opendir } = lazyLoadFsPromises(); 1401cb0ef41Sopenharmony_ci 1411cb0ef41Sopenharmony_ci try { 1421cb0ef41Sopenharmony_ci const files = await opendir(folder); 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci for await (const file of files) { 1451cb0ef41Sopenharmony_ci if (this.#closed) { 1461cb0ef41Sopenharmony_ci break; 1471cb0ef41Sopenharmony_ci } 1481cb0ef41Sopenharmony_ci 1491cb0ef41Sopenharmony_ci const f = pathJoin(folder, file.name); 1501cb0ef41Sopenharmony_ci 1511cb0ef41Sopenharmony_ci if (!this.#files.has(f)) { 1521cb0ef41Sopenharmony_ci this.emit('change', 'rename', pathRelative(this.#rootPath, f)); 1531cb0ef41Sopenharmony_ci 1541cb0ef41Sopenharmony_ci if (file.isSymbolicLink()) { 1551cb0ef41Sopenharmony_ci this.#symbolicFiles.add(f); 1561cb0ef41Sopenharmony_ci } 1571cb0ef41Sopenharmony_ci 1581cb0ef41Sopenharmony_ci if (file.isFile()) { 1591cb0ef41Sopenharmony_ci this.#watchFile(f); 1601cb0ef41Sopenharmony_ci } else { 1611cb0ef41Sopenharmony_ci this.#files.set(f, file); 1621cb0ef41Sopenharmony_ci 1631cb0ef41Sopenharmony_ci if (file.isDirectory() && !file.isSymbolicLink()) { 1641cb0ef41Sopenharmony_ci await this.#watchFolder(f); 1651cb0ef41Sopenharmony_ci } 1661cb0ef41Sopenharmony_ci } 1671cb0ef41Sopenharmony_ci } 1681cb0ef41Sopenharmony_ci } 1691cb0ef41Sopenharmony_ci } catch (error) { 1701cb0ef41Sopenharmony_ci this.emit('error', error); 1711cb0ef41Sopenharmony_ci } 1721cb0ef41Sopenharmony_ci } 1731cb0ef41Sopenharmony_ci 1741cb0ef41Sopenharmony_ci #watchFile(file) { 1751cb0ef41Sopenharmony_ci if (this.#closed) { 1761cb0ef41Sopenharmony_ci return; 1771cb0ef41Sopenharmony_ci } 1781cb0ef41Sopenharmony_ci 1791cb0ef41Sopenharmony_ci const { watchFile } = lazyLoadFsSync(); 1801cb0ef41Sopenharmony_ci const existingStat = this.#files.get(file); 1811cb0ef41Sopenharmony_ci 1821cb0ef41Sopenharmony_ci watchFile(file, { 1831cb0ef41Sopenharmony_ci persistent: this.#options.persistent, 1841cb0ef41Sopenharmony_ci }, (currentStats, previousStats) => { 1851cb0ef41Sopenharmony_ci if (existingStat && !existingStat.isDirectory() && 1861cb0ef41Sopenharmony_ci currentStats.nlink !== 0 && existingStat.mtimeMs === currentStats.mtimeMs) { 1871cb0ef41Sopenharmony_ci return; 1881cb0ef41Sopenharmony_ci } 1891cb0ef41Sopenharmony_ci 1901cb0ef41Sopenharmony_ci this.#files.set(file, currentStats); 1911cb0ef41Sopenharmony_ci 1921cb0ef41Sopenharmony_ci if (currentStats.birthtimeMs === 0 && previousStats.birthtimeMs !== 0) { 1931cb0ef41Sopenharmony_ci // The file is now deleted 1941cb0ef41Sopenharmony_ci this.#files.delete(file); 1951cb0ef41Sopenharmony_ci this.emit('change', 'rename', pathRelative(this.#rootPath, file)); 1961cb0ef41Sopenharmony_ci this.#unwatchFiles(file); 1971cb0ef41Sopenharmony_ci } else if (file === this.#rootPath && this.#watchingFile) { 1981cb0ef41Sopenharmony_ci // This case will only be triggered when watching a file with fs.watch 1991cb0ef41Sopenharmony_ci this.emit('change', 'change', pathBasename(file)); 2001cb0ef41Sopenharmony_ci } else if (this.#symbolicFiles.has(file)) { 2011cb0ef41Sopenharmony_ci // Stats from watchFile does not return correct value for currentStats.isSymbolicLink() 2021cb0ef41Sopenharmony_ci // Since it is only valid when using fs.lstat(). Therefore, check the existing symbolic files. 2031cb0ef41Sopenharmony_ci this.emit('change', 'rename', pathRelative(this.#rootPath, file)); 2041cb0ef41Sopenharmony_ci } else if (currentStats.isDirectory()) { 2051cb0ef41Sopenharmony_ci this.#watchFolder(file); 2061cb0ef41Sopenharmony_ci } 2071cb0ef41Sopenharmony_ci }); 2081cb0ef41Sopenharmony_ci } 2091cb0ef41Sopenharmony_ci 2101cb0ef41Sopenharmony_ci [kFSWatchStart](filename) { 2111cb0ef41Sopenharmony_ci filename = pathResolve(getValidatedPath(filename)); 2121cb0ef41Sopenharmony_ci 2131cb0ef41Sopenharmony_ci try { 2141cb0ef41Sopenharmony_ci const file = lazyLoadFsSync().statSync(filename); 2151cb0ef41Sopenharmony_ci 2161cb0ef41Sopenharmony_ci this.#rootPath = filename; 2171cb0ef41Sopenharmony_ci this.#closed = false; 2181cb0ef41Sopenharmony_ci this.#watchingFile = file.isFile(); 2191cb0ef41Sopenharmony_ci 2201cb0ef41Sopenharmony_ci if (file.isDirectory()) { 2211cb0ef41Sopenharmony_ci this.#files.set(filename, file); 2221cb0ef41Sopenharmony_ci 2231cb0ef41Sopenharmony_ci PromisePrototypeThen( 2241cb0ef41Sopenharmony_ci traverse(filename, this.#files, this.#symbolicFiles), 2251cb0ef41Sopenharmony_ci () => { 2261cb0ef41Sopenharmony_ci for (const f of this.#files.keys()) { 2271cb0ef41Sopenharmony_ci this.#watchFile(f); 2281cb0ef41Sopenharmony_ci } 2291cb0ef41Sopenharmony_ci }, 2301cb0ef41Sopenharmony_ci ); 2311cb0ef41Sopenharmony_ci } else { 2321cb0ef41Sopenharmony_ci this.#watchFile(filename); 2331cb0ef41Sopenharmony_ci } 2341cb0ef41Sopenharmony_ci } catch (error) { 2351cb0ef41Sopenharmony_ci if (error.code === 'ENOENT') { 2361cb0ef41Sopenharmony_ci error.filename = filename; 2371cb0ef41Sopenharmony_ci throw error; 2381cb0ef41Sopenharmony_ci } 2391cb0ef41Sopenharmony_ci } 2401cb0ef41Sopenharmony_ci 2411cb0ef41Sopenharmony_ci } 2421cb0ef41Sopenharmony_ci 2431cb0ef41Sopenharmony_ci ref() { 2441cb0ef41Sopenharmony_ci this.#files.forEach((file) => { 2451cb0ef41Sopenharmony_ci if (file instanceof StatWatcher) { 2461cb0ef41Sopenharmony_ci file.ref(); 2471cb0ef41Sopenharmony_ci } 2481cb0ef41Sopenharmony_ci }); 2491cb0ef41Sopenharmony_ci } 2501cb0ef41Sopenharmony_ci 2511cb0ef41Sopenharmony_ci unref() { 2521cb0ef41Sopenharmony_ci this.#files.forEach((file) => { 2531cb0ef41Sopenharmony_ci if (file instanceof StatWatcher) { 2541cb0ef41Sopenharmony_ci file.unref(); 2551cb0ef41Sopenharmony_ci } 2561cb0ef41Sopenharmony_ci }); 2571cb0ef41Sopenharmony_ci } 2581cb0ef41Sopenharmony_ci 2591cb0ef41Sopenharmony_ci [SymbolAsyncIterator]() { 2601cb0ef41Sopenharmony_ci const { signal } = this.#options; 2611cb0ef41Sopenharmony_ci const promiseExecutor = signal == null ? 2621cb0ef41Sopenharmony_ci (resolve) => { 2631cb0ef41Sopenharmony_ci this.once('change', (eventType, filename) => { 2641cb0ef41Sopenharmony_ci resolve({ __proto__: null, value: { eventType, filename } }); 2651cb0ef41Sopenharmony_ci }); 2661cb0ef41Sopenharmony_ci } : (resolve, reject) => { 2671cb0ef41Sopenharmony_ci const onAbort = () => reject(new AbortError(undefined, { cause: signal.reason })); 2681cb0ef41Sopenharmony_ci if (signal.aborted) return onAbort(); 2691cb0ef41Sopenharmony_ci kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation; 2701cb0ef41Sopenharmony_ci signal.addEventListener('abort', onAbort, { __proto__: null, once: true, [kResistStopPropagation]: true }); 2711cb0ef41Sopenharmony_ci this.once('change', (eventType, filename) => { 2721cb0ef41Sopenharmony_ci signal.removeEventListener('abort', onAbort); 2731cb0ef41Sopenharmony_ci resolve({ __proto__: null, value: { eventType, filename } }); 2741cb0ef41Sopenharmony_ci }); 2751cb0ef41Sopenharmony_ci }; 2761cb0ef41Sopenharmony_ci return { 2771cb0ef41Sopenharmony_ci next: () => (this.#closed ? 2781cb0ef41Sopenharmony_ci { __proto__: null, done: true } : 2791cb0ef41Sopenharmony_ci new Promise(promiseExecutor)), 2801cb0ef41Sopenharmony_ci [SymbolAsyncIterator]() { return this; }, 2811cb0ef41Sopenharmony_ci }; 2821cb0ef41Sopenharmony_ci } 2831cb0ef41Sopenharmony_ci} 2841cb0ef41Sopenharmony_ci 2851cb0ef41Sopenharmony_cimodule.exports = { 2861cb0ef41Sopenharmony_ci FSWatcher, 2871cb0ef41Sopenharmony_ci kFSWatchStart, 2881cb0ef41Sopenharmony_ci}; 289