Comparing version 0.8.2 to 0.8.3
@@ -193,8 +193,13 @@ var assert = require("assert"); | ||
function openExclusiveP(file) { | ||
function openFileP(file, mode) { | ||
return makePromise(function(callback) { | ||
// The 'x' in "wx+" means the file must be newly created. | ||
fs.open(file, "wx+", callback); | ||
fs.open(file, mode || "w+", callback); | ||
}); | ||
} | ||
exports.openFileP = openFileP; | ||
function openExclusiveP(file) { | ||
// The 'x' in "wx+" means the file must be newly created. | ||
return openFileP(file, "wx+"); | ||
} | ||
exports.openExclusiveP = openExclusiveP; | ||
@@ -201,0 +206,0 @@ |
@@ -9,2 +9,3 @@ var assert = require("assert"); | ||
var util = require("./util"); | ||
var hasOwn = Object.prototype.hasOwnProperty; | ||
@@ -21,5 +22,18 @@ function Watcher(sourceDir, persistent) { | ||
sourceCache: { value: {} }, | ||
watched: { value: {} }, | ||
persistent: { value: !!persistent } | ||
dirWatcher: { value: new DirWatcher(sourceDir, persistent) } | ||
}); | ||
function handle(event, relativePath) { | ||
if (self.dirWatcher.ready) { | ||
self.getFileHandler(relativePath)(event); | ||
} | ||
} | ||
self.dirWatcher.on("added", function(relativePath) { | ||
handle("added", relativePath); | ||
}).on("deleted", function(relativePath) { | ||
handle("deleted", relativePath); | ||
}).on("changed", function(relativePath) { | ||
handle("changed", relativePath); | ||
}); | ||
} | ||
@@ -30,2 +44,7 @@ | ||
Wp.watch = function(relativePath) { | ||
this.dirWatcher.add(path.dirname(path.join( | ||
this.sourceDir, relativePath))); | ||
}; | ||
Wp.readFileP = function(relativePath) { | ||
@@ -50,37 +69,188 @@ relativePath = path.normalize(relativePath); | ||
Wp.watch = function(relativePath) { | ||
Wp.getFileHandler = util.cachedMethod(function(relativePath) { | ||
var self = this; | ||
var watched = self.watched; | ||
return function handler(event) { | ||
Q.all([ | ||
self.readFileP(relativePath).catch(orNull), | ||
self.noCacheReadFileP(relativePath).catch(orNull) | ||
]).spread(function(oldData, newData) { | ||
if (oldData !== newData) | ||
self.emit("changed", relativePath); | ||
}).done(); | ||
}; | ||
}); | ||
if (!self.persistent) | ||
function orNull(err) { | ||
return null; | ||
} | ||
Wp.close = function() { | ||
this.dirWatcher.close(); | ||
}; | ||
/** | ||
* DirWatcher code adapted from Jeffrey Lin's original implementation: | ||
* https://github.com/jeffreylin/jsx_transformer_fun/blob/master/dirWatcher.js | ||
* | ||
* Invariant: this only watches the dir inode, not the actual path. | ||
* That means the dir can't be renamed and swapped with another dir. | ||
*/ | ||
function DirWatcher(inputPath, persistent) { | ||
assert.ok(this instanceof DirWatcher); | ||
var self = this; | ||
var absPath = path.resolve(inputPath); | ||
if (!fs.statSync(absPath).isDirectory()) { | ||
throw new Error(inputPath + "is not a directory!"); | ||
} | ||
EventEmitter.call(self); | ||
self.ready = false; | ||
self.on("ready", function(){ | ||
self.ready = true; | ||
}); | ||
Object.defineProperties(self, { | ||
// Map of absDirPaths to fs.FSWatcher objects from fs.watch(). | ||
watchers: { value: {} }, | ||
dirContents: { value: {} }, | ||
rootPath: { value: absPath }, | ||
persistent: { value: !!persistent } | ||
}); | ||
process.nextTick(function() { | ||
self.add(absPath); | ||
self.emit("ready"); | ||
}); | ||
} | ||
util.inherits(DirWatcher, EventEmitter); | ||
var DWp = DirWatcher.prototype; | ||
DWp.add = function(absDirPath) { | ||
var self = this; | ||
if (hasOwn.call(self.watchers, absDirPath)) { | ||
return; | ||
} | ||
relativePath = path.normalize(relativePath); | ||
if (!watched.hasOwnProperty(relativePath)) { | ||
var fullPath = path.join(self.sourceDir, relativePath); | ||
var options = { persistent: self.persistent }; | ||
self.watchers[absDirPath] = fs.watch(absDirPath, { | ||
persistent: self.persistent | ||
}).on("change", function(event, filename) { | ||
self.updateDirContents(absDirPath, event, filename); | ||
}); | ||
function handler(event) { | ||
var oldP = self.readFileP(relativePath); | ||
var newP = self.noCacheReadFileP(relativePath); | ||
// Update internal dir contents. | ||
self.updateDirContents(absDirPath); | ||
Q.all([oldP, newP]).spread(function(oldData, newData) { | ||
if (oldData !== newData) | ||
self.emit("changed", relativePath); | ||
}).done(); | ||
// Since we've never seen this path before, recursively add child | ||
// directories of this path. TODO: Don't do fs.readdirSync on the | ||
// same dir twice in a row. We already do an fs.statSync in | ||
// this.updateDirContents() and we're just going to do another one | ||
// here... | ||
fs.readdirSync(absDirPath).forEach(function(filename) { | ||
var filepath = path.join(absDirPath, filename); | ||
// Look for directories. | ||
if (fs.statSync(filepath).isDirectory()) { | ||
self.add(filepath); | ||
} | ||
}); | ||
}; | ||
try { | ||
fs.watch(fullPath, options, handler); | ||
// Don't mark the file as watched if fs.watch threw an exception. | ||
watched[relativePath] = true; | ||
} catch (e) { | ||
util.log.err( | ||
"unable to watch file " + relativePath + | ||
" (" + e + ")", | ||
"yellow"); | ||
DWp.updateDirContents = function(absDirPath, event, fsWatchReportedFilename) { | ||
var self = this; | ||
if (!hasOwn.call(self.dirContents, absDirPath)) { | ||
self.dirContents[absDirPath] = []; | ||
} | ||
var oldContents = self.dirContents[absDirPath]; | ||
var newContents = fs.readdirSync(absDirPath); | ||
var deleted = {}; | ||
var added = {}; | ||
oldContents.forEach(function(filename) { | ||
deleted[filename] = true; | ||
}); | ||
newContents.forEach(function(filename) { | ||
if (hasOwn.call(deleted, filename)) { | ||
delete deleted[filename]; | ||
} else { | ||
added[filename] = true; | ||
} | ||
}); | ||
var deletedNames = Object.keys(deleted); | ||
deletedNames.forEach(function(filename) { | ||
self.emit( | ||
"deleted", | ||
path.relative( | ||
self.rootPath, | ||
path.join(absDirPath, filename) | ||
) | ||
); | ||
}); | ||
var addedNames = Object.keys(added); | ||
addedNames.forEach(function(filename) { | ||
self.emit( | ||
"added", | ||
path.relative( | ||
self.rootPath, | ||
path.join(absDirPath, filename) | ||
) | ||
); | ||
}); | ||
// So changed is not deleted or added? | ||
if (fsWatchReportedFilename && | ||
!hasOwn.call(deleted, fsWatchReportedFilename) && | ||
!hasOwn.call(added, fsWatchReportedFilename)) | ||
{ | ||
self.emit( | ||
"changed", | ||
path.relative( | ||
self.rootPath, | ||
path.join(absDirPath, fsWatchReportedFilename) | ||
) | ||
); | ||
} | ||
// If any of the things removed were directories, remove their watchers. | ||
// If a dir was moved, hopefully two changed events fired? | ||
// 1) event in dir where it was removed | ||
// 2) event in dir where it was moved to (added) | ||
deletedNames.forEach(function(filename) { | ||
var filepath = path.join(absDirPath, filename); | ||
delete self.dirContents[filepath]; | ||
delete self.watchers[filepath]; | ||
}); | ||
// if any of the things added were directories, recursively deal with them | ||
addedNames.forEach(function(filename) { | ||
var filepath = path.join(absDirPath, filename); | ||
if (fs.existsSync(filepath) && | ||
fs.statSync(filepath).isDirectory()) | ||
{ | ||
self.add(filepath); | ||
// mighttttttt need a self.updateDirContents() here in case | ||
// we're somehow adding a path that replaces another one...? | ||
} | ||
}); | ||
// Update state of internal dir contents. | ||
self.dirContents[absDirPath] = newContents; | ||
}; | ||
DWp.close = function() { | ||
var watchers = this.watchers; | ||
Object.keys(watchers).forEach(function(filename) { | ||
watchers[filename].close(); | ||
}); | ||
}; | ||
function run(cmd, args) { | ||
@@ -87,0 +257,0 @@ return spawn(cmd, args, { |
@@ -17,3 +17,3 @@ { | ||
], | ||
"version": "0.8.2", | ||
"version": "0.8.3", | ||
"license": "MIT", | ||
@@ -20,0 +20,0 @@ "homepage": "http://github.com/benjamn/commoner", |
@@ -391,1 +391,79 @@ var Watcher = require("../lib/watcher").Watcher; | ||
}; | ||
exports.testWatcherBasic = function(t, assert) { | ||
var watcher = new Watcher(sourceDir); | ||
var dummy = "dummy.js"; | ||
var dummyFile = path.join(sourceDir, dummy); | ||
function waitForChangeP() { | ||
return util.makePromise(function(callback) { | ||
watcher.on("changed", function(path) { | ||
callback(null, path); | ||
}); | ||
}); | ||
} | ||
util.unlinkP(dummyFile).then(function() { | ||
return util.openExclusiveP(dummyFile).then(function(fd) { | ||
return util.writeFdP(fd, "dummy"); | ||
}); | ||
}).then(function() { | ||
return Q.all([ | ||
watcher.readFileP("home.js"), | ||
watcher.readFileP(dummy) | ||
]); | ||
}).then(function() { | ||
return Q.all([ | ||
util.unlinkP(dummyFile), | ||
waitForChangeP() | ||
]); | ||
}).done(function() { | ||
watcher.close(); | ||
t.finish(); | ||
}); | ||
}; | ||
exports.testWatchDirectory = function(t, assert) { | ||
var watcher = new Watcher(sourceDir); | ||
var watchMe = "watchMe.js"; | ||
var fullPath = path.join(watcher.sourceDir, watchMe); | ||
function waitForChangeP() { | ||
return util.makePromise(function(callback) { | ||
watcher.once("changed", function(path) { | ||
callback(null, path); | ||
}); | ||
}); | ||
} | ||
function write(content) { | ||
return util.openFileP(fullPath).then(function(fd) { | ||
var promise = waitForChangeP(); | ||
util.writeFdP(fd, content); | ||
return promise; | ||
}); | ||
} | ||
util.unlinkP(fullPath).then(function() { | ||
return watcher.readFileP(watchMe).then(function(source) { | ||
assert.ok(false, "readFileP should have failed"); | ||
}, function(err) { | ||
assert.strictEqual(err.code, "ENOENT"); | ||
}); | ||
}).then(function() { | ||
return write("first"); | ||
}).then(function() { | ||
return write("second"); | ||
}).then(function() { | ||
var promise = waitForChangeP(); | ||
util.unlinkP(fullPath); | ||
return promise; | ||
}).then(function() { | ||
return write("third"); | ||
}).fin(function() { | ||
return util.unlinkP(fullPath); | ||
}).done(function() { | ||
watcher.close(); | ||
t.finish(); | ||
}); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
78934
1911