1 |
3a515b92
|
cagy
|
'use strict';
|
2 |
|
|
|
3 |
|
|
var fs = require('fs');
|
4 |
|
|
var sysPath = require('path');
|
5 |
|
|
var readdirp = require('readdirp');
|
6 |
|
|
var isBinaryPath = require('is-binary-path');
|
7 |
|
|
|
8 |
|
|
// fs.watch helpers
|
9 |
|
|
|
10 |
|
|
// object to hold per-process fs.watch instances
|
11 |
|
|
// (may be shared across chokidar FSWatcher instances)
|
12 |
|
|
var FsWatchInstances = Object.create(null);
|
13 |
|
|
|
14 |
|
|
|
15 |
|
|
// Private function: Instantiates the fs.watch interface
|
16 |
|
|
|
17 |
|
|
// * path - string, path to be watched
|
18 |
|
|
// * options - object, options to be passed to fs.watch
|
19 |
|
|
// * listener - function, main event handler
|
20 |
|
|
// * errHandler - function, handler which emits info about errors
|
21 |
|
|
// * emitRaw - function, handler which emits raw event data
|
22 |
|
|
|
23 |
|
|
// Returns new fsevents instance
|
24 |
|
|
function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
|
25 |
|
|
var handleEvent = function(rawEvent, evPath) {
|
26 |
|
|
listener(path);
|
27 |
|
|
emitRaw(rawEvent, evPath, {watchedPath: path});
|
28 |
|
|
|
29 |
|
|
// emit based on events occurring for files from a directory's watcher in
|
30 |
|
|
// case the file's watcher misses it (and rely on throttling to de-dupe)
|
31 |
|
|
if (evPath && path !== evPath) {
|
32 |
|
|
fsWatchBroadcast(
|
33 |
|
|
sysPath.resolve(path, evPath), 'listeners', sysPath.join(path, evPath)
|
34 |
|
|
);
|
35 |
|
|
}
|
36 |
|
|
};
|
37 |
|
|
try {
|
38 |
|
|
return fs.watch(path, options, handleEvent);
|
39 |
|
|
} catch (error) {
|
40 |
|
|
errHandler(error);
|
41 |
|
|
}
|
42 |
|
|
}
|
43 |
|
|
|
44 |
|
|
// Private function: Helper for passing fs.watch event data to a
|
45 |
|
|
// collection of listeners
|
46 |
|
|
|
47 |
|
|
// * fullPath - string, absolute path bound to the fs.watch instance
|
48 |
|
|
// * type - string, listener type
|
49 |
|
|
// * val[1..3] - arguments to be passed to listeners
|
50 |
|
|
|
51 |
|
|
// Returns nothing
|
52 |
|
|
function fsWatchBroadcast(fullPath, type, val1, val2, val3) {
|
53 |
|
|
if (!FsWatchInstances[fullPath]) return;
|
54 |
|
|
FsWatchInstances[fullPath][type].forEach(function(listener) {
|
55 |
|
|
listener(val1, val2, val3);
|
56 |
|
|
});
|
57 |
|
|
}
|
58 |
|
|
|
59 |
|
|
// Private function: Instantiates the fs.watch interface or binds listeners
|
60 |
|
|
// to an existing one covering the same file system entry
|
61 |
|
|
|
62 |
|
|
// * path - string, path to be watched
|
63 |
|
|
// * fullPath - string, absolute path
|
64 |
|
|
// * options - object, options to be passed to fs.watch
|
65 |
|
|
// * handlers - object, container for event listener functions
|
66 |
|
|
|
67 |
|
|
// Returns close function
|
68 |
|
|
function setFsWatchListener(path, fullPath, options, handlers) {
|
69 |
|
|
var listener = handlers.listener;
|
70 |
|
|
var errHandler = handlers.errHandler;
|
71 |
|
|
var rawEmitter = handlers.rawEmitter;
|
72 |
|
|
var container = FsWatchInstances[fullPath];
|
73 |
|
|
var watcher;
|
74 |
|
|
if (!options.persistent) {
|
75 |
|
|
watcher = createFsWatchInstance(
|
76 |
|
|
path, options, listener, errHandler, rawEmitter
|
77 |
|
|
);
|
78 |
|
|
return watcher.close.bind(watcher);
|
79 |
|
|
}
|
80 |
|
|
if (!container) {
|
81 |
|
|
watcher = createFsWatchInstance(
|
82 |
|
|
path,
|
83 |
|
|
options,
|
84 |
|
|
fsWatchBroadcast.bind(null, fullPath, 'listeners'),
|
85 |
|
|
errHandler, // no need to use broadcast here
|
86 |
|
|
fsWatchBroadcast.bind(null, fullPath, 'rawEmitters')
|
87 |
|
|
);
|
88 |
|
|
if (!watcher) return;
|
89 |
|
|
var broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers');
|
90 |
|
|
watcher.on('error', function(error) {
|
91 |
|
|
container.watcherUnusable = true; // documented since Node 10.4.1
|
92 |
|
|
// Workaround for https://github.com/joyent/node/issues/4337
|
93 |
|
|
if (process.platform === 'win32' && error.code === 'EPERM') {
|
94 |
|
|
fs.open(path, 'r', function(err, fd) {
|
95 |
|
|
if (!err) fs.close(fd, function(err) {
|
96 |
|
|
if (!err) broadcastErr(error);
|
97 |
|
|
});
|
98 |
|
|
});
|
99 |
|
|
} else {
|
100 |
|
|
broadcastErr(error);
|
101 |
|
|
}
|
102 |
|
|
});
|
103 |
|
|
container = FsWatchInstances[fullPath] = {
|
104 |
|
|
listeners: [listener],
|
105 |
|
|
errHandlers: [errHandler],
|
106 |
|
|
rawEmitters: [rawEmitter],
|
107 |
|
|
watcher: watcher
|
108 |
|
|
};
|
109 |
|
|
} else {
|
110 |
|
|
container.listeners.push(listener);
|
111 |
|
|
container.errHandlers.push(errHandler);
|
112 |
|
|
container.rawEmitters.push(rawEmitter);
|
113 |
|
|
}
|
114 |
|
|
var listenerIndex = container.listeners.length - 1;
|
115 |
|
|
|
116 |
|
|
// removes this instance's listeners and closes the underlying fs.watch
|
117 |
|
|
// instance if there are no more listeners left
|
118 |
|
|
return function close() {
|
119 |
|
|
delete container.listeners[listenerIndex];
|
120 |
|
|
delete container.errHandlers[listenerIndex];
|
121 |
|
|
delete container.rawEmitters[listenerIndex];
|
122 |
|
|
if (!Object.keys(container.listeners).length) {
|
123 |
|
|
if (!container.watcherUnusable) { // check to protect against issue #730
|
124 |
|
|
container.watcher.close();
|
125 |
|
|
}
|
126 |
|
|
delete FsWatchInstances[fullPath];
|
127 |
|
|
}
|
128 |
|
|
};
|
129 |
|
|
}
|
130 |
|
|
|
131 |
|
|
// fs.watchFile helpers
|
132 |
|
|
|
133 |
|
|
// object to hold per-process fs.watchFile instances
|
134 |
|
|
// (may be shared across chokidar FSWatcher instances)
|
135 |
|
|
var FsWatchFileInstances = Object.create(null);
|
136 |
|
|
|
137 |
|
|
// Private function: Instantiates the fs.watchFile interface or binds listeners
|
138 |
|
|
// to an existing one covering the same file system entry
|
139 |
|
|
|
140 |
|
|
// * path - string, path to be watched
|
141 |
|
|
// * fullPath - string, absolute path
|
142 |
|
|
// * options - object, options to be passed to fs.watchFile
|
143 |
|
|
// * handlers - object, container for event listener functions
|
144 |
|
|
|
145 |
|
|
// Returns close function
|
146 |
|
|
function setFsWatchFileListener(path, fullPath, options, handlers) {
|
147 |
|
|
var listener = handlers.listener;
|
148 |
|
|
var rawEmitter = handlers.rawEmitter;
|
149 |
|
|
var container = FsWatchFileInstances[fullPath];
|
150 |
|
|
var listeners = [];
|
151 |
|
|
var rawEmitters = [];
|
152 |
|
|
if (
|
153 |
|
|
container && (
|
154 |
|
|
container.options.persistent < options.persistent ||
|
155 |
|
|
container.options.interval > options.interval
|
156 |
|
|
)
|
157 |
|
|
) {
|
158 |
|
|
// "Upgrade" the watcher to persistence or a quicker interval.
|
159 |
|
|
// This creates some unlikely edge case issues if the user mixes
|
160 |
|
|
// settings in a very weird way, but solving for those cases
|
161 |
|
|
// doesn't seem worthwhile for the added complexity.
|
162 |
|
|
listeners = container.listeners;
|
163 |
|
|
rawEmitters = container.rawEmitters;
|
164 |
|
|
fs.unwatchFile(fullPath);
|
165 |
|
|
container = false;
|
166 |
|
|
}
|
167 |
|
|
if (!container) {
|
168 |
|
|
listeners.push(listener);
|
169 |
|
|
rawEmitters.push(rawEmitter);
|
170 |
|
|
container = FsWatchFileInstances[fullPath] = {
|
171 |
|
|
listeners: listeners,
|
172 |
|
|
rawEmitters: rawEmitters,
|
173 |
|
|
options: options,
|
174 |
|
|
watcher: fs.watchFile(fullPath, options, function(curr, prev) {
|
175 |
|
|
container.rawEmitters.forEach(function(rawEmitter) {
|
176 |
|
|
rawEmitter('change', fullPath, {curr: curr, prev: prev});
|
177 |
|
|
});
|
178 |
|
|
var currmtime = curr.mtime.getTime();
|
179 |
|
|
if (curr.size !== prev.size || currmtime > prev.mtime.getTime() || currmtime === 0) {
|
180 |
|
|
container.listeners.forEach(function(listener) {
|
181 |
|
|
listener(path, curr);
|
182 |
|
|
});
|
183 |
|
|
}
|
184 |
|
|
})
|
185 |
|
|
};
|
186 |
|
|
} else {
|
187 |
|
|
container.listeners.push(listener);
|
188 |
|
|
container.rawEmitters.push(rawEmitter);
|
189 |
|
|
}
|
190 |
|
|
var listenerIndex = container.listeners.length - 1;
|
191 |
|
|
|
192 |
|
|
// removes this instance's listeners and closes the underlying fs.watchFile
|
193 |
|
|
// instance if there are no more listeners left
|
194 |
|
|
return function close() {
|
195 |
|
|
delete container.listeners[listenerIndex];
|
196 |
|
|
delete container.rawEmitters[listenerIndex];
|
197 |
|
|
if (!Object.keys(container.listeners).length) {
|
198 |
|
|
fs.unwatchFile(fullPath);
|
199 |
|
|
delete FsWatchFileInstances[fullPath];
|
200 |
|
|
}
|
201 |
|
|
};
|
202 |
|
|
}
|
203 |
|
|
|
204 |
|
|
// fake constructor for attaching nodefs-specific prototype methods that
|
205 |
|
|
// will be copied to FSWatcher's prototype
|
206 |
|
|
function NodeFsHandler() {}
|
207 |
|
|
|
208 |
|
|
// Private method: Watch file for changes with fs.watchFile or fs.watch.
|
209 |
|
|
|
210 |
|
|
// * path - string, path to file or directory.
|
211 |
|
|
// * listener - function, to be executed on fs change.
|
212 |
|
|
|
213 |
|
|
// Returns close function for the watcher instance
|
214 |
|
|
NodeFsHandler.prototype._watchWithNodeFs =
|
215 |
|
|
function(path, listener) {
|
216 |
|
|
var directory = sysPath.dirname(path);
|
217 |
|
|
var basename = sysPath.basename(path);
|
218 |
|
|
var parent = this._getWatchedDir(directory);
|
219 |
|
|
parent.add(basename);
|
220 |
|
|
var absolutePath = sysPath.resolve(path);
|
221 |
|
|
var options = {persistent: this.options.persistent};
|
222 |
|
|
if (!listener) listener = Function.prototype; // empty function
|
223 |
|
|
|
224 |
|
|
var closer;
|
225 |
|
|
if (this.options.usePolling) {
|
226 |
|
|
options.interval = this.enableBinaryInterval && isBinaryPath(basename) ?
|
227 |
|
|
this.options.binaryInterval : this.options.interval;
|
228 |
|
|
closer = setFsWatchFileListener(path, absolutePath, options, {
|
229 |
|
|
listener: listener,
|
230 |
|
|
rawEmitter: this.emit.bind(this, 'raw')
|
231 |
|
|
});
|
232 |
|
|
} else {
|
233 |
|
|
closer = setFsWatchListener(path, absolutePath, options, {
|
234 |
|
|
listener: listener,
|
235 |
|
|
errHandler: this._handleError.bind(this),
|
236 |
|
|
rawEmitter: this.emit.bind(this, 'raw')
|
237 |
|
|
});
|
238 |
|
|
}
|
239 |
|
|
return closer;
|
240 |
|
|
};
|
241 |
|
|
|
242 |
|
|
// Private method: Watch a file and emit add event if warranted
|
243 |
|
|
|
244 |
|
|
// * file - string, the file's path
|
245 |
|
|
// * stats - object, result of fs.stat
|
246 |
|
|
// * initialAdd - boolean, was the file added at watch instantiation?
|
247 |
|
|
// * callback - function, called when done processing as a newly seen file
|
248 |
|
|
|
249 |
|
|
// Returns close function for the watcher instance
|
250 |
|
|
NodeFsHandler.prototype._handleFile =
|
251 |
|
|
function(file, stats, initialAdd, callback) {
|
252 |
|
|
var dirname = sysPath.dirname(file);
|
253 |
|
|
var basename = sysPath.basename(file);
|
254 |
|
|
var parent = this._getWatchedDir(dirname);
|
255 |
|
|
// stats is always present
|
256 |
|
|
var prevStats = stats;
|
257 |
|
|
|
258 |
|
|
// if the file is already being watched, do nothing
|
259 |
|
|
if (parent.has(basename)) return callback();
|
260 |
|
|
|
261 |
|
|
// kick off the watcher
|
262 |
|
|
var closer = this._watchWithNodeFs(file, function(path, newStats) {
|
263 |
|
|
if (!this._throttle('watch', file, 5)) return;
|
264 |
|
|
if (!newStats || newStats && newStats.mtime.getTime() === 0) {
|
265 |
|
|
fs.stat(file, function(error, newStats) {
|
266 |
|
|
// Fix issues where mtime is null but file is still present
|
267 |
|
|
if (error) {
|
268 |
|
|
this._remove(dirname, basename);
|
269 |
|
|
} else {
|
270 |
|
|
// Check that change event was not fired because of changed only accessTime.
|
271 |
|
|
var at = newStats.atime.getTime();
|
272 |
|
|
var mt = newStats.mtime.getTime();
|
273 |
|
|
if (!at || at <= mt || mt !== prevStats.mtime.getTime()) {
|
274 |
|
|
this._emit('change', file, newStats);
|
275 |
|
|
}
|
276 |
|
|
prevStats = newStats;
|
277 |
|
|
}
|
278 |
|
|
}.bind(this));
|
279 |
|
|
// add is about to be emitted if file not already tracked in parent
|
280 |
|
|
} else if (parent.has(basename)) {
|
281 |
|
|
// Check that change event was not fired because of changed only accessTime.
|
282 |
|
|
var at = newStats.atime.getTime();
|
283 |
|
|
var mt = newStats.mtime.getTime();
|
284 |
|
|
if (!at || at <= mt || mt !== prevStats.mtime.getTime()) {
|
285 |
|
|
this._emit('change', file, newStats);
|
286 |
|
|
}
|
287 |
|
|
prevStats = newStats;
|
288 |
|
|
}
|
289 |
|
|
}.bind(this));
|
290 |
|
|
|
291 |
|
|
// emit an add event if we're supposed to
|
292 |
|
|
if (!(initialAdd && this.options.ignoreInitial)) {
|
293 |
|
|
if (!this._throttle('add', file, 0)) return;
|
294 |
|
|
this._emit('add', file, stats);
|
295 |
|
|
}
|
296 |
|
|
|
297 |
|
|
if (callback) callback();
|
298 |
|
|
return closer;
|
299 |
|
|
};
|
300 |
|
|
|
301 |
|
|
// Private method: Handle symlinks encountered while reading a dir
|
302 |
|
|
|
303 |
|
|
// * entry - object, entry object returned by readdirp
|
304 |
|
|
// * directory - string, path of the directory being read
|
305 |
|
|
// * path - string, path of this item
|
306 |
|
|
// * item - string, basename of this item
|
307 |
|
|
|
308 |
|
|
// Returns true if no more processing is needed for this entry.
|
309 |
|
|
NodeFsHandler.prototype._handleSymlink =
|
310 |
|
|
function(entry, directory, path, item) {
|
311 |
|
|
var full = entry.fullPath;
|
312 |
|
|
var dir = this._getWatchedDir(directory);
|
313 |
|
|
|
314 |
|
|
if (!this.options.followSymlinks) {
|
315 |
|
|
// watch symlink directly (don't follow) and detect changes
|
316 |
|
|
this._readyCount++;
|
317 |
|
|
fs.realpath(path, function(error, linkPath) {
|
318 |
|
|
if (dir.has(item)) {
|
319 |
|
|
if (this._symlinkPaths[full] !== linkPath) {
|
320 |
|
|
this._symlinkPaths[full] = linkPath;
|
321 |
|
|
this._emit('change', path, entry.stat);
|
322 |
|
|
}
|
323 |
|
|
} else {
|
324 |
|
|
dir.add(item);
|
325 |
|
|
this._symlinkPaths[full] = linkPath;
|
326 |
|
|
this._emit('add', path, entry.stat);
|
327 |
|
|
}
|
328 |
|
|
this._emitReady();
|
329 |
|
|
}.bind(this));
|
330 |
|
|
return true;
|
331 |
|
|
}
|
332 |
|
|
|
333 |
|
|
// don't follow the same symlink more than once
|
334 |
|
|
if (this._symlinkPaths[full]) return true;
|
335 |
|
|
else this._symlinkPaths[full] = true;
|
336 |
|
|
};
|
337 |
|
|
|
338 |
|
|
// Private method: Read directory to add / remove files from `@watched` list
|
339 |
|
|
// and re-read it on change.
|
340 |
|
|
|
341 |
|
|
// * dir - string, fs path.
|
342 |
|
|
// * stats - object, result of fs.stat
|
343 |
|
|
// * initialAdd - boolean, was the file added at watch instantiation?
|
344 |
|
|
// * depth - int, depth relative to user-supplied path
|
345 |
|
|
// * target - string, child path actually targeted for watch
|
346 |
|
|
// * wh - object, common watch helpers for this path
|
347 |
|
|
// * callback - function, called when dir scan is complete
|
348 |
|
|
|
349 |
|
|
// Returns close function for the watcher instance
|
350 |
|
|
NodeFsHandler.prototype._handleDir =
|
351 |
|
|
function(dir, stats, initialAdd, depth, target, wh, callback) {
|
352 |
|
|
var parentDir = this._getWatchedDir(sysPath.dirname(dir));
|
353 |
|
|
var tracked = parentDir.has(sysPath.basename(dir));
|
354 |
|
|
if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) {
|
355 |
|
|
if (!wh.hasGlob || wh.globFilter(dir)) this._emit('addDir', dir, stats);
|
356 |
|
|
}
|
357 |
|
|
|
358 |
|
|
// ensure dir is tracked (harmless if redundant)
|
359 |
|
|
parentDir.add(sysPath.basename(dir));
|
360 |
|
|
this._getWatchedDir(dir);
|
361 |
|
|
|
362 |
|
|
var read = function(directory, initialAdd, done) {
|
363 |
|
|
// Normalize the directory name on Windows
|
364 |
|
|
directory = sysPath.join(directory, '');
|
365 |
|
|
|
366 |
|
|
if (!wh.hasGlob) {
|
367 |
|
|
var throttler = this._throttle('readdir', directory, 1000);
|
368 |
|
|
if (!throttler) return;
|
369 |
|
|
}
|
370 |
|
|
|
371 |
|
|
var previous = this._getWatchedDir(wh.path);
|
372 |
|
|
var current = [];
|
373 |
|
|
|
374 |
|
|
readdirp({
|
375 |
|
|
root: directory,
|
376 |
|
|
entryType: 'all',
|
377 |
|
|
fileFilter: wh.filterPath,
|
378 |
|
|
directoryFilter: wh.filterDir,
|
379 |
|
|
depth: 0,
|
380 |
|
|
lstat: true
|
381 |
|
|
}).on('data', function(entry) {
|
382 |
|
|
var item = entry.path;
|
383 |
|
|
var path = sysPath.join(directory, item);
|
384 |
|
|
current.push(item);
|
385 |
|
|
|
386 |
|
|
if (entry.stat.isSymbolicLink() &&
|
387 |
|
|
this._handleSymlink(entry, directory, path, item)) return;
|
388 |
|
|
|
389 |
|
|
// Files that present in current directory snapshot
|
390 |
|
|
// but absent in previous are added to watch list and
|
391 |
|
|
// emit `add` event.
|
392 |
|
|
if (item === target || !target && !previous.has(item)) {
|
393 |
|
|
this._readyCount++;
|
394 |
|
|
|
395 |
|
|
// ensure relativeness of path is preserved in case of watcher reuse
|
396 |
|
|
path = sysPath.join(dir, sysPath.relative(dir, path));
|
397 |
|
|
|
398 |
|
|
this._addToNodeFs(path, initialAdd, wh, depth + 1);
|
399 |
|
|
}
|
400 |
|
|
}.bind(this)).on('end', function() {
|
401 |
|
|
var wasThrottled = throttler ? throttler.clear() : false;
|
402 |
|
|
if (done) done();
|
403 |
|
|
|
404 |
|
|
// Files that absent in current directory snapshot
|
405 |
|
|
// but present in previous emit `remove` event
|
406 |
|
|
// and are removed from @watched[directory].
|
407 |
|
|
previous.children().filter(function(item) {
|
408 |
|
|
return item !== directory &&
|
409 |
|
|
current.indexOf(item) === -1 &&
|
410 |
|
|
// in case of intersecting globs;
|
411 |
|
|
// a path may have been filtered out of this readdir, but
|
412 |
|
|
// shouldn't be removed because it matches a different glob
|
413 |
|
|
(!wh.hasGlob || wh.filterPath({
|
414 |
|
|
fullPath: sysPath.resolve(directory, item)
|
415 |
|
|
}));
|
416 |
|
|
}).forEach(function(item) {
|
417 |
|
|
this._remove(directory, item);
|
418 |
|
|
}, this);
|
419 |
|
|
|
420 |
|
|
// one more time for any missed in case changes came in extremely quickly
|
421 |
|
|
if (wasThrottled) read(directory, false);
|
422 |
|
|
}.bind(this)).on('error', this._handleError.bind(this));
|
423 |
|
|
}.bind(this);
|
424 |
|
|
|
425 |
|
|
var closer;
|
426 |
|
|
|
427 |
|
|
if (this.options.depth == null || depth <= this.options.depth) {
|
428 |
|
|
if (!target) read(dir, initialAdd, callback);
|
429 |
|
|
closer = this._watchWithNodeFs(dir, function(dirPath, stats) {
|
430 |
|
|
// if current directory is removed, do nothing
|
431 |
|
|
if (stats && stats.mtime.getTime() === 0) return;
|
432 |
|
|
|
433 |
|
|
read(dirPath, false);
|
434 |
|
|
});
|
435 |
|
|
} else {
|
436 |
|
|
callback();
|
437 |
|
|
}
|
438 |
|
|
return closer;
|
439 |
|
|
};
|
440 |
|
|
|
441 |
|
|
// Private method: Handle added file, directory, or glob pattern.
|
442 |
|
|
// Delegates call to _handleFile / _handleDir after checks.
|
443 |
|
|
|
444 |
|
|
// * path - string, path to file or directory.
|
445 |
|
|
// * initialAdd - boolean, was the file added at watch instantiation?
|
446 |
|
|
// * depth - int, depth relative to user-supplied path
|
447 |
|
|
// * target - string, child path actually targeted for watch
|
448 |
|
|
// * callback - function, indicates whether the path was found or not
|
449 |
|
|
|
450 |
|
|
// Returns nothing
|
451 |
|
|
NodeFsHandler.prototype._addToNodeFs =
|
452 |
|
|
function(path, initialAdd, priorWh, depth, target, callback) {
|
453 |
|
|
if (!callback) callback = Function.prototype;
|
454 |
|
|
var ready = this._emitReady;
|
455 |
|
|
if (this._isIgnored(path) || this.closed) {
|
456 |
|
|
ready();
|
457 |
|
|
return callback(null, false);
|
458 |
|
|
}
|
459 |
|
|
|
460 |
|
|
var wh = this._getWatchHelpers(path, depth);
|
461 |
|
|
if (!wh.hasGlob && priorWh) {
|
462 |
|
|
wh.hasGlob = priorWh.hasGlob;
|
463 |
|
|
wh.globFilter = priorWh.globFilter;
|
464 |
|
|
wh.filterPath = priorWh.filterPath;
|
465 |
|
|
wh.filterDir = priorWh.filterDir;
|
466 |
|
|
}
|
467 |
|
|
|
468 |
|
|
// evaluate what is at the path we're being asked to watch
|
469 |
|
|
fs[wh.statMethod](wh.watchPath, function(error, stats) {
|
470 |
|
|
if (this._handleError(error)) return callback(null, path);
|
471 |
|
|
if (this._isIgnored(wh.watchPath, stats)) {
|
472 |
|
|
ready();
|
473 |
|
|
return callback(null, false);
|
474 |
|
|
}
|
475 |
|
|
|
476 |
|
|
var initDir = function(dir, target) {
|
477 |
|
|
return this._handleDir(dir, stats, initialAdd, depth, target, wh, ready);
|
478 |
|
|
}.bind(this);
|
479 |
|
|
|
480 |
|
|
var closer;
|
481 |
|
|
if (stats.isDirectory()) {
|
482 |
|
|
closer = initDir(wh.watchPath, target);
|
483 |
|
|
} else if (stats.isSymbolicLink()) {
|
484 |
|
|
var parent = sysPath.dirname(wh.watchPath);
|
485 |
|
|
this._getWatchedDir(parent).add(wh.watchPath);
|
486 |
|
|
this._emit('add', wh.watchPath, stats);
|
487 |
|
|
closer = initDir(parent, path);
|
488 |
|
|
|
489 |
|
|
// preserve this symlink's target path
|
490 |
|
|
fs.realpath(path, function(error, targetPath) {
|
491 |
|
|
this._symlinkPaths[sysPath.resolve(path)] = targetPath;
|
492 |
|
|
ready();
|
493 |
|
|
}.bind(this));
|
494 |
|
|
} else {
|
495 |
|
|
closer = this._handleFile(wh.watchPath, stats, initialAdd, ready);
|
496 |
|
|
}
|
497 |
|
|
|
498 |
|
|
if (closer) {
|
499 |
|
|
this._closers[path] = this._closers[path] || [];
|
500 |
|
|
this._closers[path].push(closer);
|
501 |
|
|
}
|
502 |
|
|
callback(null, false);
|
503 |
|
|
}.bind(this));
|
504 |
|
|
};
|
505 |
|
|
|
506 |
|
|
module.exports = NodeFsHandler;
|