Skip to content

Commit

Permalink
Merge 7d23266 into 4b97ddd
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Nov 6, 2019
2 parents 4b97ddd + 7d23266 commit 934620f
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 43 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,40 @@ var wp = new Watchpack({
// All subdirectories are ignored too
});

// Watchpack.prototype.watch(files: Iterable<string>, directories: Iterable<string>, startTime?: number)
wp.watch(listOfFiles, listOfDirectories, Date.now() - 10000);
// Watchpack.prototype.watch({
// files: Iterable<string>,
// directories: Iterable<string>,
// missing: Iterable<string>,
// startTime?: number
// })
wp.watch({
files: listOfFiles,
directories: listOfDirectories,
missing: listOfNotExistingItems,
startTime: Date.now() - 10000
});
// starts watching these files and directories
// calling this again will override the files and directories

wp.on("change", function(filePath, mtime) {
// files: can be files or directories, for files: content and existance changes are tracked
// for directories: only existance and timestamp changes are tracked
// directories: only directories, directory content (and content of children, ...) and
// existance changes are tracked.
// assumed to exist, when directory is not found without futher information a remove event is emitted
// missing: can be files or directores,
// only existance changes are tracked
// expected to not exist, no remove event is emitted when not found initially
// files and directories are assumed to exist, when they are not found without futher information a remove event is emitted
// missing is assumed to not exist and no remove event is emitted

wp.on("change", function(filePath, mtime, explanation) {
// filePath: the changed file
// mtime: last modified time for the changed file (null if file was removed)
// for folders it's a time before that all changes in the directory happened
// mtime: last modified time for the changed file
// explanation: textual information how this change was detected
});

wp.on("remove", function(filePath, explanation) {
// filePath: the removed file or directory
// explanation: textual information how this change was detected
});

wp.on("aggregated", function(changes, removals) {
Expand Down
65 changes: 55 additions & 10 deletions lib/DirectoryWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class DirectoryWatcher extends EventEmitter {
? 5007
: false;
this.initialScanRemoved = new Set();
this.initialScanFinished = undefined;
/** @type {Map<string, Set<Watcher>>} */
this.watchers = new Map();
this.parentWatcher = null;
this.refs = 0;
Expand Down Expand Up @@ -312,10 +314,20 @@ class DirectoryWatcher extends EventEmitter {
watcher.emit("change", safeTime);
});
}
} else if (this.initialScan && this.initialScanRemoved.has(filePath)) {
} else if (this.initialScan) {
if (this.initialScanRemoved.has(filePath)) {
process.nextTick(() => {
if (this.closed) return;
watcher.emit("remove");
});
}
} else if (
!this.directories.has(filePath) &&
watcher.checkStartTime(this.initialScanFinished, false)
) {
process.nextTick(() => {
if (this.closed) return;
watcher.emit("remove");
watcher.emit("initial-missing", "watch (missing on attach)");
});
}
return watcher;
Expand Down Expand Up @@ -360,7 +372,7 @@ class DirectoryWatcher extends EventEmitter {
if (filename === path.basename(this.path)) {
// This may indicate that the directory itself was removed
if (!fs.existsSync(this.path)) {
this.onDirectoryRemoved();
this.onDirectoryRemoved("stat failed");
}
}
}
Expand Down Expand Up @@ -402,7 +414,7 @@ class DirectoryWatcher extends EventEmitter {
if (err.code !== "EPERM" && err.code !== "ENOENT") {
console.error("Watchpack Error (watcher): " + err);
}
this.onDirectoryRemoved();
this.onDirectoryRemoved("watch error");
}
}

Expand All @@ -418,16 +430,18 @@ class DirectoryWatcher extends EventEmitter {
}
}

