DirectoryWatcher.js 20 KB

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