diff options
Diffstat (limited to 'node_modules/chokidar/lib')
-rw-r--r-- | node_modules/chokidar/lib/fsevents-handler.js | 405 | ||||
-rw-r--r-- | node_modules/chokidar/lib/nodefs-handler.js | 488 |
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; |