Built motion from commit 10af8726.|2.6.34
[motion2.git] / legacy-libs / walkdir / walkdir.js
1 var EventEmitter = require('events').EventEmitter,
2 _fs = require('fs'),
3 _path = require('path'),
4 sep = _path.sep||'/';// 0.6.x
5
6
7 module.exports = walkdir;
8
9 walkdir.find = walkdir.walk = walkdir;
10
11 walkdir.sync = function(path,options,eventHandler){
12   if(typeof options == 'function') cb = options;
13   options = options || {};
14   options.sync = true;
15   return walkdir(path,options,eventHandler);
16 };
17
18 // return promise.
19 walkdir.async = function(path,options,eventHandler){
20   return new Promise((resolve,reject)=>{
21     if(typeof options == 'function') cb = options;
22     options = options || {};
23
24     let emitter = walkdir(path,options,eventHandler)
25
26     emitter.on('error',reject)
27     emitter.on('fail',(path,err)=>{
28       err.message = 'Error walking": '+path+' '+err.message
29       if(err) reject(err)
30     })
31
32     let allPaths = {}
33     emitter.on('path',(path,stat)=>{
34       if(options.no_return !== true) allPaths[path] = stat;
35     })
36     emitter.on('end',()=>{
37       if(options.no_return !== true){
38         return resolve(options.return_object?allPaths:Object.keys(allPaths))
39       }
40       resolve()
41     })
42   })
43 }
44
45 function walkdir(path,options,cb){
46
47   if(typeof options == 'function') cb = options;
48
49   options = options || {};
50   if(options.find_links === undefined){
51     options.find_links = true;
52   }
53   
54   var fs = options.fs || _fs;
55
56   var emitter = new EventEmitter(),
57   dontTraverse = [],
58   allPaths = (options.return_object?{}:[]),
59   resolved = false,
60   inos = {},
61   stop = 0,
62   pause = null,
63   ended = 0, 
64   jobs=0, 
65   job = function(value) {
66     jobs += value;
67     if(value < 1 && !tick) {
68       tick = 1;
69       process.nextTick(function(){
70         tick = 0;
71         if(jobs <= 0 && !ended) {
72           ended = 1;
73           emitter.emit('end');
74         }
75       });
76     }
77   }, tick = 0;
78
79   emitter.ignore = function(path){
80     if(Array.isArray(path)) dontTraverse.push.apply(dontTraverse,path)
81     else dontTraverse.push(path)
82     return this
83   }
84
85   //mapping is stat functions to event names.   
86   var statIs = [['isFile','file'],['isDirectory','directory'],['isSymbolicLink','link'],['isSocket','socket'],['isFIFO','fifo'],['isBlockDevice','blockdevice'],['isCharacterDevice','characterdevice']];
87
88   var statter = function (path,first,depth) {
89     job(1);
90     var statAction = function fn(err,stat,data) {
91
92       job(-1);
93       if(stop) return;
94
95       // in sync mode i found that node will sometimes return a null stat and no error =(
96       // this is reproduceable in file descriptors that no longer exist from this process
97       // after a readdir on /proc/3321/task/3321/ for example. Where 3321 is this pid
98       // node @ v0.6.10 
99       if(err || !stat) { 
100         emitter.emit('fail',path,err);
101         return;
102       }
103
104
105       //if i have evented this inode already dont again.
106       var fileName = _path.basename(path);
107       var fileKey = stat.dev + '-' + stat.ino + '-' + fileName;
108       if(options.track_inodes !== false) {
109         if(inos[fileKey] && stat.ino) return;
110         inos[fileKey] = 1;
111       }
112
113       if (first && stat.isDirectory()) {
114         emitter.emit('targetdirectory',path,stat,depth);
115         return;
116       }
117
118       emitter.emit('path', path, stat, depth);
119
120       var i,name;
121
122       for(var j=0,k=statIs.length;j<k;j++) {
123         if(stat[statIs[j][0]]()) {
124           emitter.emit(statIs[j][1],path,stat,depth);
125           break;
126         }
127       }
128     };
129     
130     if(options.sync) {
131       var stat,ex;
132       try{
133         stat = fs[options.find_links?'lstatSync':'statSync'](path);
134       } catch (e) {
135         ex = e;
136       }
137
138       statAction(ex,stat);
139     } else {
140         fs[options.find_links?'lstat':'stat'](path,statAction);
141     }
142   },readdir = function(path,stat,depth){
143     if(!resolved) {
144       path = _path.resolve(path);
145       resolved = 1;
146     }
147
148     if(options.max_depth && depth >= options.max_depth){
149       emitter.emit('maxdepth',path,stat,depth);
150       return;
151     }
152
153     if(dontTraverse.length){
154       for(var i=0;i<dontTraverse.length;++i){
155         if(dontTraverse[i] == path) {
156           dontTraverse.splice(i,1)
157           return;
158         }
159       }
160     }
161
162     job(1);
163     var readdirAction = function(err,files) {
164       job(-1);
165       if (err || !files) {
166         //permissions error or invalid files
167         emitter.emit('fail',path,err);
168         return;
169       }
170
171       if(!files.length) {
172         // empty directory event.
173         emitter.emit('empty',path,stat,depth);
174         return;     
175       }
176
177       if(path == sep) path='';
178       if(options.filter){
179         var res = options.filter(path,files)
180         if(!res){
181           throw new Error('option.filter funtion must return a array of strings or a promise')
182         }
183         // support filters that return a promise
184         if(res.then){
185           job(1)
186           res.then((files)=>{
187             job(-1)
188             for(var i=0,j=files.length;i<j;i++){
189               statter(path+sep+files[i],false,(depth||0)+1);
190             }
191           })
192           return;
193         }
194         //filtered files.
195         files = res
196       }
197       for(var i=0,j=files.length;i<j;i++){
198         statter(path+sep+files[i],false,(depth||0)+1);
199       }
200
201     };
202
203
204     //use same pattern for sync as async api
205     if(options.sync) {
206       var e,files;
207       try {
208           files = fs.readdirSync(path);
209       } catch (e) { }
210
211       readdirAction(e,files);
212     } else {
213       fs.readdir(path,readdirAction);
214     }
215   };
216
217   if (options.follow_symlinks) {
218     var linkAction = function(err,path,depth){
219       job(-1);
220       //TODO should fail event here on error?
221       statter(path,false,depth);
222     };
223
224     emitter.on('link',function(path,stat,depth){
225       job(1);
226       if(options.sync) {
227         var lpath,ex;
228         try {
229           lpath = fs.readlinkSync(path);
230         } catch(e) {
231           ex = e;
232         }
233         linkAction(ex,_path.resolve(_path.dirname(path),lpath),depth);
234
235       } else {
236         fs.readlink(path,function(err,lpath){
237           linkAction(err,_path.resolve(_path.dirname(path),lpath),depth);
238         });
239       }
240     });
241   }
242
243   if (cb) {
244     emitter.on('path',cb);
245   }
246
247   if (options.sync) {
248     if(!options.no_return){
249       emitter.on('path',function(path,stat){
250         if(options.return_object) allPaths[path] = stat;
251         else allPaths.push(path);
252       });
253     }
254   }
255
256   if (!options.no_recurse) {
257     emitter.on('directory',readdir);
258   }
259   //directory that was specified by argument.
260   emitter.once('targetdirectory',readdir);
261   //only a fail on the path specified by argument is fatal 
262   emitter.once('fail',function(_path,err){
263     //if the first dir fails its a real error
264     if(path == _path) {
265       emitter.emit('error',path,err);
266     }
267   });
268
269   statter(path,1);
270   if (options.sync) {
271     return allPaths;
272   } else {
273     //support stopping everything.
274     emitter.end = emitter.stop = function(){stop = 1;};
275     //support pausing everything
276     var emitQ = [];
277     emitter.pause = function(){
278       job(1);
279       pause = true;
280       emitter.emit = function(){
281         emitQ.push(arguments);
282       };
283     };
284     // support getting the show going again
285     emitter.resume = function(){
286       if(!pause) return;
287       pause = false;
288       // not pending
289       job(-1);
290       //replace emit
291       emitter.emit = EventEmitter.prototype.emit;
292       // local ref
293       var q = emitQ;
294       // clear ref to prevent infinite loops
295       emitQ = [];
296       while(q.length) {
297         emitter.emit.apply(emitter,q.shift());
298       }
299     };
300
301     return emitter;
302   }
303
304 }