DirectoryWatcher.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  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. filePath !== this.path &&
  331. !this.directories.has(filePath) &&
  332. watcher.checkStartTime(this.initialScanFinished, false)
  333. ) {
  334. process.nextTick(() => {
  335. if (this.closed) return;
  336. watcher.emit("initial-missing", "watch (missing on attach)");
  337. });
  338. }
  339. return watcher;
  340. }
  341. onWatchEvent(eventType, filename) {
  342. if (this.closed) return;
  343. if (!filename) {
  344. // In some cases no filename is provided
  345. // This seem to happen on windows
  346. // So some event happened but we don't know which file is affected
  347. // We have to do a full scan of the directory
  348. this.doScan(false);
  349. return;
  350. }
  351. const filePath = path.join(this.path, filename);
  352. if (this.ignored(filePath)) return;
  353. if (this._activeEvents.get(filename) === undefined) {
  354. this._activeEvents.set(filename, false);
  355. const checkStats = () => {
  356. if (this.closed) return;
  357. this._activeEvents.set(filename, false);
  358. fs.lstat(filePath, (err, stats) => {
  359. if (this.closed) return;
  360. if (this._activeEvents.get(filename) === true) {
  361. process.nextTick(checkStats);
  362. return;
  363. }
  364. this._activeEvents.delete(filename);
  365. // ENOENT happens when the file/directory doesn't exist
  366. // EPERM happens when the containing directory doesn't exist
  367. if (err) {
  368. if (
  369. err.code !== "ENOENT" &&
  370. err.code !== "EPERM" &&
  371. err.code !== "EBUSY"
  372. ) {
  373. this.onStatsError(err);
  374. } else {
  375. if (filename === path.basename(this.path)) {
  376. // This may indicate that the directory itself was removed
  377. if (!fs.existsSync(this.path)) {
  378. this.onDirectoryRemoved("stat failed");
  379. }
  380. }
  381. }
  382. }
  383. this.lastWatchEvent = Date.now();
  384. if (!stats) {
  385. this.setMissing(filePath, false, eventType);
  386. } else if (stats.isDirectory()) {
  387. this.setDirectory(
  388. filePath,
  389. +stats.birthtime || 1,
  390. false,
  391. eventType
  392. );
  393. } else if (stats.isFile() || stats.isSymbolicLink()) {
  394. if (stats.mtime) {
  395. ensureFsAccuracy(stats.mtime);
  396. }
  397. this.setFileTime(
  398. filePath,
  399. +stats.mtime || +stats.ctime || 1,
  400. false,
  401. false,
  402. eventType
  403. );
  404. }
  405. });
  406. };
  407. process.nextTick(checkStats);
  408. } else {
  409. this._activeEvents.set(filename, true);
  410. }
  411. }
  412. onWatcherError(err) {
  413. if (this.closed) return;
  414. if (err) {
  415. if (err.code !== "EPERM" && err.code !== "ENOENT") {
  416. console.error("Watchpack Error (watcher): " + err);
  417. }
  418. this.onDirectoryRemoved("watch error");
  419. }
  420. }
  421. onStatsError(err) {
  422. if (err) {
  423. console.error("Watchpack Error (stats): " + err);
  424. }
  425. }
  426. onScanError(err) {
  427. if (err) {
  428. console.error("Watchpack Error (initial scan): " + err);
  429. }
  430. this.onScanFinished();
  431. }
  432. onScanFinished() {
  433. if (this.polledWatching) {
  434. this.timeout = setTimeout(() => {
  435. if (this.closed) return;
  436. this.doScan(false);
  437. }, this.polledWatching);
  438. }
  439. }
  440. onDirectoryRemoved(reason) {
  441. if (this.watcher) {
  442. this.watcher.close();
  443. this.watcher = null;
  444. }
  445. this.watchInParentDirectory();
  446. const type = `directory-removed (${reason})`;
  447. for (const directory of this.directories.keys()) {
  448. this.setMissing(directory, null, type);
  449. }
  450. for (const file of this.files.keys()) {
  451. this.setMissing(file, null, type);
  452. }
  453. }
  454. watchInParentDirectory() {
  455. if (!this.parentWatcher) {
  456. const parentDir = path.dirname(this.path);
  457. // avoid watching in the root directory
  458. // removing directories in the root directory is not supported
  459. if (path.dirname(parentDir) === parentDir) return;
  460. this.parentWatcher = this.watcherManager.watchFile(this.path, 1);
  461. this.parentWatcher.on("change", (mtime, type) => {
  462. if (this.closed) return;
  463. // On non-osx platforms we don't need this watcher to detect
  464. // directory removal, as an EPERM error indicates that
  465. if ((!IS_OSX || this.polledWatching) && this.parentWatcher) {
  466. this.parentWatcher.close();
  467. this.parentWatcher = null;
  468. }
  469. // Try to create the watcher when parent directory is found
  470. if (!this.watcher) {
  471. this.createWatcher();
  472. this.doScan(false);
  473. // directory was created so we emit an event
  474. this.forEachWatcher(this.path, w =>
  475. w.emit("change", this.path, mtime, type, false)
  476. );
  477. }
  478. });
  479. this.parentWatcher.on("remove", () => {
  480. this.onDirectoryRemoved("parent directory removed");
  481. });
  482. }
  483. }
  484. doScan(initial) {
  485. if (this.scanning) {
  486. if (this.scanAgain) {
  487. if (!initial) this.scanAgainInitial = false;
  488. } else {
  489. this.scanAgain = true;
  490. this.scanAgainInitial = initial;
  491. }
  492. return;
  493. }
  494. this.scanning = true;
  495. if (this.timeout) {
  496. clearTimeout(this.timeout);
  497. this.timeout = undefined;
  498. }
  499. process.nextTick(() => {
  500. if (this.closed) return;
  501. fs.readdir(this.path, (err, items) => {
  502. if (this.closed) return;
  503. if (err) {
  504. if (err.code === "ENOENT" || err.code === "EPERM") {
  505. this.onDirectoryRemoved("scan readdir failed");
  506. } else {
  507. this.onScanError(err);
  508. }
  509. this.initialScan = false;
  510. this.initialScanFinished = Date.now();
  511. if (initial) {
  512. for (const watchers of this.watchers.values()) {
  513. for (const watcher of watchers) {
  514. if (watcher.checkStartTime(this.initialScanFinished, false)) {
  515. watcher.emit(
  516. "initial-missing",
  517. "scan (parent directory missing in initial scan)"
  518. );
  519. }
  520. }
  521. }
  522. }
  523. if (this.scanAgain) {
  524. this.scanAgain = false;
  525. this.doScan(this.scanAgainInitial);
  526. } else {
  527. this.scanning = false;
  528. }
  529. return;
  530. }
  531. const itemPaths = new Set(
  532. items.map(item => path.join(this.path, item.normalize("NFC")))
  533. );
  534. for (const file of this.files.keys()) {
  535. if (!itemPaths.has(file)) {
  536. this.setMissing(file, initial, "scan (missing)");
  537. }
  538. }
  539. for (const directory of this.directories.keys()) {
  540. if (!itemPaths.has(directory)) {
  541. this.setMissing(directory, initial, "scan (missing)");
  542. }
  543. }
  544. if (this.scanAgain) {
  545. // Early repeat of scan
  546. this.scanAgain = false;
  547. this.doScan(initial);
  548. return;
  549. }
  550. const itemFinished = needCalls(itemPaths.size + 1, () => {
  551. if (this.closed) return;
  552. this.initialScan = false;
  553. this.initialScanRemoved = null;
  554. this.initialScanFinished = Date.now();
  555. if (initial) {
  556. const missingWatchers = new Map(this.watchers);
  557. missingWatchers.delete(withoutCase(this.path));
  558. for (const item of itemPaths) {
  559. missingWatchers.delete(withoutCase(item));
  560. }
  561. for (const watchers of missingWatchers.values()) {
  562. for (const watcher of watchers) {
  563. if (watcher.checkStartTime(this.initialScanFinished, false)) {
  564. watcher.emit(
  565. "initial-missing",
  566. "scan (missing in initial scan)"
  567. );
  568. }
  569. }
  570. }
  571. }
  572. if (this.scanAgain) {
  573. this.scanAgain = false;
  574. this.doScan(this.scanAgainInitial);
  575. } else {
  576. this.scanning = false;
  577. this.onScanFinished();
  578. }
  579. });
  580. for (const itemPath of itemPaths) {
  581. fs.lstat(itemPath, (err2, stats) => {
  582. if (this.closed) return;
  583. if (err2) {
  584. if (
  585. err2.code === "ENOENT" ||
  586. err2.code === "EPERM" ||
  587. err2.code === "EACCES" ||
  588. err2.code === "EBUSY"
  589. ) {
  590. this.setMissing(itemPath, initial, "scan (" + err2.code + ")");
  591. } else {
  592. this.onScanError(err2);
  593. }
  594. itemFinished();
  595. return;
  596. }
  597. if (stats.isFile() || stats.isSymbolicLink()) {
  598. if (stats.mtime) {
  599. ensureFsAccuracy(stats.mtime);
  600. }
  601. this.setFileTime(
  602. itemPath,
  603. +stats.mtime || +stats.ctime || 1,
  604. initial,
  605. true,
  606. "scan (file)"
  607. );
  608. } else if (stats.isDirectory()) {
  609. if (!initial || !this.directories.has(itemPath))
  610. this.setDirectory(
  611. itemPath,
  612. +stats.birthtime || 1,
  613. initial,
  614. "scan (dir)"
  615. );
  616. }
  617. itemFinished();
  618. });
  619. }
  620. itemFinished();
  621. });
  622. });
  623. }
  624. getTimes() {
  625. const obj = Object.create(null);
  626. let safeTime = this.lastWatchEvent;
  627. for (const [file, entry] of this.files) {
  628. fixupEntryAccuracy(entry);
  629. safeTime = Math.max(safeTime, entry.safeTime);
  630. obj[file] = Math.max(entry.safeTime, entry.timestamp);
  631. }
  632. if (this.nestedWatching) {
  633. for (const w of this.directories.values()) {
  634. const times = w.directoryWatcher.getTimes();
  635. for (const file of Object.keys(times)) {
  636. const time = times[file];
  637. safeTime = Math.max(safeTime, time);
  638. obj[file] = time;
  639. }
  640. }
  641. obj[this.path] = safeTime;
  642. }
  643. if (!this.initialScan) {
  644. for (const watchers of this.watchers.values()) {
  645. for (const watcher of watchers) {
  646. const path = watcher.path;
  647. if (!Object.prototype.hasOwnProperty.call(obj, path)) {
  648. obj[path] = null;
  649. }
  650. }
  651. }
  652. }
  653. return obj;
  654. }
  655. collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
  656. let safeTime = this.lastWatchEvent;
  657. for (const [file, entry] of this.files) {
  658. fixupEntryAccuracy(entry);
  659. safeTime = Math.max(safeTime, entry.safeTime);
  660. fileTimestamps.set(file, entry);
  661. }
  662. if (this.nestedWatching) {
  663. for (const w of this.directories.values()) {
  664. safeTime = Math.max(
  665. safeTime,
  666. w.directoryWatcher.collectTimeInfoEntries(
  667. fileTimestamps,
  668. directoryTimestamps
  669. )
  670. );
  671. }
  672. fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  673. directoryTimestamps.set(this.path, {
  674. safeTime
  675. });
  676. } else {
  677. for (const dir of this.directories.keys()) {
  678. // No additional info about this directory
  679. // but maybe another DirectoryWatcher has info
  680. fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
  681. if (!directoryTimestamps.has(dir))
  682. directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
  683. }
  684. fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  685. directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
  686. }
  687. if (!this.initialScan) {
  688. for (const watchers of this.watchers.values()) {
  689. for (const watcher of watchers) {
  690. const path = watcher.path;
  691. if (!fileTimestamps.has(path)) {
  692. fileTimestamps.set(path, null);
  693. }
  694. }
  695. }
  696. }
  697. return safeTime;
  698. }
  699. close() {
  700. this.closed = true;
  701. this.initialScan = false;
  702. if (this.watcher) {
  703. this.watcher.close();
  704. this.watcher = null;
  705. }
  706. if (this.nestedWatching) {
  707. for (const w of this.directories.values()) {
  708. w.close();
  709. }
  710. this.directories.clear();
  711. }
  712. if (this.parentWatcher) {
  713. this.parentWatcher.close();
  714. this.parentWatcher = null;
  715. }
  716. this.emit("closed");
  717. }
  718. }
  719. module.exports = DirectoryWatcher;
  720. module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY;
  721. function fixupEntryAccuracy(entry) {
  722. if (entry.accuracy > FS_ACCURACY) {
  723. entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
  724. entry.accuracy = FS_ACCURACY;
  725. }
  726. }
  727. function ensureFsAccuracy(mtime) {
  728. if (!mtime) return;
  729. if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
  730. else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
  731. else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
  732. else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
  733. }