aboutsummaryrefslogtreecommitdiff
path: root/node_modules/chokidar/lib/fsevents-handler.js
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-06-04 08:51:01 +0200
committerMinteck <contact@minteck.org>2022-06-04 08:51:01 +0200
commit383285ecd5292bf9a825e05904955b937de84cc9 (patch)
tree0a53b6f02c1604b078044567c03dc1b6c944c8c2 /node_modules/chokidar/lib/fsevents-handler.js
downloadequestriadb-383285ecd5292bf9a825e05904955b937de84cc9.tar.gz
equestriadb-383285ecd5292bf9a825e05904955b937de84cc9.tar.bz2
equestriadb-383285ecd5292bf9a825e05904955b937de84cc9.zip
Initial commit
Diffstat (limited to 'node_modules/chokidar/lib/fsevents-handler.js')
-rw-r--r--node_modules/chokidar/lib/fsevents-handler.js524
1 files changed, 524 insertions, 0 deletions
diff --git a/node_modules/chokidar/lib/fsevents-handler.js b/node_modules/chokidar/lib/fsevents-handler.js
new file mode 100644
index 0000000..0f7f2cb
--- /dev/null
+++ b/node_modules/chokidar/lib/fsevents-handler.js
@@ -0,0 +1,524 @@
+'use strict';
+
+const fs = require('fs');
+const sysPath = require('path');
+const { promisify } = require('util');
+
+let fsevents;
+try {
+ fsevents = require('fsevents');
+} catch (error) {
+ if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error);
+}
+
+if (fsevents) {
+ // TODO: real check
+ const mtch = process.version.match(/v(\d+)\.(\d+)/);
+ if (mtch && mtch[1] && mtch[2]) {
+ const maj = Number.parseInt(mtch[1], 10);
+ const min = Number.parseInt(mtch[2], 10);
+ if (maj === 8 && min < 16) {
+ fsevents = undefined;
+ }
+ }
+}
+
+const {
+ EV_ADD,
+ EV_CHANGE,
+ EV_ADD_DIR,
+ EV_UNLINK,
+ EV_ERROR,
+ STR_DATA,
+ STR_END,
+ FSEVENT_CREATED,
+ FSEVENT_MODIFIED,
+ FSEVENT_DELETED,
+ FSEVENT_MOVED,
+ // FSEVENT_CLONED,
+ FSEVENT_UNKNOWN,
+ FSEVENT_TYPE_FILE,
+ FSEVENT_TYPE_DIRECTORY,
+ FSEVENT_TYPE_SYMLINK,
+
+ ROOT_GLOBSTAR,
+ DIR_SUFFIX,
+ DOT_SLASH,
+ FUNCTION_TYPE,
+ EMPTY_FN,
+ IDENTITY_FN
+} = require('./constants');
+
+const Depth = (value) => isNaN(value) ? {} : {depth: value};
+
+const stat = promisify(fs.stat);
+const lstat = promisify(fs.lstat);
+const realpath = promisify(fs.realpath);
+
+const statMethods = { stat, lstat };
+
+/**
+ * @typedef {String} Path
+ */
+
+/**
+ * @typedef {Object} FsEventsWatchContainer
+ * @property {Set<Function>} listeners
+ * @property {Function} rawEmitter
+ * @property {{stop: Function}} watcher
+ */
+
+// fsevents instance helper functions
+/**
+ * Object to hold per-process fsevents instances (may be shared across chokidar FSWatcher instances)
+ * @type {Map<Path,FsEventsWatchContainer>}
+ */
+const FSEventsWatchers = new Map();
+
+// Threshold of duplicate path prefixes at which to start
+// consolidating going forward
+const consolidateThreshhold = 10;
+
+const wrongEventFlags = new Set([
+ 69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912
+]);
+
+/**
+ * Instantiates the fsevents interface
+ * @param {Path} path path to be watched
+ * @param {Function} callback called when fsevents is bound and ready
+ * @returns {{stop: Function}} new fsevents instance
+ */
+const createFSEventsInstance = (path, callback) => {
+ const stop = fsevents.watch(path, callback);
+ return {stop};
+};
+
+/**
+ * Instantiates the fsevents interface or binds listeners to an existing one covering
+ * the same file tree.
+ * @param {Path} path - to be watched
+ * @param {Path} realPath - real path for symlinks
+ * @param {Function} listener - called when fsevents emits events
+ * @param {Function} rawEmitter - passes data to listeners of the 'raw' event
+ * @returns {Function} closer
+ */
+function setFSEventsListener(path, realPath, listener, rawEmitter) {
+ let watchPath = sysPath.extname(realPath) ? sysPath.dirname(realPath) : realPath;
+
+ const parentPath = sysPath.dirname(watchPath);
+ let cont = FSEventsWatchers.get(watchPath);
+
+ // If we've accumulated a substantial number of paths that
+ // could have been consolidated by watching one directory
+ // above the current one, create a watcher on the parent
+ // path instead, so that we do consolidate going forward.
+ if (couldConsolidate(parentPath)) {
+ watchPath = parentPath;
+ }
+
+ const resolvedPath = sysPath.resolve(path);
+ const hasSymlink = resolvedPath !== realPath;
+
+ const filteredListener = (fullPath, flags, info) => {
+ if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath);
+ if (
+ fullPath === resolvedPath ||
+ !fullPath.indexOf(resolvedPath + sysPath.sep)
+ ) listener(fullPath, flags, info);
+ };
+
+ // check if there is already a watcher on a parent path
+ // modifies `watchPath` to the parent path when it finds a match
+ let watchedParent = false;
+ for (const watchedPath of FSEventsWatchers.keys()) {
+ if (realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep) === 0) {
+ watchPath = watchedPath;
+ cont = FSEventsWatchers.get(watchPath);
+ watchedParent = true;
+ break;
+ }
+ }
+
+ if (cont || watchedParent) {
+ cont.listeners.add(filteredListener);
+ } else {
+ cont = {
+ listeners: new Set([filteredListener]),
+ rawEmitter,
+ watcher: createFSEventsInstance(watchPath, (fullPath, flags) => {
+ if (!cont.listeners.size) return;
+ const info = fsevents.getInfo(fullPath, flags);
+ cont.listeners.forEach(list => {
+ list(fullPath, flags, info);
+ });
+
+ cont.rawEmitter(info.event, fullPath, info);
+ })
+ };
+ FSEventsWatchers.set(watchPath, cont);
+ }
+
+ // removes this instance's listeners and closes the underlying fsevents
+ // instance if there are no more listeners left
+ return () => {
+ const lst = cont.listeners;
+
+ lst.delete(filteredListener);
+ if (!lst.size) {
+ FSEventsWatchers.delete(watchPath);
+ if (cont.watcher) return cont.watcher.stop().then(() => {
+ cont.rawEmitter = cont.watcher = undefined;
+ Object.freeze(cont);
+ });
+ }
+ };
+}
+
+// Decide whether or not we should start a new higher-level
+// parent watcher
+const couldConsolidate = (path) => {
+ let count = 0;
+ for (const watchPath of FSEventsWatchers.keys()) {
+ if (watchPath.indexOf(path) === 0) {
+ count++;
+ if (count >= consolidateThreshhold) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+// returns boolean indicating whether fsevents can be used
+const canUse = () => fsevents && FSEventsWatchers.size < 128;
+
+// determines subdirectory traversal levels from root to path
+const calcDepth = (path, root) => {
+ let i = 0;
+ while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++;
+ return i;
+};
+
+// returns boolean indicating whether the fsevents' event info has the same type
+// as the one returned by fs.stat
+const sameTypes = (info, stats) => (
+ info.type === FSEVENT_TYPE_DIRECTORY && stats.isDirectory() ||
+ info.type === FSEVENT_TYPE_SYMLINK && stats.isSymbolicLink() ||
+ info.type === FSEVENT_TYPE_FILE && stats.isFile()
+)
+
+/**
+ * @mixin
+ */
+class FsEventsHandler {
+
+/**
+ * @param {import('../index').FSWatcher} fsw
+ */
+constructor(fsw) {
+ this.fsw = fsw;
+}
+checkIgnored(path, stats) {
+ const ipaths = this.fsw._ignoredPaths;
+ if (this.fsw._isIgnored(path, stats)) {
+ ipaths.add(path);
+ if (stats && stats.isDirectory()) {
+ ipaths.add(path + ROOT_GLOBSTAR);
+ }
+ return true;
+ }
+
+ ipaths.delete(path);
+ ipaths.delete(path + ROOT_GLOBSTAR);
+}
+
+addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts) {
+ const event = watchedDir.has(item) ? EV_CHANGE : EV_ADD;
+ this.handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts);
+}
+
+async checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts) {
+ try {
+ const stats = await stat(path)
+ if (this.fsw.closed) return;
+ if (sameTypes(info, stats)) {
+ this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ } else {
+ this.handleEvent(EV_UNLINK, path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ }
+ } catch (error) {
+ if (error.code === 'EACCES') {
+ this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ } else {
+ this.handleEvent(EV_UNLINK, path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ }
+ }
+}
+
+handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts) {
+ if (this.fsw.closed || this.checkIgnored(path)) return;
+
+ if (event === EV_UNLINK) {
+ const isDirectory = info.type === FSEVENT_TYPE_DIRECTORY
+ // suppress unlink events on never before seen files
+ if (isDirectory || watchedDir.has(item)) {
+ this.fsw._remove(parent, item, isDirectory);
+ }
+ } else {
+ if (event === EV_ADD) {
+ // track new directories
+ if (info.type === FSEVENT_TYPE_DIRECTORY) this.fsw._getWatchedDir(path);
+
+ if (info.type === FSEVENT_TYPE_SYMLINK && opts.followSymlinks) {
+ // push symlinks back to the top of the stack to get handled
+ const curDepth = opts.depth === undefined ?
+ undefined : calcDepth(fullPath, realPath) + 1;
+ return this._addToFsEvents(path, false, true, curDepth);
+ }
+
+ // track new paths
+ // (other than symlinks being followed, which will be tracked soon)
+ this.fsw._getWatchedDir(parent).add(item);
+ }
+ /**
+ * @type {'add'|'addDir'|'unlink'|'unlinkDir'}
+ */
+ const eventName = info.type === FSEVENT_TYPE_DIRECTORY ? event + DIR_SUFFIX : event;
+ this.fsw._emit(eventName, path);
+ if (eventName === EV_ADD_DIR) this._addToFsEvents(path, false, true);
+ }
+}
+
+/**
+ * Handle symlinks encountered during directory scan
+ * @param {String} watchPath - file/dir path to be watched with fsevents
+ * @param {String} realPath - real path (in case of symlinks)
+ * @param {Function} transform - path transformer
+ * @param {Function} globFilter - path filter in case a glob pattern was provided
+ * @returns {Function} closer for the watcher instance
+*/
+_watchWithFsEvents(watchPath, realPath, transform, globFilter) {
+ if (this.fsw.closed || this.fsw._isIgnored(watchPath)) return;
+ const opts = this.fsw.options;
+ const watchCallback = async (fullPath, flags, info) => {
+ if (this.fsw.closed) return;
+ if (
+ opts.depth !== undefined &&
+ calcDepth(fullPath, realPath) > opts.depth
+ ) return;
+ const path = transform(sysPath.join(
+ watchPath, sysPath.relative(watchPath, fullPath)
+ ));
+ if (globFilter && !globFilter(path)) return;
+ // ensure directories are tracked
+ const parent = sysPath.dirname(path);
+ const item = sysPath.basename(path);
+ const watchedDir = this.fsw._getWatchedDir(
+ info.type === FSEVENT_TYPE_DIRECTORY ? path : parent
+ );
+
+ // correct for wrong events emitted
+ if (wrongEventFlags.has(flags) || info.event === FSEVENT_UNKNOWN) {
+ if (typeof opts.ignored === FUNCTION_TYPE) {
+ let stats;
+ try {
+ stats = await stat(path);
+ } catch (error) {}
+ if (this.fsw.closed) return;
+ if (this.checkIgnored(path, stats)) return;
+ if (sameTypes(info, stats)) {
+ this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ } else {
+ this.handleEvent(EV_UNLINK, path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ }
+ } else {
+ this.checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ }
+ } else {
+ switch (info.event) {
+ case FSEVENT_CREATED:
+ case FSEVENT_MODIFIED:
+ return this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ case FSEVENT_DELETED:
+ case FSEVENT_MOVED:
+ return this.checkExists(path, fullPath, realPath, parent, watchedDir, item, info, opts);
+ }
+ }
+ };
+
+ const closer = setFSEventsListener(
+ watchPath,
+ realPath,
+ watchCallback,
+ this.fsw._emitRaw
+ );
+
+ this.fsw._emitReady();
+ return closer;
+}
+
+/**
+ * Handle symlinks encountered during directory scan
+ * @param {String} linkPath path to symlink
+ * @param {String} fullPath absolute path to the symlink
+ * @param {Function} transform pre-existing path transformer
+ * @param {Number} curDepth level of subdirectories traversed to where symlink is
+ * @returns {Promise<void>}
+ */
+async _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) {
+ // don't follow the same symlink more than once
+ if (this.fsw.closed || this.fsw._symlinkPaths.has(fullPath)) return;
+
+ this.fsw._symlinkPaths.set(fullPath, true);
+ this.fsw._incrReadyCount();
+
+ try {
+ const linkTarget = await realpath(linkPath);
+ if (this.fsw.closed) return;
+ if (this.fsw._isIgnored(linkTarget)) {
+ return this.fsw._emitReady();
+ }
+
+ this.fsw._incrReadyCount();
+
+ // add the linkTarget for watching with a wrapper for transform
+ // that causes emitted paths to incorporate the link's path
+ this._addToFsEvents(linkTarget || linkPath, (path) => {
+ let aliasedPath = linkPath;
+ if (linkTarget && linkTarget !== DOT_SLASH) {
+ aliasedPath = path.replace(linkTarget, linkPath);
+ } else if (path !== DOT_SLASH) {
+ aliasedPath = sysPath.join(linkPath, path);
+ }
+ return transform(aliasedPath);
+ }, false, curDepth);
+ } catch(error) {
+ if (this.fsw._handleError(error)) {
+ return this.fsw._emitReady();
+ }
+ }
+}
+
+/**
+ *
+ * @param {Path} newPath
+ * @param {fs.Stats} stats
+ */
+emitAdd(newPath, stats, processPath, opts, forceAdd) {
+ const pp = processPath(newPath);
+ const isDir = stats.isDirectory();
+ const dirObj = this.fsw._getWatchedDir(sysPath.dirname(pp));
+ const base = sysPath.basename(pp);
+
+ // ensure empty dirs get tracked
+ if (isDir) this.fsw._getWatchedDir(pp);
+ if (dirObj.has(base)) return;
+ dirObj.add(base);
+
+ if (!opts.ignoreInitial || forceAdd === true) {
+ this.fsw._emit(isDir ? EV_ADD_DIR : EV_ADD, pp, stats);
+ }
+}
+
+initWatch(realPath, path, wh, processPath) {
+ if (this.fsw.closed) return;
+ const closer = this._watchWithFsEvents(
+ wh.watchPath,
+ sysPath.resolve(realPath || wh.watchPath),
+ processPath,
+ wh.globFilter
+ );
+ this.fsw._addPathCloser(path, closer);
+}
+
+/**
+ * Handle added path with fsevents
+ * @param {String} path file/dir path or glob pattern
+ * @param {Function|Boolean=} transform converts working path to what the user expects
+ * @param {Boolean=} forceAdd ensure add is emitted
+ * @param {Number=} priorDepth Level of subdirectories already traversed.
+ * @returns {Promise<void>}
+ */
+async _addToFsEvents(path, transform, forceAdd, priorDepth) {
+ if (this.fsw.closed) {
+ return;
+ }
+ const opts = this.fsw.options;
+ const processPath = typeof transform === FUNCTION_TYPE ? transform : IDENTITY_FN;
+
+ const wh = this.fsw._getWatchHelpers(path);
+
+ // evaluate what is at the path we're being asked to watch
+ try {
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
+ if (this.fsw.closed) return;
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
+ throw null;
+ }
+ if (stats.isDirectory()) {
+ // emit addDir unless this is a glob parent
+ if (!wh.globFilter) this.emitAdd(processPath(path), stats, processPath, opts, forceAdd);
+
+ // don't recurse further if it would exceed depth setting
+ if (priorDepth && priorDepth > opts.depth) return;
+
+ // scan the contents of the dir
+ this.fsw._readdirp(wh.watchPath, {
+ fileFilter: entry => wh.filterPath(entry),
+ directoryFilter: entry => wh.filterDir(entry),
+ ...Depth(opts.depth - (priorDepth || 0))
+ }).on(STR_DATA, (entry) => {
+ // need to check filterPath on dirs b/c filterDir is less restrictive
+ if (this.fsw.closed) {
+ return;
+ }
+ if (entry.stats.isDirectory() && !wh.filterPath(entry)) return;
+
+ const joinedPath = sysPath.join(wh.watchPath, entry.path);
+ const {fullPath} = entry;
+
+ if (wh.followSymlinks && entry.stats.isSymbolicLink()) {
+ // preserve the current depth here since it can't be derived from
+ // real paths past the symlink
+ const curDepth = opts.depth === undefined ?
+ undefined : calcDepth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;
+
+ this._handleFsEventsSymlink(joinedPath, fullPath, processPath, curDepth);
+ } else {
+ this.emitAdd(joinedPath, entry.stats, processPath, opts, forceAdd);
+ }
+ }).on(EV_ERROR, EMPTY_FN).on(STR_END, () => {
+ this.fsw._emitReady();
+ });
+ } else {
+ this.emitAdd(wh.watchPath, stats, processPath, opts, forceAdd);
+ this.fsw._emitReady();
+ }
+ } catch (error) {
+ if (!error || this.fsw._handleError(error)) {
+ // TODO: Strange thing: "should not choke on an ignored watch path" will be failed without 2 ready calls -__-
+ this.fsw._emitReady();
+ this.fsw._emitReady();
+ }
+ }
+
+ if (opts.persistent && forceAdd !== true) {
+ if (typeof transform === FUNCTION_TYPE) {
+ // realpath has already been resolved
+ this.initWatch(undefined, path, wh, processPath);
+ } else {
+ let realPath;
+ try {
+ realPath = await realpath(wh.watchPath);
+ } catch (e) {}
+ this.initWatch(realPath, path, wh, processPath);
+ }
+ }
+}
+
+}
+
+module.exports = FsEventsHandler;
+module.exports.canUse = canUse;