1 | 'use strict';
|
2 |
|
3 | const { EventEmitter } = require('events');
|
4 | const fs = require('fs');
|
5 | const sysPath = require('path');
|
6 | const { promisify } = require('util');
|
7 | const readdirp = require('readdirp');
|
8 | const anymatch = require('anymatch').default;
|
9 | const globParent = require('glob-parent');
|
10 | const isGlob = require('is-glob');
|
11 | const braces = require('braces');
|
12 | const normalizePath = require('normalize-path');
|
13 |
|
14 | const NodeFsHandler = require('./lib/nodefs-handler');
|
15 | const FsEventsHandler = require('./lib/fsevents-handler');
|
16 | const {
|
17 | EV_ALL,
|
18 | EV_READY,
|
19 | EV_ADD,
|
20 | EV_CHANGE,
|
21 | EV_UNLINK,
|
22 | EV_ADD_DIR,
|
23 | EV_UNLINK_DIR,
|
24 | EV_RAW,
|
25 | EV_ERROR,
|
26 |
|
27 | STR_CLOSE,
|
28 | STR_END,
|
29 |
|
30 | BACK_SLASH_RE,
|
31 | DOUBLE_SLASH_RE,
|
32 | SLASH_OR_BACK_SLASH_RE,
|
33 | DOT_RE,
|
34 | REPLACER_RE,
|
35 |
|
36 | SLASH,
|
37 | SLASH_SLASH,
|
38 | BRACE_START,
|
39 | BANG,
|
40 | ONE_DOT,
|
41 | TWO_DOTS,
|
42 | GLOBSTAR,
|
43 | SLASH_GLOBSTAR,
|
44 | ANYMATCH_OPTS,
|
45 | STRING_TYPE,
|
46 | FUNCTION_TYPE,
|
47 | EMPTY_STR,
|
48 | EMPTY_FN,
|
49 |
|
50 | isWindows,
|
51 | isMacos,
|
52 | isIBMi
|
53 | } = require('./lib/constants');
|
54 |
|
55 | const stat = promisify(fs.stat);
|
56 | const readdir = promisify(fs.readdir);
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | const arrify = (value = []) => Array.isArray(value) ? value : [value];
|
79 | const flatten = (list, result = []) => {
|
80 | list.forEach(item => {
|
81 | if (Array.isArray(item)) {
|
82 | flatten(item, result);
|
83 | } else {
|
84 | result.push(item);
|
85 | }
|
86 | });
|
87 | return result;
|
88 | };
|
89 |
|
90 | const unifyPaths = (paths_) => {
|
91 | |
92 |
|
93 |
|
94 | const paths = flatten(arrify(paths_));
|
95 | if (!paths.every(p => typeof p === STRING_TYPE)) {
|
96 | throw new TypeError(`Non-string provided as watch path: ${paths}`);
|
97 | }
|
98 | return paths.map(normalizePathToUnix);
|
99 | };
|
100 |
|
101 |
|
102 |
|
103 | const toUnix = (string) => {
|
104 | let str = string.replace(BACK_SLASH_RE, SLASH);
|
105 | let prepend = false;
|
106 | if (str.startsWith(SLASH_SLASH)) {
|
107 | prepend = true;
|
108 | }
|
109 | while (str.match(DOUBLE_SLASH_RE)) {
|
110 | str = str.replace(DOUBLE_SLASH_RE, SLASH);
|
111 | }
|
112 | if (prepend) {
|
113 | str = SLASH + str;
|
114 | }
|
115 | return str;
|
116 | };
|
117 |
|
118 |
|
119 |
|
120 | const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path)));
|
121 |
|
122 | const normalizeIgnored = (cwd = EMPTY_STR) => (path) => {
|
123 | if (typeof path !== STRING_TYPE) return path;
|
124 | return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));
|
125 | };
|
126 |
|
127 | const getAbsolutePath = (path, cwd) => {
|
128 | if (sysPath.isAbsolute(path)) {
|
129 | return path;
|
130 | }
|
131 | if (path.startsWith(BANG)) {
|
132 | return BANG + sysPath.join(cwd, path.slice(1));
|
133 | }
|
134 | return sysPath.join(cwd, path);
|
135 | };
|
136 |
|
137 | const undef = (opts, key) => opts[key] === undefined;
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | class DirEntry {
|
145 | |
146 |
|
147 |
|
148 |
|
149 | constructor(dir, removeWatcher) {
|
150 | this.path = dir;
|
151 | this._removeWatcher = removeWatcher;
|
152 |
|
153 | this.items = new Set();
|
154 | }
|
155 |
|
156 | add(item) {
|
157 | const {items} = this;
|
158 | if (!items) return;
|
159 | if (item !== ONE_DOT && item !== TWO_DOTS) items.add(item);
|
160 | }
|
161 |
|
162 | async remove(item) {
|
163 | const {items} = this;
|
164 | if (!items) return;
|
165 | items.delete(item);
|
166 | if (items.size > 0) return;
|
167 |
|
168 | const dir = this.path;
|
169 | try {
|
170 | await readdir(dir);
|
171 | } catch (err) {
|
172 | if (this._removeWatcher) {
|
173 | this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));
|
174 | }
|
175 | }
|
176 | }
|
177 |
|
178 | has(item) {
|
179 | const {items} = this;
|
180 | if (!items) return;
|
181 | return items.has(item);
|
182 | }
|
183 |
|
184 | |
185 |
|
186 |
|
187 | getChildren() {
|
188 | const {items} = this;
|
189 | if (!items) return;
|
190 | return [...items.values()];
|
191 | }
|
192 |
|
193 | dispose() {
|
194 | this.items.clear();
|
195 | delete this.path;
|
196 | delete this._removeWatcher;
|
197 | delete this.items;
|
198 | Object.freeze(this);
|
199 | }
|
200 | }
|
201 |
|
202 | const STAT_METHOD_F = 'stat';
|
203 | const STAT_METHOD_L = 'lstat';
|
204 | class WatchHelper {
|
205 | constructor(path, watchPath, follow, fsw) {
|
206 | this.fsw = fsw;
|
207 | this.path = path = path.replace(REPLACER_RE, EMPTY_STR);
|
208 | this.watchPath = watchPath;
|
209 | this.fullWatchPath = sysPath.resolve(watchPath);
|
210 | this.hasGlob = watchPath !== path;
|
211 |
|
212 | if (path === EMPTY_STR) this.hasGlob = false;
|
213 | this.globSymlink = this.hasGlob && follow ? undefined : false;
|
214 | this.globFilter = this.hasGlob ? anymatch(path, undefined, ANYMATCH_OPTS) : false;
|
215 | this.dirParts = this.getDirParts(path);
|
216 | this.dirParts.forEach((parts) => {
|
217 | if (parts.length > 1) parts.pop();
|
218 | });
|
219 | this.followSymlinks = follow;
|
220 | this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
|
221 | }
|
222 |
|
223 | checkGlobSymlink(entry) {
|
224 |
|
225 |
|
226 | if (this.globSymlink === undefined) {
|
227 | this.globSymlink = entry.fullParentDir === this.fullWatchPath ?
|
228 | false : {realPath: entry.fullParentDir, linkPath: this.fullWatchPath};
|
229 | }
|
230 |
|
231 | if (this.globSymlink) {
|
232 | return entry.fullPath.replace(this.globSymlink.realPath, this.globSymlink.linkPath);
|
233 | }
|
234 |
|
235 | return entry.fullPath;
|
236 | }
|
237 |
|
238 | entryPath(entry) {
|
239 | return sysPath.join(this.watchPath,
|
240 | sysPath.relative(this.watchPath, this.checkGlobSymlink(entry))
|
241 | );
|
242 | }
|
243 |
|
244 | filterPath(entry) {
|
245 | const {stats} = entry;
|
246 | if (stats && stats.isSymbolicLink()) return this.filterDir(entry);
|
247 | const resolvedPath = this.entryPath(entry);
|
248 | const matchesGlob = this.hasGlob && typeof this.globFilter === FUNCTION_TYPE ?
|
249 | this.globFilter(resolvedPath) : true;
|
250 | return matchesGlob &&
|
251 | this.fsw._isntIgnored(resolvedPath, stats) &&
|
252 | this.fsw._hasReadPermissions(stats);
|
253 | }
|
254 |
|
255 | getDirParts(path) {
|
256 | if (!this.hasGlob) return [];
|
257 | const parts = [];
|
258 | const expandedPath = path.includes(BRACE_START) ? braces.expand(path) : [path];
|
259 | expandedPath.forEach((path) => {
|
260 | parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH_RE));
|
261 | });
|
262 | return parts;
|
263 | }
|
264 |
|
265 | filterDir(entry) {
|
266 | if (this.hasGlob) {
|
267 | const entryParts = this.getDirParts(this.checkGlobSymlink(entry));
|
268 | let globstar = false;
|
269 | this.unmatchedGlob = !this.dirParts.some((parts) => {
|
270 | return parts.every((part, i) => {
|
271 | if (part === GLOBSTAR) globstar = true;
|
272 | return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i], ANYMATCH_OPTS);
|
273 | });
|
274 | });
|
275 | }
|
276 | return !this.unmatchedGlob && this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
|
277 | }
|
278 | }
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | class FSWatcher extends EventEmitter {
|
289 |
|
290 | constructor(_opts) {
|
291 | super();
|
292 |
|
293 | const opts = {};
|
294 | if (_opts) Object.assign(opts, _opts);
|
295 |
|
296 |
|
297 | this._watched = new Map();
|
298 |
|
299 | this._closers = new Map();
|
300 |
|
301 | this._ignoredPaths = new Set();
|
302 |
|
303 |
|
304 | this._throttled = new Map();
|
305 |
|
306 |
|
307 | this._symlinkPaths = new Map();
|
308 |
|
309 | this._streams = new Set();
|
310 | this.closed = false;
|
311 |
|
312 |
|
313 | if (undef(opts, 'persistent')) opts.persistent = true;
|
314 | if (undef(opts, 'ignoreInitial')) opts.ignoreInitial = false;
|
315 | if (undef(opts, 'ignorePermissionErrors')) opts.ignorePermissionErrors = false;
|
316 | if (undef(opts, 'interval')) opts.interval = 100;
|
317 | if (undef(opts, 'binaryInterval')) opts.binaryInterval = 300;
|
318 | if (undef(opts, 'disableGlobbing')) opts.disableGlobbing = false;
|
319 | opts.enableBinaryInterval = opts.binaryInterval !== opts.interval;
|
320 |
|
321 |
|
322 | if (undef(opts, 'useFsEvents')) opts.useFsEvents = !opts.usePolling;
|
323 |
|
324 |
|
325 | const canUseFsEvents = FsEventsHandler.canUse();
|
326 | if (!canUseFsEvents) opts.useFsEvents = false;
|
327 |
|
328 |
|
329 |
|
330 | if (undef(opts, 'usePolling') && !opts.useFsEvents) {
|
331 | opts.usePolling = isMacos;
|
332 | }
|
333 |
|
334 |
|
335 | if(isIBMi) {
|
336 | opts.usePolling = true;
|
337 | }
|
338 |
|
339 |
|
340 |
|
341 | const envPoll = process.env.CHOKIDAR_USEPOLLING;
|
342 | if (envPoll !== undefined) {
|
343 | const envLower = envPoll.toLowerCase();
|
344 |
|
345 | if (envLower === 'false' || envLower === '0') {
|
346 | opts.usePolling = false;
|
347 | } else if (envLower === 'true' || envLower === '1') {
|
348 | opts.usePolling = true;
|
349 | } else {
|
350 | opts.usePolling = !!envLower;
|
351 | }
|
352 | }
|
353 | const envInterval = process.env.CHOKIDAR_INTERVAL;
|
354 | if (envInterval) {
|
355 | opts.interval = Number.parseInt(envInterval, 10);
|
356 | }
|
357 |
|
358 |
|
359 | if (undef(opts, 'atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents;
|
360 | if (opts.atomic) this._pendingUnlinks = new Map();
|
361 |
|
362 | if (undef(opts, 'followSymlinks')) opts.followSymlinks = true;
|
363 |
|
364 | if (undef(opts, 'awaitWriteFinish')) opts.awaitWriteFinish = false;
|
365 | if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {};
|
366 | const awf = opts.awaitWriteFinish;
|
367 | if (awf) {
|
368 | if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000;
|
369 | if (!awf.pollInterval) awf.pollInterval = 100;
|
370 | this._pendingWrites = new Map();
|
371 | }
|
372 | if (opts.ignored) opts.ignored = arrify(opts.ignored);
|
373 |
|
374 | let readyCalls = 0;
|
375 | this._emitReady = () => {
|
376 | readyCalls++;
|
377 | if (readyCalls >= this._readyCount) {
|
378 | this._emitReady = EMPTY_FN;
|
379 | this._readyEmitted = true;
|
380 |
|
381 | process.nextTick(() => this.emit(EV_READY));
|
382 | }
|
383 | };
|
384 | this._emitRaw = (...args) => this.emit(EV_RAW, ...args);
|
385 | this._readyEmitted = false;
|
386 | this.options = opts;
|
387 |
|
388 |
|
389 | if (opts.useFsEvents) {
|
390 | this._fsEventsHandler = new FsEventsHandler(this);
|
391 | } else {
|
392 | this._nodeFsHandler = new NodeFsHandler(this);
|
393 | }
|
394 |
|
395 |
|
396 | Object.freeze(opts);
|
397 | }
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | add(paths_, _origAdd, _internal) {
|
409 | const {cwd, disableGlobbing} = this.options;
|
410 | this.closed = false;
|
411 | let paths = unifyPaths(paths_);
|
412 | if (cwd) {
|
413 | paths = paths.map((path) => {
|
414 | const absPath = getAbsolutePath(path, cwd);
|
415 |
|
416 |
|
417 | if (disableGlobbing || !isGlob(path)) {
|
418 | return absPath;
|
419 | }
|
420 | return normalizePath(absPath);
|
421 | });
|
422 | }
|
423 |
|
424 |
|
425 | paths = paths.filter((path) => {
|
426 | if (path.startsWith(BANG)) {
|
427 | this._ignoredPaths.add(path.slice(1));
|
428 | return false;
|
429 | }
|
430 |
|
431 |
|
432 | this._ignoredPaths.delete(path);
|
433 | this._ignoredPaths.delete(path + SLASH_GLOBSTAR);
|
434 |
|
435 |
|
436 |
|
437 | this._userIgnored = undefined;
|
438 |
|
439 | return true;
|
440 | });
|
441 |
|
442 | if (this.options.useFsEvents && this._fsEventsHandler) {
|
443 | if (!this._readyCount) this._readyCount = paths.length;
|
444 | if (this.options.persistent) this._readyCount += paths.length;
|
445 | paths.forEach((path) => this._fsEventsHandler._addToFsEvents(path));
|
446 | } else {
|
447 | if (!this._readyCount) this._readyCount = 0;
|
448 | this._readyCount += paths.length;
|
449 | Promise.all(
|
450 | paths.map(async path => {
|
451 | const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd);
|
452 | if (res) this._emitReady();
|
453 | return res;
|
454 | })
|
455 | ).then(results => {
|
456 | if (this.closed) return;
|
457 | results.filter(item => item).forEach(item => {
|
458 | this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
|
459 | });
|
460 | });
|
461 | }
|
462 |
|
463 | return this;
|
464 | }
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | unwatch(paths_) {
|
472 | if (this.closed) return this;
|
473 | const paths = unifyPaths(paths_);
|
474 | const {cwd} = this.options;
|
475 |
|
476 | paths.forEach((path) => {
|
477 |
|
478 | if (!sysPath.isAbsolute(path) && !this._closers.has(path)) {
|
479 | if (cwd) path = sysPath.join(cwd, path);
|
480 | path = sysPath.resolve(path);
|
481 | }
|
482 |
|
483 | this._closePath(path);
|
484 |
|
485 | this._ignoredPaths.add(path);
|
486 | if (this._watched.has(path)) {
|
487 | this._ignoredPaths.add(path + SLASH_GLOBSTAR);
|
488 | }
|
489 |
|
490 |
|
491 |
|
492 | this._userIgnored = undefined;
|
493 | });
|
494 |
|
495 | return this;
|
496 | }
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 | close() {
|
503 | if (this.closed) return this._closePromise;
|
504 | this.closed = true;
|
505 |
|
506 |
|
507 | this.removeAllListeners();
|
508 | const closers = [];
|
509 | this._closers.forEach(closerList => closerList.forEach(closer => {
|
510 | const promise = closer();
|
511 | if (promise instanceof Promise) closers.push(promise);
|
512 | }));
|
513 | this._streams.forEach(stream => stream.destroy());
|
514 | this._userIgnored = undefined;
|
515 | this._readyCount = 0;
|
516 | this._readyEmitted = false;
|
517 | this._watched.forEach(dirent => dirent.dispose());
|
518 | ['closers', 'watched', 'streams', 'symlinkPaths', 'throttled'].forEach(key => {
|
519 | this[`_${key}`].clear();
|
520 | });
|
521 |
|
522 | this._closePromise = closers.length ? Promise.all(closers).then(() => undefined) : Promise.resolve();
|
523 | return this._closePromise;
|
524 | }
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 | getWatched() {
|
531 | const watchList = {};
|
532 | this._watched.forEach((entry, dir) => {
|
533 | const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;
|
534 | watchList[key || ONE_DOT] = entry.getChildren().sort();
|
535 | });
|
536 | return watchList;
|
537 | }
|
538 |
|
539 | emitWithAll(event, args) {
|
540 | this.emit(...args);
|
541 | if (event !== EV_ERROR) this.emit(EV_ALL, ...args);
|
542 | }
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 | async _emit(event, path, val1, val2, val3) {
|
558 | if (this.closed) return;
|
559 |
|
560 | const opts = this.options;
|
561 | if (isWindows) path = sysPath.normalize(path);
|
562 | if (opts.cwd) path = sysPath.relative(opts.cwd, path);
|
563 |
|
564 | const args = [event, path];
|
565 | if (val3 !== undefined) args.push(val1, val2, val3);
|
566 | else if (val2 !== undefined) args.push(val1, val2);
|
567 | else if (val1 !== undefined) args.push(val1);
|
568 |
|
569 | const awf = opts.awaitWriteFinish;
|
570 | let pw;
|
571 | if (awf && (pw = this._pendingWrites.get(path))) {
|
572 | pw.lastChange = new Date();
|
573 | return this;
|
574 | }
|
575 |
|
576 | if (opts.atomic) {
|
577 | if (event === EV_UNLINK) {
|
578 | this._pendingUnlinks.set(path, args);
|
579 | setTimeout(() => {
|
580 | this._pendingUnlinks.forEach((entry, path) => {
|
581 | this.emit(...entry);
|
582 | this.emit(EV_ALL, ...entry);
|
583 | this._pendingUnlinks.delete(path);
|
584 | });
|
585 | }, typeof opts.atomic === 'number' ? opts.atomic : 100);
|
586 | return this;
|
587 | }
|
588 | if (event === EV_ADD && this._pendingUnlinks.has(path)) {
|
589 | event = args[0] = EV_CHANGE;
|
590 | this._pendingUnlinks.delete(path);
|
591 | }
|
592 | }
|
593 |
|
594 | if (awf && (event === EV_ADD || event === EV_CHANGE) && this._readyEmitted) {
|
595 | const awfEmit = (err, stats) => {
|
596 | if (err) {
|
597 | event = args[0] = EV_ERROR;
|
598 | args[1] = err;
|
599 | this.emitWithAll(event, args);
|
600 | } else if (stats) {
|
601 |
|
602 | if (args.length > 2) {
|
603 | args[2] = stats;
|
604 | } else {
|
605 | args.push(stats);
|
606 | }
|
607 | this.emitWithAll(event, args);
|
608 | }
|
609 | };
|
610 |
|
611 | this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit);
|
612 | return this;
|
613 | }
|
614 |
|
615 | if (event === EV_CHANGE) {
|
616 | const isThrottled = !this._throttle(EV_CHANGE, path, 50);
|
617 | if (isThrottled) return this;
|
618 | }
|
619 |
|
620 | if (opts.alwaysStat && val1 === undefined &&
|
621 | (event === EV_ADD || event === EV_ADD_DIR || event === EV_CHANGE)
|
622 | ) {
|
623 | const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path;
|
624 | let stats;
|
625 | try {
|
626 | stats = await stat(fullPath);
|
627 | } catch (err) {}
|
628 |
|
629 | if (!stats || this.closed) return;
|
630 | args.push(stats);
|
631 | }
|
632 | this.emitWithAll(event, args);
|
633 |
|
634 | return this;
|
635 | }
|
636 |
|
637 |
|
638 |
|
639 |
|
640 |
|
641 |
|
642 | _handleError(error) {
|
643 | const code = error && error.code;
|
644 | if (error && code !== 'ENOENT' && code !== 'ENOTDIR' &&
|
645 | (!this.options.ignorePermissionErrors || (code !== 'EPERM' && code !== 'EACCES'))
|
646 | ) {
|
647 | this.emit(EV_ERROR, error);
|
648 | }
|
649 | return error || this.closed;
|
650 | }
|
651 |
|
652 |
|
653 |
|
654 |
|
655 |
|
656 |
|
657 |
|
658 |
|
659 | _throttle(actionType, path, timeout) {
|
660 | if (!this._throttled.has(actionType)) {
|
661 | this._throttled.set(actionType, new Map());
|
662 | }
|
663 |
|
664 |
|
665 | const action = this._throttled.get(actionType);
|
666 |
|
667 | const actionPath = action.get(path);
|
668 |
|
669 | if (actionPath) {
|
670 | actionPath.count++;
|
671 | return false;
|
672 | }
|
673 |
|
674 | let timeoutObject;
|
675 | const clear = () => {
|
676 | const item = action.get(path);
|
677 | const count = item ? item.count : 0;
|
678 | action.delete(path);
|
679 | clearTimeout(timeoutObject);
|
680 | if (item) clearTimeout(item.timeoutObject);
|
681 | return count;
|
682 | };
|
683 | timeoutObject = setTimeout(clear, timeout);
|
684 | const thr = {timeoutObject, clear, count: 0};
|
685 | action.set(path, thr);
|
686 | return thr;
|
687 | }
|
688 |
|
689 | _incrReadyCount() {
|
690 | return this._readyCount++;
|
691 | }
|
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 |
|
699 |
|
700 |
|
701 | _awaitWriteFinish(path, threshold, event, awfEmit) {
|
702 | let timeoutHandler;
|
703 |
|
704 | let fullPath = path;
|
705 | if (this.options.cwd && !sysPath.isAbsolute(path)) {
|
706 | fullPath = sysPath.join(this.options.cwd, path);
|
707 | }
|
708 |
|
709 | const now = new Date();
|
710 |
|
711 | const awaitWriteFinish = (prevStat) => {
|
712 | fs.stat(fullPath, (err, curStat) => {
|
713 | if (err || !this._pendingWrites.has(path)) {
|
714 | if (err && err.code !== 'ENOENT') awfEmit(err);
|
715 | return;
|
716 | }
|
717 |
|
718 | const now = Number(new Date());
|
719 |
|
720 | if (prevStat && curStat.size !== prevStat.size) {
|
721 | this._pendingWrites.get(path).lastChange = now;
|
722 | }
|
723 | const pw = this._pendingWrites.get(path);
|
724 | const df = now - pw.lastChange;
|
725 |
|
726 | if (df >= threshold) {
|
727 | this._pendingWrites.delete(path);
|
728 | awfEmit(undefined, curStat);
|
729 | } else {
|
730 | timeoutHandler = setTimeout(
|
731 | awaitWriteFinish,
|
732 | this.options.awaitWriteFinish.pollInterval,
|
733 | curStat
|
734 | );
|
735 | }
|
736 | });
|
737 | };
|
738 |
|
739 | if (!this._pendingWrites.has(path)) {
|
740 | this._pendingWrites.set(path, {
|
741 | lastChange: now,
|
742 | cancelWait: () => {
|
743 | this._pendingWrites.delete(path);
|
744 | clearTimeout(timeoutHandler);
|
745 | return event;
|
746 | }
|
747 | });
|
748 | timeoutHandler = setTimeout(
|
749 | awaitWriteFinish,
|
750 | this.options.awaitWriteFinish.pollInterval
|
751 | );
|
752 | }
|
753 | }
|
754 |
|
755 | _getGlobIgnored() {
|
756 | return [...this._ignoredPaths.values()];
|
757 | }
|
758 |
|
759 |
|
760 |
|
761 |
|
762 |
|
763 |
|
764 |
|
765 | _isIgnored(path, stats) {
|
766 | if (this.options.atomic && DOT_RE.test(path)) return true;
|
767 | if (!this._userIgnored) {
|
768 | const {cwd} = this.options;
|
769 | const ign = this.options.ignored;
|
770 |
|
771 | const ignored = ign && ign.map(normalizeIgnored(cwd));
|
772 | const paths = arrify(ignored)
|
773 | .filter((path) => typeof path === STRING_TYPE && !isGlob(path))
|
774 | .map((path) => path + SLASH_GLOBSTAR);
|
775 | const list = this._getGlobIgnored().map(normalizeIgnored(cwd)).concat(ignored, paths);
|
776 | this._userIgnored = anymatch(list, undefined, ANYMATCH_OPTS);
|
777 | }
|
778 |
|
779 | return this._userIgnored([path, stats]);
|
780 | }
|
781 |
|
782 | _isntIgnored(path, stat) {
|
783 | return !this._isIgnored(path, stat);
|
784 | }
|
785 |
|
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 |
|
792 | _getWatchHelpers(path, depth) {
|
793 | const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path);
|
794 | const follow = this.options.followSymlinks;
|
795 |
|
796 | return new WatchHelper(path, watchPath, follow, this);
|
797 | }
|
798 |
|
799 |
|
800 |
|
801 |
|
802 |
|
803 |
|
804 |
|
805 |
|
806 |
|
807 | _getWatchedDir(directory) {
|
808 | if (!this._boundRemove) this._boundRemove = this._remove.bind(this);
|
809 | const dir = sysPath.resolve(directory);
|
810 | if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove));
|
811 | return this._watched.get(dir);
|
812 | }
|
813 |
|
814 |
|
815 |
|
816 |
|
817 |
|
818 |
|
819 |
|
820 |
|
821 |
|
822 |
|
823 | _hasReadPermissions(stats) {
|
824 | if (this.options.ignorePermissionErrors) return true;
|
825 |
|
826 |
|
827 | const md = stats && Number.parseInt(stats.mode, 10);
|
828 | const st = md & 0o777;
|
829 | const it = Number.parseInt(st.toString(8)[0], 10);
|
830 | return Boolean(4 & it);
|
831 | }
|
832 |
|
833 |
|
834 |
|
835 |
|
836 |
|
837 |
|
838 |
|
839 |
|
840 |
|
841 | _remove(directory, item, isDirectory) {
|
842 |
|
843 |
|
844 |
|
845 | const path = sysPath.join(directory, item);
|
846 | const fullPath = sysPath.resolve(path);
|
847 | isDirectory = isDirectory != null
|
848 | ? isDirectory
|
849 | : this._watched.has(path) || this._watched.has(fullPath);
|
850 |
|
851 |
|
852 |
|
853 | if (!this._throttle('remove', path, 100)) return;
|
854 |
|
855 |
|
856 | if (!isDirectory && !this.options.useFsEvents && this._watched.size === 1) {
|
857 | this.add(directory, item, true);
|
858 | }
|
859 |
|
860 |
|
861 |
|
862 | const wp = this._getWatchedDir(path);
|
863 | const nestedDirectoryChildren = wp.getChildren();
|
864 |
|
865 |
|
866 | nestedDirectoryChildren.forEach(nested => this._remove(path, nested));
|
867 |
|
868 |
|
869 | const parent = this._getWatchedDir(directory);
|
870 | const wasTracked = parent.has(item);
|
871 | parent.remove(item);
|
872 |
|
873 |
|
874 |
|
875 |
|
876 |
|
877 |
|
878 | if (this._symlinkPaths.has(fullPath)) {
|
879 | this._symlinkPaths.delete(fullPath);
|
880 | }
|
881 |
|
882 |
|
883 | let relPath = path;
|
884 | if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);
|
885 | if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
886 | const event = this._pendingWrites.get(relPath).cancelWait();
|
887 | if (event === EV_ADD) return;
|
888 | }
|
889 |
|
890 |
|
891 |
|
892 | this._watched.delete(path);
|
893 | this._watched.delete(fullPath);
|
894 | const eventName = isDirectory ? EV_UNLINK_DIR : EV_UNLINK;
|
895 | if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);
|
896 |
|
897 |
|
898 | if (!this.options.useFsEvents) {
|
899 | this._closePath(path);
|
900 | }
|
901 | }
|
902 |
|
903 |
|
904 |
|
905 |
|
906 |
|
907 | _closePath(path) {
|
908 | this._closeFile(path)
|
909 | const dir = sysPath.dirname(path);
|
910 | this._getWatchedDir(dir).remove(sysPath.basename(path));
|
911 | }
|
912 |
|
913 |
|
914 |
|
915 |
|
916 |
|
917 | _closeFile(path) {
|
918 | const closers = this._closers.get(path);
|
919 | if (!closers) return;
|
920 | closers.forEach(closer => closer());
|
921 | this._closers.delete(path);
|
922 | }
|
923 |
|
924 |
|
925 |
|
926 |
|
927 |
|
928 |
|
929 | _addPathCloser(path, closer) {
|
930 | if (!closer) return;
|
931 | let list = this._closers.get(path);
|
932 | if (!list) {
|
933 | list = [];
|
934 | this._closers.set(path, list);
|
935 | }
|
936 | list.push(closer);
|
937 | }
|
938 |
|
939 | _readdirp(root, opts) {
|
940 | if (this.closed) return;
|
941 | const options = {type: EV_ALL, alwaysStat: true, lstat: true, ...opts};
|
942 | let stream = readdirp(root, options);
|
943 | this._streams.add(stream);
|
944 | stream.once(STR_CLOSE, () => {
|
945 | stream = undefined;
|
946 | });
|
947 | stream.once(STR_END, () => {
|
948 | if (stream) {
|
949 | this._streams.delete(stream);
|
950 | stream = undefined;
|
951 | }
|
952 | });
|
953 | return stream;
|
954 | }
|
955 |
|
956 | }
|
957 |
|
958 |
|
959 | exports.FSWatcher = FSWatcher;
|
960 |
|
961 |
|
962 |
|
963 |
|
964 |
|
965 |
|
966 |
|
967 | const watch = (paths, options) => {
|
968 | const watcher = new FSWatcher(options);
|
969 | watcher.add(paths);
|
970 | return watcher;
|
971 | };
|
972 |
|
973 | exports.watch = watch;
|