watch.js | |
---|---|
Node-watch Is a small nodejs module/lib to watch for file changes. Filechanges are: | /*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>.
*
*/ |
Merged https://github.com/tigerbot version | |
Install:Local (in "./node_modules"): npm install nodewatch Global : npm install nodewatch -g Usage:var watch = require('nodewatch'); // Adding 2 dirs relative from process.cwd() // Adding Abolute paths works as well // (Nested dirs are not watched) // and add the callback | |
nodejs requirements: EventEmitter, fs, path, util | var EventEmitter = require("events").EventEmitter, util = require("util"); |
Watch class declarationextends from EventEmitter | var WatchClass = function() {
"use strict"; |
PUBLIC METHODS | |
Watch class Constructor |
function Watch(options) { |
Call the super constructor first to initialize the emitter | EventEmitter.call(this); |
objects to track the files and directories we are explicitly told to watch, and the files and directories that we're watching because they are in directories we're explicitly to to watch | this.__topLvlWatchers = [];
this.__watchedItems = {};
this.fs = require('fs');
this.path = require('path');
}
util.inherits(Watch, EventEmitter); |
Public method: add(path , [recursive])
returns this object |
Watch.prototype.add = function(str_file_or_path, recursive) {
recursive = recursive || false;
return this.__handle(true, str_file_or_path, recursive);
};
|
Public method: remove(path)
returns this object |
Watch.prototype.remove = function(str_file_or_path) { |
we don't really need to specify recursive here, __handle doesn't use it anyway if add isn't true | return this.__handle(false, str_file_or_path);
};
|
Public method: onChange(callback)add a callback cb : function(file,prev,curr,action){ /* do something with file,prev,curr */ }; When an event triggers, 4 arguments are send to the callback listener
and return this object |
Watch.prototype.onChange = function(cb) {
if (typeof cb === 'function') {
this.on('change', cb);
}else{
throw new Error('Non-function provided as the callback for onChange');
}
return this;
};
|
Public method: clearListeners() |
Watch.prototype.clearListeners = function() {
this.removeAllListeners("change");
this.removeAllListeners();
return this;
};
|
PRIVATE METHODS | |
Private method: __handle(boolean, string)String is a absolute or relative path to a file or dir. First str_file_or_path is normalized as a valid path, relative paths are made absolute depending on process.cwd() The boolean add (true == add, false == remove) is passed to the _file or _dir method returns this object |
Watch.prototype.__handle = function(add, str_file_or_path, recursive) {
str_file_or_path = this.path.resolve(str_file_or_path); |
__handle is only called by the public add/remove functions, so we can add/remove anything that gets to this point to/from the list of explicitly watched items | if (add) {
if(this.__topLvlWatchers.indexOf(str_file_or_path) === -1){ |
Only add when it's not allready there | this.__topLvlWatchers.push(str_file_or_path);
}
} else {
var index = this.__topLvlWatchers.indexOf(str_file_or_path);
if (index >= 0) {
this.__topLvlWatchers.splice(index, 1);
}
} |
Do not proccess deleted files | var stat = null;
try{
stat = this.fs.statSync(str_file_or_path);
}catch(e){
if (add) { |
We should throw on 'add' to be backwards compatible On the public interface | throw e;
}else{
stat = false; |
If the file is deleted but still being watched make sure we remove the listeners for it. | if (self.__watchedItems.hasOwnProperty(str_file_or_path)) {
self.fs.unwatchFile(str_file_or_path);
delete self.__watchedItems[str_file_or_path];
}
}
}
if(stat){
if (stat.isFile()) {
return this.__file(add, str_file_or_path);
}
if (stat.isDirectory()) {
return this.__dir(add, str_file_or_path, recursive);
}
}
};
|
Private method: __dir(boolean, string, recursive)walk a dir and pass the files with the 'add' boolean We put a watch on a folder but this folder is never reported it's purely to rescan a changed folder or detect NEW created files | Watch.prototype.__dir = function(add, dir, recursive) {
var self = this;
recursive = recursive || false;
if(add){
if(!self.__watchedItems.hasOwnProperty(dir)){
self.__watchedItems[dir]= { recursive : recursive };
self.__rescan(add,dir,recursive,false);
self.fs.watchFile(dir, function(curr, prev) { |
Something about the directory has changed, most likely a file has been added. Rescan the directory to see if anything new is found, and tell it to report any new files to the user. | self.__rescan(add,dir,recursive, true);
});
}else{
throw new Error('Folder already being watched');
}
}else{
if(self.__watchedItems.hasOwnProperty(dir)){
self.__rescan(add,dir,self.__watchedItems[dir].recursive, false);
delete self.__watchedItems[dir];
}
self.fs.unwatchFile(dir);
}
return self;
};
|
Private method: __file(boolean, string)Finally add (add==true) or remove a file from watching, only files should be passed here, therefore recursive is obsolete. Handle only files In the watch usage, this should not be async | Watch.prototype.__file = function(add, file) {
var self = this;
var is_file = false;
try {
is_file = self.fs.statSync(file).isFile();
}catch(e){
is_file = false;
}
if(!is_file){
return self;
}
if (add) { |
Make sure we don't accidently put multiple watchers on a single file, as this might cause us to report changes several times and might also cause a memory leak. | if (self.__watchedItems.hasOwnProperty(file)) {
throw new Error('File already being watched');
return self;
} |
It doesn't really matter what we assign to this key right now, we merely have to assign something. | self.__watchedItems[file] = true;
self.fs.watchFile(file, function watchMe(prev, curr) {
try { |
This will cause an error when a file is deleted | var stat = self.fs.statSync(file).isFile(); |
Else, register a change | if (prev.mtime.getTime() !== curr.mtime.getTime()) {
self.emit("change", file, prev, curr,'change');
}
} |
A file inside the directory has been removed, emit event | catch(e) {
if (e.code === 'ENOENT') {
self.emit("change", file, prev, curr,'delete'); |
If this file isn't explicitly being watched, unwatch it now. Otherwise we'll wait until the user explicitly removes it from the list | if (self.__topLvlWatchers.indexOf(file) < 0) {
self.fs.unwatchFile(file);
delete self.__watchedItems[file];
}
return;
}else{
throw(e);
}
}
});
} else { |
console.log('UN WATCH:'+file); | self.fs.unwatchFile(file);
delete self.__watchedItems[file];
}
return self;
}; |
Walk a dir and start watching FILES (only Files), if add === false STOP listening if recursive? Walk the tree, but only ADD files for listening if reportNew? emit the change event for any file we find that wasn't being watched before | Watch.prototype.__rescan = function(add,folder, recursive, reportNew){
var self = this;
self.fs.stat(folder, function(err,stat){
if(err){
if(err.code !== 'ENOENT'){
throw err;
return;
} |
The watched directory has been deleted, so if it's part of a recursively watched directory remove it's watcher. We won't need to call anything recursively to remove watchers for any descendants because they to will have been deleted and have this callback. But what when we want to remove a watched folder, and this folder is not deleted | console.log('__rescan');
if(self.__topLvlWatchers.indexOf(folder) < 0){
self.fs.unwatchFile(folder);
delete self.__watchedItems[folder];
}
}else{
var files = self.fs.readdirSync(folder);
files.forEach(function (file) {
var full_path = self.path.join(folder, file);
var stat; |
We only need to check this path if we aren't already watching it, or if we need to remove it and all of it's possible descendants. | if (!add || !self.__watchedItems.hasOwnProperty(full_path)) {
stat = self.fs.statSync(full_path);
if (stat.isFile()) {
self.__file(add, full_path); |
if we aren't in the process of removing listeners and we get to this point we have a new file that should be reported if this isn't the initial scan of a directory. | if (add && reportNew) {
self.emit('change', full_path, stat, stat, 'new');
} |
If we read a directory, call recursively to | } else if (recursive && stat.isDirectory()) {
self.__dir(add, full_path, recursive);
}
}
});
}
});
};
return Watch;
}();
module.exports = new WatchClass;
|