| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 | 
							- // vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
 
- 'use strict';
 
- const EventEmitter = require('events').EventEmitter;
 
- const fs = require('fs');
 
- const platform = require('os').platform();
 
- const path = require('path');
 
- const common = require('./common');
 
- /**
 
-  * Constants
 
-  */
 
- const DEFAULT_DELAY = common.DEFAULT_DELAY;
 
- const CHANGE_EVENT = common.CHANGE_EVENT;
 
- const DELETE_EVENT = common.DELETE_EVENT;
 
- const ADD_EVENT = common.ADD_EVENT;
 
- const ALL_EVENT = common.ALL_EVENT;
 
- /**
 
-  * Export `NodeWatcher` class.
 
-  * Watches `dir`.
 
-  *
 
-  * @class NodeWatcher
 
-  * @param {String} dir
 
-  * @param {Object} opts
 
-  * @public
 
-  */
 
- module.exports = class NodeWatcher extends EventEmitter {
 
-   constructor(dir, opts) {
 
-     super();
 
-     common.assignOptions(this, opts);
 
-     this.watched = Object.create(null);
 
-     this.changeTimers = Object.create(null);
 
-     this.dirRegistery = Object.create(null);
 
-     this.root = path.resolve(dir);
 
-     this.watchdir = this.watchdir.bind(this);
 
-     this.register = this.register.bind(this);
 
-     this.checkedEmitError = this.checkedEmitError.bind(this);
 
-     this.watchdir(this.root);
 
-     common.recReaddir(
 
-       this.root,
 
-       this.watchdir,
 
-       this.register,
 
-       this.emit.bind(this, 'ready'),
 
-       this.checkedEmitError,
 
-       this.ignored
 
-     );
 
-   }
 
-   /**
 
-    * Register files that matches our globs to know what to type of event to
 
-    * emit in the future.
 
-    *
 
-    * Registery looks like the following:
 
-    *
 
-    *  dirRegister => Map {
 
-    *    dirpath => Map {
 
-    *       filename => true
 
-    *    }
 
-    *  }
 
-    *
 
-    * @param {string} filepath
 
-    * @return {boolean} whether or not we have registered the file.
 
-    * @private
 
-    */
 
-   register(filepath) {
 
-     const relativePath = path.relative(this.root, filepath);
 
-     if (
 
-       !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
 
-     ) {
 
-       return false;
 
-     }
 
-     const dir = path.dirname(filepath);
 
-     if (!this.dirRegistery[dir]) {
 
-       this.dirRegistery[dir] = Object.create(null);
 
-     }
 
-     const filename = path.basename(filepath);
 
-     this.dirRegistery[dir][filename] = true;
 
-     return true;
 
-   }
 
-   /**
 
-    * Removes a file from the registery.
 
-    *
 
-    * @param {string} filepath
 
-    * @private
 
-    */
 
-   unregister(filepath) {
 
-     const dir = path.dirname(filepath);
 
-     if (this.dirRegistery[dir]) {
 
-       const filename = path.basename(filepath);
 
-       delete this.dirRegistery[dir][filename];
 
-     }
 
-   }
 
-   /**
 
-    * Removes a dir from the registery.
 
-    *
 
-    * @param {string} dirpath
 
-    * @private
 
-    */
 
-   unregisterDir(dirpath) {
 
-     if (this.dirRegistery[dirpath]) {
 
-       delete this.dirRegistery[dirpath];
 
-     }
 
-   }
 
-   /**
 
-    * Checks if a file or directory exists in the registery.
 
-    *
 
-    * @param {string} fullpath
 
-    * @return {boolean}
 
-    * @private
 
-    */
 
-   registered(fullpath) {
 
-     const dir = path.dirname(fullpath);
 
-     return (
 
-       this.dirRegistery[fullpath] ||
 
-       (this.dirRegistery[dir] &&
 
-         this.dirRegistery[dir][path.basename(fullpath)])
 
-     );
 
-   }
 
-   /**
 
-    * Emit "error" event if it's not an ignorable event
 
-    *
 
-    * @param error
 
-    * @private
 
-    */
 
-   checkedEmitError(error) {
 
-     if (!isIgnorableFileError(error)) {
 
-       this.emit('error', error);
 
-     }
 
-   }
 
-   /**
 
-    * Watch a directory.
 
-    *
 
-    * @param {string} dir
 
-    * @private
 
-    */
 
-   watchdir(dir) {
 
-     if (this.watched[dir]) {
 
-       return;
 
-     }
 
-     const watcher = fs.watch(
 
-       dir,
 
-       {
 
-         persistent: true
 
-       },
 
-       this.normalizeChange.bind(this, dir)
 
-     );
 
-     this.watched[dir] = watcher;
 
-     watcher.on('error', this.checkedEmitError);
 
-     if (this.root !== dir) {
 
-       this.register(dir);
 
-     }
 
-   }
 
-   /**
 
-    * Stop watching a directory.
 
-    *
 
-    * @param {string} dir
 
-    * @private
 
-    */
 
-   stopWatching(dir) {
 
-     if (this.watched[dir]) {
 
-       this.watched[dir].close();
 
-       delete this.watched[dir];
 
-     }
 
-   }
 
-   /**
 
-    * End watching.
 
-    *
 
-    * @public
 
-    */
 
-   close() {
 
-     Object.keys(this.watched).forEach(this.stopWatching, this);
 
-     this.removeAllListeners();
 
-     return Promise.resolve();
 
-   }
 
-   /**
 
-    * On some platforms, as pointed out on the fs docs (most likely just win32)
 
-    * the file argument might be missing from the fs event. Try to detect what
 
-    * change by detecting if something was deleted or the most recent file change.
 
-    *
 
-    * @param {string} dir
 
-    * @param {string} event
 
-    * @param {string} file
 
-    * @public
 
-    */
 
-   detectChangedFile(dir, event, callback) {
 
-     if (!this.dirRegistery[dir]) {
 
-       return;
 
-     }
 
-     let found = false;
 
-     let closest = {
 
-       mtime: 0
 
-     };
 
-     let c = 0;
 
-     Object.keys(this.dirRegistery[dir]).forEach(function (file, i, arr) {
 
-       fs.lstat(path.join(dir, file), (error, stat) => {
 
-         if (found) {
 
-           return;
 
-         }
 
-         if (error) {
 
-           if (isIgnorableFileError(error)) {
 
-             found = true;
 
-             callback(file);
 
-           } else {
 
-             this.emit('error', error);
 
-           }
 
-         } else {
 
-           if (stat.mtime > closest.mtime) {
 
-             stat.file = file;
 
-             closest = stat;
 
-           }
 
-           if (arr.length === ++c) {
 
-             callback(closest.file);
 
-           }
 
-         }
 
-       });
 
-     }, this);
 
-   }
 
-   /**
 
-    * Normalize fs events and pass it on to be processed.
 
-    *
 
-    * @param {string} dir
 
-    * @param {string} event
 
-    * @param {string} file
 
-    * @public
 
-    */
 
-   normalizeChange(dir, event, file) {
 
-     if (!file) {
 
-       this.detectChangedFile(dir, event, actualFile => {
 
-         if (actualFile) {
 
-           this.processChange(dir, event, actualFile);
 
-         }
 
-       });
 
-     } else {
 
-       this.processChange(dir, event, path.normalize(file));
 
-     }
 
-   }
 
-   /**
 
-    * Process changes.
 
-    *
 
-    * @param {string} dir
 
-    * @param {string} event
 
-    * @param {string} file
 
-    * @public
 
-    */
 
-   processChange(dir, event, file) {
 
-     const fullPath = path.join(dir, file);
 
-     const relativePath = path.join(path.relative(this.root, dir), file);
 
-     fs.lstat(fullPath, (error, stat) => {
 
-       if (error && error.code !== 'ENOENT') {
 
-         this.emit('error', error);
 
-       } else if (!error && stat.isDirectory()) {
 
-         // win32 emits usless change events on dirs.
 
-         if (event !== 'change') {
 
-           this.watchdir(fullPath);
 
-           if (
 
-             common.isFileIncluded(
 
-               this.globs,
 
-               this.dot,
 
-               this.doIgnore,
 
-               relativePath
 
-             )
 
-           ) {
 
-             this.emitEvent(ADD_EVENT, relativePath, stat);
 
-           }
 
-         }
 
-       } else {
 
-         const registered = this.registered(fullPath);
 
-         if (error && error.code === 'ENOENT') {
 
-           this.unregister(fullPath);
 
-           this.stopWatching(fullPath);
 
-           this.unregisterDir(fullPath);
 
-           if (registered) {
 
-             this.emitEvent(DELETE_EVENT, relativePath);
 
-           }
 
-         } else if (registered) {
 
-           this.emitEvent(CHANGE_EVENT, relativePath, stat);
 
-         } else {
 
-           if (this.register(fullPath)) {
 
-             this.emitEvent(ADD_EVENT, relativePath, stat);
 
-           }
 
-         }
 
-       }
 
-     });
 
-   }
 
-   /**
 
-    * Triggers a 'change' event after debounding it to take care of duplicate
 
-    * events on os x.
 
-    *
 
-    * @private
 
-    */
 
-   emitEvent(type, file, stat) {
 
-     const key = type + '-' + file;
 
-     const addKey = ADD_EVENT + '-' + file;
 
-     if (type === CHANGE_EVENT && this.changeTimers[addKey]) {
 
-       // Ignore the change event that is immediately fired after an add event.
 
-       // (This happens on Linux).
 
-       return;
 
-     }
 
-     clearTimeout(this.changeTimers[key]);
 
-     this.changeTimers[key] = setTimeout(() => {
 
-       delete this.changeTimers[key];
 
-       if (type === ADD_EVENT && stat.isDirectory()) {
 
-         // Recursively emit add events and watch for sub-files/folders
 
-         common.recReaddir(
 
-           path.resolve(this.root, file),
 
-           function emitAddDir(dir, stats) {
 
-             this.watchdir(dir);
 
-             this.rawEmitEvent(ADD_EVENT, path.relative(this.root, dir), stats);
 
-           }.bind(this),
 
-           function emitAddFile(file, stats) {
 
-             this.register(file);
 
-             this.rawEmitEvent(ADD_EVENT, path.relative(this.root, file), stats);
 
-           }.bind(this),
 
-           function endCallback() {},
 
-           this.checkedEmitError,
 
-           this.ignored
 
-         );
 
-       } else {
 
-         this.rawEmitEvent(type, file, stat);
 
-       }
 
-     }, DEFAULT_DELAY);
 
-   }
 
-   /**
 
-    * Actually emit the events
 
-    */
 
-   rawEmitEvent(type, file, stat) {
 
-     this.emit(type, file, this.root, stat);
 
-     this.emit(ALL_EVENT, type, file, this.root, stat);
 
-   }
 
- };
 
- /**
 
-  * Determine if a given FS error can be ignored
 
-  *
 
-  * @private
 
-  */
 
- function isIgnorableFileError(error) {
 
-   return (
 
-     error.code === 'ENOENT' || // Workaround Windows node issue #4337.
 
-     (error.code === 'EPERM' && platform === 'win32')
 
-   );
 
- }
 
 
  |