diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown index 69a8030c5b..dee28d943c 100644 --- a/doc/api/fs.markdown +++ b/doc/api/fs.markdown @@ -575,10 +575,14 @@ no-op, not an error. Watch for changes on `filename`, where `filename` is either a file or a directory. The returned object is a [fs.FSWatcher](#fs_class_fs_fswatcher). -The second argument is optional. The `options` if provided should be an object -containing a boolean member `persistent`, which indicates whether the process -should continue to run as long as files are being watched. The default is -`{ persistent: true }`. +The second argument is optional. The `options` if provided should be an object. +The supported boolean members are `persistent` and `recursive`. `persistent` +indicates whether the process should continue to run as long as files are being +watched. `recursive` indicates whether all subdirectories should be watched, or +only the current directory. This applies when a directory is specified, and only +on supported platforms (See Caveats below). + +The default is `{ persistent: true, recursive: false }`. The listener callback gets two arguments `(event, filename)`. `event` is either 'rename' or 'change', and `filename` is the name of the file which triggered @@ -591,6 +595,10 @@ the event. The `fs.watch` API is not 100% consistent across platforms, and is unavailable in some situations. +The recursive option is currently supported on OS X. Only FSEvents supports this +type of file watching so it is unlikely any additional platforms will be added +soon. + #### Availability @@ -599,7 +607,8 @@ This feature depends on the underlying operating system providing a way to be notified of filesystem changes. * On Linux systems, this uses `inotify`. -* On BSD systems (including OS X), this uses `kqueue`. +* On BSD systems, this uses `kqueue`. +* On OS X, this uses `kqueue` for files and 'FSEvents' for directories. * On SunOS systems (including Solaris and SmartOS), this uses `event ports`. * On Windows systems, this feature depends on `ReadDirectoryChangesW`. diff --git a/lib/fs.js b/lib/fs.js index b92aa435ba..77266ebe8e 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1014,9 +1014,11 @@ function FSWatcher() { } util.inherits(FSWatcher, EventEmitter); -FSWatcher.prototype.start = function(filename, persistent) { +FSWatcher.prototype.start = function(filename, persistent, recursive) { nullCheck(filename); - var err = this._handle.start(pathModule._makeLong(filename), persistent); + var err = this._handle.start(pathModule._makeLong(filename), + persistent, + recursive); if (err) { this._handle.close(); throw errnoException(err, 'watch'); @@ -1042,9 +1044,10 @@ fs.watch = function(filename) { } if (util.isUndefined(options.persistent)) options.persistent = true; + if (util.isUndefined(options.recursive)) options.recursive = false; watcher = new FSWatcher(); - watcher.start(filename, options.persistent); + watcher.start(filename, options.persistent, options.recursive); if (listener) { watcher.addListener('change', listener); diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 4941ded074..1fc901e2fa 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -108,12 +108,15 @@ void FSEventWrap::Start(const FunctionCallbackInfo& args) { String::Utf8Value path(args[0]); - int err = uv_fs_event_init(wrap->env()->event_loop(), &wrap->handle_); + int flags = 0; + if (args[1]->IsTrue()) + flags |= UV_FS_EVENT_RECURSIVE; + int err = uv_fs_event_init(wrap->env()->event_loop(), &wrap->handle_); if (err == 0) { wrap->initialized_ = true; - err = uv_fs_event_start(&wrap->handle_, OnEvent, *path, 0); + err = uv_fs_event_start(&wrap->handle_, OnEvent, *path, flags); if (err == 0) { // Check for persistent argument diff --git a/test/simple/test-fs-watch-recursive.js b/test/simple/test-fs-watch-recursive.js new file mode 100644 index 0000000000..a5ebad605f --- /dev/null +++ b/test/simple/test-fs-watch-recursive.js @@ -0,0 +1,64 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var path = require('path'); +var fs = require('fs'); + +if (process.platform === 'darwin') { + var watchSeenOne = 0; + + var testDir = common.tmpDir; + + var filenameOne = 'watch.txt'; + var testsubdir = path.join(testDir, 'testsubdir'); + var relativePathOne = path.join('testsubdir', filenameOne); + var filepathOne = path.join(testsubdir, filenameOne); + + process.on('exit', function() { + assert.ok(watchSeenOne > 0); + }); + + function cleanup() { + try { fs.unlinkSync(filepathOne); } catch (e) { } + try { fs.rmdirSync(testsubdir); } catch (e) { } + }; + + try { fs.mkdirSync(testsubdir, 0700); } catch (e) {} + fs.writeFileSync(filepathOne, 'hello'); + + assert.doesNotThrow(function() { + var watcher = fs.watch(testDir, {recursive: true}); + watcher.on('change', function(event, filename) { + assert.ok('change' === event || 'rename' === event); + assert.equal(relativePathOne, filename); + + watcher.close(); + cleanup(); + ++watchSeenOne; + }); + }); + + setTimeout(function() { + fs.writeFileSync(filepathOne, 'world'); + }, 10); +}