diff --git a/bower.json b/bower.json index 24d9c70..cefaa22 100644 --- a/bower.json +++ b/bower.json @@ -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" diff --git a/gruntfile.js b/gruntfile.js index 13fa88e..aaec148 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -45,7 +45,8 @@ module.exports = function(grunt) { options: { paths: { "src": "../src", - "build": "../build" + "build": "../build", + "EventEmitter": "../bower_components/eventemitter2/lib/eventemitter2" }, baseUrl: "lib", name: "build/almond", diff --git a/lib/eventemitter.js b/lib/eventemitter.js deleted file mode 100644 index 4bd9dc9..0000000 --- a/lib/eventemitter.js +++ /dev/null @@ -1,66 +0,0 @@ -define(function(require) { - - // Based on https://github.com/diy/intercom.js/blob/master/lib/events.js - // Copyright 2012 DIY Co Apache License, Version 2.0 - // http://www.apache.org/licenses/LICENSE-2.0 - - function removeItem(item, array) { - for (var i = array.length - 1; i >= 0; i--) { - if (array[i] === item) { - array.splice(i, 1); - } - } - return array; - } - - function EventEmitter() {} - - EventEmitter.createInterface = function(space) { - var methods = {}; - - methods.on = function(name, fn) { - if (typeof this[space] === 'undefined') { - this[space] = {}; - } - if (!this[space].hasOwnProperty(name)) { - this[space][name] = []; - } - this[space][name].push(fn); - }; - - methods.off = function(name, fn) { - if (typeof this[space] === 'undefined') return; - if (this[space].hasOwnProperty(name)) { - removeItem(fn, this[space][name]); - } - }; - - methods.trigger = function(name) { - if (typeof this[space] !== 'undefined' && this[space].hasOwnProperty(name)) { - var args = Array.prototype.slice.call(arguments, 1); - for (var i = 0; i < this[space][name].length; i++) { - this[space][name][i].apply(this[space][name][i], args); - } - } - }; - - return methods; - }; - - var pvt = EventEmitter.createInterface('_handlers'); - EventEmitter.prototype._on = pvt.on; - EventEmitter.prototype._off = pvt.off; - EventEmitter.prototype._trigger = pvt.trigger; - - var pub = EventEmitter.createInterface('handlers'); - EventEmitter.prototype.on = function() { - pub.on.apply(this, arguments); - Array.prototype.unshift.call(arguments, 'on'); - this._trigger.apply(this, arguments); - }; - EventEmitter.prototype.off = pub.off; - EventEmitter.prototype.trigger = pub.trigger; - - return EventEmitter; - -}); diff --git a/lib/intercom.js b/lib/intercom.js index 6c3bae8..62a10d0 100644 --- a/lib/intercom.js +++ b/lib/intercom.js @@ -4,7 +4,7 @@ define(function(require) { // Copyright 2012 DIY Co Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 - var EventEmitter = require('eventemitter'); + var EventEmitter = require('EventEmitter'); var guid = require('src/shared').guid; function throttle(delay, fn) { diff --git a/src/fs.js b/src/fs.js index 414e716..b563ffb 100644 --- a/src/fs.js +++ b/src/fs.js @@ -59,6 +59,7 @@ define(function(require) { var adapters = require('src/adapters/adapters'); var Shell = require('src/shell'); var Intercom = require('intercom'); + var EventEmitter = require('EventEmitter'); /* * DirectoryEntry @@ -1551,6 +1552,45 @@ define(function(require) { } + /* + * FSWatcher based loosely 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(path) { + // Watch for exact filename, or parent path when recursive is true + if(filename === path || (recursive && filename.indexOf(path + '/') === 0)) { + self.emit('change', filename); + } + } + + // We support, but ignore the second arg, which node.js uses. + self.start = function(filename_, persistent_, recursive_) { + if(isNullPath(filename)) { + throw new Error('Path must be a string without null bytes.'); + } + recursive = recursive_ === true; + // TODO: get realpath for symlinks on filename... + filename = filename_; + + var intercom = Intercom.getInstance(); + intercom.on('change', onchange); + }; + + self.close = function() { + var intercom = Intercom.getInstance(); + intercom.off('change', onchange); + }; + } + FSWatcher.prototype = new EventEmitter(); + FSWatcher.prototype.constructor = FSWatcher; + + /* * FileSystem * @@ -1628,12 +1668,31 @@ define(function(require) { queue = null; } + // FileSystem watch events are broadcast between windows via intercom + var intercom = Intercom.getInstance(); + + // 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.addListener('change', listener); + + return watcher; + }; + // Open file system storage provider provider.open(function(err, needsFormatting) { function complete(error) { - // FileSystem watch events are broadcast between windows via intercom - var intercom = Intercom.getInstance(); - // Wrap the provider so we can extend the context with fs flags, intercom. // From this point forward we won't call open again, so drop it. fs.provider = { diff --git a/tests/require-config.js b/tests/require-config.js index 2415177..662f9ad 100644 --- a/tests/require-config.js +++ b/tests/require-config.js @@ -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", diff --git a/tests/spec/fs.watch.spec.js b/tests/spec/fs.watch.spec.js new file mode 100644 index 0000000..3ed77a6 --- /dev/null +++ b/tests/spec/fs.watch.spec.js @@ -0,0 +1,39 @@ +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(); + + fs.watch('/myfile', function(filename) { + expect(filename).to.equal('/myfile'); + 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(); + + fs.watch('/', { recursive: true }, function(filename) { + expect(filename).to.equal('/'); + done(); + }); + + fs.writeFile('/myfile', 'data', function(error) { + if(error) throw error; + }); + }); + }); + +}); diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 688d231..f08c55f 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -34,6 +34,7 @@ define([ "spec/path-resolution.spec", "spec/times.spec", "spec/time-flags.spec", + "spec/fs.watch.spec", // Filer.FileSystem.providers.* "spec/providers/providers.spec",