DirectoryWatcher.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const EventEmitter = require("events").EventEmitter;
  7. const fs = require("graceful-fs");
  8. const path = require("path");
  9. const watchEventSource = require("./watchEventSource");
  10. const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({});
  11. let FS_ACCURACY = 2000;
  12. const IS_OSX = require("os").platform() === "darwin";
  13. const IS_WIN = require("os").platform() === "win32";
  14. const WATCHPACK_POLLING = process.env.WATCHPACK_POLLING;
  15. const FORCE_POLLING =
  16. `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
  17. ? +WATCHPACK_POLLING
  18. : !!WATCHPACK_POLLING && WATCHPACK_POLLING !== "false";
  19. function withoutCase(str) {
  20. return str.toLowerCase();
  21. }
  22. function needCalls(times, callback) {
  23. return function() {
  24. if (--times === 0) {
  25. return callback();
  26. }
  27. };
  28. }
  29. class Watcher extends EventEmitter {
  30. constructor(directoryWatcher, filePath, startTime) {
  31. super();
  32. this.directoryWatcher = directoryWatcher;
  33. this.path = filePath;
  34. this.startTime = startTime && +startTime;
  35. }
  36. checkStartTime(mtime, initial) {
  37. const startTime = this.startTime;
  38. if (typeof startTime !== "number") return !initial;
  39. return startTime <= mtime;
  40. }
  41. close() {
  42. this.emit("closed");
  43. }
  44. }
  45. class DirectoryWatcher extends EventEmitter {
  46. constructor(watcherManager, directoryPath, options) {
  47. super();
  48. if (FORCE_POLLING) {
  49. options.poll = FORCE_POLLING;
  50. }
  51. this.watcherManager = watcherManager;
  52. this.options = options;
  53. this.path = directoryPath;
  54. // safeTime is the point in time after which reading is safe to be unchanged
  55. // timestamp is a value that should be compared with another timestamp (mtime)
  56. /** @type {Map<string, { safeTime: number, timestamp: number }} */
  57. this.files = new Map();
  58. /** @type {Map<string, number>} */
  59. this.filesWithoutCase = new Map();
  60. this.directories = new Map();
  61. this.lastWatchEvent = 0;
  62. this.initialScan = true;
  63. this.ignored = options.ignored || (() => false);
  64. this.nestedWatching = false;
  65. this.polledWatching =
  66. typeof options.poll === "number"
  67. ? options.poll
  68. : options.poll
  69. ? 5007
  70. : false;
  71. this.timeout = undefined;
  72. this.initialScanRemoved = new Set();
  73. this.initialScanFinished = undefined;
  74. /** @type {Map<string, Set<Watcher>>} */
  75. this.watchers = new Map();
  76. this.parentWatcher = null;
  77. this.refs = 0;
  78. this._activeEvents = new Map();
  79. this.closed = false;
  80. this.scanning = false;
  81. this.scanAgain = false;
  82. this.scanAgainInitial = false;
  83. this.createWatcher();
  84. this.doScan(true);
  85. }
  86. createWatcher() {
  87. try {
  88. if (this.polledWatching) {
  89. this.watcher = {
  90. close: () => {
  91. if (this.timeout) {
  92. clearTimeout(this.timeout);
  93. this.timeout = undefined;
  94. }
  95. }
  96. };
  97. } else {
  98. if (IS_OSX) {
  99. this.watchInParentDirectory();
  100. }
  101. this.watcher = watchEventSource.watch(this.path);
  102. this.watcher.on("change", this.onWatchEvent.bind(this));
  103. this.watcher.on("error", this.onWatcherError.bind(this));
  104. }
  105. } catch (err) {
  106. this.onWatcherError(err);
  107. }
  108. }
  109. forEachWatcher(path, fn) {
  110. const watchers = this.watchers.get(withoutCase(path));
  111. if (watchers !== undefined) {
  112. for (const w of watchers) {
  113. fn(w);
  114. }
  115. }
  116. }
  117. setMissing(itemPath, initial, type) {
  118. if (this.initialScan) {
  119. this.initialScanRemoved.add(itemPath);
  120. }
  121. const oldDirectory = this.directories.get(itemPath);
  122. if (oldDirectory) {
  123. if (this.nestedWatching) oldDirectory.close();
  124. this.directories.delete(itemPath);
  125. this.forEachWatcher(itemPath, w => w.emit("remove", type));
  126. if (!initial) {
  127. this.forEachWatcher(this.path, w =>
  128. w.emit("change", itemPath, null, type, initial)
  129. );
  130. }
  131. }
  132. const oldFile = this.files.get(itemPath);
  133. if (oldFile) {
  134. this.files.delete(itemPath);
  135. const key = withoutCase(itemPath);
  136. const count = this.filesWithoutCase.get(key) - 1;
  137. if (count <= 0) {
  138. this.filesWithoutCase.delete(key);
  139. this.forEachWatcher(itemPath, w => w.emit("remove", type));
  140. } else {
  141. this.filesWithoutCase.set(key, count);
  142. }
  143. if (!initial) {
  144. this.forEachWatcher(this.path, w =>
  145. w.emit("change", itemPath, null, type, initial)
  146. );
  147. }
  148. }
  149. }
  150. setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) {
  151. const now = Date.now();
  152. if (this.ignored(filePath)) return;
  153. const old = this.files.get(filePath);
  154. let safeTime, accuracy;
  155. if (initial) {
  156. safeTime = Math.min(now, mtime) + FS_ACCURACY;
  157. accuracy = FS_ACCURACY;
  158. } else {
  159. safeTime = now;
  160. accuracy = 0;
  161. if (old && old.timestamp === mtime && mtime + FS_ACCURACY < now) {
  162. // We are sure that mtime is untouched
  163. // This can be caused by some file attribute change
  164. // e. g. when access time has been changed
  165. // but the file content is untouched
  166. return;
  167. }
  168. }
  169. if (ignoreWhenEqual && old && old.timestamp === mtime) return;
  170. this.files.set(filePath, {
  171. safeTime,
  172. accuracy,
  173. timestamp: mtime
  174. });
  175. if (!old) {
  176. const key = withoutCase(filePath);
  177. const count = this.filesWithoutCase.get(key);
  178. this.filesWithoutCase.set(key, (count || 0) + 1);
  179. if (count !== undefined) {
  180. // There is already a file with case-insensitive-equal name
  181. // On a case-insensitive filesystem we may miss the renaming
  182. // when only casing is changed.
  183. // To be sure that our information is correct
  184. // we trigger a rescan here
  185. this.doScan(false);
  186. }
  187. this.forEachWatcher(filePath, w => {
  188. if (!initial || w.checkStartTime(safeTime, initial)) {
  189. w.emit("change", mtime, type);
  190. }
  191. });
  192. } else if (!initial) {
  193. this.forEachWatcher(filePath, w => w.emit("change", mtime, type));
  194. }
  195. this.forEachWatcher(this.path, w => {
  196. if (!initial || w.checkStartTime(safeTime, initial)) {
  197. w.emit("change", filePath, safeTime, type, initial);
  198. }
  199. });
  200. }
  201. setDirectory(directoryPath, birthtime, initial, type) {
  202. if (this.ignored(directoryPath)) return;
  203. if (directoryPath === this.path) {
  204. if (!initial) {
  205. this.forEachWatcher(this.path, w =>
  206. w.emit("change", directoryPath, birthtime, type, initial)
  207. );
  208. }
  209. } else {
  210. const old = this.directories.get(directoryPath);
  211. if (!old) {
  212. const now = Date.now();
  213. if (this.nestedWatching) {
  214. this.createNestedWatcher(directoryPath);
  215. } else {
  216. this.directories.set(directoryPath, true);
  217. }
  218. let safeTime;
  219. if (initial) {
  220. safeTime = Math.min(now, birthtime) + FS_ACCURACY;
  221. } else {
  222. safeTime = now;
  223. }
  224. this.forEachWatcher(directoryPath, w => {
  225. if (!initial || w.checkStartTime(safeTime, false)) {
  226. w.emit("change", birthtime, type);
  227. }
  228. });
  229. this.forEachWatcher(this.path, w => {
  230. if (!initial || w.checkStartTime(safeTime, initial)) {
  231. w.emit("change", directoryPath, safeTime, type, initial);
  232. }
  233. });
  234. }
  235. }
  236. }
  237. createNestedWatcher(directoryPath) {
  238. const watcher = this.watcherManager.watchDirectory(directoryPath, 1);
  239. watcher.on("change", (filePath, mtime, type, initial) => {
  240. this.forEachWatcher(this.path, w => {
  241. if (!initial || w.checkStartTime(mtime, initial)) {
  242. w.emit("change", filePath, mtime, type, initial);
  243. }
  244. });
  245. });
  246. this.directories.set(directoryPath, watcher);
  247. }
  248. setNestedWatching(flag) {
  249. if (this.nestedWatching !== !!flag) {
  250. this.nestedWatching = !!flag;
  251. if (this.nestedWatching) {
  252. for (const directory of this.directories.keys()) {
  253. this.createNestedWatcher(directory);
  254. }
  255. } else {
  256. for (const [directory, watcher] of this.directories) {
  257. watcher.close();
  258. this.directories.set(directory, true);
  259. }
  260. }
  261. }
  262. }
  263. watch(filePath, startTime) {
  264. const key = withoutCase(filePath);
  265. let watchers = this.watchers.get(key);
  266. if (watchers === undefined) {
  267. watchers = new Set();
  268. this.watchers.set(key, watchers);
  269. }
  270. this.refs++;
  271. const watcher = new Watcher(this, filePath, startTime);
  272. watcher.on("closed", () => {
  273. if (--this.refs <= 0) {
  274. this.close();
  275. return;
  276. }
  277. watchers.delete(watcher);
  278. if (watchers.size === 0) {
  279. this.watchers.delete(key);
  280. if (this.path === filePath) this.setNestedWatching(false);
  281. }
  282. });
  283. watchers.add(watcher);
  284. let safeTime;
  285. if (filePath === this.path) {
  286. this.setNestedWatching(true);
  287. safeTime = this.lastWatchEvent;
  288. for (const entry of this.files.values()) {
  289. fixupEntryAccuracy(entry);
  290. safeTime = Math.max(safeTime, entry.safeTime);
  291. }
  292. } else {
  293. const entry = this.files.get(filePath);
  294. if (entry) {
  295. fixupEntryAccuracy(entry);
  296. safeTime = entry.safeTime;
  297. } else {
  298. safeTime = 0;
  299. }
  300. }
  301. if (safeTime) {
  302. if (safeTime >= startTime) {
  303. process.nextTick(() => {
  304. if (this.closed) return;
  305. if (filePath === this.path) {
  306. watcher.emit(
  307. "change",
  308. filePath,
  309. safeTime,
  310. "watch (outdated on attach)",
  311. true
  312. );
  313. } else {
  314. watcher.emit(
  315. "change",
  316. safeTime,
  317. "watch (outdated on attach)",
  318. true
  319. );
  320. }
  321. });
  322. }
  323. } else if (this.initialScan) {
  324. if (this.initialScanRemoved.has(filePath)) {
  325. process.nextTick(() => {
  326. if (this.closed) return;
  327. watcher.emit("remove");
  328. });
  329. }
  330. } else if (
  331. filePath !== this.path &&
  332. !this.directories.has(filePath) &&
  333. watcher.checkStartTime(this.initialScanFinished, false)
  334. ) {
  335. process.nextTick(() => {
  336. if (this.closed) return;
  337. watcher.emit("initial-missing", "watch (missing on attach)");
  338. });
  339. }
  340. return watcher;
  341. }
  342. onWatchEvent(eventType, filename) {
  343. if (this.closed) return;
  344. if (!filename) {
  345. // In some cases no filename is provided
  346. // This seem to happen on windows
  347. // So some event happened but we don't know which file is affected
  348. // We have to do a full scan of the directory
  349. this.doScan(false);
  350. return;
  351. }
  352. const filePath = path.join(this.path, filename);
  353. if (this.ignored(filePath)) return;
  354. if (this._activeEvents.get(filename) === undefined) {
  355. this._activeEvents.set(filename, false);
  356. const checkStats = () => {
  357. if (this.closed) return;
  358. this._activeEvents.set(filename, false);
  359. fs.lstat(filePath, (err, stats) => {
  360. if (this.closed) return;
  361. if (this._activeEvents.get(filename) === true) {
  362. process.nextTick(checkStats);
  363. return;
  364. }
  365. this._activeEvents.delete(filename);
  366. // ENOENT happens when the file/directory doesn't exist
  367. // EPERM happens when the containing directory doesn't exist
  368. if (err) {
  369. if (
  370. err.code !== "ENOENT" &&
  371. err.code !== "EPERM" &&
  372. err.code !== "EBUSY"
  373. ) {
  374. this.onStatsError(err);
  375. } else {
  376. if (filename === path.basename(this.path)) {
  377. // This may indicate that the directory itself was removed
  378. if (!fs.existsSync(this.path)) {
  379. this.onDirectoryRemoved("stat failed");
  380. }
  381. }
  382. }
  383. }
  384. this.lastWatchEvent = Date.now();
  385. if (!stats) {
  386. this.setMissing(filePath, false, eventType);
  387. } else if (stats.isDirectory()) {
  388. this.setDirectory(
  389. filePath,
  390. +stats.birthtime || 1,
  391. false,
  392. eventType
  393. );
  394. } else if (stats.isFile() || stats.isSymbolicLink()) {
  395. if (stats.mtime) {
  396. ensureFsAccuracy(stats.mtime);
  397. }
  398. this.setFileTime(
  399. filePath,
  400. +stats.mtime || +stats.ctime || 1,
  401. false,
  402. false,
  403. eventType
  404. );
  405. }
  406. });
  407. };
  408. process.nextTick(checkStats);
  409. } else {
  410. this._activeEvents.set(filename, true);
  411. }
  412. }
  413. onWatcherError(err) {
  414. if (this.closed) return;
  415. if (err) {
  416. if (err.code !== "EPERM" && err.code !== "ENOENT") {
  417. console.error("Watchpack Error (watcher): " + err);
  418. }
  419. this.onDirectoryRemoved("watch error");
  420. }
  421. }
  422. onStatsError(err) {
  423. if (err) {
  424. console.error("Watchpack Error (stats): " + err);
  425. }
  426. }
  427. onScanError(err) {
  428. if (err) {
  429. console.error("Watchpack Error (initial scan): " + err);
  430. }
  431. this.onScanFinished();
  432. }
  433. onScanFinished() {
  434. if (this.polledWatching) {
  435. this.timeout = setTimeout(() => {
  436. if (this.closed) return;
  437. this.doScan(false);
  438. }, this.polledWatching);
  439. }
  440. }
  441. onDirectoryRemoved(reason) {
  442. if (this.watcher) {
  443. this.watcher.close();
  444. this.watcher = null;
  445. }
  446. this.watchInParentDirectory();
  447. const type = `directory-removed (${reason})`;
  448. for (const directory of this.directories.keys()) {
  449. this.setMissing(directory, null, type);
  450. }
  451. for (const file of this.files.keys()) {
  452. this.setMissing(file, null, type);
  453. }
  454. }
  455. watchInParentDirectory() {
  456. if (!this.parentWatcher) {
  457. const parentDir = path.dirname(this.path);
  458. // avoid watching in the root directory
  459. // removing directories in the root directory is not supported
  460. if (path.dirname(parentDir) === parentDir) return;
  461. this.parentWatcher = this.watcherManager.watchFile(this.path, 1);
  462. this.parentWatcher.on("change", (mtime, type) => {
  463. if (this.closed) return;
  464. // On non-osx platforms we don't need this watcher to detect
  465. // directory removal, as an EPERM error indicates that
  466. if ((!IS_OSX || this.polledWatching) && this.parentWatcher) {
  467. this.parentWatcher.close();
  468. this.parentWatcher = null;
  469. }
  470. // Try to create the watcher when parent directory is found
  471. if (!this.watcher) {
  472. this.createWatcher();
  473. this.doScan(false);
  474. // directory was created so we emit an event
  475. this.forEachWatcher(this.path, w =>
  476. w.emit("change", this.path, mtime, type, false)
  477. );
  478. }
  479. });
  480. this.parentWatcher.on("remove", () => {
  481. this.onDirectoryRemoved("parent directory removed");
  482. });
  483. }
  484. }
  485. doScan(initial) {
  486. if (this.scanning) {
  487. if (this.scanAgain) {
  488. if (!initial) this.scanAgainInitial = false;
  489. } else {
  490. this.scanAgain = true;
  491. this.scanAgainInitial = initial;
  492. }
  493. return;
  494. }
  495. this.scanning = true;
  496. if (this.timeout) {
  497. clearTimeout(this.timeout);
  498. this.timeout = undefined;
  499. }
  500. process.nextTick(() => {
  501. if (this.closed) return;
  502. fs.readdir(this.path, (err, items) => {
  503. if (this.closed) return;
  504. if (err) {
  505. if (err.code === "ENOENT" || err.code === "EPERM") {
  506. this.onDirectoryRemoved("scan readdir failed");
  507. } else {
  508. this.onScanError(err);
  509. }
  510. this.initialScan = false;
  511. this.initialScanFinished = Date.now();
  512. if (initial) {
  513. for (const watchers of this.watchers.values()) {
  514. for (const watcher of watchers) {
  515. if (watcher.checkStartTime(this.initialScanFinished, false)) {
  516. watcher.emit(
  517. "initial-missing",
  518. "scan (parent directory missing in initial scan)"
  519. );
  520. }
  521. }
  522. }
  523. }
  524. if (this.scanAgain) {
  525. this.scanAgain = false;
  526. this.doScan(this.scanAgainInitial);
  527. } else {
  528. this.scanning = false;
  529. }
  530. return;
  531. }
  532. const itemPaths = new Set(
  533. items.map(item => path.join(this.path, item.normalize("NFC")))
  534. );
  535. for (const file of this.files.keys()) {
  536. if (!itemPaths.has(file)) {
  537. this.setMissing(file, initial, "scan (missing)");
  538. }
  539. }
  540. for (const directory of this.directories.keys()) {
  541. if (!itemPaths.has(directory)) {
  542. this.setMissing(directory, initial, "scan (missing)");
  543. }
  544. }
  545. if (this.scanAgain) {
  546. // Early repeat of scan
  547. this.scanAgain = false;
  548. this.doScan(initial);
  549. return;
  550. }
  551. const itemFinished = needCalls(itemPaths.size + 1, () => {
  552. if (this.closed) return;
  553. this.initialScan = false;
  554. this.initialScanRemoved = null;
  555. this.initialScanFinished = Date.now();
  556. if (initial) {
  557. const missingWatchers = new Map(this.watchers);
  558. missingWatchers.delete(withoutCase(this.path));
  559. for (const item of itemPaths) {
  560. missingWatchers.delete(withoutCase(item));
  561. }
  562. for (const watchers of missingWatchers.values()) {
  563. for (const watcher of watchers) {
  564. if (watcher.checkStartTime(this.initialScanFinished, false)) {
  565. watcher.emit(
  566. "initial-missing",
  567. "scan (missing in initial scan)"
  568. );
  569. }
  570. }
  571. }
  572. }
  573. if (this.scanAgain) {
  574. this.scanAgain = false;
  575. this.doScan(this.scanAgainInitial);
  576. } else {
  577. this.scanning = false;
  578. this.onScanFinished();
  579. }
  580. });
  581. for (const itemPath of itemPaths) {
  582. fs.lstat(itemPath, (err2, stats) => {
  583. if (this.closed) return;
  584. if (err2) {
  585. if (
  586. err2.code === "ENOENT" ||
  587. err2.code === "EPERM" ||
  588. err2.code === "EACCES" ||
  589. err2.code === "EBUSY" ||
  590. // TODO https://github.com/libuv/libuv/pull/4566
  591. (err2.code === "EINVAL" && IS_WIN)
  592. ) {
  593. this.setMissing(itemPath, initial, "scan (" + err2.code + ")");
  594. } else {
  595. this.onScanError(err2);
  596. }
  597. itemFinished();
  598. return;
  599. }
  600. if (stats.isFile() || stats.isSymbolicLink()) {
  601. if (stats.mtime) {
  602. ensureFsAccuracy(stats.mtime);
  603. }
  604. this.setFileTime(
  605. itemPath,
  606. +stats.mtime || +stats.ctime || 1,
  607. initial,
  608. true,
  609. "scan (file)"
  610. );
  611. } else if (stats.isDirectory()) {
  612. if (!initial || !this.directories.has(itemPath))
  613. this.setDirectory(
  614. itemPath,
  615. +stats.birthtime || 1,
  616. initial,
  617. "scan (dir)"
  618. );
  619. }
  620. itemFinished();
  621. });
  622. }
  623. itemFinished();
  624. });
  625. });
  626. }
  627. getTimes() {
  628. const obj = Object.create(null);
  629. let safeTime = this.lastWatchEvent;
  630. for (const [file, entry] of this.files) {
  631. fixupEntryAccuracy(entry);
  632. safeTime = Math.max(safeTime, entry.safeTime);
  633. obj[file] = Math.max(entry.safeTime, entry.timestamp);
  634. }
  635. if (this.nestedWatching) {
  636. for (const w of this.directories.values()) {
  637. const times = w.directoryWatcher.getTimes();
  638. for (const file of Object.keys(times)) {
  639. const time = times[file];
  640. safeTime = Math.max(safeTime, time);
  641. obj[file] = time;
  642. }
  643. }
  644. obj[this.path] = safeTime;
  645. }
  646. if (!this.initialScan) {
  647. for (const watchers of this.watchers.values()) {
  648. for (const watcher of watchers) {
  649. const path = watcher.path;
  650. if (!Object.prototype.hasOwnProperty.call(obj, path)) {
  651. obj[path] = null;
  652. }
  653. }
  654. }
  655. }
  656. return obj;
  657. }
  658. collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
  659. let safeTime = this.lastWatchEvent;
  660. for (const [file, entry] of this.files) {
  661. fixupEntryAccuracy(entry);
  662. safeTime = Math.max(safeTime, entry.safeTime);
  663. fileTimestamps.set(file, entry);
  664. }
  665. if (this.nestedWatching) {
  666. for (const w of this.directories.values()) {
  667. safeTime = Math.max(
  668. safeTime,
  669. w.directoryWatcher.collectTimeInfoEntries(
  670. fileTimestamps,
  671. directoryTimestamps
  672. )
  673. );
  674. }
  675. fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  676. directoryTimestamps.set(this.path, {
  677. safeTime
  678. });
  679. } else {
  680. for (const dir of this.directories.keys()) {
  681. // No additional info about this directory
  682. // but maybe another DirectoryWatcher has info
  683. fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
  684. if (!directoryTimestamps.has(dir))
  685. directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
  686. }
  687. fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  688. directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  689. }
  690. if (!this.initialScan) {
  691. for (const watchers of this.watchers.values()) {
  692. for (const watcher of watchers) {
  693. const path = watcher.path;
  694. if (!fileTimestamps.has(path)) {
  695. fileTimestamps.set(path, null);
  696. }
  697. }
  698. }
  699. }
  700. return safeTime;
  701. }
  702. close() {
  703. this.closed = true;
  704. this.initialScan = false;
  705. if (this.watcher) {
  706. this.watcher.close();
  707. this.watcher = null;
  708. }
  709. if (this.nestedWatching) {
  710. for (const w of this.directories.values()) {
  711. w.close();
  712. }
  713. this.directories.clear();
  714. }
  715. if (this.parentWatcher) {
  716. this.parentWatcher.close();
  717. this.parentWatcher = null;
  718. }
  719. this.emit("closed");
  720. }
  721. }
  722. module.exports = DirectoryWatcher;
  723. module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY;
  724. function fixupEntryAccuracy(entry) {
  725. if (entry.accuracy > FS_ACCURACY) {
  726. entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
  727. entry.accuracy = FS_ACCURACY;
  728. }
  729. }
  730. function ensureFsAccuracy(mtime) {
  731. if (!mtime) return;
  732. if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
  733. else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
  734. else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
  735. else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
  736. }