255 lines
5.9 KiB
JavaScript
255 lines
5.9 KiB
JavaScript
var EventEmitter = require('events').EventEmitter,
|
|
_fs = require('fs'),
|
|
_path = require('path'),
|
|
sep = _path.sep||'/';// 0.6.x
|
|
|
|
|
|
module.exports = walkdir;
|
|
|
|
walkdir.find = walkdir.walk = walkdir;
|
|
|
|
walkdir.sync = function(path,options,cb){
|
|
if(typeof options == 'function') cb = options;
|
|
options = options || {};
|
|
options.sync = true;
|
|
return walkdir(path,options,cb);
|
|
|
|
};
|
|
|
|
function walkdir(path,options,cb){
|
|
|
|
if(typeof options == 'function') cb = options;
|
|
|
|
options = options || {};
|
|
|
|
var fs = options.fs || _fs;
|
|
|
|
var emitter = new EventEmitter(),
|
|
dontTraverse = [],
|
|
allPaths = (options.return_object?{}:[]),
|
|
resolved = false,
|
|
inos = {},
|
|
stop = 0,
|
|
pause = null,
|
|
ended = 0,
|
|
jobs=0,
|
|
job = function(value) {
|
|
jobs += value;
|
|
if(value < 1 && !tick) {
|
|
tick = 1;
|
|
process.nextTick(function(){
|
|
tick = 0;
|
|
if(jobs <= 0 && !ended) {
|
|
ended = 1;
|
|
emitter.emit('end');
|
|
}
|
|
});
|
|
}
|
|
}, tick = 0;
|
|
|
|
emitter.ignore = function(path){
|
|
if(Array.isArray(path)) dontTraverse.push.apply(dontTraverse,path)
|
|
else dontTraverse.push(path)
|
|
return this
|
|
}
|
|
|
|
//mapping is stat functions to event names.
|
|
var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];
|
|
|
|
var statter = function (path,first,depth) {
|
|
job(1);
|
|
var statAction = function fn(err,stat,data) {
|
|
|
|
job(-1);
|
|
if(stop) return;
|
|
|
|
// in sync mode i found that node will sometimes return a null stat and no error =(
|
|
// this is reproduceable in file descriptors that no longer exist from this process
|
|
// after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
|
|
// node @ v0.6.10
|
|
if(err || !stat) {
|
|
emitter.emit('fail',path,err);
|
|
return;
|
|
}
|
|
|
|
|
|
//if i have evented this inode already dont again.
|
|
var fileName = _path.basename(path);
|
|
var fileKey = stat.dev + '-' + stat.ino + '-' + fileName;
|
|
if(inos[fileKey] && stat.ino) return;
|
|
inos[fileKey] = 1;
|
|
|
|
if (first && stat.isDirectory()) {
|
|
emitter.emit('targetdirectory',path,stat,depth);
|
|
return;
|
|
}
|
|
|
|
emitter.emit('path', path, stat,depth);
|
|
|
|
var i,name;
|
|
|
|
for(var j=0,k=statIs.length;j<k;j++) {
|
|
if(stat[statIs[j][0]]()) {
|
|
emitter.emit(statIs[j][1],path,stat,depth);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
if(options.sync) {
|
|
var stat,ex;
|
|
try{
|
|
stat = fs.lstatSync(path);
|
|
} catch (e) {
|
|
ex = e;
|
|
}
|
|
|
|
statAction(ex,stat);
|
|
} else {
|
|
fs.lstat(path,statAction);
|
|
}
|
|
},readdir = function(path,stat,depth){
|
|
if(!resolved) {
|
|
path = _path.resolve(path);
|
|
resolved = 1;
|
|
}
|
|
|
|
if(options.max_depth && depth >= options.max_depth){
|
|
emitter.emit('maxdepth',path,stat,depth);
|
|
return;
|
|
}
|
|
|
|
if(dontTraverse.length){
|
|
for(var i=0;i<dontTraverse.length;++i){
|
|
if(dontTraverse[i] == path) {
|
|
dontTraverse.splice(i,1)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
job(1);
|
|
var readdirAction = function(err,files) {
|
|
job(-1);
|
|
if (err || !files) {
|
|
//permissions error or invalid files
|
|
emitter.emit('fail',path,err);
|
|
return;
|
|
}
|
|
|
|
if(!files.length) {
|
|
// empty directory event.
|
|
emitter.emit('empty',path,stat,depth);
|
|
return;
|
|
}
|
|
|
|
if(path == sep) path='';
|
|
for(var i=0,j=files.length;i<j;i++){
|
|
statter(path+sep+files[i],false,(depth||0)+1);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//use same pattern for sync as async api
|
|
if(options.sync) {
|
|
var e,files;
|
|
try {
|
|
files = fs.readdirSync(path);
|
|
} catch (e) { }
|
|
|
|
readdirAction(e,files);
|
|
} else {
|
|
fs.readdir(path,readdirAction);
|
|
}
|
|
};
|
|
|
|
if (options.follow_symlinks) {
|
|
var linkAction = function(err,path,depth){
|
|
job(-1);
|
|
//TODO should fail event here on error?
|
|
statter(path,false,depth);
|
|
};
|
|
|
|
emitter.on('link',function(path,stat,depth){
|
|
job(1);
|
|
if(options.sync) {
|
|
var lpath,ex;
|
|
try {
|
|
lpath = fs.readlinkSync(path);
|
|
} catch(e) {
|
|
ex = e;
|
|
}
|
|
linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);
|
|
|
|
} else {
|
|
fs.readlink(path,function(err,lpath){
|
|
linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (cb) {
|
|
emitter.on('path',cb);
|
|
}
|
|
|
|
if (options.sync) {
|
|
if(!options.no_return){
|
|
emitter.on('path',function(path,stat){
|
|
if(options.return_object) allPaths[path] = stat;
|
|
else allPaths.push(path);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!options.no_recurse) {
|
|
emitter.on('directory',readdir);
|
|
}
|
|
//directory that was specified by argument.
|
|
emitter.once('targetdirectory',readdir);
|
|
//only a fail on the path specified by argument is fatal
|
|
emitter.once('fail',function(_path,err){
|
|
//if the first dir fails its a real error
|
|
if(path == _path) {
|
|
emitter.emit('error',path,err);
|
|
}
|
|
});
|
|
|
|
statter(path,1);
|
|
if (options.sync) {
|
|
return allPaths;
|
|
} else {
|
|
//support stopping everything.
|
|
emitter.end = emitter.stop = function(){stop = 1;};
|
|
//support pausing everything
|
|
var emitQ = [];
|
|
emitter.pause = function(){
|
|
job(1);
|
|
pause = true;
|
|
emitter.emit = function(){
|
|
emitQ.push(arguments);
|
|
};
|
|
};
|
|
// support getting the show going again
|
|
emitter.resume = function(){
|
|
if(!pause) return;
|
|
pause = false;
|
|
// not pending
|
|
job(-1);
|
|
//replace emit
|
|
emitter.emit = EventEmitter.prototype.emit;
|
|
// local ref
|
|
var q = emitQ;
|
|
// clear ref to prevent infinite loops
|
|
emitQ = [];
|
|
while(q.length) {
|
|
emitter.emit.apply(emitter,q.shift());
|
|
}
|
|
};
|
|
|
|
return emitter;
|
|
}
|
|
|
|
}
|