(function () { "use strict"; var fs = require('fs'), fstat = fs.lstat, Futures = require('futures'), EventEmitter = require('events').EventEmitter, upath = require('path'), // "FIFO" isn't easy to convert to camelCase 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" ]; // 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(emitter, 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(emitter, 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(stats, fnodes) { 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); //console.log(isType, fnodeTypesPlural[i], stats.name); // TODO throw to break; } }); /* // Won't really ever happen if (!stats.type) { stats.error = new Error(upath.join(path, stats.name) + ' isAnUndefinedType'); } */ } function create(path) { var emitter = new EventEmitter(), paths = [], path; function next() { // path could be local if dirHandler were anonymous //console.log('LEN: '+ paths.length); if (0 == paths.length) { emitter.emit('end'); return; } path = paths.pop(); //console.log("POP: " + path); fs.readdir(path, dirHandler); } function nodesHandler(nodes, args) { //console.log('USE: ' + path); var statses = []; var nodeGroups = {}; fnodeTypesPlural.concat("nodes", "errors").forEach(function (fnodeTypePlural) { nodeGroups[fnodeTypePlural] = []; }); args.forEach(function (arg, i) { var file = nodes[i], err = arg[0], stats = arg[1]; if (err) { stats = { error: err, name: file }; emitter.emit('error', err, path, stats); } if (stats) { stats.name = file; sortFnodesByType(stats, nodeGroups); emitter.emit('stat', path, stats); } }); emitter.emit('stats', path, statses); nodeGroups['directories'].forEach(function (stat) { paths.push(path + '/' + stat.name); //console.log('PUSH: ' + path + '/' + stat.name); }); /* //console.log('USE: ' + path); next(); */ emitPluralEvents(emitter, path, nodeGroups, next); } function dirHandler(err, nodes) { //console.log("HANDLE: " + path); var join = Futures.join(), i; if (err) { emitter.emit('error', err, path); } if (!nodes || 0 == nodes.length) { //console.log('EMPTY: ' + path); return next(); } // TODO don't duplicate efforts emitter.emit('nodes', path, nodes); for (i = 0; i < nodes.length; i += 1) { fstat(path + '/' + nodes[i], join.deliverer()); } join.when(function () { var args = Array.prototype.slice.call(arguments); nodesHandler(nodes, args); }); } //paths.push([path]); paths.push(path); next(); return emitter; } module.exports = create; }());