Files
archived-nodemon/lib/monitor/watch.js

246 lines
7.7 KiB
JavaScript

'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();
if (config.system.useFind) {
// let's do a sanity check for the amount of files we'll be watching for...
var cmds = [];
config.dirs.forEach(function(dir) {
cmds.push('find -L "' + dir + '" -type f ' + ignoredFileTypesForFind(dir) + ' | wc');
});
exec(cmds.join(';'), function (error, stdout) {
var total = 0;
stdout.split(/\n/).map(function (line) {
total += line.trim().split(/\s+/)[0] * 1;
});
if (total > 25000) {
utils.log.fail('watching ' + (total).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' files - this might cause high cpu usage. To reduce use "--watch".');
}
});
// 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 ' + ignoredFileTypesForFind(dir) + ' -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 ignoredFileTypesForFind(dir) {
// search within dir for ignored files and paths
if (!config.options.ignore.length) {
return '';
}
var paths = [];
if (dir.charAt(dir.length - 1) != '/') {
dir += '/';
}
config.options.ignore.forEach(function (path) {
var pathIsDir = (path.charAt(path.length - 1) == '/');
if (pathIsDir) {
paths.push('! -ipath "' + dir + path + '*"');
}
paths.push('! -ipath "' + dir + path + '"');
});
return ' \\( ' + paths.join(' -and ') + ' \\)';
}
function filterAndRestart(files) {
if (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;
}
}
if (config.system.useFind || config.options.legacyWatch) {
if (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
if ((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);
}
});
}
};