UNPKG

dirwatcher

Version:

recursively watch a directory for modifications

174 lines (141 loc) 3.9 kB
var events = require('events') var path = require('path') var util = require('util') var debounce = require('debounce') var filewatcher = require('filewatcher') var find = require('findit') var minimatch = require('minimatch') var statdir = require('statdir') var EventEmitter = events.EventEmitter module.exports = function(dir, opts) { return new DirWatcher(dir, opts) } module.exports.filewatcher = filewatcher function match(p) { if (typeof p == 'string') p = minimatch.makeRe(p) if (p instanceof RegExp) { var re = p p = function(f) { return re.test(path.basename(f)) } } return p } function filesOnly(f, stat) { return stat.isFile() } function DirWatcher(root, opts) { var self = this // list of directory snapshots this.snapshots = {} EventEmitter.call(this) if (!opts || typeof opts == 'string' || typeof opts == 'function' || opts instanceof RegExp) opts = { include: opts } this.match = match(opts.include) || filesOnly var skip = match(opts.skip) // watcher to watch each dir var watcher = this.watcher = filewatcher({ fallback: false }) watcher.on('error', function(err) { self.emit('error', err) }) var steady = debounce(function() { self.emit('steady') }, 10) /** * Emit an event of the given type for each file that matches the * configured pattern. */ function emit(files, type) { self.filter(files).forEach(function(f) { self.emit(type, f.path, f.stat) }) steady() } watcher.on('change', function(dir, stat) { var snap if (stat.deleted) { var snap = self.snapshots[dir] if (snap) { delete self.snapshots[dir] watcher.remove(dir) emit(snap, 'removed') } return } statdir(dir, function(err, stats) { if (err) { if (err.code != 'ENOENT') self.emit('error', err) return } snap = self.snapshots[dir] if (!snap) return var diff = statdir.diff(snap, stats) self.snapshots[dir] = stats diff.added.forEach(function(f) { // if a directory was added add it to the watch list if (f.stat.isDirectory()) { if (!skip || !skip(f.path, f.stat)) add(f.path) } }) diff.removed.forEach(function(f) { var s = self.snapshots[f] if (s) { // if a directory was removed emit remove events for each file delete self.snapshots[f] emit(s, 'removed') } }) for (var type in diff) emit(diff[type], type) }) }) function add(dir) { var end var pending = 0 function readyCheck() { if (end && !pending) self.emit('ready') steady() } var finder = find(dir || root) finder.on('directory', function(d, stat, stop) { // skip this directory? if (d != root && skip && skip(d, stat)) return stop() pending++ statdir(d, function(err, stats) { pending-- if (err) return self.emit('error', err) if (dir) emit(stats, 'added') self.snapshots[d] = stats watcher.add(d) readyCheck() }) }) if (!dir) finder.on('end', function() { end = true readyCheck() }) } process.nextTick(add) } util.inherits(DirWatcher, EventEmitter) /* Stops watching and removes all listeners */ DirWatcher.prototype.stop = function() { this.watcher.removeAll() this.watcher.removeAllListeners() this.removeAllListeners() this.snapshots = {} } /* Returns a list of all watched files */ DirWatcher.prototype.files = function() { var all = [] for (var d in this.snapshots) all.push.apply(all, this.snapshots[d]) return this.filter(all).map(function(f) { return f.path }) } DirWatcher.prototype.filter = function(files) { return files.filter(function(f) { return this.match(f.path, f.stat) }, this) }