onDirectoryRemoved() {
onDirectoryRemoved(reason) {
if (this.watcher) {
this.watcher.close(), (this.watcher = null);
this.watcher.close();
this.watcher = null;
}
this.watchInParentDirectory();
const type = `directory-removed (${reason})`;
for (const directory of this.directories.keys()) {
this.setMissing(directory, null, "directory-removed");
this.setMissing(directory, null, type);
}
for (const file of this.files.keys()) {
this.setMissing(file, null, "directory-removed");
this.setMissing(file, null, type);
}
}

Expand Down Expand Up @@ -460,7 +474,7 @@ class DirectoryWatcher extends EventEmitter {
}
});
this.parentWatcher.on("remove", () => {
this.onDirectoryRemoved();
this.onDirectoryRemoved("parent directory removed");
});
}
}
Expand All @@ -480,11 +494,24 @@ class DirectoryWatcher extends EventEmitter {
if (this.closed) return;
if (err) {
if (err.code === "ENOENT" || err.code === "EPERM") {
this.onDirectoryRemoved();
this.onDirectoryRemoved("scan readdir failed");
} else {
this.onScanError(err);
}
this.initialScan = false;
this.initialScanFinished = Date.now();
if (initial) {
for (const watchers of this.watchers.values()) {
for (const watcher of watchers) {
if (watcher.checkStartTime(this.initialScanFinished, false)) {
watcher.emit(
"initial-missing",
"scan (parent directory missing in initial scan)"
);
}
}
}
}
if (this.scanAgain) {
this.scanAgain = false;
this.doScan(this.scanAgainInitial);
Expand Down Expand Up @@ -557,6 +584,24 @@ class DirectoryWatcher extends EventEmitter {
if (this.closed) return;
this.initialScan = false;
this.initialScanRemoved = null;
this.initialScanFinished = Date.now();
if (initial) {
const missingWatchers = new Map(this.watchers);
missingWatchers.delete(withoutCase(this.path));
for (const item of itemPaths) {
missingWatchers.delete(withoutCase(item));
}
for (const watchers of missingWatchers.values()) {
for (const watcher of watchers) {
if (watcher.checkStartTime(this.initialScanFinished, false)) {
watcher.emit(
"initial-missing",
"scan (missing in initial scan)"
);
}
}
}
}
if (this.scanAgain) {
this.scanAgain = false;
this.doScan(this.scanAgainInitial);
Expand Down
99 changes: 76 additions & 23 deletions lib/watchpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const globToRegExp = require("glob-to-regexp");

let EXISTANCE_ONLY_TIME_ENTRY; // lazy required

const EMPTY_ARRAY = [];

function addWatchersToSet(watchers, set) {
for (const w of watchers) {
if (w !== true && !set.has(w.directoryWatcher)) {
Expand Down Expand Up @@ -64,25 +66,36 @@ class Watchpack extends EventEmitter {
this.options = options;
this.watcherOptions = cachedNormalizeOptions(options);
this.watcherManager = getWatcherManager(this.watcherOptions);
this.fileWatchers = [];
this.dirWatchers = [];
this.watchers = [];
this.paused = false;
this.aggregatedChanges = new Set();
this.aggregatedRemovals = new Set();
this.aggregateTimeout = 0;
this._onTimeout = this._onTimeout.bind(this);
}

watch(files, directories, startTime) {
watch(arg1, arg2, arg3) {
let files, directories, missing, startTime;
if (!arg2) {
({
files = EMPTY_ARRAY,
directories = EMPTY_ARRAY,
missing = EMPTY_ARRAY,
startTime
} = arg1);
} else {
files = arg1;
directories = arg2;
missing = EMPTY_ARRAY;
startTime = arg3;
}
this.paused = false;
const oldFileWatchers = this.fileWatchers;
const oldDirWatchers = this.dirWatchers;
const oldWatchers = this.watchers;
const ignored = this.watcherOptions.ignored;
const filter = ignored
? path => !ignored.test(path.replace(/\\/g, "/"))
: () => true;
this.fileWatchers = [];
this.dirWatchers = [];
this.watchers = [];
if (this.watcherOptions.followSymlinks) {
const resolver = new LinkResolver();
for (const file of files) {
Expand All @@ -93,7 +106,20 @@ class Watchpack extends EventEmitter {
file,
this.watcherManager.watchFile(innerFile, startTime)
);
if (watcher) this.fileWatchers.push(watcher);
if (watcher) this.watchers.push(watcher);
}
}
}
}
for (const file of missing) {
if (filter(file)) {
for (const innerFile of resolver.resolve(file)) {
if (file === innerFile || filter(innerFile)) {
const watcher = this._missingWatcher(
file,
this.watcherManager.watchFile(innerFile, startTime)
);
if (watcher) this.watchers.push(watcher);
}
}
}
Expand All @@ -109,7 +135,7 @@ class Watchpack extends EventEmitter {
? this.watcherManager.watchDirectory(innerItem, startTime)
: this.watcherManager.watchFile(innerItem, startTime)
);
if (watcher) this.dirWatchers.push(watcher);
if (watcher) this.watchers.push(watcher);
}
first = false;
}
Expand All @@ -122,7 +148,16 @@ class Watchpack extends EventEmitter {
file,
this.watcherManager.watchFile(file, startTime)
);
if (watcher) this.fileWatchers.push(watcher);
if (watcher) this.watchers.push(watcher);
}
}
for (const file of missing) {
if (filter(file)) {
const watcher = this._missingWatcher(
file,
this.watcherManager.watchFile(file, startTime)
);
if (watcher) this.watchers.push(watcher);
}
}
for (const dir of directories) {
Expand All @@ -131,21 +166,18 @@ class Watchpack extends EventEmitter {
dir,
this.watcherManager.watchDirectory(dir, startTime)
);
if (watcher) this.dirWatchers.push(watcher);
if (watcher) this.watchers.push(watcher);
}
}
}
for (const w of oldFileWatchers) w.close();
for (const w of oldDirWatchers) w.close();
for (const w of oldWatchers) w.close();
}

close() {
this.paused = true;
if (this.aggregateTimeout) clearTimeout(this.aggregateTimeout);
for (const w of this.fileWatchers) w.close();
for (const w of this.dirWatchers) w.close();
this.fileWatchers.length = 0;
this.dirWatchers.length = 0;
for (const w of this.watchers) w.close();
this.watchers.length = 0;
}

pause() {
Expand All @@ -155,8 +187,7 @@ class Watchpack extends EventEmitter {

getTimes() {
const directoryWatchers = new Set();
addWatchersToSet(this.fileWatchers, directoryWatchers);
addWatchersToSet(this.dirWatchers, directoryWatchers);
addWatchersToSet(this.watchers, directoryWatchers);
const obj = Object.create(null);
for (const w of directoryWatchers) {
const times = w.getTimes();
Expand All @@ -171,8 +202,7 @@ class Watchpack extends EventEmitter {
.EXISTANCE_ONLY_TIME_ENTRY;
}
const directoryWatchers = new Set();
addWatchersToSet(this.fileWatchers, directoryWatchers);
addWatchersToSet(this.dirWatchers, directoryWatchers);
addWatchersToSet(this.watchers, directoryWatchers);
const map = new Map();
for (const w of directoryWatchers) {
const times = w.getTimeInfoEntries();
Expand All @@ -192,8 +222,23 @@ class Watchpack extends EventEmitter {
return map;
}

_missingWatcher(file, watcher) {
if (watcher) {
watcher.on("change", (mtime, type) => {
this._onChange(file, mtime, file, type);
});
watcher.on("remove", type => {
this._onRemove(file, file, type);
});
}
return watcher;
}

_fileWatcher(file, watcher) {
if (watcher) {
watcher.on("initial-missing", type => {
this._onRemove(file, file, type);
});
watcher.on("change", (mtime, type) => {
this._onChange(file, mtime, file, type);
});
Expand All @@ -205,9 +250,15 @@ class Watchpack extends EventEmitter {
}

_dirWatcher(item, watcher) {
watcher.on("initial-missing", type => {
this._onRemove(item, item, type);
});
watcher.on("change", (file, mtime, type) => {
this._onChange(item, mtime, file, type);
});
watcher.on("remove", type => {
this._onRemove(item, item, type);
});
return watcher;
}

Expand All @@ -216,18 +267,20 @@ class Watchpack extends EventEmitter {
if (this.paused) return;
this.emit("change", file, mtime, type);
if (this.aggregateTimeout) clearTimeout(this.aggregateTimeout);
this.aggregatedRemovals.delete(item);
this.aggregatedChanges.add(item);
this.aggregateTimeout = setTimeout(
this._onTimeout,
this.options.aggregateTimeout
);
}

_onRemove(item, file) {
_onRemove(item, file, type) {
file = file || item;
if (this.paused) return;
this.emit("remove", item);
this.emit("remove", file, type);
if (this.aggregateTimeout) clearTimeout(this.aggregateTimeout);
this.aggregatedChanges.delete(item);
this.aggregatedRemovals.add(item);
this.aggregateTimeout = setTimeout(
this._onTimeout,
Expand Down
Loading

0 comments on commit 934620f

Please sign in to comment.