directory-cache
Advanced tools
Comparing version 2.0.3 to 3.0.1
266
index.js
@@ -30,3 +30,9 @@ var $u = require('util') | ||
this.directory = directory | ||
this._pathJoin = _.partial(pathJoin, this.directory) | ||
this._joinStatReadAdd = async.seq(this._pathJoin, stat, maybeRead, _.bind(this._addFile, this)) | ||
this._joinStatReadUpdate = async.seq(this._pathJoin, stat, maybeRead, _.bind(this._updateFile, this)) | ||
this.filter = function(file) { return false } | ||
@@ -69,3 +75,3 @@ | ||
/* | ||
initialize the cache | ||
initialize the cache, this will create a new watcher if one wasn't attached first | ||
@@ -82,24 +88,19 @@ @param {Function} initCallback callback for when the initialization completes | ||
var initSequence = [ | ||
_.bind(fs.readdir, fs, this.directory), | ||
_.bind(readFiles, null, this.directory), | ||
_.bind(addFiles, this) | ||
] | ||
var done = _.partial(initSequenceDone, this, initCallback) | ||
if (!this._watcher) { | ||
initSequence.push(_.bind(DirectoryWatcher.create, null, this.directory)) | ||
initSequence.push(_.bind(this.attachWatcher, this)) | ||
} | ||
var attachWatcher = _.bind(this.attachWatcher, this) | ||
var self = this | ||
async.waterfall(initSequence, _.bind(initSequenceDone, this, initCallback)) | ||
} | ||
DirectoryWatcher.create(this.directory, function(err, watcher) { | ||
if (err) return done(err) | ||
function initSequenceDone(initCallback, err) { | ||
if (err) { | ||
debug('ERROR: %s', err.message) | ||
initCallback(err) | ||
attachWatcher(watcher) | ||
mapFiles(self._joinStatReadAdd, watcher.files, done) | ||
}) | ||
} else { | ||
debug('cache initialized successfully') | ||
initCallback(null, this) | ||
mapFiles(this._joinStatReadAdd, this._watcher.files, done) | ||
} | ||
@@ -109,21 +110,66 @@ } | ||
/* | ||
stop this cache, freezing its current state | ||
*/ | ||
DirectoryCache.prototype.stop = function() { | ||
if (this.watcher) { | ||
this.watcher.removeAllListeners('add') | ||
this.watcher.removeAllListeners('change') | ||
this.watcher.removeAllListeners('delete') | ||
this.watcher.kill() | ||
} | ||
} | ||
/* | ||
get an array of the names of all the files in the cached directory | ||
@returns {Array} | ||
*/ | ||
DirectoryCache.prototype.getFilenames = function() { | ||
return _.keys(this.cache) | ||
} | ||
/* | ||
@param {String} file name of the file to delete | ||
@returns {String|Buffer} contents of the file from the cache or undefined if its not there | ||
*/ | ||
DirectoryCache.prototype.getFile = function(file) { | ||
return this.cache[file] | ||
} | ||
/* | ||
disable automatic json parsing | ||
*/ | ||
DirectoryCache.prototype.disableJsonParsing = function() { | ||
this.parseJson = false | ||
} | ||
/* | ||
enable automatic json parsing | ||
*/ | ||
DirectoryCache.prototype.enableJsonParsing = function() { | ||
this.parseJson = true | ||
} | ||
/* | ||
attach a watcher to this cache, this is an optional method. init() will | ||
create a new watcher attachWatcher() is never called | ||
create a new watcher if attachWatcher() is never called | ||
@param {DirectoryWatcher} watcher a DirectoryWatcher instance (directory-watcher on npm) | ||
@param {Function} _callback api | ||
*/ | ||
DirectoryCache.prototype.attachWatcher = function(watcher, _callback) { | ||
var read = _.bind(readFiles, null, this.directory) | ||
var update = _.bind(updateFiles, this) | ||
var add = _.bind(addFiles, this) | ||
DirectoryCache.prototype.attachWatcher = function(watcher) { | ||
var deleteOne = _.bind(this._deleteFile, this) | ||
var handleError = _.bind(maybeEmitError, this) | ||
watcher.on('add', function (files) { | ||
async.waterfall([_.bind(read, null, files), add], handleError) | ||
var maybeEmitError = _.bind(this._maybeEmitError, this) | ||
var self = this | ||
watcher.on('add', function (files) { | ||
mapFiles(self._joinStatReadAdd, files, maybeEmitError) | ||
}) | ||
watcher.on('change', function (files) { | ||
async.waterfall([_.bind(read, null, files), update], handleError) | ||
mapFiles(self._joinStatReadUpdate, files, maybeEmitError) | ||
}) | ||
@@ -134,37 +180,22 @@ | ||
}) | ||
watcher.on('error', maybeEmitError) | ||
this._watcher = watcher | ||
if (_callback) | ||
_callback(null) | ||
} | ||
/* | ||
get an array of the names of all the files in the cached directory | ||
add a file to the cache, | ||
the file must reside in the cache directory (this is not enforced) | ||
@returns {Array} | ||
*/ | ||
DirectoryCache.prototype.getFilenames = function() { | ||
return _.keys(this.cache) | ||
} | ||
/* | ||
stop this cache, freezing its current state | ||
*/ | ||
DirectoryCache.prototype.stop = function() { | ||
if (this.watcher) { | ||
this.watcher.kill() | ||
} | ||
} | ||
/* | ||
add a file to the cache, the file most reside in the cache directory (this is not validated) | ||
@private | ||
@param {String} file name of the file | ||
@param {String|Buffer} data initial value of the file content to cache | ||
@param {fs.Stat} stat a stat object | ||
@param {Function} callback | ||
*/ | ||
DirectoryCache.prototype._addFile = function(file, data) { | ||
debug('adding %s', file) | ||
DirectoryCache.prototype._addFile = function(file, data, stat, callback) { | ||
debug('_addFile( %s, %s)', file, data) | ||
data = this._cacheFile(file, data) | ||
@@ -175,6 +206,10 @@ | ||
this.emit('add', file, data) | ||
callback(null) | ||
} | ||
/* | ||
update a file in the cache, the file most reside in the cache directory (this is not validated) | ||
update a file in the cache, | ||
the file must reside in the cache directory (this is not enforced) | ||
the file must already exist in the cache (enforced) | ||
@@ -184,5 +219,7 @@ @private | ||
@param {String|Buffer} data updated value of the file content to cache | ||
@param {fs.Stat} stat a stat object | ||
@param {Function} callback | ||
*/ | ||
DirectoryCache.prototype._updateFile = function(file, data) { | ||
debug('updating %s', file) | ||
DirectoryCache.prototype._updateFile = function(file, data, stat, callback) { | ||
debug('_updateFile( %s, %s )', file, data) | ||
@@ -194,2 +231,4 @@ if (!file in this.cache) throw new Error('cannot update a new file, use _addFile() instead') | ||
this.emit('update', file, data) | ||
callback(null) | ||
} | ||
@@ -218,25 +257,3 @@ | ||
/* | ||
@param {String} file name of the file to delete | ||
@returns {String|Buffer} contents of the file from the cache or undefined if its not there | ||
*/ | ||
DirectoryCache.prototype.getFile = function(file) { | ||
return this.cache[file] | ||
} | ||
/* | ||
disable automatic json parsing | ||
*/ | ||
DirectoryCache.prototype.disableJsonParsing = function() { | ||
this.parseJson = false | ||
} | ||
/* | ||
enable automatic json parsing | ||
*/ | ||
DirectoryCache.prototype.enableJsonParsing = function() { | ||
this.parseJson = true | ||
} | ||
/* | ||
caching implementation, used by _addFile and _updateFile | ||
@@ -261,3 +278,3 @@ | ||
function maybeEmitError(err) { | ||
DirectoryCache.prototype._maybeEmitError = function(err) { | ||
if (err) | ||
@@ -267,41 +284,90 @@ this.emit('error', err) | ||
function readFiles(directory, files, callback) { | ||
debug('reading files') | ||
/* | ||
used as a subtask in asyncs to do stuff on files | ||
var functors = {} | ||
@param {Function} | ||
@param {Array} files | ||
@param {Function} callback | ||
for (var i = 0; i < files.length; i++) | ||
functors[files[i]] = _.bind(fs.readFile, fs, path.join(directory, files[i])) | ||
@private | ||
*/ | ||
function mapFiles(operation, files, callback) { | ||
debug('mapFiles(...)') | ||
async.parallel(functors, callback) | ||
async.map(files, operation, callback) | ||
} | ||
function addFiles(files, callback) { | ||
debug('adding files') | ||
/* | ||
path join for async flows | ||
*/ | ||
function pathJoin(directory, file, callback) { | ||
debug('pathJoin(%s, %s)', directory, file) | ||
callback(null, path.join(directory, file), file) | ||
} | ||
/* | ||
for (var file in files) { | ||
var data = files[file] | ||
@param {String} fullpath full path to a file | ||
@param {String} file basename | ||
@param {Function} callback | ||
this._addFile(file, data) | ||
} | ||
@private | ||
*/ | ||
function stat(fullpath, file, callback) { | ||
debug('stat(%s, %s)', fullpath, file) | ||
fs.stat(fullpath, function(err, stat) { | ||
if (err) return callback(err) | ||
if (callback) | ||
callback(null) | ||
callback(null, fullpath, file, stat) | ||
}) | ||
} | ||
function updateFiles(files, callback) { | ||
debug('updating files') | ||
/* | ||
maybe read a file if indeed its a normal file | ||
@param {String} fullpath full path to a file | ||
@param {String} file basename | ||
@param {fs.Stat} stat an fs.Stat object | ||
@param {Function} callback | ||
@private | ||
*/ | ||
function maybeRead(fullpath, file, stat, callback) { | ||
for (var file in files) { | ||
var data = files[file] | ||
if (stat.isFile()) { | ||
fs.readFile(fullpath, function(err, content) { | ||
if (err) return callback(err) | ||
this._updateFile(file, data) | ||
callback(null, file, content, stat) | ||
}) | ||
} else { | ||
callback(null, file, undefined, stat) | ||
} | ||
} | ||
if (callback) | ||
callback(null) | ||
/* | ||
init help | ||
@private | ||
*/ | ||
function initSequenceDone(cache, initCallback, err) { | ||
if (err) { | ||
debug('ERROR: %s', err.message) | ||
initCallback(err) | ||
} else { | ||
debug('cache initialized successfully') | ||
initCallback(null, cache) | ||
} | ||
} | ||
/* | ||
checks if a file has a .json extension | ||
@param {String} filename the name of the file | ||
@private | ||
*/ | ||
function isJsonFile(filename) { | ||
return filename.slice(-5).toLowerCase() === '.json' | ||
} |
{ | ||
"name": "directory-cache", | ||
"version": "2.0.3", | ||
"version": "3.0.1", | ||
"description": "A directory cache + watch util", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
60
test.js
@@ -8,2 +8,4 @@ var assert = require('assert') | ||
var fs = require('fs') | ||
var EventEmitter = require('events').EventEmitter | ||
var TEST_DIR = path.join(__dirname, 'testdir') | ||
@@ -16,5 +18,11 @@ var testNumber = 0 | ||
var mockWatcher | ||
beforeEach(function (done) { | ||
testNumber++ | ||
mockWatcher = new EventEmitter() | ||
mockWatcher.files = ['1.json'] | ||
currentTestDir = TEST_DIR + testNumber.toString() | ||
@@ -49,3 +57,3 @@ | ||
it('loads the content of the files in the target directory when created', function () { | ||
it('loads the content of the files in the target directory when created', function () { | ||
assert.deepEqual(cache.cache['1.json'], { a: 1 }) | ||
@@ -55,2 +63,38 @@ assert.strictEqual(cache.count, 1) | ||
it('can use an external watcher, attached before initialization', function (done) { | ||
var cache2 = DirectoryCache.create(currentTestDir) | ||
cache2.attachWatcher(mockWatcher) | ||
cache2.init(function(err) { | ||
if (err) done(err) | ||
cache2.on('update', function(file, data) { | ||
assert.strictEqual(file, '1.json') | ||
done() | ||
}) | ||
mockWatcher.emit('change', ['1.json']) | ||
}) | ||
}) | ||
it('can use an external watcher, attached after initialization', function(done) { | ||
cache.attachWatcher(mockWatcher) | ||
cache.on('update', function(file, data) { | ||
assert.strictEqual(file, '1.json') | ||
done() | ||
}) | ||
mockWatcher.emit('change', ['1.json']) | ||
}) | ||
it('emits error events', function (done) { | ||
cache.attachWatcher(mockWatcher) | ||
cache.on('error', function(err) { | ||
assert.ok(err instanceof Error) | ||
done() | ||
}) | ||
mockWatcher.emit('error', new Error()) | ||
}) | ||
describe('updates the cache when', function () { | ||
@@ -100,2 +144,16 @@ it('files are added', function (done) { | ||
}) | ||
it('files that are not normal files raise events but without data', function(done) { | ||
assert.strictEqual(cache.count, 1) | ||
cache.on('add', function(file, data) { | ||
assert.ok(data === undefined) | ||
assert.strictEqual(file, 'moo') | ||
done() | ||
}) | ||
fs.mkdir(path.join(currentTestDir, 'moo'), function(err) { | ||
if (err) return done(err) | ||
}) | ||
}) | ||
}) | ||
@@ -102,0 +160,0 @@ }) |
14201
396