├── example ├── inner │ └── teapot ├── test.js ├── test_strict.js ├── test_norecurse.js ├── test_remove.js ├── test_remove_norecurse.js ├── test_buffer.js ├── test_buffer_norecurse.js └── test_stop.js ├── TODO ├── package.json ├── LICENSE ├── History.md ├── lib ├── watcher.js └── stalker.js └── Readme.md /example/inner/teapot: -------------------------------------------------------------------------------- 1 | I'm a teapot 2 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Things I should do: 2 | 3 | - fixed broken unit tests 4 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', function(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('I see ' + f); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /example/test_strict.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', {strict: true}, function(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('I see ' + f); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /example/test_norecurse.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', {recurse: false}, function(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('I see ' + f); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /example/test_remove.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', function(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('Added: ' + f); 9 | }, function(err, f) { 10 | if (err) { 11 | console.log('Error was ' + err); 12 | return; 13 | } 14 | console.log('Removed: ' + f); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /example/test_remove_norecurse.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', {recurse: false}, function (err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('Added: ' + f); 9 | }, function(err, f) { 10 | if (err) { 11 | console.log('Error was ' + err); 12 | return; 13 | } 14 | console.log('Removed: ' + f); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /example/test_buffer.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', {buffer: 5000}, function _add(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | f.forEach(function _forEach(_f) { 9 | console.log('Added: ' + _f); 10 | }); 11 | }, function _remove(err, f) { 12 | if (err) { 13 | console.log('Error was ' + err); 14 | return; 15 | } 16 | f.forEach(function _forEach(_f) { 17 | console.log('Remove: ' + _f); 18 | }); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Justin Slattery (http://fzysqr.com/)", 3 | "name": "stalker", 4 | "description": "Monitor directory trees for new files then do... something.", 5 | "version": "0.0.20", 6 | "homepage": "https://github.com/jslatts/stalker", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/jslatts/stalker.git" 10 | }, 11 | "main": "./lib/stalker.js", 12 | "scripts": {}, 13 | "engines": { 14 | "node": ">= 0.8.6" 15 | }, 16 | "dependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /example/test_buffer_norecurse.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', {recurse: false, buffer: 5000}, function _add(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | f.forEach(function _forEach(_f) { 9 | console.log('Added: ' + _f); 10 | }); 11 | }, function _remove(err, f) { 12 | if (err) { 13 | console.log('Error was ' + err); 14 | return; 15 | } 16 | f.forEach(function _forEach(_f) { 17 | console.log('Remove: ' + _f); 18 | }); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /example/test_stop.js: -------------------------------------------------------------------------------- 1 | stalker = require('../'); 2 | 3 | stalker.watch('./example', function(err, f) { 4 | if (err) { 5 | console.log('Error was ' + err); 6 | return; 7 | } 8 | console.log('I see ' + f); 9 | }); 10 | 11 | stalker.watch('./example/inner', function(err, f) { 12 | if (err) { 13 | console.log('Error was ' + err); 14 | return; 15 | } 16 | console.log('I see ' + f); 17 | }); 18 | 19 | setTimeout(function() { 20 | console.log('Police were called. I\'m out.') //WTF? 21 | stalker.stop('./example') 22 | }, 5000); 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011 Justin Slattery (Justin.Slattery@fzysqr.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.0.15 / 2011-12-29 2 | ================== 3 | 4 | * Issue #5 - finish basic implementation of stop. Should work. I think. Volunteer test writing will be accepted. 5 | * Issue #3 - all tests are passing on OSX. Let me know if yous aren't. 6 | 7 | 0.0.13 / 2011-07-19 8 | ================== 9 | 10 | * Issue #4 - adding recurse flag to limit stalker to original directory. 11 | 12 | ================== 13 | 14 | * Fixing issue #2 - Certain actions cause the add callback to fire when it shouldn't. 15 | 16 | 0.0.11 / 2011-06-29 17 | ================== 18 | 19 | * Integrating pull request for better hidden file test. 20 | 21 | 0.0.10 / 2011-06-29 22 | ================== 23 | 24 | * Update package.json for node 0.4.9 25 | 26 | 0.0.9 / 2011-06-20 27 | ================== 28 | 29 | * Adding removal callback 30 | 31 | 0.0.7 / 2011-06-19 32 | ================== 33 | 34 | * Replaced jasmine tests with vows.js 35 | 36 | ================== 37 | 38 | * Implemented optional batching mechanism 39 | * Added new example 40 | 41 | 0.0.5 / 2011-06-07 42 | ================== 43 | 44 | * Fixed bugs 45 | * First public release 46 | 47 | 0.0.2 / 2011-05-30 48 | ================== 49 | 50 | * Fixed docs. 51 | 52 | 0.0.1 / 2011-05-30 53 | ================== 54 | 55 | * Initial release 56 | -------------------------------------------------------------------------------- /lib/watcher.js: -------------------------------------------------------------------------------- 1 | //watcher.js 2 | //Watcher object to keep track of directory state 3 | (function () { 4 | if (typeof exports === 'undefined') { 5 | throw new Error('watcher.js must be loaded as a module.'); 6 | } 7 | 8 | var fs = require('fs'); 9 | var path = require('path'); 10 | 11 | module.exports.makeWatcher = function makeWatcher() { 12 | var watched = []; 13 | 14 | var removeSubDir = function (dir, fn){ 15 | //console.log('called removeSubDir with ' + dir) 16 | //console.dir(watched) 17 | if (!watched[dir]) return; 18 | 19 | if (watched[dir].files) { 20 | Object.keys(watched[dir].files).forEach(function _forEach(iFile) { 21 | //If we have a key for this, then its a dir. Recurse 22 | //Otherwise, we have a file, so fire the removal callback 23 | var fPath = dir + '/' + iFile; 24 | if (watched[fPath]) { 25 | 26 | removeSubDir(fPath, fn); 27 | } 28 | else { 29 | return fn && fn(null, fPath); // Fire the removal callback 30 | } 31 | }); 32 | } 33 | 34 | delete(watched[dir]); // Delete the directory once the children are removed 35 | }; 36 | 37 | return { 38 | //Returns false if a file is not being tracked. Will ignore mtime unless 39 | //strict is truthy. 40 | checkFile: function (fPath, strict, fn) { 41 | if (typeof(strict) === 'function') { 42 | fn = strict; 43 | strict = false; 44 | } 45 | 46 | var dir = path.dirname(fPath); 47 | var base = path.basename(fPath); 48 | 49 | if (typeof watched === 'undefined') 50 | { 51 | return fn && fn('[checkFile] watched array not properly defined.'); 52 | } 53 | else { 54 | fs.stat(fPath, function onStat(err, stats) { 55 | //If we get back an error, then the directory doesn't exist. So we aren't watching it, duh! 56 | if (err) { return fn && fn(null, false); } 57 | 58 | //If the watched [] has not been initialized, we know the file isn't tracked 59 | if (typeof watched[dir] === 'undefined' || typeof watched[dir].files === 'undefined') { 60 | return fn && fn(null, false); 61 | } 62 | 63 | if (strict) { 64 | return fn && fn(null, watched[dir].files[base] === stats.mtime.valueOf()); 65 | } 66 | else { 67 | return fn && fn(null, typeof watched[dir].files[base] !== 'undefined'); 68 | } 69 | 70 | }); 71 | } 72 | }, 73 | addFile: function (fPath, fn) { 74 | var dir = path.dirname(fPath); 75 | var base = path.basename(fPath); 76 | 77 | if (typeof watched[dir] === 'undefined') { watched[dir] = {}; } 78 | if (typeof watched[dir].files === 'undefined') { watched[dir].files = {}; } 79 | 80 | fs.stat(fPath, function onStat(err, stats) { 81 | if (err) { return fn && fn(err); } 82 | watched[dir].files[base] = stats.mtime.valueOf(); 83 | 84 | return fn && fn(null); 85 | }); 86 | }, 87 | reset: function (fPath, fn) { 88 | delete(watched[fPath]); 89 | return fn && fn(null); 90 | }, 91 | kill: function (fPath, fn) { 92 | //no string, KILL EVERYTHING, RAWR! 93 | if (typeof fPath !== 'string') { 94 | Object.keys(watched).forEach(function (f) { 95 | removeSubDir(f, null); 96 | }) 97 | 98 | return fn && fn(null) 99 | } 100 | else { 101 | removeSubDir(fPath, fn); 102 | } 103 | }, 104 | syncFolder: function(dir, fnRemove) { 105 | //Ugly, but we can't tell if something is gone, so loop through and delete any 106 | //missing files from our watched object 107 | 108 | if (typeof watched[dir] === 'undefined' || typeof watched[dir].files === 'undefined') { 109 | return; 110 | } 111 | 112 | Object.keys(watched[dir].files).forEach(function _forEach(tFile) { 113 | var fPath = path.join(dir, tFile); 114 | fs.stat(fPath, function _stat(err, stats) { 115 | if (err) { 116 | //If we have a key for this, then its a dir. Recurse 117 | if (watched[fPath]) { 118 | removeSubDir(fPath, fnRemove); 119 | } 120 | else { 121 | delete(watched[dir].files[tFile]); // Delete the entry from the parent 122 | return fnRemove && fnRemove(null, fPath); 123 | } 124 | } 125 | }); 126 | }); 127 | } 128 | }; 129 | }; 130 | }()); 131 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Stalker 2 | 3 | Stalker is a utility to watch a directory tree for incoming files. When if finds 4 | one, it will fire off a callback function __OF YOUR CHOICE!__ 5 | 6 | It should be smart enough to handle multiple files/folders being dropped into the 7 | directory being stalked. 8 | 9 | ## Installation 10 | 11 | $ npm install stalker 12 | 13 | ## How to use 14 | 15 | var stalker = require('stalker'); 16 | 17 | stalker.watch('some_directory', function (err, file) { 18 | console.log('I saw a file. It was going like this: ' + file); 19 | }); 20 | 21 | ## Options 22 | 23 | You can pass in a buffer time in milliseconds to make stalker batch up 24 | the files and send them back in an array. The timer only starts when a new file 25 | drops. 26 | 27 | stalker.watch('./example', {buffer: 5000}, function(err, f) { 28 | f.forEach(function _forEach(_f) { 29 | console.log('I see ' + _f); 30 | }); 31 | }); 32 | 33 | Setting recurse to false will keep stalker for walking down directories. 34 | 35 | stalker.watch('./example', {recurse: false}, function(err, f) { 36 | console.log('I see ' + _f); 37 | }); 38 | 39 | Setting strict to true will make stalker return for modified files. 40 | *This has unpredictable behavior on windows/OSX. I have only used it 41 | on Linux* 42 | 43 | stalker.watch('./example', {strict: true}, function(err, f) { 44 | console.log('I see ' + _f); 45 | }); 46 | 47 | Stalker will also take a second callback that will be fired when files are removed: 48 | 49 | stalker.watch('./example', function(err, f) { 50 | console.log('Added: ' + f); 51 | }, function(err, f) { 52 | console.log('Removed: ' + f); 53 | }); 54 | 55 | The removal callback also works in batch mode. 56 | 57 | The startSilent parameter will prevent stalker from telling you about all the 58 | existing files in the directory: 59 | 60 | stalker.watch('./example', {startSilent: true}, function(err, f) { 61 | if (err) { 62 | console.log('Error was ' + err); 63 | return; 64 | } 65 | console.log('I see ' + f); 66 | }); 67 | 68 | 69 | ## Running tests 70 | 71 | I couldn't handle vows.js anymore. I ripped it out. Will replace with something 72 | else in the future. For now, run example/*.js and play around adding and 73 | removing files from the example directory: 74 | 75 | node example/test.js 76 | 77 | ## License 78 | 79 | (The MIT License) 80 | 81 | Copyright (c) 2011 Justin Slattery (Justin.Slattery@fzysqr.com) 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining 84 | a copy of this software and associated documentation files (the 85 | 'Software'), to deal in the Software without restriction, including 86 | without limitation the rights to use, copy, modify, merge, publish, 87 | distribute, sublicense, and/or sell copies of the Software, and to 88 | permit persons to whom the Software is furnished to do so, subject to 89 | the following conditions: 90 | 91 | The above copyright notice and this permission notice shall be 92 | included in all copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 95 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 96 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 97 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 98 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 99 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 100 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 101 | 102 | ## Gir 103 | :/::-.` 104 | .o-```.:+o++//- 105 | ++`````--------/o++:` 106 | .o.````.-------------:++/:---------.` 107 | ++````.-------------------+oo+++++//+oso/:. 108 | ://o: `s-````-----------------------:oo- `-:+o+.--` 109 | -+/::shy:-s````.-------:++++/:-------------+o: +/:/o` 110 | /+::::hhhhy````-----:+++/:::::+++:------------/s- .::/` 111 | o/::::ohhhh+``.----/o+::::::::::::+o:------------+o` 112 | o/::::+dhhhd.`.---:o+::::::::::::::::s:------------:s/ 113 | +/::::/hhhhhs`----/o::::::::::::::::::/y-------------:ss` 114 | -+::::/hhhhhh.----/o::::::::::::::::::::m+-----------/oooh- 115 | o::::+hhhhhy.-----s::::::::::::::::::::sho---------:/oooooy/ 116 | .o:::shhhhho`-----:o:::::::::::::::::::odd/--------:+oooooooy/ 117 | .o/ohhhhhy:`.-----:s:::::::::::::::::/yhho--------/oooooooooy/ 118 | `------y-``-------o+::::::::::::::/shhh+-------:+ooooooooy+` 119 | y``.:oo+:---/o/:::::::::/oyhhhs:-------/ooooooooys. 120 | y``-y.o.+o----/ossooosyhhhhyo:-------:+ooooooosy- 121 | y``++-/`-Nh------/+osssoo/:--------:/ooooooooy: 122 | o-`-sNmhNMMo----------------------/+oooooooy+ 123 | `y`.-NMMMMMm--------------------/+oooooooyo` 124 | :+`/MMMMMMh------------------/+oooooooyo` 125 | :o.smNNds:---------------:/+oooooooyo. 126 | .o/-------------------:/+oooooooy+` 127 | .+o:-------------:/+oooooooss/` 128 | :+o+:-----://+oooooooys+. 129 | `:/+osyssoossyysohdo/` 130 | `.----.` ./sds/. 131 | .:oymho/:///` 132 | `-`+-:sh:-o-:::/s 133 | .o+ooos:/s/s:++:+s 134 | +h/:yho+/:s/sooyd` 135 | .s.:/ooss+yoy 136 | /+-////o+soh` 137 | `://o+.s/:--:/oyo` 138 | .+++:s/. .//+y/:` 139 | /+.-/y- :o+h 140 | y`-:oy+ //--os/` 141 | //+oysy` //.-:+oys+- 142 | /+/++oyso: 143 | `.` 144 | 145 | 146 | -------------------------------------------------------------------------------- /lib/stalker.js: -------------------------------------------------------------------------------- 1 | //stalker.js 2 | //Watches a directory for file changes and fires the callbacks when found 3 | (function () { 4 | if (typeof exports === 'undefined') { 5 | throw new Error('stalker.js must be loaded as a module.'); 6 | } 7 | 8 | var fs = require('fs'); 9 | var path = require('path'); 10 | 11 | //Helpful object friend 12 | var watcher = require('./watcher').makeWatcher(); 13 | 14 | //Sucky work around for lame fs.watch() api 15 | var handles = {}; 16 | 17 | var options = {}; 18 | 19 | var st = exports; 20 | 21 | var folderChanged = function(folderPath, fnAdd, fnRemove) { 22 | return function (event, filename) { 23 | var reset = true; 24 | if (event === 'change') { 25 | reset = false; 26 | } 27 | 28 | //Because of the goofy fs.watch() api, we have to close and recreate the 29 | //file watch handle. Except sometimes the file was moved and this blows up 30 | //So close, then check and recreate if it still exists 31 | if (reset) { 32 | handles[folderPath].close(); 33 | } 34 | 35 | fs.stat(folderPath, function(err) { 36 | if (err) { return; } 37 | 38 | if (reset) { 39 | handles[folderPath] = fs.watch(folderPath, folderChanged(folderPath, fnAdd, fnRemove)); 40 | } 41 | 42 | fs.readdir(folderPath, function _readdir(err, files) { 43 | if (err) { console.log('read');return fnAdd && fnAdd(err); } 44 | 45 | files.forEach(function _forEach(file){ 46 | if (file[0] === '.') { return; } //ignore files starting with "." 47 | 48 | var fPath = path.join(folderPath, file); 49 | fs.stat(fPath, function _stat(err, stats) { 50 | if (err) { return fnAdd && fnAdd(err); } 51 | 52 | //If we have a file, send it to our callback 53 | if (stats.isFile()) { 54 | 55 | watcher.checkFile(fPath, options.strict, function _checkFile(err, result) { 56 | if (err) { return fnAdd && fnAdd(err); } 57 | if (!result) { 58 | watcher.addFile(fPath, function onAddFile() { 59 | return fnAdd && fnAdd(null, fPath); 60 | }); 61 | } 62 | }); 63 | } 64 | else if (stats.isDirectory() && options.recurse) { 65 | watchFolderTree(fPath, fnAdd, fnRemove, Infinity); //if we have a dir, match sure it is watched 66 | } 67 | }); 68 | }); 69 | }); 70 | 71 | watcher.syncFolder(folderPath, fnRemove); 72 | }); 73 | }; 74 | }; 75 | 76 | //Takes a folder root and a callback and recurses through to a directory tree 77 | //Calls fn(null, directory) whenever a file is added to one of the watched 78 | //directories 79 | var watchFolderTree = function (fPath, fnAdd, fnRemove, depth) { 80 | fs.stat(fPath, function onStat(err, stats) { 81 | if (err) { return fnAdd && fnAdd(err); } 82 | 83 | if (stats.isDirectory() && depth > 0) { 84 | //If we have a directory, watch it and recurse down 85 | watcher.checkFile(fPath, options.strict, function _checkFile(err, result) { 86 | if (err) { return fnAdd && fnAdd(err); } 87 | 88 | if (!result) { 89 | watcher.addFile(fPath, function _addFile() { 90 | //Clear out any old listeners. Is there a better way? 91 | if (typeof(handles[fPath]) === 'object') { handles[fPath].close(); } 92 | 93 | handles[fPath] = fs.watch(fPath, folderChanged(fPath, fnAdd, fnRemove)); 94 | }); 95 | } 96 | }); 97 | 98 | //Recurse over anything in this directory 99 | fs.readdir(fPath, function _readdir(err, files) { 100 | if (err) { return fnAdd && fnAdd(err); } 101 | 102 | files.forEach(function (file) { 103 | if (file[0] === '.') { return; } //ignore files starting with "." 104 | 105 | var rPath = path.join(fPath, file); 106 | // console.log('depth later ' + depth) 107 | watchFolderTree(rPath, fnAdd, fnRemove, depth - 1 ); 108 | }); 109 | }); 110 | } 111 | else if (stats.isFile()) { 112 | watcher.checkFile(fPath, options.strict, function _checkFile(err, result) { 113 | if (err) { return fnAdd && fnAdd(err); } 114 | if (!result) { 115 | watcher.addFile(fPath, function _addFile() { 116 | //If startSilent has been requested, do not fire the callbacks on first read of the directory 117 | if (!options.startSilent) { 118 | return fnAdd && fnAdd(null, fPath); 119 | } 120 | }); 121 | } 122 | }); 123 | } 124 | }); 125 | }; 126 | 127 | st.stop = function(reqPath, fn) { 128 | if (typeof reqPath === 'function') { 129 | fn = reqPath 130 | reqPath = null 131 | } 132 | 133 | var nPath = path.resolve(reqPath) 134 | 135 | fs.exists(nPath, function _exists(exists) { 136 | if (!exists) { 137 | nPath = nPath || '' 138 | return fn && fn('Path does not exist: ' + nPath) 139 | } 140 | }) 141 | 142 | fs.unwatchFile(nPath); 143 | watcher.kill(nPath, fn); 144 | } 145 | 146 | st.watch = function(reqPath, opts, fnAdd, fnRemove) { 147 | if (typeof opts === 'function') { 148 | //do not require options 149 | fnRemove = fnAdd; 150 | fnAdd = opts; 151 | opts = {}; 152 | } 153 | 154 | var original_fnAdd = fnAdd; 155 | var original_fnRemove = fnRemove; 156 | if (typeof fnAdd !== 'function') { 157 | throw { 158 | name : 'TypeError', 159 | message : 'Must provide a callback function' 160 | }; 161 | } 162 | 163 | var nPath = path.resolve(reqPath); 164 | 165 | options = opts; 166 | options.recurse = typeof options.recurse === 'undefined' ? true : options.recurse; 167 | options.strict = typeof options.strict === 'undefined' ? false : options.strict; 168 | options.startSilent = typeof options.startSilent === 'undefined' ? false : options.startSilent; 169 | 170 | //If a buffer time is passed, then wrap the passed in callback 171 | if (typeof options.buffer === 'number') { 172 | fnAdd = (function maker() { 173 | var fileBuffer = []; 174 | var flushTimer; 175 | 176 | //Setup the buffer timer to flush contents 177 | function flushBuffer() { 178 | if (fileBuffer.length > 0 && original_fnAdd) { 179 | var tempBuffer = fileBuffer; 180 | fileBuffer = []; 181 | 182 | original_fnAdd(null, tempBuffer); 183 | } 184 | 185 | flushTimer = null; 186 | } 187 | 188 | return function new_fnAdd(err, file) { 189 | if (err) { return original_fnAdd(err); } 190 | 191 | if (!flushTimer) { 192 | flushTimer = setTimeout(flushBuffer, options.buffer); 193 | } 194 | 195 | fileBuffer.push(file); 196 | }; 197 | }()); 198 | if (typeof fnRemove === 'function') { 199 | fnRemove = (function maker() { 200 | var fileBuffer = []; 201 | var flushTimer; 202 | 203 | //Setup the buffer timer to flush contents 204 | function flushBuffer() { 205 | if (fileBuffer.length > 0 && original_fnRemove) { 206 | var tempBuffer = fileBuffer; 207 | fileBuffer = []; 208 | 209 | original_fnRemove(null, tempBuffer); 210 | } 211 | 212 | flushTimer = null; 213 | } 214 | 215 | return function new_fnRemove(err, file) { 216 | if (err) { return original_fnRemove(err); } 217 | 218 | if (!flushTimer) { 219 | flushTimer = setTimeout(flushBuffer, options.buffer); 220 | } 221 | 222 | fileBuffer.push(file); 223 | }; 224 | }()); 225 | } 226 | } 227 | 228 | fs.exists(nPath, function _exists(exists) { 229 | if (!exists) { 230 | nPath = nPath || ''; 231 | return fnAdd && fnAdd('Path does not exist: ' + nPath); 232 | } 233 | 234 | var depth = options.recurse ? Infinity : 1; 235 | watchFolderTree(nPath, fnAdd, fnRemove, depth); 236 | }); 237 | }; 238 | }()); 239 | 240 | --------------------------------------------------------------------------------