Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

v2.3.4. Bugfix. Improvement.

- v2.3.4 January 8, 2013
	- Better handling and detection of failed watching operations
	- Better handling of duplicated events
	- Watching is now an atomic operation
		- If watching fails for a descendant, we will close everything related to that watch operation of the eve
	- We now prefer the `watch` method over the `watchFile` method
		- This offers great reliability and way less CPU and memory foot print
		- If you still wish to prefer `watchFile`, then set the new configuration option `preferredMethod` to `watchFile`
	- Closes [issue #30](#30) thanks to [Howard Tyson](https://github.com/tizzo)
  • Loading branch information...
commit 770cd651bfac6a8e79f53284515936662f131f5a 1 parent a1fe761
@balupton balupton authored
View
7 .travis.yml
@@ -1,12 +1,15 @@
language: node_js
-before_script: "make test-prepare"
+install: "npm install"
+before_script: "./node_modules/.bin/cake test-prepare"
+script: "./node_modules/.bin/cake test"
node_js:
- 0.4
- 0.6
- 0.8
+ - 0.9
notifications:
irc:
- - "irc.freenode.org#bevry"
+ - "irc.freenode.org#bevry-dev"
email:
recipients:
- watchr@bevry.me
View
132 Cakefile
@@ -0,0 +1,132 @@
+# This file was originally created by Benjamin Lupton <b@lupton.cc> (http://balupton.com)
+# and is currently licensed under the Creative Commons Zero (http://creativecommons.org/publicdomain/zero/1.0/)
+# making it public domain so you can do whatever you wish with it without worry (you can even remove this notice!)
+#
+# If you change something here, be sure to reflect the changes in:
+# - the scripts section of the package.json file
+# - the .travis.yml file
+
+
+# -----------------
+# Variables
+
+WINDOWS = process.platform.indexOf('win') is 0
+NODE = process.execPath
+NPM = if WINDOWS then process.execPath.replace('node.exe','npm.cmd') else 'npm'
+EXT = (if WINDOWS then '.cmd' else '')
+APP = process.cwd()
+BIN = "#{APP}/node_modules/.bin"
+CAKE = "#{BIN}/cake#{EXT}"
+COFFEE = "#{BIN}/coffee#{EXT}"
+OUT = "#{APP}/out"
+SRC = "#{APP}/src"
+
+
+# -----------------
+# Requires
+
+pathUtil = require('path')
+{exec,spawn} = require('child_process')
+safe = (next,fn) ->
+ return (err) ->
+ return next(err) if err
+ return fn()
+
+
+# -----------------
+# Actions
+
+clean = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ args = [
+ '-Rf'
+ OUT
+ pathUtil.join(APP,'node_modules')
+ pathUtil.join(APP,'*out')
+ pathUtil.join(APP,'*log')
+ ]
+ spawn('rm', args, {stdio:'inherit',cwd:APP}).on('exit',next)
+
+compile = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ spawn(COFFEE, ['-co', OUT, SRC], {stdio:'inherit',cwd:APP}).on('exit',next)
+
+example = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ compile opts, safe next, ->
+ command = pathUtil.resolve("#{__dirname}/bin/watchr")
+ spawn(command, [], {stdio:'inherit',cwd:APP,env:process.env}).on('exit',next)
+
+watch = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ spawn(COFFEE, ['-wco', OUT, SRC], {stdio:'inherit',cwd:APP}).on('exit',next)
+
+install = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ spawn(NPM, ['install'], {stdio:'inherit',cwd:APP}).on('exit',next)
+
+reset = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ clean opts, safe next, ->
+ install opts, safe next, ->
+ compile opts, next
+
+setup = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ install opts, safe next, ->
+ compile opts, next
+
+test = (opts,next) ->
+ (next = opts; opts = {}) unless next?
+ spawn(NPM, ['test'], {stdio:'inherit',cwd:APP}).on('exit',next)
+
+finish = (err) ->
+ throw err if err
+ console.log('OK')
+
+
+# -----------------
+# Commands
+
+# clean
+task 'clean', 'clean up instance', ->
+ clean finish
+
+# compile
+task 'compile', 'compile our files', ->
+ compile finish
+
+# dev/watch
+task 'dev', 'watch and recompile our files', ->
+ watch finish
+task 'watch', 'watch and recompile our files', ->
+ watch finish
+
+# example
+task 'example', 'run the example file', ->
+ example finish
+
+# install
+task 'install', 'install dependencies', ->
+ install finish
+
+# reset
+task 'reset', 'reset instance', ->
+ reset finish
+
+# setup
+task 'setup', 'setup for development', ->
+ setup finish
+
+# test
+task 'test', 'run our tests', ->
+ test finish
+
+# test-debug
+task 'test-debug', 'run our tests in debug mode', ->
+ test {debug:true}, finish
+
+# test-prepare
+task 'test-prepare', 'prepare out tests', ->
+ setup finish
+
View
10 History.md
@@ -1,5 +1,15 @@
## History
+- v2.3.4 January 8, 2013
+ - Better handling and detection of failed watching operations
+ - Better handling of duplicated events
+ - Watching is now an atomic operation
+ - If watching fails for a descendant, we will close everything related to that watch operation of the eve
+ - We now prefer the `watch` method over the `watchFile` method
+ - This offers great reliability and way less CPU and memory foot print
+ - If you still wish to prefer `watchFile`, then set the new configuration option `preferredMethod` to `watchFile`
+ - Closes [issue #30](https://github.com/bevry/watchr/issues/30) thanks to [Howard Tyson](https://github.com/tizzo)
+
- v2.3.3 January 8, 2013
- Added `outputLog` option
- Added `ignorePaths` option
View
66 Makefile
@@ -1,66 +0,0 @@
-# If you change something here, be sure to reflect the changes in:
-# - the scripts section of the package.json file
-# - the .travis.yml file
-
-# -----------------
-# Variables
-
-BIN=node_modules/.bin
-COFFEE=$(BIN)/coffee
-OUT=out
-SRC=src
-
-
-# -----------------
-# Documentation
-
-# Usage: coffee [options] path/to/script.coffee -- [args]
-# -b, --bare compile without a top-level function wrapper
-# -c, --compile compile to JavaScript and save as .js files
-# -o, --output set the output directory for compiled JavaScript
-# -w, --watch watch scripts for changes and rerun commands
-
-
-# -----------------
-# Commands
-
-# Watch and recompile our files
-dev:
- $(COFFEE) -cbwo $(OUT) $(SRC)
-
-# Compile our files
-compile:
- $(COFFEE) -cbo $(OUT) $(SRC)
-
-# Clean up
-clean:
- rm -Rf $(OUT) node_modules *.log
-
-# Install dependencies
-install:
- npm install
-
-# Reset
-reset:
- make clean
- make install
- make compile
-
-# Ensure everything is ready for our tests (used by things like travis)
-test-prepare:
- make reset
-
-# Run our tests
-test:
- npm test
-
-# Example
-example-run:
- make compile
- ./bin/watchr
-example:
- make example-run -i
-
-
-# Ensure the listed commands always re-run and are never cached
-.PHONY: dev compile clean install reset test-prepare test example example-run
View
29 README.md
@@ -16,13 +16,14 @@ You install it via `npm install watchr` and use it via `require('watchr').watch(
- when using the `path` configuration option: `err, watcherInstance`
- when using the `paths` configuration option: `err, [watcherInstance,...]`
- `stat` (optional, defaults to `null`) a file stat object to use for the path, instead of fetching a new one
+- `interval` (optional, defaults to `100`) for systems that poll to detect file changes, how often should it poll in millseconds
+- `persistent` (optional, defaults to `true`) whether or not we should keep the node process alive for as long as files are still being watched
+- `duplicateDelay` (optional, defaults to `1000`) sometimes events will fire really fast, this delay is set in place so we don't fire the same event within the timespan
+- `preferredMethod` (optional, defaults to `watch`) which watching method should try first? `watch` or `watchFile`
- `ignorePaths` (optional, defaults to `false`) an array of full paths to ignore
- `ignoreHiddenFiles` (optional, defaults to `false`) whether or not to ignored files which filename starts with a `.`
- `ignoreCommonPatterns` (optional, defaults to `true`) whether or not to ignore common undesirable file patterns (e.g. `.svn`, `.git`, `.DS_Store`, `thumbs.db`, etc)
- `ignoreCustomPatterns` (optional, defaults to `null`) any custom ignore patterns that you would also like to ignore along with the common patterns
-- `interval` (optional, defaults to `100`) for systems that poll to detect file changes, how often should it poll in millseconds
-- `persistent` (optional, defaults to `true`) whether or not we should keep the node process alive for as long as files are still being watched
-
The following events are available to your via the listeners:
@@ -39,7 +40,7 @@ To wrap it all together, it would look like this:
``` javascript
// Require
-var watchr = require('watchr')
+var watchr = require('watchr');
// Watch a directory or file
console.log('Watch our paths');
@@ -53,15 +54,22 @@ watchr.watch({
console.log('an error occured:', err);
},
watching: function(err,watcherInstance,isWatching){
- console.log('a new watcher instance finished setting up', arguments);
+ if (err) {
+ console.log("watching the path " + watcherInstance.path + " failed with error", err);
+ } else {
+ console.log("watching the path " + watcherInstance.path + " completed");
+ }
},
change: function(changeType,filePath,fileCurrentStat,filePreviousStat){
console.log('a change event occured:',arguments);
}
},
next: function(err,watchers){
- // Watching all setup
- console.log('Now watching our paths', arguments);
+ if (err) {
+ return console.log("watching everything failed with error", err);
+ } else {
+ console.log('watching everything completed', watchers);
+ }
// Close watchers after 10 seconds
setTimeout(function(){
@@ -75,7 +83,12 @@ watchr.watch({
});
```
-You can test the above code snippet by installing watchr globally by running `npm install -g watchr` to install watchr, then `watchr <pathToWatch>` to watchr a particular path, and performing some file system modifications on that path.
+You can test the above code snippet by running the following:
+
+```
+npm install watchr
+./node_modules/.bin/watchr
+```
## Support
View
43 bin/watchr
@@ -1,2 +1,43 @@
#!/usr/bin/env node
-require(__dirname+'/../out/bin/watchr');
+// Require
+var watchr = require(__dirname+'/../out/lib/watchr');
+
+// Watch a directory or file
+console.log('Watch our paths');
+watchr.watch({
+ paths: [process.cwd()],
+ listeners: {
+ log: function(logLevel){
+ console.log('a log message occured:', arguments);
+ },
+ error: function(err){
+ console.log('an error occured:', err);
+ },
+ watching: function(err,watcherInstance,isWatching){
+ if (err) {
+ console.log("watching the path " + watcherInstance.path + " failed with error", err);
+ } else {
+ console.log("watching the path " + watcherInstance.path + " completed");
+ }
+ },
+ change: function(changeType,filePath,fileCurrentStat,filePreviousStat){
+ console.log('a change event occured:',arguments);
+ }
+ },
+ next: function(err,watchers){
+ if (err) {
+ return console.log("watching everything failed with error", err);
+ } else {
+ console.log('watching everything completed', watchers);
+ }
+
+ // Close watchers after 10 seconds
+ setTimeout(function(){
+ var i;
+ console.log('Stop watching our paths');
+ for ( i=0; i<watchers.length; i++ ) {
+ watchers[i].close();
+ }
+ },10*1000);
+ }
+});
View
35 example.js
@@ -1,35 +0,0 @@
-// Require
-var watchr = require('./out/lib/watchr');
-
-// Watch a directory or file
-console.log('Watch our paths');
-watchr.watch({
- paths: [__dirname],
- listeners: {
- log: function(logLevel){
- console.log('a log message occured:', arguments);
- },
- error: function(err){
- console.log('an error occured:', err);
- },
- watching: function(err,watcherInstance,isWatching){
- console.log('a new watcher instance finished setting up', arguments);
- },
- change: function(changeType,filePath,fileCurrentStat,filePreviousStat){
- console.log('a change event occured:',arguments);
- }
- },
- next: function(err,watchers){
- // Watching all setup
- console.log('Now watching our paths', arguments);
-
- // Close watchers after 10 seconds
- setTimeout(function(){
- var i;
- console.log('Stop watching our paths');
- for ( i=0; i<watchers.length; i++ ) {
- watchers[i].close();
- }
- },10*1000);
- }
-});
View
4 package.json
@@ -1,6 +1,6 @@
{
"name": "watchr",
- "version": "2.3.3",
+ "version": "2.3.4",
"description": "Better file system watching for Node.js",
"homepage": "https://github.com/bevry/watchr",
"keywords": [
@@ -30,7 +30,7 @@
"node": ">=0.4"
},
"dependencies": {
- "bal-util": "~1.15.4"
+ "bal-util": "~1.16.3"
},
"devDependencies": {
"coffee-script": "~1.4.0",
View
24 src/bin/watchr.coffee
@@ -1,24 +0,0 @@
-#!/usr/bin/env coffee
-path = require('path')
-watchr = require(__dirname+'/../lib/watchr')
-cwd = process.cwd()
-watchPath =
- if process.argv.length is 3
- path.resolve(cwd,process.argv[2])
- else
- cwd
-changes = 0
-watchr.watch(
- path: watchPath
- listeners:
- log: (args...) ->
- console.log('a log message occured:', args);
- error: (err) ->
- console.log('an error occured:', err);
- watching: (args...) ->
- console.log('a new watcher instance finished setting up', args);
- change: (args...) ->
- console.log('a change event occured:',args);
- next: (args...) ->
- console.log('watching for all our paths has completed', args);
-)
View
397 src/lib/watchr.coffee
@@ -14,6 +14,27 @@ balUtil = require('bal-util')
# This provides us with the event system that we use for binding and trigger events
EventEmitter = require('events').EventEmitter
+# Require the optional domain logic introduced in node 0.8
+try
+ domain = require('domain')
+catch err
+ domain = false
+
+# Safe Execute
+# Wrap the method in a domain if we can, otherwise wrap it in a try catch
+# If an error occurs, go to the next callback
+safeExecute = (next,method) ->
+ if domain
+ d = domain.create()
+ d.on 'error', (err) ->
+ return next(err,false)
+ d.run(method)
+ else
+ try
+ method()
+ catch err
+ next(err,false)
+
###
Now to make watching files more convient and managed, we'll create a class which we can use to attach to each file.
It'll provide us with the API and abstraction we need to accomplish difficult things like recursion.
@@ -25,6 +46,7 @@ Events:
- `watching` for when watching of the path has completed, receives the arguments `err, watcherInstance, isWatching`
- `change` for listening to change events, receives the arguments `changeType, fullPath, currentStat, previousStat`
###
+watchersTotal = 0
watchers = {}
Watcher = class extends EventEmitter
# The path this class instance is attached to
@@ -57,9 +79,6 @@ Watcher = class extends EventEmitter
# A single path to watch
path: null
- # Should we output log messages?
- outputLog: false
-
# Listener (optional, detaults to null)
# single change listener, forwaded to @listen
listener: null
@@ -72,6 +91,29 @@ Watcher = class extends EventEmitter
# a file stat object to use for the path, instead of fetching a new one
stat: null
+ # Should we output log messages?
+ outputLog: false
+
+ # Interval (optional, defaults to `100`)
+ # for systems that poll to detect file changes, how often should it poll in millseconds
+ # if you are watching a lot of files, make this value larger otherwise you will have huge memory load
+ # only appliable to the `watchFile` watching method
+ interval: 100
+
+ # Persistent (optional, defaults to `true`)
+ # whether or not we should keep the node process alive for as long as files are still being watched
+ # only appliable to the `watchFile` watching method
+ persistent: true
+
+ # Duplicate Delay (optional, defaults to `1000`)
+ # sometimes events will fire really fast, this delay is set in place to ensure we don't fire the same event
+ # within the duplicateDelay timespan
+ duplicateDelay: 1*1000
+
+ # Preferred Method (optional, detaults to `watch`)
+ # which node.js fsUtil watching method should we attempt first, either `watch` or `watchFile`
+ preferredMethod: 'watch'
+
# Ignore Paths (optional, defaults to `false`)
# array of paths that we should ignore
ignorePaths: false
@@ -88,13 +130,6 @@ Watcher = class extends EventEmitter
# any custom ignore patterns that you would also like to ignore along with the common patterns
ignoreCustomPatterns: null
- # Interval (optional, defaults to `100`)
- # for systems that poll to detect file changes, how often should it poll in millseconds
- interval: 100
-
- # Persistent (optional, defaults to `true`)
- # whether or not we should keep the node process alive for as long as files are still being watched
- persistent: true
# Now it's time to construct our watcher
# We give it a path, and give it some events to use
@@ -160,7 +195,7 @@ Watcher = class extends EventEmitter
# We need something to bubble events up from a child file all the way up the top
bubble: (args...) =>
# Log
- @log('debug',"bubble on #{@path} with the args:",args)
+ #@log('debug',"bubble on #{@path} with the args:",args)
# Trigger
@emit(args...)
@@ -217,6 +252,42 @@ Watcher = class extends EventEmitter
@
###
+ Emit Safe
+ Sometimes events can fire incredibly quickly in which case we'll determine multiple events
+ This alias for emit('change',...) will check to see if the event has already been fired recently
+ and if it has, then ignore it
+ ###
+ cacheTimeout: null
+ cachedEvents: null
+ emitSafe: (args...) ->
+ # Prepare
+ me = @
+ config = @config
+
+ # Clear duplicate timeout
+ clearTimeout(@cacheTimeout) if @cacheTimeout?
+ @cacheTimeout = setTimeout(
+ ->
+ me.cachedEvents = []
+ me.cacheTimeout = null
+ config.duplicateDelay
+ )
+ @cachedEvents ?= []
+
+ # Check duplicate
+ thisEvent = args.toString()
+ if thisEvent in @cachedEvents
+ @log('debug',"event ignored on #{@path} due to duplicate:", args)
+ return @
+ @cachedEvents.push(thisEvent)
+
+ # Fire the event
+ @emit(args...)
+
+ # Chain
+ @
+
+ ###
Listener
A change event has fired
@@ -226,8 +297,7 @@ Watcher = class extends EventEmitter
- for deleted and updated files, it will fire on the file
- for created files, it will fire on the directory
- fsWatcher:
- - eventName is always 'change'
- - 'rename' is not yet implemented by node
+ - eventName is either 'change' or 'rename', this value cannot be trusted
- currentStat still exists even for deleted/renamed files
- previousStat is accurate, however we already have this
- for deleted and changed files, it will fire on the file
@@ -283,53 +353,53 @@ Watcher = class extends EventEmitter
if isTheSame() is false
# Scan children
balUtil.readdir fileFullPath, (err,newFileRelativePaths) =>
- # Error
+ # Error?
return @emit('error',err) if err
+ # Check for deleted files
+ # by cycling through our known children
+ balUtil.each @children, (childFileWatcher,childFileRelativePath) =>
+ # Skip if this is a new file (not a deleted file)
+ return if childFileRelativePath in newFileRelativePaths
+
+ # Fetch full path
+ childFileFullPath = pathUtil.join(fileFullPath,childFileRelativePath)
+
+ # Skip if ignored file
+ return if @isIgnoredPath(childFileFullPath)
+
+ # Emit the event
+ @log('debug','determined delete:',childFileFullPath,'via:',fileFullPath)
+ @closeChild(childFileRelativePath,'deleted')
+
# Check for new files
balUtil.each newFileRelativePaths, (childFileRelativePath) =>
- # Already watching the new file
+ # Skip if we are already watching this file
return if @children[childFileRelativePath]?
- # Not yet watching the new file
+ @children[childFileRelativePath] = false # reserve this file
# Fetch full path
childFileFullPath = pathUtil.join(fileFullPath,childFileRelativePath)
- # Is ignored file?
+ # Skip if ignored file
return if @isIgnoredPath(childFileFullPath)
# Fetch the stat for the new file
balUtil.stat childFileFullPath, (err,childFileStat) =>
- # Error
+ # Error?
return @emit('error',err) if err
# Emit the event
@log('debug','determined create:',childFileFullPath,'via:',fileFullPath)
- @emit('change','create',childFileFullPath,childFileStat,null)
+ @emitSafe('change','create',childFileFullPath,childFileStat,null)
@watchChild(childFileFullPath,childFileRelativePath,childFileStat)
- # Check for deleted files
- balUtil.each @children, (childFileWatcher,childFileRelativePath) =>
- # File still exists
- return if childFileRelativePath in newFileRelativePaths
- # File was deleted
-
- # Fetch full path
- childFileFullPath = pathUtil.join(fileFullPath,childFileRelativePath)
-
- # Is ignored file?
- return if @isIgnoredPath(childFileFullPath)
-
- # Emit the event
- @log('debug','determined delete:',childFileFullPath,'via:',fileFullPath)
- @closeChild(childFileRelativePath,'deleted')
-
# If we are a file, lets simply emit the change event
else
# It has changed, so let's emit a change event
@log('debug','determined update:',fileFullPath)
- @emit('change','update',fileFullPath,currentStat,previousStat)
+ @emitSafe('change','update',fileFullPath,currentStat,previousStat)
# Check if the file still exists
balUtil.exists fileFullPath, (exists) ->
@@ -369,35 +439,40 @@ Watcher = class extends EventEmitter
for own childRelativePath of @children
@closeChild(childRelativePath,reason)
- # Close listener
+ # Close watchFile listener
if @method is 'watchFile'
fsUtil.unwatchFile(@path)
- else if @method is 'watch' and @fswatcher
+
+ # Close watch listener
+ if @fswatcher?
@fswatcher.close()
@fswatcher = null
# Updated state
if reason is 'deleted'
@state = 'deleted'
- @emit('change','delete',@path,null,@stat)
+ @emitSafe('change','delete',@path,null,@stat)
+ else if reason is 'failure'
+ @state = 'closed'
+ @log('warn',"Failed to watch the path #{@path}")
else
@state = 'closed'
# Delete our watchers reference
- delete watchers[@path] if watchers[@path]?
+ if watchers[@path]?
+ delete watchers[@path]
+ watchersTotal--
# Chain
@
# Close a child
closeChild: (fileRelativePath,reason) ->
- # Prepare
- watcher = @children[fileRelativePath]
-
# Check
- if watcher
+ if @children[fileRelativePath]?
+ watcher = @children[fileRelativePath]
+ watcher.close(reason) if watcher # could be "fase" for reservation
delete @children[fileRelativePath]
- watcher.close(reason)
# Chain
@
@@ -413,37 +488,35 @@ Watcher = class extends EventEmitter
me = @
config = @config
- # Watch the file
- watcher = watch(
+ # Watch the file if we aren't already
+ me.children[fileRelativePath] or= watch(
+ # Custom
path: fileFullPath
stat: fileStat
- ignorePaths: config.ignorePaths
- ignoreHiddenFiles: config.ignoreHiddenFiles
- ignoreCommonPatterns: config.ignoreCommonPatterns
- ignoreCustomPatterns: config.ignoreCustomPatterns
listeners:
- 'change': (args...) =>
+ 'log': me.bubbler('log')
+ 'change': (args...) ->
[changeType,path] = args
if changeType is 'delete' and path is fileFullPath
- @closeChild(fileRelativePath,'deleted')
+ me.closeChild(fileRelativePath,'deleted')
me.bubble('change', args...)
'error': me.bubbler('error')
- next: (args...) ->
- # Prepare
- [err] = args
-
- # Stop if an error happened
- return next?(err) if err
-
- # Store the child watcher in us
- me.children[fileRelativePath] = watcher
-
- # Proceed to the next file
- next?(args...)
+ next: next
+
+ # Inherit
+ outputLog: config.outputLog
+ interval: config.interval
+ persistent: config.persistent
+ duplicateDelay: config.duplicateDelay
+ preferredMethod: config.preferredMethod
+ ignorePaths: config.ignorePaths
+ ignoreHiddenFiles: config.ignoreHiddenFiles
+ ignoreCommonPatterns: config.ignoreCommonPatterns
+ ignoreCustomPatterns: config.ignoreCustomPatterns
)
# Return
- return watcher
+ return me.children[fileRelativePath]
# Is Ignored Path
isIgnoredPath: (path,opts={}) =>
@@ -462,6 +535,123 @@ Watcher = class extends EventEmitter
return ignore
###
+ Watch Children
+ next(err,result)
+ ###
+ watchChildren: (next) ->
+ # Prepare
+ me = @
+ config = @config
+
+ # Cycle through the directory if necessary
+ if @isDirectory
+ balUtil.scandir(
+ # Path
+ path: @path
+
+ # Options
+ ignorePaths: config.ignorePaths
+ ignoreHiddenFiles: config.ignoreHiddenFiles
+ ignoreCommonPatterns: config.ignoreCommonPatterns
+ ignoreCustomPatterns: config.ignoreCustomPatterns
+ recurse: false
+
+ # Next
+ next: (err) ->
+ return next(err,err? is false)
+
+ # File and Directory Actions
+ action: (fileFullPath,fileRelativePath,nextFile,fileStat) ->
+ # Check we are still releveant
+ if me.state isnt 'active'
+ return nextFile(null,true) # skip without error
+
+ # Watch this child
+ me.watchChild fileFullPath, fileRelativePath, fileStat, (err) ->
+ nextFile(err)
+ )
+ else
+ next(null,true)
+
+ # Chain
+ return @
+
+ ###
+ Watch Self
+ ###
+ watchSelf: (next) ->
+ # Prepare
+ me = @
+ config = @config
+
+ # Reset the method
+ @method = null
+
+ # Setup our watch methods
+ methods =
+ # Try with fsUtil.watch
+ watch: (next) ->
+ # Check
+ return next(null,false) unless fsUtil.watch?
+
+ # Watch
+ safeExecute next, ->
+ me.fswatcher = fsUtil.watch me.path, (args...) ->
+ me.listener.apply(me,args)
+ me.method = 'watch'
+ return next(null,true)
+
+ # Try fsUtil.watchFile
+ watchFile: (next) ->
+ # Check
+ return next(null,false) unless fsUtil.watchFile?
+
+ # Watch
+ safeExecute next, ->
+ watchFileOpts =
+ persistent: config.persistent
+ interval: config.interval
+ fsUtil.watchFile me.path, watchFileOpts, (args...) ->
+ me.listener.apply(me,args)
+ me.method = 'watchFile'
+ return next(null,true)
+
+ # Complete
+ complete = (success) ->
+ success ?= true
+ if success
+ me.state = 'active'
+ next(null,true)
+ else
+ me.close('failure')
+ next(null,false)
+
+ # Watch
+ if config.preferredMethod is 'watch'
+ # Try watch
+ methods.watch (err,success) ->
+ me.emit('error',err) if err
+ return complete(success) if success
+
+ # Try watchFile
+ methods.watchFile (err,success) ->
+ me.emit('error',err) if err
+ return complete(success)
+ else
+ # Try watchFile
+ methods.watchFile (err,success) ->
+ me.emit('error',err) if err
+ return complete(success) if success
+
+ # Try watch
+ methods.watchFile (err,success) ->
+ me.emit('error',err) if err
+ return complete(success)
+
+ # Chain
+ return @
+
+ ###
Watch
Setup the native watching handlers for our path so we can receive updates on when things happen
If the next argument has been received, then add it is a once listener for the watching event
@@ -469,6 +659,7 @@ Watcher = class extends EventEmitter
If we are a directory, let's recurse
If we are deleted, then don't error but return the isWatching argument of our completion callback as false
Once watching has completed for this directory and all children, then emit the watching event
+ next(err,watcherInstance,success)
###
watch: (next) ->
# Prepare
@@ -501,68 +692,29 @@ Watcher = class extends EventEmitter
# Log
@log('debug',"watch: #{@path}")
- # Prepare Start Watching
- startWatching = =>
- # Create a set of tasks
- tasks = new balUtil.Group (err) =>
- return @emit('watching',err,@,false) if err
- return @emit('watching',err,@,true)
- tasks.total = 2
-
- # Cycle through the directory if necessary
- if @isDirectory
- balUtil.scandir(
- # Path
- path: @path
-
- # Options
- ignorePaths: config.ignorePaths
- ignoreHiddenFiles: config.ignoreHiddenFiles
- ignoreCommonPatterns: config.ignoreCommonPatterns
- ignoreCustomPatterns: config.ignoreCustomPatterns
- recurse: false
-
- # Next
- next: (err) ->
- tasks.complete(err)
-
- # File and Directory Actions
- action: (fileFullPath,fileRelativePath,nextFile,fileStat) ->
- # Watch it
- me.watchChild fileFullPath, fileRelativePath, fileStat, (err) ->
- nextFile(err)
- )
+ # Prepare
+ complete = (err,result) ->
+ # Prepare
+ err ?= null
+ result ?= true
+
+ # Handle
+ if err or !result
+ me.close()
+ me.emit('watching',err,me,false)
else
- tasks.complete()
-
- # Watch the current file/directory
- try
- # Try first with fsUtil.watchFile
- watchFileOpts =
- persistent: config.persistent
- interval: config.interval
- fsUtil.watchFile @path, watchFileOpts, (args...) ->
- me.listener.apply(me,args)
- @method = 'watchFile'
- catch err
- # Then try with fsUtil.watch
- @fswatcher = fsUtil.watch @path, (args...) ->
- me.listener.apply(me,args)
- @method = 'watch'
-
- # We are now watching so set the state as active
- @state = 'active'
- tasks.complete()
+ me.emit('watching',null,me,true)
# Check if we still exist
- balUtil.exists @path, (exists) =>
+ balUtil.exists @path, (exists) ->
# Check
- unless exists
- # We don't exist anymore, move along
- return @emit('watching',null,@,false)
+ return complete(null,false) unless exists
# Start watching
- startWatching()
+ me.watchSelf (err,result) ->
+ return complete(err,result) if err or !result
+ me.watchChildren (err,result) ->
+ return complete(err,result)
# Chain
@
@@ -606,6 +758,7 @@ createWatcher = (opts,next) ->
watcher = new Watcher opts, (err) ->
next?(err,watcher)
watchers[path] = watcher
+ ++watchersTotal
# Return
return watcher
View
15 src/test/everything.test.coffee
@@ -37,15 +37,16 @@ joe.suite 'watchr', (suite,test) ->
watcher = null
# Change detection
- actualChanges = 0
+ changes = []
checkChanges = (expectedChanges,next) ->
wait batchDelay, ->
- assert.equal(actualChanges, expectedChanges, "#{actualChanges} changes ran out of #{expectedChanges} changes")
- actualChanges = 0
+ console.log(changes) if changes.length isnt expectedChanges
+ assert.equal(changes.length, expectedChanges, "#{changes.length} changes ran out of #{expectedChanges} changes")
+ changes = []
next()
changeHappened = (args...) ->
- ++actualChanges
- console.log("a watch event occured: #{actualChanges}", args) if debug
+ changes.push(args)
+ console.log("a watch event occured: #{changes.length}", args) if debug
# Files changes
writeFile = (fileRelativePath) ->
@@ -75,9 +76,9 @@ joe.suite 'watchr', (suite,test) ->
watchr.watch(
path: outPath
listener: changeHappened
- ignorePaths: [outPath+'/blah']
+ ignorePaths: [pathUtil.join(outPath,'blah')]
ignoreHiddenFiles: true
- #outputLog: true
+ outputLog: true
next: (err,_watcher) ->
watcher = _watcher
wait batchDelay, -> done(err)
Please sign in to comment.
Something went wrong with that request. Please try again.