You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							503 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
	
	
							503 lines
						
					
					
						
							17 KiB
						
					
					
				'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 occurring 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); | 
						|
  // stats is always present | 
						|
  var prevStats = stats; | 
						|
 | 
						|
  // 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 { | 
						|
          // Check that change event was not fired because of changed only accessTime. | 
						|
          var at = newStats.atime.getTime(); | 
						|
          var mt = newStats.mtime.getTime(); | 
						|
          if (!at || at <= mt || mt !== prevStats.mtime.getTime()) { | 
						|
            this._emit('change', file, newStats); | 
						|
          } | 
						|
          prevStats = newStats; | 
						|
        } | 
						|
      }.bind(this)); | 
						|
    // add is about to be emitted if file not already tracked in parent | 
						|
    } else if (parent.has(basename)) { | 
						|
      // Check that change event was not fired because of changed only accessTime. | 
						|
      var at = newStats.atime.getTime(); | 
						|
      var mt = newStats.mtime.getTime(); | 
						|
      if (!at || at <= mt ||  mt !== prevStats.mtime.getTime()) { | 
						|
        this._emit('change', file, newStats); | 
						|
      } | 
						|
      prevStats = 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;
 | 
						|
 |