diff --git a/README.md b/README.md index 774d1dd..2924722 100644 --- a/README.md +++ b/README.md @@ -114,14 +114,19 @@ const { changes, removals } = wp.getAggregated(); // when futher changes happen // Can also be used when paused. -// Watchpack.prototype.getTimeInfoEntries() -var fileTimes = wp.getTimeInfoEntries(); -// returns a Map with all known time info objects for files and directories +// Watchpack.prototype.collectTimeInfoEntries(fileInfoEntries: Map, directoryInfoEntries: Map) +wp.collectTimeInfoEntries(fileInfoEntries, directoryInfoEntries); +// collects time info objects for all known files and directories // this include info from files not directly watched // key: absolute path, value: object with { safeTime, timestamp } // safeTime: a point in time at which it is safe to say all changes happened before that // timestamp: only for files, the mtime timestamp of the file +// Watchpack.prototype.getTimeInfoEntries() +var fileTimes = wp.getTimeInfoEntries(); +// returns a Map with all known time info objects for files and directories +// similar to collectTimeInfoEntries but returns a single map with all entries + // (deprecated) // Watchpack.prototype.getTimes() var fileTimes = wp.getTimes(); diff --git a/lib/DirectoryWatcher.js b/lib/DirectoryWatcher.js index 962219d..1b98703 100644 --- a/lib/DirectoryWatcher.js +++ b/lib/DirectoryWatcher.js @@ -39,7 +39,6 @@ class Watcher extends EventEmitter { this.directoryWatcher = directoryWatcher; this.path = filePath; this.startTime = startTime && +startTime; - this._cachedTimeInfoEntries = undefined; } checkStartTime(mtime, initial) { @@ -130,8 +129,6 @@ class DirectoryWatcher extends EventEmitter { } setMissing(itemPath, initial, type) { - this._cachedTimeInfoEntries = undefined; - if (this.initialScan) { this.initialScanRemoved.add(itemPath); } @@ -200,7 +197,6 @@ class DirectoryWatcher extends EventEmitter { accuracy, timestamp: mtime }); - this._cachedTimeInfoEntries = undefined; if (!old) { const key = withoutCase(filePath); @@ -243,7 +239,6 @@ class DirectoryWatcher extends EventEmitter { if (!old) { const now = Date.now(); - this._cachedTimeInfoEntries = undefined; if (this.nestedWatching) { this.createNestedWatcher(directoryPath); } else { @@ -274,7 +269,6 @@ class DirectoryWatcher extends EventEmitter { createNestedWatcher(directoryPath) { const watcher = this.watcherManager.watchDirectory(directoryPath, 1); watcher.on("change", (filePath, mtime, type, initial) => { - this._cachedTimeInfoEntries = undefined; this.forEachWatcher(this.path, w => { if (!initial || w.checkStartTime(mtime, initial)) { w.emit("change", filePath, mtime, type, initial); @@ -287,7 +281,6 @@ class DirectoryWatcher extends EventEmitter { setNestedWatching(flag) { if (this.nestedWatching !== !!flag) { this.nestedWatching = !!flag; - this._cachedTimeInfoEntries = undefined; if (this.nestedWatching) { for (const directory of this.directories.keys()) { this.createNestedWatcher(directory); @@ -425,7 +418,6 @@ class DirectoryWatcher extends EventEmitter { } } this.lastWatchEvent = Date.now(); - this._cachedTimeInfoEntries = undefined; if (!stats) { this.setMissing(filePath, false, eventType); } else if (stats.isDirectory()) { @@ -709,48 +701,47 @@ class DirectoryWatcher extends EventEmitter { return obj; } - getTimeInfoEntries() { - if (this._cachedTimeInfoEntries !== undefined) - return this._cachedTimeInfoEntries; - const map = new Map(); + collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { let safeTime = this.lastWatchEvent; for (const [file, entry] of this.files) { fixupEntryAccuracy(entry); safeTime = Math.max(safeTime, entry.safeTime); - map.set(file, entry); + fileTimestamps.set(file, entry); } if (this.nestedWatching) { for (const w of this.directories.values()) { - const timeInfoEntries = w.directoryWatcher.getTimeInfoEntries(); - for (const [file, entry] of timeInfoEntries) { - if (entry) { - safeTime = Math.max(safeTime, entry.safeTime); - } - map.set(file, entry); - } + safeTime = Math.max( + safeTime, + w.directoryWatcher.collectTimeInfoEntries( + fileTimestamps, + directoryTimestamps, + safeTime + ) + ); } - map.set(this.path, { + fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + directoryTimestamps.set(this.path, { safeTime }); } else { for (const dir of this.directories.keys()) { // No additional info about this directory - map.set(dir, EXISTANCE_ONLY_TIME_ENTRY); + directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY); } - map.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); } if (!this.initialScan) { for (const watchers of this.watchers.values()) { for (const watcher of watchers) { const path = watcher.path; - if (!map.has(path)) { - map.set(path, null); + if (!directoryTimestamps.has(path)) { + directoryTimestamps.set(path, null); } } } - this._cachedTimeInfoEntries = map; } - return map; + return safeTime; } close() { diff --git a/lib/watchpack.js b/lib/watchpack.js index 4ce34e3..ad86464 100644 --- a/lib/watchpack.js +++ b/lib/watchpack.js @@ -10,26 +10,14 @@ const EventEmitter = require("events").EventEmitter; const globToRegExp = require("glob-to-regexp"); const watchEventSource = require("./watchEventSource"); -let EXISTANCE_ONLY_TIME_ENTRY; // lazy required - const EMPTY_ARRAY = []; const EMPTY_OPTIONS = {}; -function addWatchpackWatchersToSet(watchers, set) { +function addWatchersToSet(watchers, set) { for (const ww of watchers) { const w = ww.watcher; if (!set.has(w.directoryWatcher)) { set.add(w.directoryWatcher); - addWatchersToSet(w.directoryWatcher.directories.values(), set); - } - } -} - -function addWatchersToSet(watchers, set) { - for (const w of watchers) { - if (w !== true && !set.has(w.directoryWatcher)) { - set.add(w.directoryWatcher); - addWatchersToSet(w.directoryWatcher.directories.values(), set); } } } @@ -324,11 +312,8 @@ class Watchpack extends EventEmitter { getTimes() { const directoryWatchers = new Set(); - addWatchpackWatchersToSet(this.fileWatchers.values(), directoryWatchers); - addWatchpackWatchersToSet( - this.directoryWatchers.values(), - directoryWatchers - ); + addWatchersToSet(this.fileWatchers.values(), directoryWatchers); + addWatchersToSet(this.directoryWatchers.values(), directoryWatchers); const obj = Object.create(null); for (const w of directoryWatchers) { const times = w.getTimes(); @@ -338,35 +323,21 @@ class Watchpack extends EventEmitter { } getTimeInfoEntries() { - if (EXISTANCE_ONLY_TIME_ENTRY === undefined) { - EXISTANCE_ONLY_TIME_ENTRY = require("./DirectoryWatcher") - .EXISTANCE_ONLY_TIME_ENTRY; - } - const directoryWatchers = new Set(); - addWatchpackWatchersToSet(this.fileWatchers.values(), directoryWatchers); - addWatchpackWatchersToSet( - this.directoryWatchers.values(), - directoryWatchers - ); const map = new Map(); - for (const w of directoryWatchers) { - const times = w.getTimeInfoEntries(); - for (const [path, entry] of times) { - if (map.has(path)) { - if (entry === EXISTANCE_ONLY_TIME_ENTRY) continue; - const value = map.get(path); - if (value === entry) continue; - if (value !== EXISTANCE_ONLY_TIME_ENTRY) { - map.set(path, Object.assign({}, value, entry)); - continue; - } - } - map.set(path, entry); - } - } + this.collectTimeInfoEntries(map, map); return map; } + collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { + const allWatchers = new Set(); + addWatchersToSet(this.fileWatchers.values(), allWatchers); + addWatchersToSet(this.directoryWatchers.values(), allWatchers); + const safeTime = { value: 0 }; + for (const w of allWatchers) { + w.collectTimeInfoEntries(fileTimestamps, directoryTimestamps, safeTime); + } + } + getAggregated() { if (this.aggregateTimer) { clearTimeout(this.aggregateTimer); diff --git a/test/DirectoryWatcher.test.js b/test/DirectoryWatcher.js similarity index 100% rename from test/DirectoryWatcher.test.js rename to test/DirectoryWatcher.js diff --git a/test/Watchpack.js b/test/Watchpack.js index 0312175..b62e020 100644 --- a/test/Watchpack.js +++ b/test/Watchpack.js @@ -121,7 +121,7 @@ describe("Watchpack", function() { it("should not watch a single ignored file (function)", function(done) { var w = new Watchpack({ aggregateTimeout: 300, - ignored: (entry) => entry.includes("a") + ignored: entry => entry.includes("a") }); var changeEvents = 0; var aggregatedEvents = 0; @@ -550,6 +550,53 @@ describe("Watchpack", function() { }); }); + it("should watch file in a sub directory (passed in maps)", function(done) { + var w = new Watchpack({ + aggregateTimeout: 1000 + }); + var changeEvents = []; + w.on("change", function(file) { + if (changeEvents[changeEvents.length - 1] === file) return; + changeEvents.push(file); + }); + w.on("aggregated", function(changes) { + Array.from(changes).should.be.eql([path.join(fixtures, "dir")]); + changeEvents.should.be.eql([path.join(fixtures, "dir", "sub", "a")]); + const files = new Map(); + const directories = new Map(); + w.collectTimeInfoEntries(files, directories); + const dir = directories.get(path.join(fixtures, "dir")); + const dirAsFile = files.get(path.join(fixtures, "dir")); + const sub = directories.get(path.join(fixtures, "dir", "sub")); + const subAsFile = files.get(path.join(fixtures, "dir", "sub")); + const a = files.get(path.join(fixtures, "dir", "sub", "a")); + dir.should.be.type("object"); + dir.should.have.property("safeTime"); + dirAsFile.should.be.type("object"); + dirAsFile.should.not.have.property("safeTime"); + sub.should.be.type("object"); + sub.should.have.property("safeTime"); + subAsFile.should.be.type("object"); + subAsFile.should.not.have.property("safeTime"); + a.should.be.type("object"); + a.should.have.property("safeTime"); + a.should.have.property("timestamp"); + sub.safeTime.should.be.aboveOrEqual(a.safeTime); + dir.safeTime.should.be.aboveOrEqual(sub.safeTime); + w.close(); + done(); + }); + testHelper.dir("dir"); + testHelper.dir(path.join("dir", "sub")); + testHelper.dir(path.join("dir", "sub2")); + testHelper.tick(function() { + w.watch([], [path.join(fixtures, "dir")]); + testHelper.tick(function() { + testHelper.file(path.join("dir", "sub", "a")); + }); + }); + }); + it("should watch 2 files in a not-existing directory", function(done) { var w = new Watchpack({ aggregateTimeout: 1000