aboutsummaryrefslogtreecommitdiff
path: root/node_modules/chokidar/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/chokidar/lib')
-rw-r--r--node_modules/chokidar/lib/fsevents-handler.js405
-rw-r--r--node_modules/chokidar/lib/nodefs-handler.js488
2 files changed, 893 insertions, 0 deletions
diff --git a/node_modules/chokidar/lib/fsevents-handler.js b/node_modules/chokidar/lib/fsevents-handler.js
new file mode 100644
index 0000000..a748a1b
--- /dev/null
+++ b/node_modules/chokidar/lib/fsevents-handler.js
@@ -0,0 +1,405 @@
+'use strict';
+
+var fs = require('fs');
+var sysPath = require('path');
+var readdirp = require('readdirp');
+var fsevents;
+try { fsevents = require('fsevents'); } catch (error) {
+ if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error)
+}
+
+// fsevents instance helper functions
+
+// object to hold per-process fsevents instances
+// (may be shared across chokidar FSWatcher instances)
+var FSEventsWatchers = Object.create(null);
+
+// Threshold of duplicate path prefixes at which to start
+// consolidating going forward
+var consolidateThreshhold = 10;
+
+// Private function: Instantiates the fsevents interface
+
+// * path - string, path to be watched
+// * callback - function, called when fsevents is bound and ready
+
+// Returns new fsevents instance
+function createFSEventsInstance(path, callback) {
+ return (new fsevents(path)).on('fsevent', callback).start();
+}
+
+// Private function: Instantiates the fsevents interface or binds listeners
+// to an existing one covering the same file tree
+
+// * path - string, path to be watched
+// * realPath - string, real path (in case of symlinks)
+// * listener - function, called when fsevents emits events
+// * rawEmitter - function, passes data to listeners of the 'raw' event
+
+// Returns close function
+function setFSEventsListener(path, realPath, listener, rawEmitter) {
+ var watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path;
+ var watchContainer;
+ var parentPath = sysPath.dirname(watchPath);
+
+ // If we've accumulated a substantial number of paths that
+ // could have been consolidated by watching one directory
+ // above the current one, create a watcher on the parent
+ // path instead, so that we do consolidate going forward.
+ if (couldConsolidate(parentPath)) {
+ watchPath = parentPath;
+ }
+
+ var resolvedPath = sysPath.resolve(path);
+ var hasSymlink = resolvedPath !== realPath;
+ function filteredListener(fullPath, flags, info) {
+ if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath);
+ if (
+ fullPath === resolvedPath ||
+ !fullPath.indexOf(resolvedPath + sysPath.sep)
+ ) listener(fullPath, flags, info);
+ }
+
+ // check if there is already a watcher on a parent path
+ // modifies `watchPath` to the parent path when it finds a match
+ function watchedParent() {
+ return Object.keys(FSEventsWatchers).some(function(watchedPath) {
+ // condition is met when indexOf returns 0
+ if (!realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep)) {
+ watchPath = watchedPath;
+ return true;
+ }
+ });
+ }
+
+ if (watchPath in FSEventsWatchers || watchedParent()) {
+ watchContainer = FSEventsWatchers[watchPath];
+ watchContainer.listeners.push(filteredListener);
+ } else {
+ watchContainer = FSEventsWatchers[watchPath] = {
+ listeners: [filteredListener],
+ rawEmitters: [rawEmitter],
+ watcher: createFSEventsInstance(watchPath, function(fullPath, flags) {
+ var info = fsevents.getInfo(fullPath, flags);
+ watchContainer.listeners.forEach(function(listener) {
+ listener(fullPath, flags, info);
+ });
+ watchContainer.rawEmitters.forEach(function(emitter) {
+ emitter(info.event, fullPath, info);
+ });
+ })
+ };
+ }
+ var listenerIndex = watchContainer.listeners.length - 1;
+
+ // removes this instance's listeners and closes the underlying fsevents
+ // instance if there are no more listeners left
+ return function close() {
+ delete watchContainer.listeners[listenerIndex];
+ delete watchContainer.rawEmitters[listenerIndex];
+ if (!Object.keys(watchContainer.listeners).length) {
+ watchContainer.watcher.stop();
+ delete FSEventsWatchers[watchPath];
+ }
+ };
+}
+
+// Decide whether or not we should start a new higher-level
+// parent watcher
+function couldConsolidate(path) {
+ var keys = Object.keys(FSEventsWatchers);
+ var count = 0;
+
+ for (var i = 0, len = keys.length; i < len; ++i) {
+ var watchPath = keys[i];
+ if (watchPath.indexOf(path) === 0) {
+ count++;
+ if (count >= consolidateThreshhold) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// returns boolean indicating whether fsevents can be used
+function canUse() {
+ return fsevents && Object.keys(FSEventsWatchers).length < 128;
+}
+
+// determines subdirectory traversal levels from root to path
+function depth(path, root) {
+ var i = 0;
+ while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++;
+ return i;
+}
+
+// fake constructor for attaching fsevents-specific prototype methods that
+// will be copied to FSWatcher's prototype
+function FsEventsHandler() {}
+
+// Private method: Handle symlinks encountered during directory scan
+
+// * watchPath - string, file/dir path to be watched with fsevents
+// * realPath - string, real path (in case of symlinks)
+// * transform - function, path transformer
+// * globFilter - function, path filter in case a glob pattern was provided
+
+// Returns close function for the watcher instance
+FsEventsHandler.prototype._watchWithFsEvents =
+function(watchPath, realPath, transform, globFilter) {
+ if (this._isIgnored(watchPath)) return;
+ var watchCallback = function(fullPath, flags, info) {
+ if (
+ this.options.depth !== undefined &&
+ depth(fullPath, realPath) > this.options.depth
+ ) return;
+ var path = transform(sysPath.join(
+ watchPath, sysPath.relative(watchPath, fullPath)
+ ));
+ if (globFilter && !globFilter(path)) return;
+ // ensure directories are tracked
+ var parent = sysPath.dirname(path);
+ var item = sysPath.basename(path);
+ var watchedDir = this._getWatchedDir(
+ info.type === 'directory' ? path : parent
+ );
+ var checkIgnored = function(stats) {
+ if (this._isIgnored(path, stats)) {
+ this._ignoredPaths[path] = true;
+ if (stats && stats.isDirectory()) {
+ this._ignoredPaths[path + '/**/*'] = true;
+ }
+ return true;
+ } else {
+ delete this._ignoredPaths[path];
+ delete this._ignoredPaths[path + '/**/*'];
+ }
+ }.bind(this);
+
+ var handleEvent = function(event) {
+ if (checkIgnored()) return;
+
+ if (event === 'unlink') {
+ // suppress unlink events on never before seen files
+ if (info.type === 'directory' || watchedDir.has(item)) {
+ this._remove(parent, item);
+ }
+ } else {
+ if (event === 'add') {
+ // track new directories
+ if (info.type === 'directory') this._getWatchedDir(path);
+
+ if (info.type === 'symlink' && this.options.followSymlinks) {
+ // push symlinks back to the top of the stack to get handled
+ var curDepth = this.options.depth === undefined ?
+ undefined : depth(fullPath, realPath) + 1;
+ return this._addToFsEvents(path, false, true, curDepth);
+ } else {
+ // track new paths
+ // (other than symlinks being followed, which will be tracked soon)
+ this._getWatchedDir(parent).add(item);
+ }
+ }
+ var eventName = info.type === 'directory' ? event + 'Dir' : event;
+ this._emit(eventName, path);
+ if (eventName === 'addDir') this._addToFsEvents(path, false, true);
+ }
+ }.bind(this);
+
+ function addOrChange() {
+ handleEvent(watchedDir.has(item) ? 'change' : 'add');
+ }
+ function checkFd() {
+ fs.open(path, 'r', function(error, fd) {
+ if (error) {
+ error.code !== 'EACCES' ?
+ handleEvent('unlink') : addOrChange();
+ } else {
+ fs.close(fd, function(err) {
+ err && err.code !== 'EACCES' ?
+ handleEvent('unlink') : addOrChange();
+ });
+ }
+ });
+ }
+ // correct for wrong events emitted
+ var wrongEventFlags = [
+ 69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912
+ ];
+ if (wrongEventFlags.indexOf(flags) !== -1 || info.event === 'unknown') {
+ if (typeof this.options.ignored === 'function') {
+ fs.stat(path, function(error, stats) {
+ if (checkIgnored(stats)) return;
+ stats ? addOrChange() : handleEvent('unlink');
+ });
+ } else {
+ checkFd();
+ }
+ } else {
+ switch (info.event) {
+ case 'created':
+ case 'modified':
+ return addOrChange();
+ case 'deleted':
+ case 'moved':
+ return checkFd();
+ }
+ }
+ }.bind(this);
+
+ var closer = setFSEventsListener(
+ watchPath,
+ realPath,
+ watchCallback,
+ this.emit.bind(this, 'raw')
+ );
+
+ this._emitReady();
+ return closer;
+};
+
+// Private method: Handle symlinks encountered during directory scan
+
+// * linkPath - string, path to symlink
+// * fullPath - string, absolute path to the symlink
+// * transform - function, pre-existing path transformer
+// * curDepth - int, level of subdirectories traversed to where symlink is
+
+// Returns nothing
+FsEventsHandler.prototype._handleFsEventsSymlink =
+function(linkPath, fullPath, transform, curDepth) {
+ // don't follow the same symlink more than once
+ if (this._symlinkPaths[fullPath]) return;
+ else this._symlinkPaths[fullPath] = true;
+
+ this._readyCount++;
+
+ fs.realpath(linkPath, function(error, linkTarget) {
+ if (this._handleError(error) || this._isIgnored(linkTarget)) {
+ return this._emitReady();
+ }
+
+ this._readyCount++;
+
+ // add the linkTarget for watching with a wrapper for transform
+ // that causes emitted paths to incorporate the link's path
+ this._addToFsEvents(linkTarget || linkPath, function(path) {
+ var dotSlash = '.' + sysPath.sep;
+ var aliasedPath = linkPath;
+ if (linkTarget && linkTarget !== dotSlash) {
+ aliasedPath = path.replace(linkTarget, linkPath);
+ } else if (path !== dotSlash) {
+ aliasedPath = sysPath.join(linkPath, path);
+ }
+ return transform(aliasedPath);
+ }, false, curDepth);
+ }.bind(this));
+};
+
+// Private method: Handle added path with fsevents
+
+// * path - string, file/directory path or glob pattern
+// * transform - function, converts working path to what the user expects
+// * forceAdd - boolean, ensure add is emitted
+// * priorDepth - int, level of subdirectories already traversed
+
+// Returns nothing
+FsEventsHandler.prototype._addToFsEvents =
+function(path, transform, forceAdd, priorDepth) {
+
+ // applies transform if provided, otherwise returns same value
+ var processPath = typeof transform === 'function' ?
+ transform : function(val) { return val; };
+
+ var emitAdd = function(newPath, stats) {
+ var pp = processPath(newPath);
+ var isDir = stats.isDirectory();
+ var dirObj = this._getWatchedDir(sysPath.dirname(pp));
+ var base = sysPath.basename(pp);
+
+ // ensure empty dirs get tracked
+ if (isDir) this._getWatchedDir(pp);
+
+ if (dirObj.has(base)) return;
+ dirObj.add(base);
+
+ if (!this.options.ignoreInitial || forceAdd === true) {
+ this._emit(isDir ? 'addDir' : 'add', pp, stats);
+ }
+ }.bind(this);
+
+ var wh = this._getWatchHelpers(path);
+
+ // evaluate what is at the path we're being asked to watch
+ fs[wh.statMethod](wh.watchPath, function(error, stats) {
+ if (this._handleError(error) || this._isIgnored(wh.watchPath, stats)) {
+ this._emitReady();
+ return this._emitReady();
+ }
+
+ if (stats.isDirectory()) {
+ // emit addDir unless this is a glob parent
+ if (!wh.globFilter) emitAdd(processPath(path), stats);
+
+ // don't recurse further if it would exceed depth setting
+ if (priorDepth && priorDepth > this.options.depth) return;
+
+ // scan the contents of the dir
+ readdirp({
+ root: wh.watchPath,
+ entryType: 'all',
+ fileFilter: wh.filterPath,
+ directoryFilter: wh.filterDir,
+ lstat: true,
+ depth: this.options.depth - (priorDepth || 0)
+ }).on('data', function(entry) {
+ // need to check filterPath on dirs b/c filterDir is less restrictive
+ if (entry.stat.isDirectory() && !wh.filterPath(entry)) return;
+
+ var joinedPath = sysPath.join(wh.watchPath, entry.path);
+ var fullPath = entry.fullPath;
+
+ if (wh.followSymlinks && entry.stat.isSymbolicLink()) {
+ // preserve the current depth here since it can't be derived from
+ // real paths past the symlink
+ var curDepth = this.options.depth === undefined ?
+ undefined : depth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;
+
+ this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth);
+ } else {
+ emitAdd(joinedPath, entry.stat);
+ }
+ }.bind(this)).on('error', function() {
+ // Ignore readdirp errors
+ }).on('end', this._emitReady);
+ } else {
+ emitAdd(wh.watchPath, stats);
+ this._emitReady();
+ }
+ }.bind(this));
+
+ if (this.options.persistent && forceAdd !== true) {
+ var initWatch = function(error, realPath) {
+ if (this.closed) return;
+ var closer = this._watchWithFsEvents(
+ wh.watchPath,
+ sysPath.resolve(realPath || wh.watchPath),
+ processPath,
+ wh.globFilter
+ );
+ if (closer) this._closers[path] = closer;
+ }.bind(this);
+
+ if (typeof transform === 'function') {
+ // realpath has already been resolved
+ initWatch();
+ } else {
+ fs.realpath(wh.watchPath, initWatch);
+ }
+ }
+};
+
+module.exports = FsEventsHandler;
+module.exports.canUse = canUse;
diff --git a/node_modules/chokidar/lib/nodefs-handler.js b/node_modules/chokidar/lib/nodefs-handler.js
new file mode 100644
index 0000000..c287732
--- /dev/null
+++ b/node_modules/chokidar/lib/nodefs-handler.js
@@ -0,0 +1,488 @@
+'use strict';
+
+var fs = require('fs');
+var sysPath = require('path');
+var readdirp = require('readdirp');
+var isBinaryPath = require('is-binary-path');
+
+// fs.watch helpers
+
+// object to hold per-process fs.watch instances
+// (may be shared across chokidar FSWatcher instances)
+var FsWatchInstances = Object.create(null);
+
+// Private function: Instantiates the fs.watch interface
+
+// * path - string, path to be watched
+// * options - object, options to be passed to fs.watch
+// * listener - function, main event handler
+// * errHandler - function, handler which emits info about errors
+// * emitRaw - function, handler which emits raw event data
+
+// Returns new fsevents instance
+function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
+ var handleEvent = function(rawEvent, evPath) {
+ listener(path);
+ emitRaw(rawEvent, evPath, {watchedPath: path});
+
+ // emit based on events occuring for files from a directory's watcher in
+ // case the file's watcher misses it (and rely on throttling to de-dupe)
+ if (evPath && path !== evPath) {
+ fsWatchBroadcast(
+ sysPath.resolve(path, evPath), 'listeners', sysPath.join(path, evPath)
+ );
+ }
+ };
+ try {
+ return fs.watch(path, options, handleEvent);
+ } catch (error) {
+ errHandler(error);
+ }
+}
+
+// Private function: Helper for passing fs.watch event data to a
+// collection of listeners
+
+// * fullPath - string, absolute path bound to the fs.watch instance
+// * type - string, listener type
+// * val[1..3] - arguments to be passed to listeners
+
+// Returns nothing
+function fsWatchBroadcast(fullPath, type, val1, val2, val3) {
+ if (!FsWatchInstances[fullPath]) return;
+ FsWatchInstances[fullPath][type].forEach(function(listener) {
+ listener(val1, val2, val3);
+ });
+}
+
+// Private function: Instantiates the fs.watch interface or binds listeners
+// to an existing one covering the same file system entry
+
+// * path - string, path to be watched
+// * fullPath - string, absolute path
+// * options - object, options to be passed to fs.watch
+// * handlers - object, container for event listener functions
+
+// Returns close function
+function setFsWatchListener(path, fullPath, options, handlers) {
+ var listener = handlers.listener;
+ var errHandler = handlers.errHandler;
+ var rawEmitter = handlers.rawEmitter;
+ var container = FsWatchInstances[fullPath];
+ var watcher;
+ if (!options.persistent) {
+ watcher = createFsWatchInstance(
+ path, options, listener, errHandler, rawEmitter
+ );
+ return watcher.close.bind(watcher);
+ }
+ if (!container) {
+ watcher = createFsWatchInstance(
+ path,
+ options,
+ fsWatchBroadcast.bind(null, fullPath, 'listeners'),
+ errHandler, // no need to use broadcast here
+ fsWatchBroadcast.bind(null, fullPath, 'rawEmitters')
+ );
+ if (!watcher) return;
+ var broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers');
+ watcher.on('error', function(error) {
+ container.watcherUnusable = true; // documented since Node 10.4.1
+ // Workaround for https://github.com/joyent/node/issues/4337
+ if (process.platform === 'win32' && error.code === 'EPERM') {
+ fs.open(path, 'r', function(err, fd) {
+ if (!err) fs.close(fd, function(err) {
+ if (!err) broadcastErr(error);
+ });
+ });
+ } else {
+ broadcastErr(error);
+ }
+ });
+ container = FsWatchInstances[fullPath] = {
+ listeners: [listener],
+ errHandlers: [errHandler],
+ rawEmitters: [rawEmitter],
+ watcher: watcher
+ };
+ } else {
+ container.listeners.push(listener);
+ container.errHandlers.push(errHandler);
+ container.rawEmitters.push(rawEmitter);
+ }
+ var listenerIndex = container.listeners.length - 1;
+
+ // removes this instance's listeners and closes the underlying fs.watch
+ // instance if there are no more listeners left
+ return function close() {
+ delete container.listeners[listenerIndex];
+ delete container.errHandlers[listenerIndex];
+ delete container.rawEmitters[listenerIndex];
+ if (!Object.keys(container.listeners).length) {
+ if (!container.watcherUnusable) { // check to protect against issue #730
+ container.watcher.close();
+ }
+ delete FsWatchInstances[fullPath];
+ }
+ };
+}
+
+// fs.watchFile helpers
+
+// object to hold per-process fs.watchFile instances
+// (may be shared across chokidar FSWatcher instances)
+var FsWatchFileInstances = Object.create(null);
+
+// Private function: Instantiates the fs.watchFile interface or binds listeners
+// to an existing one covering the same file system entry
+
+// * path - string, path to be watched
+// * fullPath - string, absolute path
+// * options - object, options to be passed to fs.watchFile
+// * handlers - object, container for event listener functions
+
+// Returns close function
+function setFsWatchFileListener(path, fullPath, options, handlers) {
+ var listener = handlers.listener;
+ var rawEmitter = handlers.rawEmitter;
+ var container = FsWatchFileInstances[fullPath];
+ var listeners = [];
+ var rawEmitters = [];
+ if (
+ container && (
+ container.options.persistent < options.persistent ||
+ container.options.interval > options.interval
+ )
+ ) {
+ // "Upgrade" the watcher to persistence or a quicker interval.
+ // This creates some unlikely edge case issues if the user mixes
+ // settings in a very weird way, but solving for those cases
+ // doesn't seem worthwhile for the added complexity.
+ listeners = container.listeners;
+ rawEmitters = container.rawEmitters;
+ fs.unwatchFile(fullPath);
+ container = false;
+ }
+ if (!container) {
+ listeners.push(listener);
+ rawEmitters.push(rawEmitter);
+ container = FsWatchFileInstances[fullPath] = {
+ listeners: listeners,
+ rawEmitters: rawEmitters,
+ options: options,
+ watcher: fs.watchFile(fullPath, options, function(curr, prev) {
+ container.rawEmitters.forEach(function(rawEmitter) {
+ rawEmitter('change', fullPath, {curr: curr, prev: prev});
+ });
+ var currmtime = curr.mtime.getTime();
+ if (curr.size !== prev.size || currmtime > prev.mtime.getTime() || currmtime === 0) {
+ container.listeners.forEach(function(listener) {
+ listener(path, curr);
+ });
+ }
+ })
+ };
+ } else {
+ container.listeners.push(listener);
+ container.rawEmitters.push(rawEmitter);
+ }
+ var listenerIndex = container.listeners.length - 1;
+
+ // removes this instance's listeners and closes the underlying fs.watchFile
+ // instance if there are no more listeners left
+ return function close() {
+ delete container.listeners[listenerIndex];
+ delete container.rawEmitters[listenerIndex];
+ if (!Object.keys(container.listeners).length) {
+ fs.unwatchFile(fullPath);
+ delete FsWatchFileInstances[fullPath];
+ }
+ };
+}
+
+// fake constructor for attaching nodefs-specific prototype methods that
+// will be copied to FSWatcher's prototype
+function NodeFsHandler() {}
+
+// Private method: Watch file for changes with fs.watchFile or fs.watch.
+
+// * path - string, path to file or directory.
+// * listener - function, to be executed on fs change.
+
+// Returns close function for the watcher instance
+NodeFsHandler.prototype._watchWithNodeFs =
+function(path, listener) {
+ var directory = sysPath.dirname(path);
+ var basename = sysPath.basename(path);
+ var parent = this._getWatchedDir(directory);
+ parent.add(basename);
+ var absolutePath = sysPath.resolve(path);
+ var options = {persistent: this.options.persistent};
+ if (!listener) listener = Function.prototype; // empty function
+
+ var closer;
+ if (this.options.usePolling) {
+ options.interval = this.enableBinaryInterval && isBinaryPath(basename) ?
+ this.options.binaryInterval : this.options.interval;
+ closer = setFsWatchFileListener(path, absolutePath, options, {
+ listener: listener,
+ rawEmitter: this.emit.bind(this, 'raw')
+ });
+ } else {
+ closer = setFsWatchListener(path, absolutePath, options, {
+ listener: listener,
+ errHandler: this._handleError.bind(this),
+ rawEmitter: this.emit.bind(this, 'raw')
+ });
+ }
+ return closer;
+};
+
+// Private method: Watch a file and emit add event if warranted
+
+// * file - string, the file's path
+// * stats - object, result of fs.stat
+// * initialAdd - boolean, was the file added at watch instantiation?
+// * callback - function, called when done processing as a newly seen file
+
+// Returns close function for the watcher instance
+NodeFsHandler.prototype._handleFile =
+function(file, stats, initialAdd, callback) {
+ var dirname = sysPath.dirname(file);
+ var basename = sysPath.basename(file);
+ var parent = this._getWatchedDir(dirname);
+
+ // if the file is already being watched, do nothing
+ if (parent.has(basename)) return callback();
+
+ // kick off the watcher
+ var closer = this._watchWithNodeFs(file, function(path, newStats) {
+ if (!this._throttle('watch', file, 5)) return;
+ if (!newStats || newStats && newStats.mtime.getTime() === 0) {
+ fs.stat(file, function(error, newStats) {
+ // Fix issues where mtime is null but file is still present
+ if (error) {
+ this._remove(dirname, basename);
+ } else {
+ this._emit('change', file, newStats);
+ }
+ }.bind(this));
+ // add is about to be emitted if file not already tracked in parent
+ } else if (parent.has(basename)) {
+ this._emit('change', file, newStats);
+ }
+ }.bind(this));
+
+ // emit an add event if we're supposed to
+ if (!(initialAdd && this.options.ignoreInitial)) {
+ if (!this._throttle('add', file, 0)) return;
+ this._emit('add', file, stats);
+ }
+
+ if (callback) callback();
+ return closer;
+};
+
+// Private method: Handle symlinks encountered while reading a dir
+
+// * entry - object, entry object returned by readdirp
+// * directory - string, path of the directory being read
+// * path - string, path of this item
+// * item - string, basename of this item
+
+// Returns true if no more processing is needed for this entry.
+NodeFsHandler.prototype._handleSymlink =
+function(entry, directory, path, item) {
+ var full = entry.fullPath;
+ var dir = this._getWatchedDir(directory);
+
+ if (!this.options.followSymlinks) {
+ // watch symlink directly (don't follow) and detect changes
+ this._readyCount++;
+ fs.realpath(path, function(error, linkPath) {
+ if (dir.has(item)) {
+ if (this._symlinkPaths[full] !== linkPath) {
+ this._symlinkPaths[full] = linkPath;
+ this._emit('change', path, entry.stat);
+ }
+ } else {
+ dir.add(item);
+ this._symlinkPaths[full] = linkPath;
+ this._emit('add', path, entry.stat);
+ }
+ this._emitReady();
+ }.bind(this));
+ return true;
+ }
+
+ // don't follow the same symlink more than once
+ if (this._symlinkPaths[full]) return true;
+ else this._symlinkPaths[full] = true;
+};
+
+// Private method: Read directory to add / remove files from `@watched` list
+// and re-read it on change.
+
+// * dir - string, fs path.
+// * stats - object, result of fs.stat
+// * initialAdd - boolean, was the file added at watch instantiation?
+// * depth - int, depth relative to user-supplied path
+// * target - string, child path actually targeted for watch
+// * wh - object, common watch helpers for this path
+// * callback - function, called when dir scan is complete
+
+// Returns close function for the watcher instance
+NodeFsHandler.prototype._handleDir =
+function(dir, stats, initialAdd, depth, target, wh, callback) {
+ var parentDir = this._getWatchedDir(sysPath.dirname(dir));
+ var tracked = parentDir.has(sysPath.basename(dir));
+ if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) {
+ if (!wh.hasGlob || wh.globFilter(dir)) this._emit('addDir', dir, stats);
+ }
+
+ // ensure dir is tracked (harmless if redundant)
+ parentDir.add(sysPath.basename(dir));
+ this._getWatchedDir(dir);
+
+ var read = function(directory, initialAdd, done) {
+ // Normalize the directory name on Windows
+ directory = sysPath.join(directory, '');
+
+ if (!wh.hasGlob) {
+ var throttler = this._throttle('readdir', directory, 1000);
+ if (!throttler) return;
+ }
+
+ var previous = this._getWatchedDir(wh.path);
+ var current = [];
+
+ readdirp({
+ root: directory,
+ entryType: 'all',
+ fileFilter: wh.filterPath,
+ directoryFilter: wh.filterDir,
+ depth: 0,
+ lstat: true
+ }).on('data', function(entry) {
+ var item = entry.path;
+ var path = sysPath.join(directory, item);
+ current.push(item);
+
+ if (entry.stat.isSymbolicLink() &&
+ this._handleSymlink(entry, directory, path, item)) return;
+
+ // Files that present in current directory snapshot
+ // but absent in previous are added to watch list and
+ // emit `add` event.
+ if (item === target || !target && !previous.has(item)) {
+ this._readyCount++;
+
+ // ensure relativeness of path is preserved in case of watcher reuse
+ path = sysPath.join(dir, sysPath.relative(dir, path));
+
+ this._addToNodeFs(path, initialAdd, wh, depth + 1);
+ }
+ }.bind(this)).on('end', function() {
+ var wasThrottled = throttler ? throttler.clear() : false;
+ if (done) done();
+
+ // Files that absent in current directory snapshot
+ // but present in previous emit `remove` event
+ // and are removed from @watched[directory].
+ previous.children().filter(function(item) {
+ return item !== directory &&
+ current.indexOf(item) === -1 &&
+ // in case of intersecting globs;
+ // a path may have been filtered out of this readdir, but
+ // shouldn't be removed because it matches a different glob
+ (!wh.hasGlob || wh.filterPath({
+ fullPath: sysPath.resolve(directory, item)
+ }));
+ }).forEach(function(item) {
+ this._remove(directory, item);
+ }, this);
+
+ // one more time for any missed in case changes came in extremely quickly
+ if (wasThrottled) read(directory, false);
+ }.bind(this)).on('error', this._handleError.bind(this));
+ }.bind(this);
+
+ var closer;
+
+ if (this.options.depth == null || depth <= this.options.depth) {
+ if (!target) read(dir, initialAdd, callback);
+ closer = this._watchWithNodeFs(dir, function(dirPath, stats) {
+ // if current directory is removed, do nothing
+ if (stats && stats.mtime.getTime() === 0) return;
+
+ read(dirPath, false);
+ });
+ } else {
+ callback();
+ }
+ return closer;
+};
+
+// Private method: Handle added file, directory, or glob pattern.
+// Delegates call to _handleFile / _handleDir after checks.
+
+// * path - string, path to file or directory.
+// * initialAdd - boolean, was the file added at watch instantiation?
+// * depth - int, depth relative to user-supplied path
+// * target - string, child path actually targeted for watch
+// * callback - function, indicates whether the path was found or not
+
+// Returns nothing
+NodeFsHandler.prototype._addToNodeFs =
+function(path, initialAdd, priorWh, depth, target, callback) {
+ if (!callback) callback = Function.prototype;
+ var ready = this._emitReady;
+ if (this._isIgnored(path) || this.closed) {
+ ready();
+ return callback(null, false);
+ }
+
+ var wh = this._getWatchHelpers(path, depth);
+ if (!wh.hasGlob && priorWh) {
+ wh.hasGlob = priorWh.hasGlob;
+ wh.globFilter = priorWh.globFilter;
+ wh.filterPath = priorWh.filterPath;
+ wh.filterDir = priorWh.filterDir;
+ }
+
+ // evaluate what is at the path we're being asked to watch
+ fs[wh.statMethod](wh.watchPath, function(error, stats) {
+ if (this._handleError(error)) return callback(null, path);
+ if (this._isIgnored(wh.watchPath, stats)) {
+ ready();
+ return callback(null, false);
+ }
+
+ var initDir = function(dir, target) {
+ return this._handleDir(dir, stats, initialAdd, depth, target, wh, ready);
+ }.bind(this);
+
+ var closer;
+ if (stats.isDirectory()) {
+ closer = initDir(wh.watchPath, target);
+ } else if (stats.isSymbolicLink()) {
+ var parent = sysPath.dirname(wh.watchPath);
+ this._getWatchedDir(parent).add(wh.watchPath);
+ this._emit('add', wh.watchPath, stats);
+ closer = initDir(parent, path);
+
+ // preserve this symlink's target path
+ fs.realpath(path, function(error, targetPath) {
+ this._symlinkPaths[sysPath.resolve(path)] = targetPath;
+ ready();
+ }.bind(this));
+ } else {
+ closer = this._handleFile(wh.watchPath, stats, initialAdd, ready);
+ }
+
+ if (closer) this._closers[path] = closer;
+ callback(null, false);
+ }.bind(this));
+};
+
+module.exports = NodeFsHandler;