Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / chokidar / lib / fsevents-handler.js
1 'use strict';
2
3 var fs = require('fs');
4 var sysPath = require('path');
5 var readdirp = require('readdirp');
6 var fsevents;
7 try { fsevents = require('fsevents'); } catch (error) {}
8
9 // fsevents instance helper functions
10
11 // object to hold per-process fsevents instances
12 // (may be shared across chokidar FSWatcher instances)
13 var FSEventsWatchers = Object.create(null);
14
15 // Threshold of duplicate path prefixes at which to start
16 // consolidating going forward
17 var consolidateThreshhold = 10;
18
19 // Private function: Instantiates the fsevents interface
20
21 // * path       - string, path to be watched
22 // * callback   - function, called when fsevents is bound and ready
23
24 // Returns new fsevents instance
25 function createFSEventsInstance(path, callback) {
26   return (new fsevents(path)).on('fsevent', callback).start();
27 }
28
29 // Private function: Instantiates the fsevents interface or binds listeners
30 // to an existing one covering the same file tree
31
32 // * path       - string, path to be watched
33 // * realPath   - string, real path (in case of symlinks)
34 // * listener   - function, called when fsevents emits events
35 // * rawEmitter - function, passes data to listeners of the 'raw' event
36
37 // Returns close function
38 function setFSEventsListener(path, realPath, listener, rawEmitter) {
39   var watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path;
40   var watchContainer;
41   var parentPath = sysPath.dirname(watchPath);
42
43   // If we've accumulated a substantial number of paths that
44   // could have been consolidated by watching one directory
45   // above the current one, create a watcher on the parent
46   // path instead, so that we do consolidate going forward.
47   if (couldConsolidate(parentPath)) {
48     watchPath = parentPath;
49   }
50
51   var resolvedPath = sysPath.resolve(path);
52   var hasSymlink = resolvedPath !== realPath;
53   function filteredListener(fullPath, flags, info) {
54     if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath);
55     if (
56       fullPath === resolvedPath ||
57       !fullPath.indexOf(resolvedPath + sysPath.sep)
58     ) listener(fullPath, flags, info);
59   }
60
61   // check if there is already a watcher on a parent path
62   // modifies `watchPath` to the parent path when it finds a match
63   function watchedParent() {
64     return Object.keys(FSEventsWatchers).some(function(watchedPath) {
65       // condition is met when indexOf returns 0
66       if (!realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep)) {
67         watchPath = watchedPath;
68         return true;
69       }
70     });
71   }
72
73   if (watchPath in FSEventsWatchers || watchedParent()) {
74     watchContainer = FSEventsWatchers[watchPath];
75     watchContainer.listeners.push(filteredListener);
76   } else {
77     watchContainer = FSEventsWatchers[watchPath] = {
78       listeners: [filteredListener],
79       rawEmitters: [rawEmitter],
80       watcher: createFSEventsInstance(watchPath, function(fullPath, flags) {
81         var info = fsevents.getInfo(fullPath, flags);
82         watchContainer.listeners.forEach(function(listener) {
83           listener(fullPath, flags, info);
84         });
85         watchContainer.rawEmitters.forEach(function(emitter) {
86           emitter(info.event, fullPath, info);
87         });
88       })
89     };
90   }
91   var listenerIndex = watchContainer.listeners.length - 1;
92
93   // removes this instance's listeners and closes the underlying fsevents
94   // instance if there are no more listeners left
95   return function close() {
96     delete watchContainer.listeners[listenerIndex];
97     delete watchContainer.rawEmitters[listenerIndex];
98     if (!Object.keys(watchContainer.listeners).length) {
99       watchContainer.watcher.stop();
100       delete FSEventsWatchers[watchPath];
101     }
102   };
103 }
104
105 // Decide whether or not we should start a new higher-level
106 // parent watcher
107 function couldConsolidate(path) {
108   var keys = Object.keys(FSEventsWatchers);
109   var count = 0;
110
111   for (var i = 0, len = keys.length; i < len; ++i) {
112     var watchPath = keys[i];
113     if (watchPath.indexOf(path) === 0) {
114       count++;
115       if (count >= consolidateThreshhold) {
116         return true;
117       }
118     }
119   }
120
121   return false;
122 }
123
124 // returns boolean indicating whether fsevents can be used
125 function canUse() {
126   return fsevents && Object.keys(FSEventsWatchers).length < 128;
127 }
128
129 // determines subdirectory traversal levels from root to path
130 function depth(path, root) {
131   var i = 0;
132   while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++;
133   return i;
134 }
135
136 // fake constructor for attaching fsevents-specific prototype methods that
137 // will be copied to FSWatcher's prototype
138 function FsEventsHandler() {}
139
140 // Private method: Handle symlinks encountered during directory scan
141
142 // * watchPath  - string, file/dir path to be watched with fsevents
143 // * realPath   - string, real path (in case of symlinks)
144 // * transform  - function, path transformer
145 // * globFilter - function, path filter in case a glob pattern was provided
146
147 // Returns close function for the watcher instance
148 FsEventsHandler.prototype._watchWithFsEvents =
149 function(watchPath, realPath, transform, globFilter) {
150   if (this._isIgnored(watchPath)) return;
151   var watchCallback = function(fullPath, flags, info) {
152     if (
153       this.options.depth !== undefined &&
154       depth(fullPath, realPath) > this.options.depth
155     ) return;
156     var path = transform(sysPath.join(
157       watchPath, sysPath.relative(watchPath, fullPath)
158     ));
159     if (globFilter && !globFilter(path)) return;
160     // ensure directories are tracked
161     var parent = sysPath.dirname(path);
162     var item = sysPath.basename(path);
163     var watchedDir = this._getWatchedDir(
164       info.type === 'directory' ? path : parent
165     );
166     var checkIgnored = function(stats) {
167       if (this._isIgnored(path, stats)) {
168         this._ignoredPaths[path] = true;
169         if (stats && stats.isDirectory()) {
170           this._ignoredPaths[path + '/**/*'] = true;
171         }
172         return true;
173       } else {
174         delete this._ignoredPaths[path];
175         delete this._ignoredPaths[path + '/**/*'];
176       }
177     }.bind(this);
178
179     var handleEvent = function(event) {
180       if (checkIgnored()) return;
181
182       if (event === 'unlink') {
183         // suppress unlink events on never before seen files
184         if (info.type === 'directory' || watchedDir.has(item)) {
185           this._remove(parent, item);
186         }
187       } else {
188         if (event === 'add') {
189           // track new directories
190           if (info.type === 'directory') this._getWatchedDir(path);
191
192           if (info.type === 'symlink' && this.options.followSymlinks) {
193             // push symlinks back to the top of the stack to get handled
194             var curDepth = this.options.depth === undefined ?
195               undefined : depth(fullPath, realPath) + 1;
196             return this._addToFsEvents(path, false, true, curDepth);
197           } else {
198             // track new paths
199             // (other than symlinks being followed, which will be tracked soon)
200             this._getWatchedDir(parent).add(item);
201           }
202         }
203         var eventName = info.type === 'directory' ? event + 'Dir' : event;
204         this._emit(eventName, path);
205         if (eventName === 'addDir') this._addToFsEvents(path, false, true);
206       }
207     }.bind(this);
208
209     function addOrChange() {
210       handleEvent(watchedDir.has(item) ? 'change' : 'add');
211     }
212     function checkFd() {
213       fs.open(path, 'r', function(error, fd) {
214         if (fd) fs.close(fd);
215         error && error.code !== 'EACCES' ?
216           handleEvent('unlink') : addOrChange();
217       });
218     }
219     // correct for wrong events emitted
220     var wrongEventFlags = [
221       69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912
222     ];
223     if (wrongEventFlags.indexOf(flags) !== -1 || info.event === 'unknown') {
224       if (typeof this.options.ignored === 'function') {
225         fs.stat(path, function(error, stats) {
226           if (checkIgnored(stats)) return;
227           stats ? addOrChange() : handleEvent('unlink');
228         });
229       } else {
230         checkFd();
231       }
232     } else {
233       switch (info.event) {
234       case 'created':
235       case 'modified':
236         return addOrChange();
237       case 'deleted':
238       case 'moved':
239         return checkFd();
240       }
241     }
242   }.bind(this);
243
244   var closer = setFSEventsListener(
245     watchPath,
246     realPath,
247     watchCallback,
248     this.emit.bind(this, 'raw')
249   );
250
251   this._emitReady();
252   return closer;
253 };
254
255 // Private method: Handle symlinks encountered during directory scan
256
257 // * linkPath   - string, path to symlink
258 // * fullPath   - string, absolute path to the symlink
259 // * transform  - function, pre-existing path transformer
260 // * curDepth   - int, level of subdirectories traversed to where symlink is
261
262 // Returns nothing
263 FsEventsHandler.prototype._handleFsEventsSymlink =
264 function(linkPath, fullPath, transform, curDepth) {
265   // don't follow the same symlink more than once
266   if (this._symlinkPaths[fullPath]) return;
267   else this._symlinkPaths[fullPath] = true;
268
269   this._readyCount++;
270
271   fs.realpath(linkPath, function(error, linkTarget) {
272     if (this._handleError(error) || this._isIgnored(linkTarget)) {
273       return this._emitReady();
274     }
275
276     this._readyCount++;
277
278     // add the linkTarget for watching with a wrapper for transform
279     // that causes emitted paths to incorporate the link's path
280     this._addToFsEvents(linkTarget || linkPath, function(path) {
281       var dotSlash = '.' + sysPath.sep;
282       var aliasedPath = linkPath;
283       if (linkTarget && linkTarget !== dotSlash) {
284         aliasedPath = path.replace(linkTarget, linkPath);
285       } else if (path !== dotSlash) {
286         aliasedPath = sysPath.join(linkPath, path);
287       }
288       return transform(aliasedPath);
289     }, false, curDepth);
290   }.bind(this));
291 };
292
293 // Private method: Handle added path with fsevents
294
295 // * path       - string, file/directory path or glob pattern
296 // * transform  - function, converts working path to what the user expects
297 // * forceAdd   - boolean, ensure add is emitted
298 // * priorDepth - int, level of subdirectories already traversed
299
300 // Returns nothing
301 FsEventsHandler.prototype._addToFsEvents =
302 function(path, transform, forceAdd, priorDepth) {
303
304   // applies transform if provided, otherwise returns same value
305   var processPath = typeof transform === 'function' ?
306     transform : function(val) { return val; };
307
308   var emitAdd = function(newPath, stats) {
309     var pp = processPath(newPath);
310     var isDir = stats.isDirectory();
311     var dirObj = this._getWatchedDir(sysPath.dirname(pp));
312     var base = sysPath.basename(pp);
313
314     // ensure empty dirs get tracked
315     if (isDir) this._getWatchedDir(pp);
316
317     if (dirObj.has(base)) return;
318     dirObj.add(base);
319
320     if (!this.options.ignoreInitial || forceAdd === true) {
321       this._emit(isDir ? 'addDir' : 'add', pp, stats);
322     }
323   }.bind(this);
324
325   var wh = this._getWatchHelpers(path);
326
327   // evaluate what is at the path we're being asked to watch
328   fs[wh.statMethod](wh.watchPath, function(error, stats) {
329     if (this._handleError(error) || this._isIgnored(wh.watchPath, stats)) {
330       this._emitReady();
331       return this._emitReady();
332     }
333
334     if (stats.isDirectory()) {
335       // emit addDir unless this is a glob parent
336       if (!wh.globFilter) emitAdd(processPath(path), stats);
337
338       // don't recurse further if it would exceed depth setting
339       if (priorDepth && priorDepth > this.options.depth) return;
340
341       // scan the contents of the dir
342       readdirp({
343         root: wh.watchPath,
344         entryType: 'all',
345         fileFilter: wh.filterPath,
346         directoryFilter: wh.filterDir,
347         lstat: true,
348         depth: this.options.depth - (priorDepth || 0)
349       }).on('data', function(entry) {
350         // need to check filterPath on dirs b/c filterDir is less restrictive
351         if (entry.stat.isDirectory() && !wh.filterPath(entry)) return;
352
353         var joinedPath = sysPath.join(wh.watchPath, entry.path);
354         var fullPath = entry.fullPath;
355
356         if (wh.followSymlinks && entry.stat.isSymbolicLink()) {
357           // preserve the current depth here since it can't be derived from
358           // real paths past the symlink
359           var curDepth = this.options.depth === undefined ?
360             undefined : depth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;
361
362           this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth);
363         } else {
364           emitAdd(joinedPath, entry.stat);
365         }
366       }.bind(this)).on('error', function() {
367         // Ignore readdirp errors
368       }).on('end', this._emitReady);
369     } else {
370       emitAdd(wh.watchPath, stats);
371       this._emitReady();
372     }
373   }.bind(this));
374
375   if (this.options.persistent && forceAdd !== true) {
376     var initWatch = function(error, realPath) {
377       var closer = this._watchWithFsEvents(
378         wh.watchPath,
379         sysPath.resolve(realPath || wh.watchPath),
380         processPath,
381         wh.globFilter
382       );
383       if (closer) this._closers[path] = closer;
384     }.bind(this);
385
386     if (typeof transform === 'function') {
387       // realpath has already been resolved
388       initWatch();
389     } else {
390       fs.realpath(wh.watchPath, initWatch);
391     }
392   }
393 };
394
395 module.exports = FsEventsHandler;
396 module.exports.canUse = canUse;