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