Merge pull request #140 from humphd/issue132
Support fs.watch(), FSWatcher. Fixes #132
This commit is contained in:
commit
0cfb29eaec
48
README.md
48
README.md
|
@ -24,7 +24,6 @@ with the following differences:
|
|||
|
||||
* No synchronous versions of methods (e.g., `mkdir()` but not `mkdirSync()`).
|
||||
* No permissions (e.g., no `chown()`, `chmod()`, etc.).
|
||||
* No support (yet) for `fs.watchFile()`, `fs.unwatchFile()`, `fs.watch()`.
|
||||
* No support for stream-based operations (e.g., `fs.ReadStream`, `fs.WriteStream`).
|
||||
|
||||
Filer has other features lacking in node.js (e.g., swappable backend
|
||||
|
@ -246,6 +245,7 @@ var fs = new Filer.FileSystem();
|
|||
* [fs.fgetxattr(fd, name, callback)](#fgetxattr)
|
||||
* [fs.removexattr(path, name, callback)](#removexattr)
|
||||
* [fs.fremovexattr(fd, name, callback)](#fremovexattr)
|
||||
* [fs.watch(filename, [options], [listener])](#watch)
|
||||
|
||||
#### fs.rename(oldPath, newPath, callback)<a name="rename"></a>
|
||||
|
||||
|
@ -954,6 +954,52 @@ fs.open('/myfile', 'r', function(err, fd) {
|
|||
});
|
||||
```
|
||||
|
||||
#### fs.watch(filename, [options], [listener])<a name="watch"></a>
|
||||
|
||||
Watch for changes to a file or directory at `filename`. The object returned is an `FSWatcher`,
|
||||
which is an [`EventEmitter`](http://nodejs.org/api/events.html) with the following additional method:
|
||||
|
||||
* `close()` - stops listening for changes, and removes all listeners from this instance. Use this
|
||||
to stop watching a file or directory after calling `fs.watch()`.
|
||||
|
||||
The only supported option is `recursive`, which if `true` will cause a watch to be placed
|
||||
on a directory, and all sub-directories and files beneath it.
|
||||
|
||||
The `listener` callback gets two arguments `(event, filename)`. `event` is either `'rename'` or `'change'`,
|
||||
(currenty only `'rename'` is supported) and `filename` is the name of the file/dir which triggered the event.
|
||||
|
||||
Unlike node.js, all watch events return a path. Also, all returned paths are absolute from the root
|
||||
vs. just a relative filename.
|
||||
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
// Example 1: create a watcher to see when a file is created
|
||||
var watcher = fs.watch('/myfile', function(event, filename) {
|
||||
// event could be 'change' or 'rename' and filename will be '/myfile'
|
||||
// Stop watching for changes
|
||||
watcher.close();
|
||||
});
|
||||
fs.writeFile('/myfile', 'data');
|
||||
|
||||
// Example 2: add the listener via watcher.on()
|
||||
var watcher = fs.watch('/myfile2');
|
||||
watcher.on('change', function(event, filename) {
|
||||
// event will be 'change' and filename will be '/myfile2'
|
||||
// Stop watching for changes
|
||||
watcher.close();
|
||||
});
|
||||
fs.writeFile('/myfile2', 'data2');
|
||||
|
||||
// Example 3: recursive watch on /data dir
|
||||
var watcher = fs.watch('/data', { recursive: true }, function(event, filename) {
|
||||
// event could be 'change' or 'rename' and filename will be '/data/subdir/file'
|
||||
// Stop watching for changes
|
||||
watcher.close();
|
||||
});
|
||||
fs.writeFile('/data/subdir/file', 'data');
|
||||
```
|
||||
|
||||
### FileSystemShell<a name="FileSystemShell"></a>
|
||||
|
||||
Many common file system shell operations are available by using a `FileSystemShell` object.
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
"name": "filer",
|
||||
"version": "0.0.4",
|
||||
"main": "dist/filer.js",
|
||||
"dependencies": {
|
||||
"eventemitter2": "~0.4.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.17.1",
|
||||
"chai": "1.9.0"
|
||||
|
|
|
@ -26,6 +26,8 @@ module.exports = function(grunt) {
|
|||
'src/index.js',
|
||||
'src/shared.js',
|
||||
'src/shell.js',
|
||||
'src/fswatcher.js',
|
||||
'src/environment.js',
|
||||
'src/providers/**/*.js',
|
||||
'src/adapters/**/*.js'
|
||||
]
|
||||
|
@ -45,7 +47,8 @@ module.exports = function(grunt) {
|
|||
options: {
|
||||
paths: {
|
||||
"src": "../src",
|
||||
"build": "../build"
|
||||
"build": "../build",
|
||||
"EventEmitter": "../bower_components/eventemitter2/lib/eventemitter2"
|
||||
},
|
||||
baseUrl: "lib",
|
||||
name: "build/almond",
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
define(function(require) {
|
||||
|
||||
// Based on https://github.com/diy/intercom.js/blob/master/lib/intercom.js
|
||||
// Copyright 2012 DIY Co Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var guid = require('src/shared').guid;
|
||||
|
||||
function throttle(delay, fn) {
|
||||
var last = 0;
|
||||
return function() {
|
||||
var now = Date.now();
|
||||
if (now - last > delay) {
|
||||
last = now;
|
||||
fn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function extend(a, b) {
|
||||
if (typeof a === 'undefined' || !a) { a = {}; }
|
||||
if (typeof b === 'object') {
|
||||
for (var key in b) {
|
||||
if (b.hasOwnProperty(key)) {
|
||||
a[key] = b[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
var localStorage = (function(window) {
|
||||
if (typeof window.localStorage === 'undefined') {
|
||||
return {
|
||||
getItem : function() {},
|
||||
setItem : function() {},
|
||||
removeItem : function() {}
|
||||
};
|
||||
}
|
||||
return window.localStorage;
|
||||
}(this));
|
||||
|
||||
function Intercom() {
|
||||
var self = this;
|
||||
var now = Date.now();
|
||||
|
||||
this.origin = guid();
|
||||
this.lastMessage = now;
|
||||
this.receivedIDs = {};
|
||||
this.previousValues = {};
|
||||
|
||||
var storageHandler = function() {
|
||||
self._onStorageEvent.apply(self, arguments);
|
||||
};
|
||||
if (document.attachEvent) {
|
||||
document.attachEvent('onstorage', storageHandler);
|
||||
} else {
|
||||
window.addEventListener('storage', storageHandler, false);
|
||||
}
|
||||
}
|
||||
|
||||
Intercom.prototype._transaction = function(fn) {
|
||||
var TIMEOUT = 1000;
|
||||
var WAIT = 20;
|
||||
var self = this;
|
||||
var executed = false;
|
||||
var listening = false;
|
||||
var waitTimer = null;
|
||||
|
||||
function lock() {
|
||||
if (executed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var activeLock = localStorage.getItem(INDEX_LOCK)|0;
|
||||
if (activeLock && now - activeLock < TIMEOUT) {
|
||||
if (!listening) {
|
||||
self._on('storage', lock);
|
||||
listening = true;
|
||||
}
|
||||
waitTimer = window.setTimeout(lock, WAIT);
|
||||
return;
|
||||
}
|
||||
executed = true;
|
||||
localStorage.setItem(INDEX_LOCK, now);
|
||||
|
||||
fn();
|
||||
unlock();
|
||||
}
|
||||
|
||||
function unlock() {
|
||||
if (listening) {
|
||||
self._off('storage', lock);
|
||||
}
|
||||
if (waitTimer) {
|
||||
window.clearTimeout(waitTimer);
|
||||
}
|
||||
localStorage.removeItem(INDEX_LOCK);
|
||||
}
|
||||
|
||||
lock();
|
||||
};
|
||||
|
||||
Intercom.prototype._cleanup_emit = throttle(100, function() {
|
||||
var self = this;
|
||||
|
||||
self._transaction(function() {
|
||||
var now = Date.now();
|
||||
var threshold = now - THRESHOLD_TTL_EMIT;
|
||||
var changed = 0;
|
||||
var messages;
|
||||
|
||||
try {
|
||||
messages = JSON.parse(localStorage.getItem(INDEX_EMIT) || '[]');
|
||||
} catch(e) {
|
||||
messages = [];
|
||||
}
|
||||
for (var i = messages.length - 1; i >= 0; i--) {
|
||||
if (messages[i].timestamp < threshold) {
|
||||
messages.splice(i, 1);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
if (changed > 0) {
|
||||
localStorage.setItem(INDEX_EMIT, JSON.stringify(messages));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Intercom.prototype._cleanup_once = throttle(100, function() {
|
||||
var self = this;
|
||||
|
||||
self._transaction(function() {
|
||||
var timestamp, ttl, key;
|
||||
var table;
|
||||
var now = Date.now();
|
||||
var changed = 0;
|
||||
|
||||
try {
|
||||
table = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
|
||||
} catch(e) {
|
||||
table = {};
|
||||
}
|
||||
for (key in table) {
|
||||
if (self._once_expired(key, table)) {
|
||||
delete table[key];
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed > 0) {
|
||||
localStorage.setItem(INDEX_ONCE, JSON.stringify(table));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Intercom.prototype._once_expired = function(key, table) {
|
||||
if (!table) {
|
||||
return true;
|
||||
}
|
||||
if (!table.hasOwnProperty(key)) {
|
||||
return true;
|
||||
}
|
||||
if (typeof table[key] !== 'object') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var ttl = table[key].ttl || THRESHOLD_TTL_ONCE;
|
||||
var now = Date.now();
|
||||
var timestamp = table[key].timestamp;
|
||||
return timestamp < now - ttl;
|
||||
};
|
||||
|
||||
Intercom.prototype._localStorageChanged = function(event, field) {
|
||||
if (event && event.key) {
|
||||
return event.key === field;
|
||||
}
|
||||
|
||||
var currentValue = localStorage.getItem(field);
|
||||
if (currentValue === this.previousValues[field]) {
|
||||
return false;
|
||||
}
|
||||
this.previousValues[field] = currentValue;
|
||||
return true;
|
||||
};
|
||||
|
||||
Intercom.prototype._onStorageEvent = function(event) {
|
||||
event = event || window.event;
|
||||
var self = this;
|
||||
|
||||
if (this._localStorageChanged(event, INDEX_EMIT)) {
|
||||
this._transaction(function() {
|
||||
var now = Date.now();
|
||||
var data = localStorage.getItem(INDEX_EMIT);
|
||||
var messages;
|
||||
|
||||
try {
|
||||
messages = JSON.parse(data || '[]');
|
||||
} catch(e) {
|
||||
messages = [];
|
||||
}
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
if (messages[i].origin === self.origin) continue;
|
||||
if (messages[i].timestamp < self.lastMessage) continue;
|
||||
if (messages[i].id) {
|
||||
if (self.receivedIDs.hasOwnProperty(messages[i].id)) continue;
|
||||
self.receivedIDs[messages[i].id] = true;
|
||||
}
|
||||
self.trigger(messages[i].name, messages[i].payload);
|
||||
}
|
||||
self.lastMessage = now;
|
||||
});
|
||||
}
|
||||
|
||||
this._trigger('storage', event);
|
||||
};
|
||||
|
||||
Intercom.prototype._emit = function(name, message, id) {
|
||||
id = (typeof id === 'string' || typeof id === 'number') ? String(id) : null;
|
||||
if (id && id.length) {
|
||||
if (this.receivedIDs.hasOwnProperty(id)) return;
|
||||
this.receivedIDs[id] = true;
|
||||
}
|
||||
|
||||
var packet = {
|
||||
id : id,
|
||||
name : name,
|
||||
origin : this.origin,
|
||||
timestamp : Date.now(),
|
||||
payload : message
|
||||
};
|
||||
|
||||
var self = this;
|
||||
this._transaction(function() {
|
||||
var data = localStorage.getItem(INDEX_EMIT) || '[]';
|
||||
var delimiter = (data === '[]') ? '' : ',';
|
||||
data = [data.substring(0, data.length - 1), delimiter, JSON.stringify(packet), ']'].join('');
|
||||
localStorage.setItem(INDEX_EMIT, data);
|
||||
self.trigger(name, message);
|
||||
|
||||
window.setTimeout(function() {
|
||||
self._cleanup_emit();
|
||||
}, 50);
|
||||
});
|
||||
};
|
||||
|
||||
Intercom.prototype.emit = function(name, message) {
|
||||
this._emit.apply(this, arguments);
|
||||
this._trigger('emit', name, message);
|
||||
};
|
||||
|
||||
Intercom.prototype.once = function(key, fn, ttl) {
|
||||
if (!Intercom.supported) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._transaction(function() {
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
|
||||
} catch(e) {
|
||||
data = {};
|
||||
}
|
||||
if (!self._once_expired(key, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
data[key] = {};
|
||||
data[key].timestamp = Date.now();
|
||||
if (typeof ttl === 'number') {
|
||||
data[key].ttl = ttl * 1000;
|
||||
}
|
||||
|
||||
localStorage.setItem(INDEX_ONCE, JSON.stringify(data));
|
||||
fn();
|
||||
|
||||
window.setTimeout(function() {
|
||||
self._cleanup_once();
|
||||
}, 50);
|
||||
});
|
||||
};
|
||||
|
||||
extend(Intercom.prototype, EventEmitter.prototype);
|
||||
|
||||
Intercom.supported = (typeof localStorage !== 'undefined');
|
||||
|
||||
var INDEX_EMIT = 'intercom';
|
||||
var INDEX_ONCE = 'intercom_once';
|
||||
var INDEX_LOCK = 'intercom_lock';
|
||||
|
||||
var THRESHOLD_TTL_EMIT = 50000;
|
||||
var THRESHOLD_TTL_ONCE = 1000 * 3600;
|
||||
|
||||
Intercom.destroy = function() {
|
||||
localStorage.removeItem(INDEX_LOCK);
|
||||
localStorage.removeItem(INDEX_EMIT);
|
||||
localStorage.removeItem(INDEX_ONCE);
|
||||
};
|
||||
|
||||
Intercom.getInstance = (function() {
|
||||
var intercom;
|
||||
return function() {
|
||||
if (!intercom) {
|
||||
intercom = new Intercom();
|
||||
}
|
||||
return intercom;
|
||||
};
|
||||
})();
|
||||
|
||||
return Intercom;
|
||||
});
|
|
@ -15,8 +15,10 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/js-platform/filer.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"bower": "~1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "~1.0.0",
|
||||
"grunt": "~0.4.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-requirejs": "~0.4.0",
|
||||
|
|
333
src/fs.js
333
src/fs.js
|
@ -58,6 +58,8 @@ define(function(require) {
|
|||
var providers = require('src/providers/providers');
|
||||
var adapters = require('src/adapters/adapters');
|
||||
var Shell = require('src/shell');
|
||||
var Intercom = require('intercom');
|
||||
var FSWatcher = require('src/fswatcher');
|
||||
|
||||
/*
|
||||
* DirectoryEntry
|
||||
|
@ -192,10 +194,17 @@ define(function(require) {
|
|||
update = true;
|
||||
}
|
||||
|
||||
function complete(error) {
|
||||
// Queue this change so we can send watch events.
|
||||
// Unlike node.js, we send the full path vs. basename/dirname only.
|
||||
context.changes.push({ event: 'change', path: path });
|
||||
callback(error);
|
||||
}
|
||||
|
||||
if(update) {
|
||||
context.put(node.id, node, callback);
|
||||
context.put(node.id, node, complete);
|
||||
} else {
|
||||
callback();
|
||||
complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1649,21 +1658,66 @@ define(function(require) {
|
|||
queue = null;
|
||||
}
|
||||
|
||||
// We support the optional `options` arg from node, but ignore it
|
||||
this.watch = function(filename, options, listener) {
|
||||
if(isNullPath(filename)) {
|
||||
throw new Error('Path must be a string without null bytes.');
|
||||
}
|
||||
if(typeof options === 'function') {
|
||||
listener = options;
|
||||
options = {};
|
||||
}
|
||||
options = options || {};
|
||||
listener = listener || nop;
|
||||
|
||||
var watcher = new FSWatcher();
|
||||
watcher.start(filename, false, options.recursive);
|
||||
watcher.on('change', listener);
|
||||
|
||||
return watcher;
|
||||
};
|
||||
|
||||
// Let other instances (in this or other windows) know about
|
||||
// any changes to this fs instance.
|
||||
function broadcastChanges(changes) {
|
||||
if(!changes.length) {
|
||||
return;
|
||||
}
|
||||
var intercom = Intercom.getInstance();
|
||||
changes.forEach(function(change) {
|
||||
intercom.emit(change.event, change.event, change.path);
|
||||
});
|
||||
}
|
||||
|
||||
// Open file system storage provider
|
||||
provider.open(function(err, needsFormatting) {
|
||||
function complete(error) {
|
||||
// Wrap the provider so we can extend the context with fs flags.
|
||||
// From this point forward we won't call open again, so drop it.
|
||||
|
||||
function wrappedContext(methodName) {
|
||||
var context = provider[methodName]();
|
||||
context.flags = flags;
|
||||
context.changes = [];
|
||||
|
||||
// When the context is finished, let the fs deal with any change events
|
||||
context.close = function() {
|
||||
var changes = context.changes;
|
||||
broadcastChanges(changes);
|
||||
changes.length = 0;
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
// Wrap the provider so we can extend the context with fs flags and
|
||||
// an array of changes (e.g., watch event 'change' and 'rename' events
|
||||
// for paths updated during the lifetime of the context). From this
|
||||
// point forward we won't call open again, so it's safe to drop it.
|
||||
fs.provider = {
|
||||
getReadWriteContext: function() {
|
||||
var context = provider.getReadWriteContext();
|
||||
context.flags = flags;
|
||||
return context;
|
||||
openReadWriteContext: function() {
|
||||
return wrappedContext('getReadWriteContext');
|
||||
},
|
||||
getReadOnlyContext: function() {
|
||||
var context = provider.getReadOnlyContext();
|
||||
context.flags = flags;
|
||||
return context;
|
||||
openReadOnlyContext: function() {
|
||||
return wrappedContext('getReadOnlyContext');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2334,14 +2388,30 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_open(fs, context, path, flags, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_open(fs, context, path, flags, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
};
|
||||
FileSystem.prototype.close = function(fd, callback) {
|
||||
_close(this, fd, maybeCallback(callback));
|
||||
callback = maybeCallback(callback);
|
||||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_close(fs, fd, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
};
|
||||
FileSystem.prototype.mkdir = function(path, mode, callback) {
|
||||
// Support passing a mode arg, but we ignore it internally for now.
|
||||
|
@ -2352,8 +2422,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_mkdir(context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_mkdir(context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2363,8 +2437,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_rmdir(context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_rmdir(context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2374,8 +2452,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_stat(context, fs.name, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_stat(context, fs.name, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2385,8 +2467,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_fstat(fs, context, fd, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_fstat(fs, context, fd, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2396,8 +2482,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_link(context, oldpath, newpath, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_link(context, oldpath, newpath, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2407,8 +2497,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_unlink(context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_unlink(context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2423,8 +2517,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_read(fs, context, fd, buffer, offset, length, position, wrapper);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
wrapper.apply(this, arguments);
|
||||
}
|
||||
_read(fs, context, fd, buffer, offset, length, position, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2434,8 +2532,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_readFile(fs, context, path, options, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_readFile(fs, context, path, options, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2445,11 +2547,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_write(fs, context, fd, buffer, offset, length, position, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_write(fs, context, fd, buffer, offset, length, position, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if(error) callback(error);
|
||||
};
|
||||
FileSystem.prototype.writeFile = function(path, data, options, callback_) {
|
||||
|
@ -2457,8 +2562,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_writeFile(fs, context, path, data, options, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_writeFile(fs, context, path, data, options, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2468,8 +2577,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_appendFile(fs, context, path, data, options, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_appendFile(fs, context, path, data, options, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2479,8 +2592,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_exists(context, fs.name, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_exists(context, fs.name, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2490,8 +2607,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_lseek(fs, context, fd, offset, whence, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_lseek(fs, context, fd, offset, whence, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2501,8 +2622,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_readdir(context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_readdir(context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2512,8 +2637,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_rename(context, oldpath, newpath, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_rename(context, oldpath, newpath, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2523,8 +2652,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_readlink(context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_readlink(context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2535,8 +2668,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_symlink(context, srcpath, dstpath, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_symlink(context, srcpath, dstpath, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2546,8 +2683,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_lstat(fs, context, path, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_lstat(fs, context, path, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2563,8 +2704,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_truncate(context, path, length, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_truncate(context, path, length, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2574,8 +2719,12 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function() {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_ftruncate(fs, context, fd, length, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_ftruncate(fs, context, fd, length, complete);
|
||||
}
|
||||
);
|
||||
if(error) callback(error);
|
||||
|
@ -2585,11 +2734,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_utimes(context, path, atime, mtime, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_utimes(context, path, atime, mtime, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2599,11 +2751,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_futimes(fs, context, fd, atime, mtime, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_futimes(fs, context, fd, atime, mtime, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2614,11 +2769,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_setxattr(context, path, name, value, _flag, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_setxattr(context, path, name, value, _flag, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2628,11 +2786,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_getxattr(context, path, name, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_getxattr(context, path, name, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2643,11 +2804,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_fsetxattr(fs, context, fd, name, value, _flag, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_fsetxattr(fs, context, fd, name, value, _flag, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2657,11 +2821,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_fgetxattr(fs, context, fd, name, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_fgetxattr(fs, context, fd, name, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2671,11 +2838,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_removexattr(context, path, name, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_removexattr(context, path, name, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -2685,11 +2855,14 @@ define(function(require) {
|
|||
var fs = this;
|
||||
var error = fs.queueOrRun(
|
||||
function () {
|
||||
var context = fs.provider.getReadWriteContext();
|
||||
_fremovexattr(fs, context, fd, name, callback);
|
||||
var context = fs.provider.openReadWriteContext();
|
||||
function complete() {
|
||||
context.close();
|
||||
callback.apply(fs, arguments);
|
||||
}
|
||||
_fremovexattr(fs, context, fd, name, complete);
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
define(function(require) {
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var isNullPath = require('src/path').isNull;
|
||||
var Intercom = require('intercom');
|
||||
|
||||
/**
|
||||
* FSWatcher based on node.js' FSWatcher
|
||||
* see https://github.com/joyent/node/blob/master/lib/fs.js
|
||||
*/
|
||||
function FSWatcher() {
|
||||
EventEmitter.call(this);
|
||||
var self = this;
|
||||
var recursive = false;
|
||||
var filename;
|
||||
|
||||
function onchange(event, path) {
|
||||
// Watch for exact filename, or parent path when recursive is true
|
||||
if(filename === path || (recursive && path.indexOf(filename + '/') === 0)) {
|
||||
self.emit('change', 'change', path);
|
||||
}
|
||||
}
|
||||
|
||||
// We support, but ignore the second arg, which node.js uses.
|
||||
self.start = function(filename_, persistent_, recursive_) {
|
||||
// Bail if we've already started (and therefore have a filename);
|
||||
if(filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isNullPath(filename_)) {
|
||||
throw new Error('Path must be a string without null bytes.');
|
||||
}
|
||||
// TODO: get realpath for symlinks on filename...
|
||||
filename = filename_;
|
||||
|
||||
// Whether to watch beneath this path or not
|
||||
recursive = recursive_ === true;
|
||||
|
||||
var intercom = Intercom.getInstance();
|
||||
intercom.on('change', onchange);
|
||||
};
|
||||
|
||||
self.close = function() {
|
||||
var intercom = Intercom.getInstance();
|
||||
intercom.off('change', onchange);
|
||||
self.removeAllListeners('change');
|
||||
};
|
||||
}
|
||||
FSWatcher.prototype = new EventEmitter();
|
||||
FSWatcher.prototype.constructor = FSWatcher;
|
||||
|
||||
return FSWatcher;
|
||||
});
|
|
@ -58,7 +58,8 @@ var config = (function() {
|
|||
"spec": "../tests/spec",
|
||||
"bugs": "../tests/bugs",
|
||||
"util": "../tests/lib/test-utils",
|
||||
"Filer": "../src/index"
|
||||
"Filer": "../src/index",
|
||||
"EventEmitter": "../bower_components/eventemitter2/lib/eventemitter2"
|
||||
},
|
||||
baseUrl: "../lib",
|
||||
optimize: "none",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
define(["Filer", "util"], function(Filer, util) {
|
||||
|
||||
describe('fs.watch', function() {
|
||||
beforeEach(util.setup);
|
||||
afterEach(util.cleanup);
|
||||
|
||||
it('should be a function', function() {
|
||||
var fs = util.fs();
|
||||
expect(typeof fs.watch).to.equal('function');
|
||||
});
|
||||
|
||||
it('should get a change event when writing a file', function(done) {
|
||||
var fs = util.fs();
|
||||
|
||||
var watcher = fs.watch('/myfile', function(event, filename) {
|
||||
expect(event).to.equal('change');
|
||||
expect(filename).to.equal('/myfile');
|
||||
watcher.close();
|
||||
done();
|
||||
});
|
||||
|
||||
fs.writeFile('/myfile', 'data', function(error) {
|
||||
if(error) throw error;
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a change event when writing a file in a dir with recursive=true', function(done) {
|
||||
var fs = util.fs();
|
||||
|
||||
var watcher = fs.watch('/', { recursive: true }, function(event, filename) {
|
||||
expect(event).to.equal('change');
|
||||
expect(filename).to.equal('/');
|
||||
watcher.close();
|
||||
done();
|
||||
});
|
||||
|
||||
fs.writeFile('/myfile', 'data', function(error) {
|
||||
if(error) throw error;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
define(["Filer", "util"], function(Filer, util) {
|
||||
|
||||
/**
|
||||
* NOTE: unlike node.js, which either doesn't give filenames (e.g., in case of
|
||||
* fd vs. path) for events, or gives only a portion thereof (e.g., basname),
|
||||
* we give full, abs paths always.
|
||||
*/
|
||||
|
||||
describe("node.js tests: https://github.com/joyent/node/blob/master/test/simple/test-fs-watch-recursive.js", function() {
|
||||
|
||||
beforeEach(util.setup);
|
||||
afterEach(util.cleanup);
|
||||
|
||||
it('should get change event for writeFile() under a recursive watched dir', function(done) {
|
||||
var fs = util.fs();
|
||||
|
||||
fs.mkdir('/test', function(error) {
|
||||
if(error) throw error;
|
||||
|
||||
fs.mkdir('/test/subdir', function(error) {
|
||||
if(error) throw error;
|
||||
|
||||
var watcher = fs.watch('/test', {recursive: true});
|
||||
watcher.on('change', function(event, filename) {
|
||||
expect(event).to.equal('change');
|
||||
// Expect to see that a new file was created in /test/subdir
|
||||
expect(filename).to.equal('/test/subdir');
|
||||
watcher.close();
|
||||
done();
|
||||
});
|
||||
|
||||
fs.writeFile('/test/subdir/watch.txt', 'world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
define(["Filer", "util"], function(Filer, util) {
|
||||
|
||||
/**
|
||||
* NOTE: unlike node.js, which either doesn't give filenames (e.g., in case of
|
||||
* fd vs. path) for events, or gives only a portion thereof (e.g., basname),
|
||||
* we give full, abs paths always.
|
||||
*/
|
||||
|
||||
var filenameOne = '/watch.txt';
|
||||
var filenameTwo = '/hasOwnProperty';
|
||||
|
||||
describe("node.js tests: https://github.com/joyent/node/blob/master/test/simple/test-fs-watch.js", function() {
|
||||
|
||||
beforeEach(util.setup);
|
||||
afterEach(util.cleanup);
|
||||
|
||||
it('should get change event for writeFile() using FSWatcher object', function(done) {
|
||||
var fs = util.fs();
|
||||
var changes = 0;
|
||||
|
||||
var watcher = fs.watch(filenameOne);
|
||||
watcher.on('change', function(event, filename) {
|
||||
expect(event).to.equal('change');
|
||||
expect(filename).to.equal(filenameOne);
|
||||
|
||||
// Make sure only one change event comes in (i.e., close() works)
|
||||
changes++;
|
||||
watcher.close();
|
||||
|
||||
fs.writeFile(filenameOne, 'hello again', function(error) {
|
||||
expect(changes).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFile(filenameOne, 'hello');
|
||||
});
|
||||
|
||||
it('should get change event for writeFile() using fs.watch() only', function(done) {
|
||||
var fs = util.fs();
|
||||
var changes = 0;
|
||||
|
||||
var watcher = fs.watch(filenameTwo, function(event, filename) {
|
||||
expect(event).to.equal('change');
|
||||
expect(filename).to.equal(filenameTwo);
|
||||
|
||||
watcher.close();
|
||||
done();
|
||||
});
|
||||
|
||||
fs.writeFile(filenameTwo, 'pardner');
|
||||
});
|
||||
|
||||
it('should allow watches on dirs', function(done) {
|
||||
var fs = util.fs();
|
||||
fs.mkdir('/tmp', function(error) {
|
||||
if(error) throw error;
|
||||
|
||||
var watcher = fs.watch('/tmp', function(event, filename) {
|
||||
// TODO: node thinks this should be 'rename', need to add rename along with change.
|
||||
expect(event).to.equal('change');
|
||||
expect(filename).to.equal('/tmp');
|
||||
watcher.close();
|
||||
done();
|
||||
});
|
||||
|
||||
fs.open('/tmp/newfile.txt', 'w', function(error, fd) {
|
||||
if(error) throw error;
|
||||
fs.close(fd);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -35,6 +35,7 @@ define([
|
|||
"spec/path-resolution.spec",
|
||||
"spec/times.spec",
|
||||
"spec/time-flags.spec",
|
||||
"spec/fs.watch.spec",
|
||||
|
||||
// Filer.FileSystem.providers.*
|
||||
"spec/providers/providers.spec",
|
||||
|
@ -58,6 +59,8 @@ define([
|
|||
// Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test)
|
||||
"spec/node-js/simple/test-fs-mkdir",
|
||||
"spec/node-js/simple/test-fs-null-bytes",
|
||||
"spec/node-js/simple/test-fs-watch",
|
||||
"spec/node-js/simple/test-fs-watch-recursive",
|
||||
|
||||
// Regressions, Bugs
|
||||
"bugs/issue105",
|
||||
|
|
Loading…
Reference in New Issue