globwatcher
Version:
watch a set of files for changes (including create/delete) by glob patterns
625 lines (540 loc) • 18.4 kB
JavaScript
// Generated by CoffeeScript 1.6.2
(function() {
var FileWatcher, GlobWatcher, Q, WatchMap, events, folderMatchesMinimatchPrefix, fs, glob, makePromise, minimatch, path, util,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__slice = [].slice;
events = require('events');
fs = require('fs');
glob = require('glob');
minimatch = require('minimatch');
path = require('path');
Q = require('q');
util = require('util');
makePromise = require("./make_promise").makePromise;
FileWatcher = require("./filewatcher").FileWatcher;
folderMatchesMinimatchPrefix = function(folderSegments, minimatchSet) {
var i, miniSegment, segment, _i, _len;
for (i = _i = 0, _len = folderSegments.length; _i < _len; i = ++_i) {
segment = folderSegments[i];
if (i >= minimatchSet.length) {
return false;
}
miniSegment = minimatchSet[i];
if (miniSegment === minimatch.GLOBSTAR) {
return true;
}
if (typeof miniSegment === "string") {
if (miniSegment !== segment) {
return false;
}
} else {
if (!miniSegment.test(segment)) {
return false;
}
}
}
return true;
};
exports.folderMatchesMinimatchPrefix = folderMatchesMinimatchPrefix;
WatchMap = (function() {
function WatchMap(map) {
this.map = map != null ? map : {};
}
WatchMap.prototype.clear = function() {
return this.map = {};
};
WatchMap.prototype.watchFolder = function(folderName) {
var _base;
return (_base = this.map)[folderName] || (_base[folderName] = {});
};
WatchMap.prototype.unwatchFolder = function(folderName) {
return delete this.map[folderName];
};
WatchMap.prototype.watchFile = function(filename, parent) {
var _base;
return ((_base = this.map)[parent] || (_base[parent] = {}))[filename] = true;
};
WatchMap.prototype.unwatchFile = function(filename, parent) {
return delete this.map[parent][filename];
};
WatchMap.prototype.getFolders = function() {
return Object.keys(this.map);
};
WatchMap.prototype.getFilenames = function(folderName) {
return Object.keys(this.map[folderName] || {});
};
WatchMap.prototype.getAllFilenames = function() {
var folder, rv, _i, _len, _ref;
rv = [];
_ref = this.getFolders();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
folder = _ref[_i];
rv = rv.concat(this.getFilenames(folder));
}
return rv;
};
WatchMap.prototype.getNestedFolders = function(folderName) {
var f, _i, _len, _ref, _results;
_ref = this.getFolders();
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
f = _ref[_i];
if (f.slice(0, folderName.length) === folderName) {
_results.push(f);
}
}
return _results;
};
WatchMap.prototype.watchingFolder = function(folderName) {
return this.map[folderName] != null;
};
WatchMap.prototype.watchingFile = function(filename, parent) {
var _ref;
if (parent == null) {
parent = path.dirname(filename);
}
return ((_ref = this.map[parent]) != null ? _ref[filename] : void 0) != null;
};
WatchMap.prototype.toDebug = function() {
var filename, folder, out, _i, _j, _len, _len1, _ref, _ref1;
out = [];
_ref = Object.keys(this.map).sort();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
folder = _ref[_i];
out.push(folder);
_ref1 = Object.keys(this.map[folder]).sort();
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
filename = _ref1[_j];
out.push(" `- " + filename);
}
}
return out.join("\n") + "\n";
};
return WatchMap;
})();
exports.globwatcher = function(pattern, options) {
return new GlobWatcher(pattern, options);
};
GlobWatcher = (function(_super) {
__extends(GlobWatcher, _super);
function GlobWatcher(patterns, options) {
if (options == null) {
options = {};
}
this.closed = false;
this.cwd = options.cwd || process.cwd();
this.debounceInterval = options.debounceInterval || 10;
this.interval = options.interval || 250;
this.debug = options.debug || (function() {});
this.persistent = options.persistent || false;
this.watchMap = new WatchMap;
this.fileWatcher = new FileWatcher(options);
this.watchers = {};
this.patterns = [];
this.minimatchSets = [];
this.checkQueue = {};
if (typeof patterns === "string") {
patterns = [patterns];
}
this.originalPatterns = patterns;
if (options.snapshot) {
this.restoreFrom(options.snapshot, patterns);
} else {
this.add.apply(this, patterns);
}
}
GlobWatcher.prototype.add = function() {
var p, patterns,
_this = this;
patterns = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
this.debug("add: " + (util.inspect(patterns)));
this.originalPatterns = this.originalPatterns.concat(patterns);
this.addPatterns(patterns);
return this.ready = Q.all((function() {
var _i, _len, _ref, _results,
_this = this;
_ref = this.patterns;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
p = _ref[_i];
_results.push(makePromise(glob)(p, {
nonegate: true
}).then(function(files) {
var filename, _j, _len1, _results1;
_results1 = [];
for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
filename = files[_j];
_results1.push(_this.addWatch(filename));
}
return _results1;
}));
}
return _results;
}).call(this)).then(function() {
_this.stopWatches();
_this.startWatches();
return Q.delay(_this.debounceInterval);
}).then(function() {
_this.debug("add complete: " + (util.inspect(patterns)));
return _this;
});
};
GlobWatcher.prototype.close = function() {
this.debug("close");
this.stopWatches();
this.watchMap.clear();
return this.closed = true;
};
GlobWatcher.prototype.check = function() {
var folders,
_this = this;
this.debug("-> check");
folders = Object.keys(this.watchers).map(function(folderName) {
return _this.folderChanged(folderName);
});
return Q.all([this.fileWatcher.check()].concat(folders)).then(function() {
return _this.debug("<- check");
});
};
GlobWatcher.prototype.currentSet = function() {
return this.watchMap.getAllFilenames();
};
GlobWatcher.prototype.snapshot = function() {
var filename, state, w, _i, _len, _ref;
state = {};
_ref = this.watchMap.getAllFilenames();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filename = _ref[_i];
w = this.fileWatcher.watchFor(filename);
if (w != null) {
state[filename] = {
mtime: w.mtime,
size: w.size
};
}
}
return state;
};
GlobWatcher.prototype.restoreFrom = function(state, patterns) {
var filename, folderName, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2,
_this = this;
this.addPatterns(patterns);
_ref = Object.keys(state);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filename = _ref[_i];
folderName = path.dirname(filename);
if (folderName !== "/") {
folderName += "/";
}
this.watchMap.watchFile(filename, folderName);
}
_ref1 = this.watchMap.getFolders();
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
folderName = _ref1[_j];
this.watchFolder(folderName);
}
_ref2 = Object.keys(state);
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
filename = _ref2[_k];
this.watchFile(filename, state[filename].mtime, state[filename].size);
}
return this.ready = Q.delay(this.debounceInterval).then(function() {
_this.debug("restore complete: " + (util.inspect(patterns)));
return _this.check();
}).then(function() {
return _this;
});
};
GlobWatcher.prototype.addPatterns = function(patterns) {
var p, set, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _results;
for (_i = 0, _len = patterns.length; _i < _len; _i++) {
p = patterns[_i];
p = this.absolutePath(p);
if (this.patterns.indexOf(p) < 0) {
this.patterns.push(p);
}
}
this.minimatchSets = [];
_ref = this.patterns;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
p = _ref[_j];
this.minimatchSets = this.minimatchSets.concat(new minimatch.Minimatch(p, {
nonegate: true
}).set);
}
_ref1 = this.minimatchSets;
_results = [];
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
set = _ref1[_k];
_results.push(this.watchPrefix(set));
}
return _results;
};
GlobWatcher.prototype.watchPrefix = function(minimatchSet) {
var index, parent, prefix;
index = 0;
while (index < minimatchSet.length && typeof minimatchSet[index] === "string") {
index += 1;
}
prefix = path.join.apply(path, ["/"].concat(__slice.call(minimatchSet.slice(0, index))));
parent = path.dirname(prefix);
while (!fs.existsSync(prefix) && parent !== path.dirname(parent)) {
prefix = path.dirname(prefix);
parent = path.dirname(parent);
}
if (fs.existsSync(prefix)) {
return this.watchMap.watchFolder(prefix + "/");
}
};
GlobWatcher.prototype.absolutePath = function(p) {
if (p[0] === '/') {
return p;
} else {
return path.join(this.cwd, p);
}
};
GlobWatcher.prototype.isMatch = function(filename) {
var p, _i, _len, _ref;
_ref = this.patterns;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
p = _ref[_i];
if (minimatch(filename, p, {
nonegate: true
})) {
return true;
}
}
return false;
};
GlobWatcher.prototype.addWatch = function(filename) {
var e, isdir, parent;
isdir = (function() {
try {
return fs.statSync(filename).isDirectory();
} catch (_error) {
e = _error;
return false;
}
})();
if (isdir) {
filename += "/";
return this.watchMap.watchFolder(filename);
} else {
parent = path.dirname(filename);
if (parent !== "/") {
parent += "/";
}
return this.watchMap.watchFile(filename, parent);
}
};
GlobWatcher.prototype.stopWatches = function() {
var filename, folderName, watcher, _i, _j, _len, _len1, _ref, _ref1, _ref2;
_ref = this.watchers;
for (filename in _ref) {
watcher = _ref[filename];
watcher.close();
}
_ref1 = this.watchMap.getFolders();
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
folderName = _ref1[_i];
_ref2 = this.watchMap.getFilenames(folderName);
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
filename = _ref2[_j];
this.fileWatcher.unwatch(filename);
}
}
return this.watchers = {};
};
GlobWatcher.prototype.startWatches = function() {
var filename, folderName, _i, _len, _ref, _results;
_ref = this.watchMap.getFolders();
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
folderName = _ref[_i];
this.watchFolder(folderName);
_results.push((function() {
var _j, _len1, _ref1, _results1;
_ref1 = this.watchMap.getFilenames(folderName);
_results1 = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
filename = _ref1[_j];
if (filename[filename.length - 1] !== "/") {
_results1.push(this.watchFile(filename));
} else {
_results1.push(void 0);
}
}
return _results1;
}).call(this));
}
return _results;
};
GlobWatcher.prototype.watchFolder = function(folderName) {
var e,
_this = this;
this.debug("watch: " + folderName);
try {
return this.watchers[folderName] = fs.watch(folderName, {
persistent: this.persistent
}, function(event) {
_this.debug("watch event: " + folderName);
_this.checkQueue[folderName] = true;
return setTimeout((function() {
return _this.scanQueue();
}), _this.debounceInterval);
});
} catch (_error) {
e = _error;
}
};
GlobWatcher.prototype.scanQueue = function() {
var f, folders, _i, _len, _results;
folders = Object.keys(this.checkQueue);
this.checkQueue = {};
_results = [];
for (_i = 0, _len = folders.length; _i < _len; _i++) {
f = folders[_i];
_results.push(this.folderChanged(f));
}
return _results;
};
GlobWatcher.prototype.watchFile = function(filename, mtime, size) {
var _this = this;
if (mtime == null) {
mtime = null;
}
if (size == null) {
size = null;
}
this.debug("watchFile: " + filename);
return this.fileWatcher.watch(filename, mtime, size).on('changed', function() {
_this.debug("watchFile event: " + filename);
return _this.emit('changed', filename);
});
};
GlobWatcher.prototype.folderChanged = function(folderName) {
var _this = this;
this.debug("-> check folder: " + folderName);
if (this.closed) {
return;
}
return makePromise(fs.readdir)(folderName).fail(function(error) {
_this.debug(" ERR: " + error);
return [];
}).then(function(current) {
var f, previous, _i, _len, _ref;
if (_this.closed) {
return;
}
current = current.map(function(filename) {
var e;
filename = path.join(folderName, filename);
try {
if (fs.statSync(filename).isDirectory()) {
filename += "/";
}
} catch (_error) {
e = _error;
}
return filename;
});
previous = _this.watchMap.getFilenames(folderName);
_ref = previous.filter(function(x) {
return current.indexOf(x) < 0;
});
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
f = _ref[_i];
if (f[f.length - 1] === '/') {
_this.folderDeleted(f);
} else {
_this.fileDeleted(f);
}
}
return Q.all((function() {
var _j, _len1, _ref1, _results;
_ref1 = current.filter(function(x) {
return previous.indexOf(x) < 0;
});
_results = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
f = _ref1[_j];
if (f[f.length - 1] === '/') {
_results.push(this.folderAdded(f));
} else {
_results.push(this.fileAdded(f, folderName));
}
}
return _results;
}).call(_this));
}).then(function() {
return _this.debug("<- check folder: " + folderName);
});
};
GlobWatcher.prototype.fileDeleted = function(filename) {
var parent;
this.debug("file deleted: " + filename);
parent = path.dirname(filename);
if (parent !== "/") {
parent += "/";
}
if (this.watchMap.watchingFile(filename, parent)) {
fs.unwatchFile(filename);
this.watchMap.unwatchFile(filename, parent);
}
return this.emit('deleted', filename);
};
GlobWatcher.prototype.folderDeleted = function(folderName) {
var filename, folder, _i, _j, _len, _len1, _ref, _ref1, _results;
this.debug("folder deleted: " + folderName);
_ref = this.watchMap.getNestedFolders(folderName);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
folder = _ref[_i];
_ref1 = this.watchMap.getFilenames(folder);
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
filename = _ref1[_j];
this.fileDeleted(filename);
}
if (this.watchers[folder]) {
this.watchers[folder].close();
delete this.watchers[folder];
}
_results.push(this.watchMap.unwatchFolder(folder));
}
return _results;
};
GlobWatcher.prototype.fileAdded = function(filename, folderName) {
if (!this.isMatch(filename)) {
return;
}
this.debug("file added: " + filename);
this.watchMap.watchFile(filename, folderName);
this.watchFile(filename);
this.emit('added', filename);
return Q(null);
};
GlobWatcher.prototype.folderAdded = function(folderName) {
if (!this.folderIsInteresting(folderName)) {
return null;
}
this.debug("folder added: " + folderName);
this.watchMap.watchFolder(folderName);
this.watchFolder(folderName);
return this.folderChanged(folderName);
};
GlobWatcher.prototype.folderIsInteresting = function(folderName) {
var folderSegments, set, _i, _len, _ref;
folderSegments = folderName.split("/").slice(0, -1);
_ref = this.minimatchSets;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
set = _ref[_i];
if (folderMatchesMinimatchPrefix(folderSegments, set)) {
return true;
}
}
return false;
};
return GlobWatcher;
})(events.EventEmitter);
}).call(this);