castor-load
Version:
Traverse a directory to build a MongoDB collection with the found files. Then it's enable to keep directory and collection synchronised.
214 lines (191 loc) • 5.32 kB
JavaScript
/*jshint node:true, laxcomma:true*/
;
var path = require('path')
, basename = path.basename(__filename, '.js')
, debug = require('debug')('castor:load:' + basename)
, util = require('util')
, crypto = require('crypto')
, assert = require('assert')
, async = require('async')
, minimatch = require('minimatch')
, clone = require('clone')
, File = require('./file.js')
;
function Mount(options)
{
if (!(this instanceof Mount)) {
return new Mount(options);
}
var self = this;
options = options || {};
self.options = {};
self.options.concurrency = options.concurrency || 1;
self.options.match = options.match || { matchBase: true };
self.handlers = [];
var worker = function (doc, done) {
var h = [function(callback){ callback(null, doc); }];
async.waterfall(h.concat(self.handlers), function(err, d) {
if (err === null && d === null) {
debug('The waterfall is deliberately broken');
}
done(err, d !== undefined ? d : {});
});
};
self.queue = async.queue(worker, self.options.concurrency);
}
Mount.prototype.use = function (pattern, fn) {
var self = this;
if (fn === undefined) {
fn = pattern;
pattern = '*';
}
assert.equal(typeof pattern, 'string');
assert.equal(typeof fn, 'function');
var fnid = crypto.createHash('md5').update(fn.toString().replace(/\s*/g, '')).digest("hex");
debug(fnid, fn.toString());
var mark = function(o) {
if (o.mountBy === undefined) {
o.mountBy = [fnid];
}
o.mountBy.push(fnid);
return o;
};
self.handlers.push(function(iDoc, next) {
debug('through', fnid);
if (!iDoc) {
return next();
}
if (Array.isArray(iDoc)) {
debug(fnid + ' received array');
return next(null, iDoc);
}
if (iDoc.mountBy && iDoc.mountBy.indexOf(fnid) >= 0) {
debug(fnid + ' ignored', iDoc.filename);
return next(null, iDoc);
}
if (minimatch(iDoc.filename, pattern, self.options.match)) {
mark(iDoc);
var oDoc = clone(iDoc, false)
, numb = 0
, nump = 0
, wrk = function (arg1, callback) {
self.appendDoc(arg1, arg1.memorizeWorkerForSubdoc, callback);
}
, qe = async.queue(wrk, 1)
;
debug('exectued', fnid);
fn(oDoc,
function submit(arg1, arg2) {
numb++;
if (arg1 instanceof Error) {
if (numb === 1) {
next(arg1);
}
else {
debug("arg1", arg1);
}
}
else if (!arg1 && arg2) {
debug(fnid + ' mounted', iDoc.filename);
if (numb === 1) {
next(null, arg2);
}
}
else if (!arg1 && !arg2) {
// last call
return;
}
else if (typeof arg1 === 'object') {
if (numb === 1) {
var se = new Error('File Exploded');
se.wait = function(cbd) {
debug('exectued', fnid);
qe.drain = function() {
cbd();
debug('finalize', fnid, numb, nump, qe.length());
};
};
next(se);
}
arg1.fid = crypto.createHash('sha1').update(arg1.location + '#' + numb).digest('hex');
arg1.filename = arg1.filename + '[' + numb + ']';
// sub Document
debug('explode', arg1.fid, arg1.filename);
qe.push(arg1, function(e) {
nump++;
debug('terminated', fnid, arg1.filename);
if (typeof arg2 === 'function') {
arg2(e);
}
});
}
else {
if (numb === 1) {
next(null, oDoc);
}
}
});
}
else {
debug(fnid + ' not applicable', iDoc.filename);
next(null, iDoc);
}
});
};
Mount.prototype.append = function (file, worker, done) {
assert(file instanceof File);
var self = this;
file.analyze(function(err, doc) {
if (err) {
done(err);
}
self.appendDoc(doc, worker, function(err) {
if (err && err.wait) {
err.wait(done);
}
else {
done(err);
}
});
});
};
Mount.prototype.appendDoc = function (doc, worker, done) {
debug('mount', doc.filename);
var self = this;
doc.memorizeWorkerForSubdoc = worker;
self.queue.push(doc, function(err, dta) {
debug('mounted', doc.filename);
if (err) {
return done(err);
}
if (Array.isArray(dta)) {
async.mapLimit(dta, self.options.concurrency, function iterator(item, callback) {
self.appendIDoc(item, function(err, idoc) {
worker(idoc, callback);
});
}, function(err, res) {
done(err);
});
}
else if (!dta) {
done(new Error('The document was drained'));
}
else {
worker(dta, function(err) {
return done(err);
});
}
});
};
Mount.prototype.appendIDoc = function (doc, next) {
var self = this;
self.queue.push(doc, function(err, dta) {
if (err) {
return next(err, dta);
}
else {
next(null, dta);
}
});
};
module.exports = Mount;