Navigation Menu

Skip to content

Commit

Permalink
fs watcher binding
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor Zinkovsky authored and ry committed Sep 23, 2011
1 parent 8d37b6c commit 8fe5712
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 0 deletions.
71 changes: 71 additions & 0 deletions lib/fs.js
Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -73,6 +73,7 @@
],

'sources': [
'src/fs_event_wrap.cc',
'src/cares_wrap.cc',
'src/handle_wrap.cc',
'src/node.cc',
Expand Down
132 changes: 132 additions & 0 deletions src/fs_event_wrap.cc
@@ -0,0 +1,132 @@
#include <node.h>
#include <handle_wrap.h>

#include <stdlib.h>

using namespace v8;

namespace node {

#define UNWRAP \
assert(!args.Holder().IsEmpty()); \
assert(args.Holder()->InternalFieldCount() > 0); \
FSEventWrap* wrap = \
static_cast<FSEventWrap*>(args.Holder()->GetPointerFromInternalField(0)); \
if (!wrap) { \
SetErrno(UV_EBADF); \
return scope.Close(Integer::New(-1)); \
}

class FSEventWrap: public HandleWrap {
public:
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments& args);
static Handle<Value> Start(const Arguments& args);

private:
FSEventWrap(Handle<Object> 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> object): HandleWrap(object,
(uv_handle_t*)&handle_) {
handle_.data = reinterpret_cast<void*>(this);
}


FSEventWrap::~FSEventWrap() {
}


void FSEventWrap::Initialize(Handle<Object> target) {
HandleWrap::Initialize(target);

HandleScope scope;

Local<FunctionTemplate> 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<FunctionTemplate>::New(t)->GetFunction());
}


Handle<Value> FSEventWrap::New(const Arguments& args) {
HandleScope scope;

assert(args.IsConstructCall());
new FSEventWrap(args.This());

return scope.Close(args.This());
}


Handle<Value> 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<String> eventStr;

FSEventWrap* wrap = reinterpret_cast<FSEventWrap*>(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<Value> argv[3] = {
Integer::New(status),
eventStr,
filename ? (Local<Value>)String::New(filename) : Local<Value>::New(v8::Null())
};

MakeCallback(wrap->object_, "onchange", 3, argv);
}
} // namespace node

NODE_MODULE(node_fs_event_wrap, node::FSEventWrap::Initialize);
1 change: 1 addition & 0 deletions src/node_extensions.h
Expand Up @@ -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

143 changes: 143 additions & 0 deletions 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);
1 change: 1 addition & 0 deletions wscript
Expand Up @@ -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
"""
Expand Down

0 comments on commit 8fe5712

Please sign in to comment.