Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support splitting of files and directories in getTimeInfoEntries #205

Merged
merged 3 commits into from Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Expand Up @@ -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<string, Entry>, directoryInfoEntries: Map<string, Entry>)
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();
Expand Down
45 changes: 18 additions & 27 deletions lib/DirectoryWatcher.js
Expand Up @@ -39,7 +39,6 @@ class Watcher extends EventEmitter {
this.directoryWatcher = directoryWatcher;
this.path = filePath;
this.startTime = startTime && +startTime;
this._cachedTimeInfoEntries = undefined;
}

checkStartTime(mtime, initial) {
Expand Down Expand Up @@ -130,8 +129,6 @@ class DirectoryWatcher extends EventEmitter {
}

setMissing(itemPath, initial, type) {
this._cachedTimeInfoEntries = undefined;

if (this.initialScan) {
this.initialScanRemoved.add(itemPath);
}
Expand Down Expand Up @@ -200,7 +197,6 @@ class DirectoryWatcher extends EventEmitter {
accuracy,
timestamp: mtime
});
this._cachedTimeInfoEntries = undefined;

if (!old) {
const key = withoutCase(filePath);
Expand Down Expand Up @@ -243,7 +239,6 @@ class DirectoryWatcher extends EventEmitter {
if (!old) {
const now = Date.now();

this._cachedTimeInfoEntries = undefined;
if (this.nestedWatching) {
this.createNestedWatcher(directoryPath);
} else {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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() {
Expand Down
57 changes: 14 additions & 43 deletions lib/watchpack.js
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down
File renamed without changes.
49 changes: 48 additions & 1 deletion test/Watchpack.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down