169 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			169 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | // Copyright 2010-2011 Mikeal Rogers
 | ||
|  | //
 | ||
|  | //    Licensed under the Apache License, Version 2.0 (the "License");
 | ||
|  | //    you may not use this file except in compliance with the License.
 | ||
|  | //    You may obtain a copy of the License at
 | ||
|  | //
 | ||
|  | //        http://www.apache.org/licenses/LICENSE-2.0
 | ||
|  | //
 | ||
|  | //    Unless required by applicable law or agreed to in writing, software
 | ||
|  | //    distributed under the License is distributed on an "AS IS" BASIS,
 | ||
|  | //    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||
|  | //    See the License for the specific language governing permissions and
 | ||
|  | //    limitations under the License.
 | ||
|  | 
 | ||
|  | var sys = require('util') | ||
|  |   , fs = require('fs') | ||
|  |   , path = require('path') | ||
|  |   , events = require('events') | ||
|  |   ; | ||
|  | 
 | ||
|  | function walk (dir, options, callback) { | ||
|  |   if (!callback) {callback = options; options = {}} | ||
|  |   if (!callback.files) callback.files = {}; | ||
|  |   if (!callback.pending) callback.pending = 0; | ||
|  |   callback.pending += 1; | ||
|  |   fs.stat(dir, function (err, stat) { | ||
|  |     if (err) return callback(err); | ||
|  |     callback.files[dir] = stat; | ||
|  |     fs.readdir(dir, function (err, files) { | ||
|  |       if (err) { | ||
|  |         if(err.code === 'EACCES' && options.ignoreUnreadableDir) return callback(); | ||
|  |         return callback(err); | ||
|  |       } | ||
|  |       callback.pending -= 1; | ||
|  |       files.forEach(function (f, index) { | ||
|  |         f = path.join(dir, f); | ||
|  |         callback.pending += 1; | ||
|  |         fs.stat(f, function (err, stat) { | ||
|  |           var enoent = false | ||
|  |             , done = false; | ||
|  | 
 | ||
|  |           if (err) { | ||
|  |             if (err.code !== 'ENOENT' && (err.code !== 'EPERM' && options.ignoreNotPermitted)) { | ||
|  |               return callback(err); | ||
|  |             } else { | ||
|  |               enoent = true; | ||
|  |             } | ||
|  |           } | ||
|  |           callback.pending -= 1; | ||
|  |           done = callback.pending === 0; | ||
|  |           if (!enoent) { | ||
|  |             if (options.ignoreDotFiles && path.basename(f)[0] === '.') return done && callback(null, callback.files); | ||
|  |             if (options.filter && !options.filter(f, stat)) return done && callback(null, callback.files); | ||
|  |             callback.files[f] = stat; | ||
|  |             if (stat.isDirectory() && !(options.ignoreDirectoryPattern && options.ignoreDirectoryPattern.test(f))) walk(f, options, callback); | ||
|  |             done = callback.pending === 0; | ||
|  |             if (done) callback(null, callback.files); | ||
|  |           } | ||
|  |         }) | ||
|  |       }) | ||
|  |       if (callback.pending === 0) callback(null, callback.files); | ||
|  |     }) | ||
|  |     if (callback.pending === 0) callback(null, callback.files); | ||
|  |   }) | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | var watchedFiles = Object.create(null); | ||
|  | 
 | ||
|  | exports.watchTree = function ( root, options, callback ) { | ||
|  |   if (!callback) {callback = options; options = {}} | ||
|  |   walk(root, options, function (err, files) { | ||
|  |     if (err) throw err; | ||
|  |     var fileWatcher = function (f) { | ||
|  |       var fsOptions = {}; | ||
|  |       if (options.interval) { | ||
|  |         fsOptions.interval = options.interval * 1000; | ||
|  |       } | ||
|  |       fs.watchFile(f, fsOptions, function (c, p) { | ||
|  |         // Check if anything actually changed in stat
 | ||
|  |         if (files[f] && !files[f].isDirectory() && c.nlink !== 0 && files[f].mtime.getTime() == c.mtime.getTime()) return; | ||
|  |         files[f] = c; | ||
|  |         if (!files[f].isDirectory()) callback(f, c, p); | ||
|  |         else { | ||
|  |           fs.readdir(f, function (err, nfiles) { | ||
|  |             if (err) return; | ||
|  |             nfiles.forEach(function (b) { | ||
|  |               var file = path.join(f, b); | ||
|  |               if (!files[file] && (options.ignoreDotFiles !== true || b[0] != '.')) { | ||
|  |                 fs.stat(file, function (err, stat) { | ||
|  |                   if (options.filter && !options.filter(file, stat)) return; | ||
|  |                   callback(file, stat, null); | ||
|  |                   files[file] = stat; | ||
|  |                   fileWatcher(file); | ||
|  |                 }) | ||
|  |               } | ||
|  |             }) | ||
|  |           }) | ||
|  |         } | ||
|  |         if (c.nlink === 0) { | ||
|  |           // unwatch removed files.
 | ||
|  |           delete files[f] | ||
|  |           fs.unwatchFile(f); | ||
|  |         } | ||
|  |       }) | ||
|  |     } | ||
|  |     fileWatcher(root); | ||
|  |     for (var i in files) { | ||
|  |       fileWatcher(i); | ||
|  |     } | ||
|  |     watchedFiles[root] = files; | ||
|  |     callback(files, null, null); | ||
|  |   }) | ||
|  | } | ||
|  | 
 | ||
|  | exports.unwatchTree = function (root) { | ||
|  |   if (!watchedFiles[root]) return; | ||
|  |   Object.keys(watchedFiles[root]).forEach(fs.unwatchFile); | ||
|  |   watchedFiles[root] = false; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.createMonitor = function (root, options, cb) { | ||
|  |   if (!cb) {cb = options; options = {}} | ||
|  |   var monitor = new events.EventEmitter(); | ||
|  |   monitor.stop = exports.unwatchTree.bind(null, root); | ||
|  | 
 | ||
|  |   var prevFile = {file: null,action: null,stat: null}; | ||
|  |   exports.watchTree(root, options, function (f, curr, prev) { | ||
|  |     // if not curr, prev, but f is an object
 | ||
|  |     if (typeof f == "object" && prev == null && curr === null) { | ||
|  |       monitor.files = f; | ||
|  |       return cb(monitor); | ||
|  |     } | ||
|  | 
 | ||
|  |     // if not prev and either prevFile.file is not f or prevFile.action is not created
 | ||
|  |     if (!prev) { | ||
|  |       if (prevFile.file != f || prevFile.action != "created") { | ||
|  |         prevFile = { file: f, action: "created", stat: curr }; | ||
|  |         return monitor.emit("created", f, curr); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // if curr.nlink is 0 and either prevFile.file is not f or prevFile.action is not removed
 | ||
|  |     if (curr) { | ||
|  |       if (curr.nlink === 0) { | ||
|  |         if (prevFile.file != f || prevFile.action != "removed") { | ||
|  |           prevFile = { file: f, action: "removed", stat: curr }; | ||
|  |           return monitor.emit("removed", f, curr); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // if prevFile.file is null or prevFile.stat.mtime is not the same as curr.mtime
 | ||
|  |     if (prevFile.file === null) { | ||
|  |       return monitor.emit("changed", f, curr, prev); | ||
|  |     } | ||
|  |     // stat might return null, so catch errors
 | ||
|  |     try { | ||
|  |       if (prevFile.stat.mtime.getTime() !== curr.mtime.getTime()) { | ||
|  |         return monitor.emit("changed", f, curr, prev); | ||
|  |       } | ||
|  |     } catch(e) { | ||
|  |       return monitor.emit("changed", f, curr, prev); | ||
|  |     } | ||
|  |   }) | ||
|  | } | ||
|  | 
 | ||
|  | exports.walk = walk; |