| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 | 
							- '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) {
 
-       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;
 
 
  |