handler.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.NodeFsHandler = exports.EVENTS = exports.isIBMi = exports.isFreeBSD = exports.isLinux = exports.isMacos = exports.isWindows = exports.IDENTITY_FN = exports.EMPTY_FN = exports.STR_CLOSE = exports.STR_END = exports.STR_DATA = void 0;
  4. const fs_1 = require("fs");
  5. const promises_1 = require("fs/promises");
  6. const sysPath = require("path");
  7. const os_1 = require("os");
  8. exports.STR_DATA = 'data';
  9. exports.STR_END = 'end';
  10. exports.STR_CLOSE = 'close';
  11. const EMPTY_FN = () => { };
  12. exports.EMPTY_FN = EMPTY_FN;
  13. const IDENTITY_FN = (val) => val;
  14. exports.IDENTITY_FN = IDENTITY_FN;
  15. const pl = process.platform;
  16. exports.isWindows = pl === 'win32';
  17. exports.isMacos = pl === 'darwin';
  18. exports.isLinux = pl === 'linux';
  19. exports.isFreeBSD = pl === 'freebsd';
  20. exports.isIBMi = (0, os_1.type)() === 'OS400';
  21. exports.EVENTS = {
  22. ALL: 'all',
  23. READY: 'ready',
  24. ADD: 'add',
  25. CHANGE: 'change',
  26. ADD_DIR: 'addDir',
  27. UNLINK: 'unlink',
  28. UNLINK_DIR: 'unlinkDir',
  29. RAW: 'raw',
  30. ERROR: 'error',
  31. };
  32. const EV = exports.EVENTS;
  33. const THROTTLE_MODE_WATCH = 'watch';
  34. const statMethods = { lstat: promises_1.lstat, stat: promises_1.stat };
  35. const KEY_LISTENERS = 'listeners';
  36. const KEY_ERR = 'errHandlers';
  37. const KEY_RAW = 'rawEmitters';
  38. const HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
  39. // prettier-ignore
  40. const binaryExtensions = new Set([
  41. '3dm', '3ds', '3g2', '3gp', '7z', 'a', 'aac', 'adp', 'afdesign', 'afphoto', 'afpub', 'ai',
  42. 'aif', 'aiff', 'alz', 'ape', 'apk', 'appimage', 'ar', 'arj', 'asf', 'au', 'avi',
  43. 'bak', 'baml', 'bh', 'bin', 'bk', 'bmp', 'btif', 'bz2', 'bzip2',
  44. 'cab', 'caf', 'cgm', 'class', 'cmx', 'cpio', 'cr2', 'cur', 'dat', 'dcm', 'deb', 'dex', 'djvu',
  45. 'dll', 'dmg', 'dng', 'doc', 'docm', 'docx', 'dot', 'dotm', 'dra', 'DS_Store', 'dsk', 'dts',
  46. 'dtshd', 'dvb', 'dwg', 'dxf',
  47. 'ecelp4800', 'ecelp7470', 'ecelp9600', 'egg', 'eol', 'eot', 'epub', 'exe',
  48. 'f4v', 'fbs', 'fh', 'fla', 'flac', 'flatpak', 'fli', 'flv', 'fpx', 'fst', 'fvt',
  49. 'g3', 'gh', 'gif', 'graffle', 'gz', 'gzip',
  50. 'h261', 'h263', 'h264', 'icns', 'ico', 'ief', 'img', 'ipa', 'iso',
  51. 'jar', 'jpeg', 'jpg', 'jpgv', 'jpm', 'jxr', 'key', 'ktx',
  52. 'lha', 'lib', 'lvp', 'lz', 'lzh', 'lzma', 'lzo',
  53. 'm3u', 'm4a', 'm4v', 'mar', 'mdi', 'mht', 'mid', 'midi', 'mj2', 'mka', 'mkv', 'mmr', 'mng',
  54. 'mobi', 'mov', 'movie', 'mp3',
  55. 'mp4', 'mp4a', 'mpeg', 'mpg', 'mpga', 'mxu',
  56. 'nef', 'npx', 'numbers', 'nupkg',
  57. 'o', 'odp', 'ods', 'odt', 'oga', 'ogg', 'ogv', 'otf', 'ott',
  58. 'pages', 'pbm', 'pcx', 'pdb', 'pdf', 'pea', 'pgm', 'pic', 'png', 'pnm', 'pot', 'potm',
  59. 'potx', 'ppa', 'ppam',
  60. 'ppm', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx', 'psd', 'pya', 'pyc', 'pyo', 'pyv',
  61. 'qt',
  62. 'rar', 'ras', 'raw', 'resources', 'rgb', 'rip', 'rlc', 'rmf', 'rmvb', 'rpm', 'rtf', 'rz',
  63. 's3m', 's7z', 'scpt', 'sgi', 'shar', 'snap', 'sil', 'sketch', 'slk', 'smv', 'snk', 'so',
  64. 'stl', 'suo', 'sub', 'swf',
  65. 'tar', 'tbz', 'tbz2', 'tga', 'tgz', 'thmx', 'tif', 'tiff', 'tlz', 'ttc', 'ttf', 'txz',
  66. 'udf', 'uvh', 'uvi', 'uvm', 'uvp', 'uvs', 'uvu',
  67. 'viv', 'vob',
  68. 'war', 'wav', 'wax', 'wbmp', 'wdp', 'weba', 'webm', 'webp', 'whl', 'wim', 'wm', 'wma',
  69. 'wmv', 'wmx', 'woff', 'woff2', 'wrm', 'wvx',
  70. 'xbm', 'xif', 'xla', 'xlam', 'xls', 'xlsb', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx', 'xm',
  71. 'xmind', 'xpi', 'xpm', 'xwd', 'xz',
  72. 'z', 'zip', 'zipx',
  73. ]);
  74. const isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
  75. // TODO: emit errors properly. Example: EMFILE on Macos.
  76. const foreach = (val, fn) => {
  77. if (val instanceof Set) {
  78. val.forEach(fn);
  79. }
  80. else {
  81. fn(val);
  82. }
  83. };
  84. const addAndConvert = (main, prop, item) => {
  85. let container = main[prop];
  86. if (!(container instanceof Set)) {
  87. main[prop] = container = new Set([container]);
  88. }
  89. container.add(item);
  90. };
  91. const clearItem = (cont) => (key) => {
  92. const set = cont[key];
  93. if (set instanceof Set) {
  94. set.clear();
  95. }
  96. else {
  97. delete cont[key];
  98. }
  99. };
  100. const delFromSet = (main, prop, item) => {
  101. const container = main[prop];
  102. if (container instanceof Set) {
  103. container.delete(item);
  104. }
  105. else if (container === item) {
  106. delete main[prop];
  107. }
  108. };
  109. const isEmptySet = (val) => (val instanceof Set ? val.size === 0 : !val);
  110. const FsWatchInstances = new Map();
  111. /**
  112. * Instantiates the fs_watch interface
  113. * @param path to be watched
  114. * @param options to be passed to fs_watch
  115. * @param listener main event handler
  116. * @param errHandler emits info about errors
  117. * @param emitRaw emits raw event data
  118. * @returns {NativeFsWatcher}
  119. */
  120. function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
  121. const handleEvent = (rawEvent, evPath) => {
  122. listener(path);
  123. emitRaw(rawEvent, evPath, { watchedPath: path });
  124. // emit based on events occurring for files from a directory's watcher in
  125. // case the file's watcher misses it (and rely on throttling to de-dupe)
  126. if (evPath && path !== evPath) {
  127. fsWatchBroadcast(sysPath.resolve(path, evPath), KEY_LISTENERS, sysPath.join(path, evPath));
  128. }
  129. };
  130. try {
  131. return (0, fs_1.watch)(path, {
  132. persistent: options.persistent,
  133. }, handleEvent);
  134. }
  135. catch (error) {
  136. errHandler(error);
  137. return undefined;
  138. }
  139. }
  140. /**
  141. * Helper for passing fs_watch event data to a collection of listeners
  142. * @param fullPath absolute path bound to fs_watch instance
  143. */
  144. const fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
  145. const cont = FsWatchInstances.get(fullPath);
  146. if (!cont)
  147. return;
  148. foreach(cont[listenerType], (listener) => {
  149. listener(val1, val2, val3);
  150. });
  151. };
  152. /**
  153. * Instantiates the fs_watch interface or binds listeners
  154. * to an existing one covering the same file system entry
  155. * @param path
  156. * @param fullPath absolute path
  157. * @param options to be passed to fs_watch
  158. * @param handlers container for event listener functions
  159. */
  160. const setFsWatchListener = (path, fullPath, options, handlers) => {
  161. const { listener, errHandler, rawEmitter } = handlers;
  162. let cont = FsWatchInstances.get(fullPath);
  163. let watcher;
  164. if (!options.persistent) {
  165. watcher = createFsWatchInstance(path, options, listener, errHandler, rawEmitter);
  166. if (!watcher)
  167. return;
  168. return watcher.close.bind(watcher);
  169. }
  170. if (cont) {
  171. addAndConvert(cont, KEY_LISTENERS, listener);
  172. addAndConvert(cont, KEY_ERR, errHandler);
  173. addAndConvert(cont, KEY_RAW, rawEmitter);
  174. }
  175. else {
  176. watcher = createFsWatchInstance(path, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, // no need to use broadcast here
  177. fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
  178. if (!watcher)
  179. return;
  180. watcher.on(EV.ERROR, async (error) => {
  181. const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
  182. if (cont)
  183. cont.watcherUnusable = true; // documented since Node 10.4.1
  184. // Workaround for https://github.com/joyent/node/issues/4337
  185. if (exports.isWindows && error.code === 'EPERM') {
  186. try {
  187. const fd = await (0, promises_1.open)(path, 'r');
  188. await fd.close();
  189. broadcastErr(error);
  190. }
  191. catch (err) {
  192. // do nothing
  193. }
  194. }
  195. else {
  196. broadcastErr(error);
  197. }
  198. });
  199. cont = {
  200. listeners: listener,
  201. errHandlers: errHandler,
  202. rawEmitters: rawEmitter,
  203. watcher,
  204. };
  205. FsWatchInstances.set(fullPath, cont);
  206. }
  207. // const index = cont.listeners.indexOf(listener);
  208. // removes this instance's listeners and closes the underlying fs_watch
  209. // instance if there are no more listeners left
  210. return () => {
  211. delFromSet(cont, KEY_LISTENERS, listener);
  212. delFromSet(cont, KEY_ERR, errHandler);
  213. delFromSet(cont, KEY_RAW, rawEmitter);
  214. if (isEmptySet(cont.listeners)) {
  215. // Check to protect against issue gh-730.
  216. // if (cont.watcherUnusable) {
  217. cont.watcher.close();
  218. // }
  219. FsWatchInstances.delete(fullPath);
  220. HANDLER_KEYS.forEach(clearItem(cont));
  221. // @ts-ignore
  222. cont.watcher = undefined;
  223. Object.freeze(cont);
  224. }
  225. };
  226. };
  227. // fs_watchFile helpers
  228. // object to hold per-process fs_watchFile instances
  229. // (may be shared across chokidar FSWatcher instances)
  230. const FsWatchFileInstances = new Map();
  231. /**
  232. * Instantiates the fs_watchFile interface or binds listeners
  233. * to an existing one covering the same file system entry
  234. * @param path to be watched
  235. * @param fullPath absolute path
  236. * @param options options to be passed to fs_watchFile
  237. * @param handlers container for event listener functions
  238. * @returns closer
  239. */
  240. const setFsWatchFileListener = (path, fullPath, options, handlers) => {
  241. const { listener, rawEmitter } = handlers;
  242. let cont = FsWatchFileInstances.get(fullPath);
  243. // let listeners = new Set();
  244. // let rawEmitters = new Set();
  245. const copts = cont && cont.options;
  246. if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
  247. // "Upgrade" the watcher to persistence or a quicker interval.
  248. // This creates some unlikely edge case issues if the user mixes
  249. // settings in a very weird way, but solving for those cases
  250. // doesn't seem worthwhile for the added complexity.
  251. // listeners = cont.listeners;
  252. // rawEmitters = cont.rawEmitters;
  253. (0, fs_1.unwatchFile)(fullPath);
  254. cont = undefined;
  255. }
  256. if (cont) {
  257. addAndConvert(cont, KEY_LISTENERS, listener);
  258. addAndConvert(cont, KEY_RAW, rawEmitter);
  259. }
  260. else {
  261. // TODO
  262. // listeners.add(listener);
  263. // rawEmitters.add(rawEmitter);
  264. cont = {
  265. listeners: listener,
  266. rawEmitters: rawEmitter,
  267. options,
  268. watcher: (0, fs_1.watchFile)(fullPath, options, (curr, prev) => {
  269. foreach(cont.rawEmitters, (rawEmitter) => {
  270. rawEmitter(EV.CHANGE, fullPath, { curr, prev });
  271. });
  272. const currmtime = curr.mtimeMs;
  273. if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
  274. foreach(cont.listeners, (listener) => listener(path, curr));
  275. }
  276. }),
  277. };
  278. FsWatchFileInstances.set(fullPath, cont);
  279. }
  280. // const index = cont.listeners.indexOf(listener);
  281. // Removes this instance's listeners and closes the underlying fs_watchFile
  282. // instance if there are no more listeners left.
  283. return () => {
  284. delFromSet(cont, KEY_LISTENERS, listener);
  285. delFromSet(cont, KEY_RAW, rawEmitter);
  286. if (isEmptySet(cont.listeners)) {
  287. FsWatchFileInstances.delete(fullPath);
  288. (0, fs_1.unwatchFile)(fullPath);
  289. cont.options = cont.watcher = undefined;
  290. Object.freeze(cont);
  291. }
  292. };
  293. };
  294. /**
  295. * @mixin
  296. */
  297. class NodeFsHandler {
  298. constructor(fsW) {
  299. this.fsw = fsW;
  300. this._boundHandleError = (error) => fsW._handleError(error);
  301. }
  302. /**
  303. * Watch file for changes with fs_watchFile or fs_watch.
  304. * @param path to file or dir
  305. * @param listener on fs change
  306. * @returns closer for the watcher instance
  307. */
  308. _watchWithNodeFs(path, listener) {
  309. const opts = this.fsw.options;
  310. const directory = sysPath.dirname(path);
  311. const basename = sysPath.basename(path);
  312. const parent = this.fsw._getWatchedDir(directory);
  313. parent.add(basename);
  314. const absolutePath = sysPath.resolve(path);
  315. const options = {
  316. persistent: opts.persistent,
  317. };
  318. if (!listener)
  319. listener = exports.EMPTY_FN;
  320. let closer;
  321. if (opts.usePolling) {
  322. const enableBin = opts.interval !== opts.binaryInterval;
  323. options.interval = enableBin && isBinaryPath(basename) ? opts.binaryInterval : opts.interval;
  324. closer = setFsWatchFileListener(path, absolutePath, options, {
  325. listener,
  326. rawEmitter: this.fsw._emitRaw,
  327. });
  328. }
  329. else {
  330. closer = setFsWatchListener(path, absolutePath, options, {
  331. listener,
  332. errHandler: this._boundHandleError,
  333. rawEmitter: this.fsw._emitRaw,
  334. });
  335. }
  336. return closer;
  337. }
  338. /**
  339. * Watch a file and emit add event if warranted.
  340. * @returns closer for the watcher instance
  341. */
  342. _handleFile(file, stats, initialAdd) {
  343. if (this.fsw.closed) {
  344. return;
  345. }
  346. const dirname = sysPath.dirname(file);
  347. const basename = sysPath.basename(file);
  348. const parent = this.fsw._getWatchedDir(dirname);
  349. // stats is always present
  350. let prevStats = stats;
  351. // if the file is already being watched, do nothing
  352. if (parent.has(basename))
  353. return;
  354. const listener = async (path, newStats) => {
  355. if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
  356. return;
  357. if (!newStats || newStats.mtimeMs === 0) {
  358. try {
  359. const newStats = await (0, promises_1.stat)(file);
  360. if (this.fsw.closed)
  361. return;
  362. // Check that change event was not fired because of changed only accessTime.
  363. const at = newStats.atimeMs;
  364. const mt = newStats.mtimeMs;
  365. if (!at || at <= mt || mt !== prevStats.mtimeMs) {
  366. this.fsw._emit(EV.CHANGE, file, newStats);
  367. }
  368. if ((exports.isMacos || exports.isLinux || exports.isFreeBSD) && prevStats.ino !== newStats.ino) {
  369. this.fsw._closeFile(path);
  370. prevStats = newStats;
  371. const closer = this._watchWithNodeFs(file, listener);
  372. if (closer)
  373. this.fsw._addPathCloser(path, closer);
  374. }
  375. else {
  376. prevStats = newStats;
  377. }
  378. }
  379. catch (error) {
  380. // Fix issues where mtime is null but file is still present
  381. this.fsw._remove(dirname, basename);
  382. }
  383. // add is about to be emitted if file not already tracked in parent
  384. }
  385. else if (parent.has(basename)) {
  386. // Check that change event was not fired because of changed only accessTime.
  387. const at = newStats.atimeMs;
  388. const mt = newStats.mtimeMs;
  389. if (!at || at <= mt || mt !== prevStats.mtimeMs) {
  390. this.fsw._emit(EV.CHANGE, file, newStats);
  391. }
  392. prevStats = newStats;
  393. }
  394. };
  395. // kick off the watcher
  396. const closer = this._watchWithNodeFs(file, listener);
  397. // emit an add event if we're supposed to
  398. if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
  399. if (!this.fsw._throttle(EV.ADD, file, 0))
  400. return;
  401. this.fsw._emit(EV.ADD, file, stats);
  402. }
  403. return closer;
  404. }
  405. /**
  406. * Handle symlinks encountered while reading a dir.
  407. * @param entry returned by readdirp
  408. * @param directory path of dir being read
  409. * @param path of this item
  410. * @param item basename of this item
  411. * @returns true if no more processing is needed for this entry.
  412. */
  413. async _handleSymlink(entry, directory, path, item) {
  414. if (this.fsw.closed) {
  415. return;
  416. }
  417. const full = entry.fullPath;
  418. const dir = this.fsw._getWatchedDir(directory);
  419. if (!this.fsw.options.followSymlinks) {
  420. // watch symlink directly (don't follow) and detect changes
  421. this.fsw._incrReadyCount();
  422. let linkPath;
  423. try {
  424. linkPath = await (0, promises_1.realpath)(path);
  425. }
  426. catch (e) {
  427. this.fsw._emitReady();
  428. return true;
  429. }
  430. if (this.fsw.closed)
  431. return;
  432. if (dir.has(item)) {
  433. if (this.fsw._symlinkPaths.get(full) !== linkPath) {
  434. this.fsw._symlinkPaths.set(full, linkPath);
  435. this.fsw._emit(EV.CHANGE, path, entry.stats);
  436. }
  437. }
  438. else {
  439. dir.add(item);
  440. this.fsw._symlinkPaths.set(full, linkPath);
  441. this.fsw._emit(EV.ADD, path, entry.stats);
  442. }
  443. this.fsw._emitReady();
  444. return true;
  445. }
  446. // don't follow the same symlink more than once
  447. if (this.fsw._symlinkPaths.has(full)) {
  448. return true;
  449. }
  450. this.fsw._symlinkPaths.set(full, true);
  451. }
  452. _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
  453. // Normalize the directory name on Windows
  454. directory = sysPath.join(directory, '');
  455. throttler = this.fsw._throttle('readdir', directory, 1000);
  456. if (!throttler)
  457. return;
  458. const previous = this.fsw._getWatchedDir(wh.path);
  459. const current = new Set();
  460. let stream = this.fsw._readdirp(directory, {
  461. fileFilter: (entry) => wh.filterPath(entry),
  462. directoryFilter: (entry) => wh.filterDir(entry),
  463. });
  464. if (!stream)
  465. return;
  466. stream
  467. .on(exports.STR_DATA, async (entry) => {
  468. if (this.fsw.closed) {
  469. stream = undefined;
  470. return;
  471. }
  472. const item = entry.path;
  473. let path = sysPath.join(directory, item);
  474. current.add(item);
  475. if (entry.stats.isSymbolicLink() &&
  476. (await this._handleSymlink(entry, directory, path, item))) {
  477. return;
  478. }
  479. if (this.fsw.closed) {
  480. stream = undefined;
  481. return;
  482. }
  483. // Files that present in current directory snapshot
  484. // but absent in previous are added to watch list and
  485. // emit `add` event.
  486. if (item === target || (!target && !previous.has(item))) {
  487. this.fsw._incrReadyCount();
  488. // ensure relativeness of path is preserved in case of watcher reuse
  489. path = sysPath.join(dir, sysPath.relative(dir, path));
  490. this._addToNodeFs(path, initialAdd, wh, depth + 1);
  491. }
  492. })
  493. .on(EV.ERROR, this._boundHandleError);
  494. return new Promise((resolve, reject) => {
  495. if (!stream)
  496. return reject();
  497. stream.once(exports.STR_END, () => {
  498. if (this.fsw.closed) {
  499. stream = undefined;
  500. return;
  501. }
  502. const wasThrottled = throttler ? throttler.clear() : false;
  503. resolve(undefined);
  504. // Files that absent in current directory snapshot
  505. // but present in previous emit `remove` event
  506. // and are removed from @watched[directory].
  507. previous
  508. .getChildren()
  509. .filter((item) => {
  510. return item !== directory && !current.has(item);
  511. })
  512. .forEach((item) => {
  513. this.fsw._remove(directory, item);
  514. });
  515. stream = undefined;
  516. // one more time for any missed in case changes came in extremely quickly
  517. if (wasThrottled)
  518. this._handleRead(directory, false, wh, target, dir, depth, throttler);
  519. });
  520. });
  521. }
  522. /**
  523. * Read directory to add / remove files from `@watched` list and re-read it on change.
  524. * @param dir fs path
  525. * @param stats
  526. * @param initialAdd
  527. * @param depth relative to user-supplied path
  528. * @param target child path targeted for watch
  529. * @param wh Common watch helpers for this path
  530. * @param realpath
  531. * @returns closer for the watcher instance.
  532. */
  533. async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) {
  534. const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
  535. const tracked = parentDir.has(sysPath.basename(dir));
  536. if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
  537. this.fsw._emit(EV.ADD_DIR, dir, stats);
  538. }
  539. // ensure dir is tracked (harmless if redundant)
  540. parentDir.add(sysPath.basename(dir));
  541. this.fsw._getWatchedDir(dir);
  542. let throttler;
  543. let closer;
  544. const oDepth = this.fsw.options.depth;
  545. if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) {
  546. if (!target) {
  547. await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
  548. if (this.fsw.closed)
  549. return;
  550. }
  551. closer = this._watchWithNodeFs(dir, (dirPath, stats) => {
  552. // if current directory is removed, do nothing
  553. if (stats && stats.mtimeMs === 0)
  554. return;
  555. this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
  556. });
  557. }
  558. return closer;
  559. }
  560. /**
  561. * Handle added file, directory, or glob pattern.
  562. * Delegates call to _handleFile / _handleDir after checks.
  563. * @param path to file or ir
  564. * @param initialAdd was the file added at watch instantiation?
  565. * @param priorWh depth relative to user-supplied path
  566. * @param depth Child path actually targeted for watch
  567. * @param target Child path actually targeted for watch
  568. */
  569. async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
  570. const ready = this.fsw._emitReady;
  571. if (this.fsw._isIgnored(path) || this.fsw.closed) {
  572. ready();
  573. return false;
  574. }
  575. const wh = this.fsw._getWatchHelpers(path);
  576. if (priorWh) {
  577. wh.filterPath = (entry) => priorWh.filterPath(entry);
  578. wh.filterDir = (entry) => priorWh.filterDir(entry);
  579. }
  580. // evaluate what is at the path we're being asked to watch
  581. try {
  582. const stats = await statMethods[wh.statMethod](wh.watchPath);
  583. if (this.fsw.closed)
  584. return;
  585. if (this.fsw._isIgnored(wh.watchPath, stats)) {
  586. ready();
  587. return false;
  588. }
  589. const follow = this.fsw.options.followSymlinks;
  590. let closer;
  591. if (stats.isDirectory()) {
  592. const absPath = sysPath.resolve(path);
  593. const targetPath = follow ? await (0, promises_1.realpath)(path) : path;
  594. if (this.fsw.closed)
  595. return;
  596. closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
  597. if (this.fsw.closed)
  598. return;
  599. // preserve this symlink's target path
  600. if (absPath !== targetPath && targetPath !== undefined) {
  601. this.fsw._symlinkPaths.set(absPath, targetPath);
  602. }
  603. }
  604. else if (stats.isSymbolicLink()) {
  605. const targetPath = follow ? await (0, promises_1.realpath)(path) : path;
  606. if (this.fsw.closed)
  607. return;
  608. const parent = sysPath.dirname(wh.watchPath);
  609. this.fsw._getWatchedDir(parent).add(wh.watchPath);
  610. this.fsw._emit(EV.ADD, wh.watchPath, stats);
  611. closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);
  612. if (this.fsw.closed)
  613. return;
  614. // preserve this symlink's target path
  615. if (targetPath !== undefined) {
  616. this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
  617. }
  618. }
  619. else {
  620. closer = this._handleFile(wh.watchPath, stats, initialAdd);
  621. }
  622. ready();
  623. if (closer)
  624. this.fsw._addPathCloser(path, closer);
  625. return false;
  626. }
  627. catch (error) {
  628. if (this.fsw._handleError(error)) {
  629. ready();
  630. return path;
  631. }
  632. }
  633. }
  634. }
  635. exports.NodeFsHandler = NodeFsHandler;