166 lines
4.9 KiB
JavaScript
166 lines
4.9 KiB
JavaScript
// TODO
|
|
// * add types by listener dynamically
|
|
// * unroll loops for better readability?
|
|
// * should emitted errors wait for `next()`?
|
|
(function (undefined) {
|
|
var fs = require('fs'),
|
|
upath = require('path'),
|
|
util = require('util'),
|
|
Futures = require('futures'),
|
|
events = require('events'),
|
|
noop = function () {},
|
|
// "FIFO" isn't easy to convert to camelCame and back reliably
|
|
isFnodeTypes = [
|
|
"isFile", "isDirectory", "isBlockDevice", "isCharacterDevice", "isSymbolicLink", "isFIFO", "isSocket"
|
|
],
|
|
fnodeTypes = [
|
|
"file", "directory", "blockDevice", "characterDevice", "symbolicLink", "FIFO", "socket"
|
|
],
|
|
fnodeTypesPlural = [
|
|
"files", "directories", "blockDevices", "characterDevices", "symbolicLinks", "FIFOs", "sockets"
|
|
];
|
|
|
|
function newVersion() {
|
|
throw new Error("New Version. Please see API on github.com/coolaj86/node-walk");
|
|
}
|
|
|
|
// Create a new walk instance
|
|
function create(path, options, cb) {
|
|
if (cb) {
|
|
newVersion();
|
|
}
|
|
|
|
var emitter = new events.EventEmitter(),
|
|
fstat = (options||{}).followLinks ? fs.stat : fs.lstat;
|
|
|
|
|
|
// Get the current number of listeners (which may change)
|
|
// Emit events to each listener
|
|
// Wait for all listeners to `next()` before continueing
|
|
// (in theory this may avoid disk thrashing)
|
|
function emitSingleEvents(path, stats, next) {
|
|
var num = 1 + emitter.listeners(stats.type).length + emitter.listeners("node").length;
|
|
|
|
function nextWhenReady() {
|
|
num -= 1;
|
|
if (0 === num) { next(); }
|
|
}
|
|
|
|
emitter.emit(stats.type, path, stats, nextWhenReady);
|
|
emitter.emit("node", path, stats, nextWhenReady);
|
|
nextWhenReady();
|
|
}
|
|
|
|
|
|
// Since the risk for disk thrashing among anything
|
|
// other than files is relatively low, all types are
|
|
// emitted at once, but all must complete before advancing
|
|
function emitPluralEvents(path, nodes, next) {
|
|
var num = 1;
|
|
|
|
function nextWhenReady() {
|
|
num -= 1;
|
|
if (0 === num) { next(); }
|
|
}
|
|
|
|
fnodeTypesPlural.concat(["nodes", "errors"]).forEach(function (fnodeType) {
|
|
if (0 === nodes[fnodeType].length) { return; }
|
|
num += emitter.listeners(fnodeType).length;
|
|
emitter.emit(fnodeType, path, nodes[fnodeType], nextWhenReady);
|
|
});
|
|
nextWhenReady();
|
|
}
|
|
|
|
|
|
// Determine each file node's type
|
|
//
|
|
function sortFnodesByType(path, stats, fnodes, nextFile) {
|
|
isFnodeTypes.forEach(function (isType, i) {
|
|
if (stats[isType]()) {
|
|
if (stats.type) { throw new Error("is_" + type + " and " + isType); }
|
|
stats.type = fnodeTypes[i];
|
|
fnodes[fnodeTypesPlural[i]].push(stats);
|
|
// TODO throw to break;
|
|
}
|
|
});
|
|
if (!stats.type) { throw new Error(upath.join(path, stats.name) + ' isAnUndefinedType'); }
|
|
emitSingleEvents(path, stats, nextFile);
|
|
}
|
|
|
|
|
|
// Asynchronously get the stats
|
|
//
|
|
function getStats(path, files, walkDirs) {
|
|
var nodeGroups = {};
|
|
|
|
fnodeTypesPlural.concat("nodes", "errors").forEach(function (fnodeTypePlural) {
|
|
nodeGroups[fnodeTypePlural] = [];
|
|
});
|
|
|
|
function nextFile() {
|
|
var file = files.pop(), dirs = [], fnames = [];
|
|
|
|
if (undefined === file) {
|
|
emitPluralEvents(path, nodeGroups, function () {
|
|
nodeGroups.directories.forEach(function (dir) {
|
|
dirs.push(dir.name);
|
|
});
|
|
walkDirs(dirs);
|
|
});
|
|
return;
|
|
}
|
|
|
|
fstat(upath.join(path, file), function (err, stats) {
|
|
stats = stats || {};
|
|
stats.name = file;
|
|
nodeGroups.nodes.push(stats);
|
|
if (err) {
|
|
stats.error = err;
|
|
stats.type = 'error';
|
|
nodeGroups.errors.push(stats);
|
|
//emitter.emit('fileError', path, stats, noop);
|
|
return nextFile();
|
|
}
|
|
sortFnodesByType(path, stats, nodeGroups, nextFile);
|
|
});
|
|
}
|
|
nextFile();
|
|
}
|
|
|
|
function walk(path, next) {
|
|
fs.readdir(path, function (err, nodes) {
|
|
if (err) {
|
|
emitter.emit('directoryError', path, { error: err, name: path }, noop);
|
|
return next(); /*TODO*/ throw err;
|
|
}
|
|
getStats(path, nodes, function (dirs) {
|
|
walkDirs(path, dirs, next);
|
|
});
|
|
});
|
|
}
|
|
|
|
function walkDirs(path, dirs, cb) {
|
|
function nextDir() {
|
|
var dir = dirs.pop();
|
|
if (undefined === dir) {
|
|
delete dirs;
|
|
return cb();
|
|
}
|
|
walk(upath.join(path, dir), nextDir);
|
|
}
|
|
nextDir();
|
|
}
|
|
|
|
walk(upath.normalize(path), function () {
|
|
emitter.emit('end');
|
|
});
|
|
emitter.walk = newVersion;
|
|
emitter.whenever = newVersion;
|
|
return emitter;
|
|
}
|
|
module.exports = create;
|
|
module.exports.isFnodeTypes = isFnodeTypes;
|
|
module.exports.fnodeTypes = fnodeTypes;
|
|
module.exports.fnodeTypesPlural = fnodeTypesPlural;
|
|
}());
|