From 8fe5712477e94879664d5d420c72f273c0a888ff Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Thu, 22 Sep 2011 16:18:08 -0700 Subject: [PATCH] fs watcher binding --- lib/fs.js | 71 +++++++++++++++++ node.gyp | 1 + src/fs_event_wrap.cc | 132 ++++++++++++++++++++++++++++++++ src/node_extensions.h | 1 + test/simple/test-fs-watch.js | 143 +++++++++++++++++++++++++++++++++++ wscript | 1 + 6 files changed, 349 insertions(+) create mode 100644 src/fs_event_wrap.cc create mode 100644 test/simple/test-fs-watch.js diff --git a/lib/fs.js b/lib/fs.js index 3ea945d861..9955e39661 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -589,6 +589,73 @@ fs.writeFileSync = function(path, data, encoding) { fs.closeSync(fd); }; + +function errnoException(errorno, syscall) { + // TODO make this more compatible with ErrnoException from src/node.cc + // Once all of Node is using this function the ErrnoException from + // src/node.cc should be removed. + var e = new Error(syscall + ' ' + errorno); + e.errno = e.code = errorno; + e.syscall = syscall; + return e; +} + + +function FSWather() { + var self = this; + var FSEvent = process.binding('fs_event_wrap').FSEvent; + this._handle = new FSEvent(); + + this._handle.onchange = function(status, event, filename) { + if (status) { + self.emit('error', errnoException(errno, 'watch')); + } else { + self.emit('change', event, filename); + } + }; +} +util.inherits(FSWather, EventEmitter); + +FSWather.prototype.start = function(filename, persistent) { + var r = this._handle.start(filename, persistent); + + if (r) { + this._handle.close(); + throw errnoException(errno, "watch") + } +}; + +FSWather.prototype.close = function() { + this._handle.close(); +}; + +fs.watch = function(filename) { + var watcher; + var options; + var listener; + + if ('object' == typeof arguments[1]) { + options = arguments[1]; + listener = arguments[2]; + } else { + options = {}; + listener = arguments[1]; + } + + if (!listener) { + throw new Error('watch requires a listener function'); + } + + if (options.persistent === undefined) options.persistent = true; + + watcher = new FSWather(); + watcher.start(filename, options.persistent); + + watcher.addListener('change', listener); + return watcher; +}; + + // Stat Change Watchers function StatWatcher() { @@ -623,6 +690,10 @@ function inStatWatchers(filename) { fs.watchFile = function(filename) { + if (isWindows) { + throw new Error('use fs.watch api instead'); + } + var stat; var options; var listener; diff --git a/node.gyp b/node.gyp index acf7fbee6f..611e4d65e9 100644 --- a/node.gyp +++ b/node.gyp @@ -73,6 +73,7 @@ ], 'sources': [ + 'src/fs_event_wrap.cc', 'src/cares_wrap.cc', 'src/handle_wrap.cc', 'src/node.cc', diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc new file mode 100644 index 0000000000..9a2df34bfc --- /dev/null +++ b/src/fs_event_wrap.cc @@ -0,0 +1,132 @@ +#include +#include + +#include + +using namespace v8; + +namespace node { + +#define UNWRAP \ + assert(!args.Holder().IsEmpty()); \ + assert(args.Holder()->InternalFieldCount() > 0); \ + FSEventWrap* wrap = \ + static_cast(args.Holder()->GetPointerFromInternalField(0)); \ + if (!wrap) { \ + SetErrno(UV_EBADF); \ + return scope.Close(Integer::New(-1)); \ + } + +class FSEventWrap: public HandleWrap { +public: + static void Initialize(Handle target); + static Handle New(const Arguments& args); + static Handle Start(const Arguments& args); + +private: + FSEventWrap(Handle object); + virtual ~FSEventWrap(); + + static void OnEvent(uv_fs_event_t* handle, const char* filename, int events, + int status); + + uv_fs_event_t handle_; +}; + + +FSEventWrap::FSEventWrap(Handle object): HandleWrap(object, + (uv_handle_t*)&handle_) { + handle_.data = reinterpret_cast(this); +} + + +FSEventWrap::~FSEventWrap() { +} + + +void FSEventWrap::Initialize(Handle target) { + HandleWrap::Initialize(target); + + HandleScope scope; + + Local t = FunctionTemplate::New(New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(String::NewSymbol("FSEvent")); + + NODE_SET_PROTOTYPE_METHOD(t, "start", Start); + NODE_SET_PROTOTYPE_METHOD(t, "close", Close); + + target->Set(String::NewSymbol("FSEvent"), + Persistent::New(t)->GetFunction()); +} + + +Handle FSEventWrap::New(const Arguments& args) { + HandleScope scope; + + assert(args.IsConstructCall()); + new FSEventWrap(args.This()); + + return scope.Close(args.This()); +} + + +Handle FSEventWrap::Start(const Arguments& args) { + HandleScope scope; + + UNWRAP + + if (args.Length() < 1 || !args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New("Bad arguments"))); + } + + String::Utf8Value path(args[0]->ToString()); + + int r = uv_fs_event_init(uv_default_loop(), &wrap->handle_, *path, OnEvent); + if (r == 0) { + // Check for persistent argument + if (!args[1]->IsTrue()) { + uv_unref(uv_default_loop()); + } + } else { + SetErrno(uv_last_error(uv_default_loop()).code); + } + + return scope.Close(Integer::New(r)); +} + + +void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename, + int events, int status) { + HandleScope scope; + Local eventStr; + + FSEventWrap* wrap = reinterpret_cast(handle->data); + + assert(wrap->object_.IsEmpty() == false); + + if (status) { + SetErrno(uv_last_error(uv_default_loop()).code); + eventStr = String::Empty(); + } else { + switch (events) { + case UV_RENAME: + eventStr = String::New("rename"); + break; + case UV_CHANGE: + eventStr = String::New("change"); + break; + } + } + + Local argv[3] = { + Integer::New(status), + eventStr, + filename ? (Local)String::New(filename) : Local::New(v8::Null()) + }; + + MakeCallback(wrap->object_, "onchange", 3, argv); +} +} // namespace node + +NODE_MODULE(node_fs_event_wrap, node::FSEventWrap::Initialize); diff --git a/src/node_extensions.h b/src/node_extensions.h index d2e486d1d5..5cc7661196 100644 --- a/src/node_extensions.h +++ b/src/node_extensions.h @@ -51,6 +51,7 @@ NODE_EXT_LIST_ITEM(node_cares_wrap) NODE_EXT_LIST_ITEM(node_stdio_wrap) NODE_EXT_LIST_ITEM(node_tty_wrap) NODE_EXT_LIST_ITEM(node_process_wrap) +NODE_EXT_LIST_ITEM(node_fs_event_wrap) NODE_EXT_LIST_END diff --git a/test/simple/test-fs-watch.js b/test/simple/test-fs-watch.js new file mode 100644 index 0000000000..d6c909c4cc --- /dev/null +++ b/test/simple/test-fs-watch.js @@ -0,0 +1,143 @@ +// 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'); + +var expectFilePath = process.platform == 'win32' || process.platform == 'linux'; + +var watchSeenOne = 0; +var watchSeenTwo = 0; +var watchSeenThree = 0; + +var startDir = process.cwd(); +var testDir = common.fixturesDir; + +var filenameOne = 'watch.txt'; +var filepathOne = path.join(testDir, filenameOne); + +var filenameTwo = 'hasOwnProperty'; +var filepathTwo = filenameTwo; +var filepathTwoAbs = path.join(testDir, filenameTwo); + +var filenameThree = 'newfile.txt'; +var testsubdir = path.join(testDir, 'testsubdir'); +var filepathThree = path.join(testsubdir, filenameThree); + + +process.addListener('exit', function() { + fs.unlinkSync(filepathOne); + fs.unlinkSync(filepathTwoAbs); + fs.unlinkSync(filepathThree); + fs.rmdirSync(testsubdir); + assert.ok(watchSeenOne > 0); + assert.ok(watchSeenTwo > 0); + assert.ok(watchSeenThree > 0); +}); + + +fs.writeFileSync(filepathOne, "hello"); + +assert.throws( + function() { + fs.watch(filepathOne); + }, + function(e) { + return e.message === 'watch requires a listener function'; + } +); + +assert.doesNotThrow( + function() { + var watcher = fs.watch(filepathOne, function(event, filename) { + assert.equal('change', event); + if (expectFilePath) { + assert.equal('watch.txt', filename); + } else { + assert.equal(null, filename); + } + watcher.close(); + ++watchSeenOne; + }); + } +); + +setTimeout(function() { + fs.writeFileSync(filepathOne, "world"); +}, 1000); + + +process.chdir(testDir); + +fs.writeFileSync(filepathTwoAbs, "howdy"); + +assert.throws( + function() { + fs.watch(filepathTwo); + }, + function(e) { + return e.message === 'watch requires a listener function'; + } +); + +assert.doesNotThrow( + function() { + var watcher = fs.watch(filepathTwo, function(event, filename) { + assert.equal('change', event); + if (expectFilePath) { + assert.equal('hasOwnProperty', filename); + } else { + assert.equal(null, filename); + } + watcher.close(); + ++watchSeenTwo; + }); + } +); + +setTimeout(function() { + fs.writeFileSync(filepathTwoAbs, "pardner"); +}, 1000); + +try { fs.unlinkSync(filepathThree); } catch(e) {} +try { fs.mkdirSync(testsubdir, 0700); } catch(e) {} + +assert.doesNotThrow( + function() { + var watcher = fs.watch(testsubdir, function(event, filename) { + assert.equal('rename', event); + if (expectFilePath) { + assert.equal('newfile.txt', filename); + } else { + assert.equal(null, filename); + } + watcher.close(); + ++watchSeenThree; + }); + } +); + +setTimeout(function() { + var fd = fs.openSync(filepathThree, 'w'); + fs.closeSync(fd); +}, 1000); \ No newline at end of file diff --git a/wscript b/wscript index 404a722200..8224902151 100644 --- a/wscript +++ b/wscript @@ -890,6 +890,7 @@ def build(bld): src/cares_wrap.cc src/stdio_wrap.cc src/tty_wrap.cc + src/fs_event_wrap.cc src/process_wrap.cc src/v8_typed_array.cc """