diff options
| author | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 | 
|---|---|---|
| committer | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 | 
| commit | de98e0b232509d5f40c135d540a70e415272ff85 (patch) | |
| tree | a79222a5b58484ab3b80d18efcaaa7ccc4769b33 /node_modules/chokidar/lib | |
| parent | e0c9d480a73fa629c1e4a47d3e721f1d2d345406 (diff) | |
node_modules
Diffstat (limited to 'node_modules/chokidar/lib')
| -rw-r--r-- | node_modules/chokidar/lib/fsevents-handler.js | 396 | ||||
| -rw-r--r-- | node_modules/chokidar/lib/nodefs-handler.js | 481 | 
2 files changed, 877 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 000000000..dbf6adeed --- /dev/null +++ b/node_modules/chokidar/lib/fsevents-handler.js @@ -0,0 +1,396 @@ +'use strict'; + +var fs = require('fs'); +var sysPath = require('path'); +var readdirp = require('readdirp'); +var fsevents; +try { fsevents = require('fsevents'); } catch (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 (fd) fs.close(fd); +        error && error.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) { +      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 000000000..9d05b6fcb --- /dev/null +++ b/node_modules/chokidar/lib/nodefs-handler.js @@ -0,0 +1,481 @@ +'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) { +      // Workaround for https://github.com/joyent/node/issues/4337 +      if (process.platform === 'win32' && error.code === 'EPERM') { +        fs.open(path, 'r', function(err, fd) { +          if (fd) fs.close(fd); +          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) { +      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() { +      if (throttler) throttler.clear(); +      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); +    }.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;  | 
