1
0
mirror of https://github.com/musix-org/musix-oss synced 2024-11-13 02:20:18 +00:00
musix-oss/node_modules/walkdir/walkdir.js
2020-03-03 22:30:50 +02:00

305 lines
7.4 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,eventHandler){
if(typeof options == 'function') cb = options;
options = options || {};
options.sync = true;
return walkdir(path,options,eventHandler);
};
// return promise.
walkdir.async = function(path,options,eventHandler){
return new Promise((resolve,reject)=>{
if(typeof options == 'function') cb = options;
options = options || {};
let emitter = walkdir(path,options,eventHandler)
emitter.on('error',reject)
emitter.on('fail',(path,err)=>{
err.message = 'Error walking": '+path+' '+err.message
if(err) reject(err)
})
let allPaths = {}
emitter.on('path',(path,stat)=>{
if(options.no_return !== true) allPaths[path] = stat;
})
emitter.on('end',()=>{
if(options.no_return !== true){
return resolve(options.return_object?allPaths:Object.keys(allPaths))
}
resolve()
})
})
}
function walkdir(path,options,cb){
if(typeof options == 'function') cb = options;
options = options || {};
if(options.find_links === undefined){
options.find_links = true;
}
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(options.track_inodes !== false) {
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[options.find_links?'lstatSync':'statSync'](path);
} catch (e) {
ex = e;
}
statAction(ex,stat);
} else {
fs[options.find_links?'lstat':'stat'](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='';
if(options.filter){
var res = options.filter(path,files)
if(!res){
throw new Error('option.filter function must return a array of strings or a promise')
}
// support filters that return a promise
if(res.then){
job(1)
res.then((files)=>{
job(-1)
for(var i=0,j=files.length;i<j;i++){
statter(path+sep+files[i],false,(depth||0)+1);
}
})
return;
}
//filtered files.
files = res
}
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) { e = _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',new Error('error reading first path in the walk '+path+'\n'+err),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;
}
}