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; | ||
|  | }()); |