Code coverage report for lib/monitor/watch.js

Statements: 31.58% (30 / 95)      Branches: 14.93% (10 / 67)      Functions: 38.1% (8 / 21)      Lines: 32.26% (30 / 93)     

All files » lib/monitor/ » watch.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 2091 1                         1       1       5 5             5     1 5   5     5 24   24 24     24 24 24 24                   1                                                                                                                                         1                                       1 22                                                                   22 22 22         1     24 24 24 22                        
'use strict';
var fs = require('fs'),
    path = require('path'),
    changedSince = require('./changed-since'),
    utils = require('../utils'),
    bus = utils.bus,
    match = require('./match'),
    config = require('../config'),
    childProcess = require('child_process'),
    exec = childProcess.exec,
    restartTimer = null,
    watched = [],
    watchers = [];
 
var changeFunction = function () {
  utils.log.error('changeFunction called when it shouldn\'t be.');
};
 
function reset() {
  // manually remove all the file watchers.
  // note that fs.unwatchFile doesn't really work, particularly in vagrant
  // shared folders and in Travis CI...
  var watcher;
  while (watchers.length) {
    watcher = watchers.pop();
    watcher.removeAllListeners('change');
    watcher.close();
  }
 
  // reset the watched directory too
  watched.length = 0;
}
 
bus.on('config:update', function () {
  reset();
 
  Eif (config.system.useFind) {
    // if native fs.watch doesn't work the way we want, we keep polling find
    // command (mac only oddly)
    changeFunction = function (lastStarted, callback) {
      var cmds = [];
 
      config.dirs.forEach(function(dir) {
        cmds.push('find -L "' + dir + '" -type f -mtime -' + ((Date.now() - config.lastStarted)/1000|0) + 's -print');
      });
 
      exec(cmds.join(';'), function (error, stdout) {
        var files = stdout.split(/\n/);
        files.pop(); // remove blank line ending and split
        callback(files);
      });
    };
  } else if (config.system.useWatch || config.system.useWatchFile) {
    bus.once('quit', reset);
 
    var watchFile = config.system.useWatch === false && (config.system.useWatchFile || config.options.legacyWatch),
        watchMethod = watchFile ? 'watchFile' : 'watch';
    changeFunction = function (lastStarted, callback) {
      // recursive watch - watch each directory and it's subdirectories, etc, etc
      function watch(err, dir) {
        try {
          if (watched.indexOf(dir) === -1 && ignoredFilter(dir)) {
            var watcher = fs[watchMethod](dir, { persistent: false }, function (event, filename) {
 
              var filepath;
 
              if (typeof filename === 'string') {
                filepath = path.join(dir, filename || '');
              } else { // was called from watchFile
                filepath = dir;
              }
 
              callback([filepath]);
            });
            watched.push(dir);
            watchers.push(watcher);
          }
 
          fs.readdir(dir, function (err, files) {
            if (err) { return; }
 
            files.forEach(function (rawfile) {
              var file = path.join(dir, rawfile);
              if (watched.indexOf(file) === -1) {
                fs.stat(file, function (err, stat) {
                  if (err || !stat) { return; }
 
                  // if we're using fs.watch, then watch directories
                  if (!watchFile && stat.isDirectory()) {
                    // recursive call to watch()
                    fs.realpath(file, watch);
                  } else {
                    // if we're in legacy mode, i.e. Vagrant + editing over
                    // shared drive, then watch the individual file
                    if (watchFile) {
                      fs.realpath(file, watch);
                    } else if (ignoredFilter(file)) {
                      watched.push(file);
                    }
                  }
                });
              }
            });
          });
        } catch (e) {
          if ('EMFILE' === e.code) {
            utils.log.error('EMFILE: Watching too many files.');
          }
          // ignoring this directory, likely it's "My Music"
          // or some such windows fangled stuff
        }
      }
 
 
      config.dirs.forEach(function (dir) {
        fs.realpath(dir, watch);
      });
    };
  } else {
    // changedSince, the fallback for when both the find method and fs.watch
    // don't work, is not compatible with the way changeFunction works. If we
    // have reached this point, changeFunction should not be called from herein
    // out.
    utils.log.error('No clean method to watch files with - please report\nto http://github.com/remy/nodemon/issues/new with `nodemon --dump`');
  }
});
 
// filter ignored files
function ignoredFilter(file) {
  if (config.options.ignore.length && config.options.ignore.re) {
    // If we are in a Windows machine
    if (utils.isWindows) {
      // Break up the file by slashes
      var fileParts = file.split(/\\/g);
 
      // Remove the first piece (C:)
      fileParts.shift();
 
      // Join the parts together with Unix slashes
      file = '/' + fileParts.join('/');
    }
 
    return !config.options.ignore.re.test(file);
  } else {
    return true;
  }
}
 
function filterAndRestart(files) {
  Iif (files.length) {
    var cwd = process.cwd();
    utils.log.detail('files triggering change check: ' + files.map(function (file) {
      return path.relative(cwd, file);
    }).join(', '));
 
    var matched = match(files, config.options.monitor, config.options.execOptions.ext);
 
    utils.log.detail('changes after filters (before/after): ' + [files.length, matched.result.length].join('/'));
 
    // reset the last check so we're only looking at recently modified files
    config.lastStarted = Date.now();
 
    if (matched.result.length) {
      if (restartTimer !== null) {
        clearTimeout(restartTimer);
      }
      restartTimer = setTimeout(function () {
        utils.log.status('restarting due to changes...');
        matched.result.map(function (file) {
          utils.log.detail(path.relative(process.cwd(), file));
        });
 
        if (config.options.verbose) {
          utils.log._log('');
        }
 
        bus.emit('restart', matched.result);
 
      }, config.options.delay);
      return;
    }
  }
 
  Eif (config.system.useFind || config.options.legacyWatch) {
    Eif (config.run) {
      setTimeout(watch, config.timeout);
    }
  }
}
 
var watch = module.exports = function () {
  // if we have useFind or useWatch (i.e. not using `find`)
  // then called `changeFunction` which is local to this script
  Eif ((config.system.useFind || config.system.useWatch || config.system.useWatchFile) && !config.options.legacyWatch) {
    changeFunction(config.lastStarted, function (files) {
      if (config.run) {
        filterAndRestart(files);
      }
    });
  } else {
    // Fallback for when both find and fs.watch don't work
    // using the `changedSince` which is external
    changedSince(config.lastStarted, function (files) {
      if (config.run) {
        filterAndRestart(files);
      }
    });
  }
};