You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// TODO: emit errors properly. Example: EMFILE on Macos.
'use strict';constfs=require('fs');constsysPath=require('path');const{ promisify }=require('util');constisBinaryPath=require('is-binary-path');const{
isWindows,
isLinux,EMPTY_FN,EMPTY_STR,KEY_LISTENERS,KEY_ERR,KEY_RAW,HANDLER_KEYS,EV_CHANGE,EV_ADD,EV_ADD_DIR,EV_ERROR,STR_DATA,STR_END,BRACE_START,STAR}=require('./constants');constTHROTTLE_MODE_WATCH='watch';constopen=promisify(fs.open);conststat=promisify(fs.stat);constlstat=promisify(fs.lstat);constclose=promisify(fs.close);constfsrealpath=promisify(fs.realpath);conststatMethods={ lstat, stat };// TODO: emit errors properly. Example: EMFILE on Macos.constforeach=(val,fn)=>{if(valinstanceofSet){val.forEach(fn);}else{fn(val);}};constaddAndConvert=(main,prop,item)=>{letcontainer=main[prop];if(!(containerinstanceofSet)){main[prop]=container=newSet([container]);}container.add(item);};constclearItem=cont=>key=>{constset=cont[key];if(setinstanceofSet){set.clear();}else{deletecont[key];}};constdelFromSet=(main,prop,item)=>{constcontainer=main[prop];if(containerinstanceofSet){container.delete(item);}elseif(container===item){deletemain[prop];}};constisEmptySet=(val)=>valinstanceofSet ? val.size===0 : !val;/** * @typedef {String} Path */// fs_watch helpers// object to hold per-process fs_watch instances// (may be shared across chokidar FSWatcher instances)/** * @typedef {Object} FsWatchContainer * @property {Set} listeners * @property {Set} errHandlers * @property {Set} rawEmitters * @property {fs.FSWatcher=} watcher * @property {Boolean=} watcherUnusable *//** * @type {Map<String,FsWatchContainer>} */constFsWatchInstances=newMap();/** * Instantiates the fs_watch interface * @param {String} path to be watched * @param {Object} options to be passed to fs_watch * @param {Function} listener main event handler * @param {Function} errHandler emits info about errors * @param {Function} emitRaw emits raw event data * @returns {fs.FSWatcher} new fsevents instance */functioncreateFsWatchInstance(path,options,listener,errHandler,emitRaw){consthandleEvent=(rawEvent,evPath)=>{listener(path);emitRaw(rawEvent,evPath,{watchedPath: path});// emit based on events occurring for files from a directory's watcher in// case the file's watcher misses it (and rely on throttling to de-dupe)if(evPath&&path!==evPath){fsWatchBroadcast(sysPath.resolve(path,evPath),KEY_LISTENERS,sysPath.join(path,evPath));}};try{returnfs.watch(path,options,handleEvent);}catch(error){errHandler(error);}}/** * Helper for passing fs_watch event data to a collection of listeners * @param {Path} fullPath absolute path bound to fs_watch instance * @param {String} type listener type * @param {*=} val1 arguments to be passed to listeners * @param {*=} val2 * @param {*=} val3 */constfsWatchBroadcast=(fullPath,type,val1,val2,val3)=>{constcont=FsWatchInstances.get(fullPath);if(!cont)return;foreach(cont[type],(listener)=>{listener(val1,val2,val3);});};/** * Instantiates the fs_watch interface or binds listeners * to an existing one covering the same file system entry * @param {String} path * @param {String} fullPath absolute path * @param {Object} options to be passed to fs_watch * @param {Object} handlers container for event listener functions */constsetFsWatchListener=(path,fullPath,options,handlers)=>{const{listener, errHandler, rawEmitter}=handlers;letcont=FsWatchInstances.get(fullPath);/** @type {fs.FSWatcher=} */letwatcher;if(!options.persistent){watcher=createFsWatchInstance(path,options,listener,errHandler,rawEmitter);returnwatcher.close.bind(watcher);}if(cont){addAndConvert(cont,KEY_LISTENERS,listener);addAndConvert(cont,KEY_ERR,errHandler);addAndConvert(cont,KEY_RAW,rawEmitter);}else{watcher=createFsWatchInstance(path,options,fsWatchBroadcast.bind(null,fullPath,KEY_LISTENERS),errHandler,// no need to use broadcast herefsWatchBroadcast.bind(null,fullPath,KEY_RAW));if(!watcher)return;watcher.on(EV_ERROR,async(error)=>{constbroadcastErr=fsWatchBroadcast.bind(null,fullPath,KEY_ERR);cont.watcherUnusable=true;// documented since Node 10.4.1// Workaround for https://github.com/joyent/node/issues/4337if(isWindows&&error.code==='EPERM'){try{constfd=awaitopen(path,'r');awaitclose(fd);broadcastErr(error);}catch(err){}}else{broadcastErr(error);}});cont={listeners: listener,errHandlers: errHandler,rawEmitters: rawEmitter,
watcher
};FsWatchInstances.set(fullPath,cont);}// const index = cont.listeners.indexOf(listener);// removes this instance's listeners and closes the underlying fs_watch// instance if there are no more listeners leftreturn()=>{delFromSet(cont,KEY_LISTENERS,listener);delFromSet(cont,KEY_ERR,errHandler);delFromSet(cont,KEY_RAW,rawEmitter);if(isEmptySet(cont.listeners)){// Check to protect against issue gh-730.// if (cont.watcherUnusable) {cont.watcher.close();// }FsWatchInstances.delete(fullPath);HANDLER_KEYS.forEach(clearItem(cont));cont.watcher=undefined;Object.freeze(cont);}};};// fs_watchFile helpers// object to hold per-process fs_watchFile instances// (may be shared across chokidar FSWatcher instances)constFsWatchFileInstances=newMap();/** * Instantiates the fs_watchFile interface or binds listeners * to an existing one covering the same file system entry * @param {String} path to be watched * @param {String} fullPath absolute path * @param {Object} options options to be passed to fs_watchFile * @param {Object} handlers container for event listener functions * @returns {Function} closer */constsetFsWatchFileListener=(path,fullPath,options,handlers)=>{const{listener, rawEmitter}=handlers;letcont=FsWatchFileInstances.get(fullPath);/* eslint-disable no-unused-vars, prefer-destructuring */letlisteners=newSet();letrawEmitters=newSet();constcopts=cont&&cont.options;if(copts&&(copts.persistent<options.persistent||copts.interval>options.interval)){// "Upgrade" the watcher to persistence or a quicker interval.// This creates some unlikely edge case issues if the user mixes// settings in a very weird way, but solving for those cases// doesn't seem worthwhile for the added complexity.listeners=cont.listeners;rawEmitters=cont.rawEmitters;fs.unwatchFile(fullPath);cont=undefined;}/* eslint-enable no-unused-vars, prefer-destructuring */if(cont){addAndConvert(cont,KEY_LISTENERS,listener);addAndConvert(cont,KEY_RAW,rawEmitter);}else{// TODO// listeners.add(listener);// rawEmitters.add(rawEmitter);cont={listeners: listener,rawEmitters: rawEmitter,
options,watcher: fs.watchFile(fullPath,options,(curr,prev)=>{foreach(cont.rawEmitters,(rawEmitter)=>{rawEmitter(EV_CHANGE,fullPath,{curr, prev});});constcurrmtime=curr.mtimeMs;if(curr.size!==prev.size||currmtime>prev.mtimeMs||currmtime===0){foreach(cont.listeners,(listener)=>listener(path,curr));}})};FsWatchFileInstances.set(fullPath,cont);}// const index = cont.listeners.indexOf(listener);// Removes this instance's listeners and closes the underlying fs_watchFile// instance if there are no more listeners left.return()=>{delFromSet(cont,KEY_LISTENERS,listener);delFromSet(cont,KEY_RAW,rawEmitter);if(isEmptySet(cont.listeners)){FsWatchFileInstances.delete(fullPath);fs.unwatchFile(fullPath);cont.options=cont.watcher=undefined;Object.freeze(cont);}};};/** * @mixin */classNodeFsHandler{/** * @param {import("../index").FSWatcher} fsW */constructor(fsW){this.fsw=fsW;this._boundHandleError=(error)=>fsW._handleError(error);}/** * Watch file for changes with fs_watchFile or fs_watch. * @param {String} path to file or dir * @param {Function} listener on fs change * @returns {Function} closer for the watcher instance */_watchWithNodeFs(path,listener){constopts=this.fsw.options;constdirectory=sysPath.dirname(path);constbasename=sysPath.basename(path);constparent=this.fsw._getWatchedDir(directory);parent.add(basename);constabsolutePath=sysPath.resolve(path);constoptions={persistent: opts.persistent};if(!listener)listener=EMPTY_FN;letcloser;if(opts.usePolling){options.interval=opts.enableBinaryInterval&&isBinaryPath(basename) ?
opts.binaryInterval : opts.interval;closer=setFsWatchFileListener(path,absolutePath,options,{
listener,rawEmitter: this.fsw._emitRaw});}else{closer=setFsWatchListener(path,absolutePath,options,{
listener,errHandler: this._boundHandleError,rawEmitter: this.fsw._emitRaw});}returncloser;}/** * Watch a file and emit add event if warranted. * @param {Path} file Path * @param {fs.Stats} stats result of fs_stat * @param {Boolean} initialAdd was the file added at watch instantiation? * @returns {Function} closer for the watcher instance */_handleFile(file,stats,initialAdd){if(this.fsw.closed){return;}constdirname=sysPath.dirname(file);constbasename=sysPath.basename(file);constparent=this.fsw._getWatchedDir(dirname);// stats is always presentletprevStats=stats;// if the file is already being watched, do nothingif(parent.has(basename))return;constlistener=async(path,newStats)=>{if(!this.fsw._throttle(THROTTLE_MODE_WATCH,file,5))return;if(!newStats||newStats.mtimeMs===0){try{constnewStats=awaitstat(file);if(this.fsw.closed)return;// Check that change event was not fired because of changed only accessTime.constat=newStats.atimeMs;constmt=newStats.mtimeMs;if(!at||at<=mt||mt!==prevStats.mtimeMs){this.fsw._emit(EV_CHANGE,file,newStats);}if(isLinux&&prevStats.ino!==newStats.ino){this.fsw._closeFile(path)prevStats=newStats;this.fsw._addPathCloser(path,this._watchWithNodeFs(file,listener));}else{prevStats=newStats;}}catch(error){// Fix issues where mtime is null but file is still presentthis.fsw._remove(dirname,basename);}// add is about to be emitted if file not already tracked in parent}elseif(parent.has(basename)){// Check that change event was not fired because of changed only accessTime.constat=newStats.atimeMs;constmt=newStats.mtimeMs;if(!at||at<=mt||mt!==prevStats.mtimeMs){this.fsw._emit(EV_CHANGE,file,newStats);}prevStats=newStats;}}// kick off the watcherconstcloser=this._watchWithNodeFs(file,listener);// emit an add event if we're supposed toif(!(initialAdd&&this.fsw.options.ignoreInitial)&&this.fsw._isntIgnored(file)){if(!this.fsw._throttle(EV_ADD,file,0))return;this.fsw._emit(EV_ADD,file,stats);}returncloser;}/** * Handle symlinks encountered while reading a dir. * @param {Object} entry returned by readdirp * @param {String} directory path of dir being read * @param {String} path of this item * @param {String} item basename of this item * @returns {Promise<Boolean>} true if no more processing is needed for this entry. */async_handleSymlink(entry,directory,path,item){if(this.fsw.closed){return;}constfull=entry.fullPath;constdir=this.fsw._getWatchedDir(directory);if(!this.fsw.options.followSymlinks){// watch symlink directly (don't follow) and detect changesthis.fsw._incrReadyCount();letlinkPath;try{linkPath=awaitfsrealpath(path);}catch(e){this.fsw._emitReady();returntrue;}if(this.fsw.closed)return;if(dir.has(item)){if(this.fsw._symlinkPaths.get(full)!==linkPath){this.fsw._symlinkPaths.set(full,linkPath);this.fsw._emit(EV_CHANGE,path,entry.stats);}}else{dir.add(item);this.fsw._symlinkPaths.set(full,linkPath);this.fsw._emit(EV_ADD,path,entry.stats);}this.fsw._emitReady();returntrue;}// don't follow the same symlink more than onceif(this.fsw._symlinkPaths.has(full)){returntrue;}this.fsw._symlinkPaths.set(full,true);}_handleRead(directory,initialAdd,wh,target,dir,depth,throttler){// Normalize the directory name on Windowsdirectory=sysPath.join(directory,EMPTY_STR);if(!wh.hasGlob){throttler=this.fsw._throttle('readdir',directory,1000);if(!throttler)return;}constprevious=this.fsw._getWatchedDir(wh.path);constcurrent=newSet();letstream=this.fsw._readdirp(directory,{fileFilter: entry=>wh.filterPath(entry),directoryFilter: entry=>wh.filterDir(entry),depth: 0}).on(STR_DATA,async(entry)=>{if(this.fsw.closed){stream=undefined;return;}constitem=entry.path;letpath=sysPath.join(directory,item);current.add(item);if(entry.stats.isSymbolicLink()&&awaitthis._handleSymlink(entry,directory,path,item)){return;}if(this.fsw.closed){stream=undefined;return;}// Files that present in current directory snapshot// but absent in previous are added to watch list and// emit `add` event.if(item===target||!target&&!previous.has(item)){this.fsw._incrReadyCount();// ensure relativeness of path is preserved in case of watcher reusepath=sysPath.join(dir,sysPath.relative(dir,path));this._addToNodeFs(path,initialAdd,wh,depth+1);}}).on(EV_ERROR,this._boundHandleError);returnnewPromise(resolve=>stream.once(STR_END,()=>{if(this.fsw.closed){stream=undefined;return;}constwasThrottled=throttler ? throttler.clear() : false;resolve();// Files that absent in current directory snapshot// but present in previous emit `remove` event// and are removed from @watched[directory].previous.getChildren().filter((item)=>{returnitem!==directory&&!current.has(item)&&// in case of intersecting globs;// a path may have been filtered out of this readdir, but// shouldn't be removed because it matches a different glob(!wh.hasGlob||wh.filterPath({fullPath: sysPath.resolve(directory,item)}));}).forEach((item)=>{this.fsw._remove(directory,item);});stream=undefined;// one more time for any missed in case changes came in extremely quicklyif(wasThrottled)this._handleRead(directory,false,wh,target,dir,depth,throttler);}));}/** * Read directory to add / remove files from `@watched` list and re-read it on change. * @param {String} dir fs path * @param {fs.Stats} stats * @param {Boolean} initialAdd * @param {Number} depth relative to user-supplied path * @param {String} target child path targeted for watch * @param {Object} wh Common watch helpers for this path * @param {String} realpath * @returns {Promise<Function>} closer for the watcher instance. */async_handleDir(dir,stats,initialAdd,depth,target,wh,realpath){constparentDir=this.fsw._getWatchedDir(sysPath.dirname(dir));consttracked=parentDir.has(sysPath.basename(dir));if(!(initialAdd&&this.fsw.options.ignoreInitial)&&!target&&!tracked){if(!wh.hasGlob||wh.globFilter(dir))this.fsw._emit(EV_ADD_DIR,dir,stats);}// ensure dir is tracked (harmless if redundant)parentDir.add(sysPath.basename(dir));this.fsw._getWatchedDir(dir);letthrottler;letcloser;constoDepth=this.fsw.options.depth;if((oDepth==null||depth<=oDepth)&&!this.fsw._symlinkPaths.has(realpath)){if(!target){awaitthis._handleRead(dir,initialAdd,wh,target,dir,depth,throttler);if(this.fsw.closed)return;}closer=this._watchWithNodeFs(dir,(dirPath,stats)=>{// if current directory is removed, do nothingif(stats&&stats.mtimeMs===0)return;this._handleRead(dirPath,false,wh,target,dir,depth,throttler);});}returncloser;}/** * Handle added file, directory, or glob pattern. * Delegates call to _handleFile / _handleDir after checks. * @param {String} path to file or ir * @param {Boolean} initialAdd was the file added at watch instantiation? * @param {Object} priorWh depth relative to user-supplied path * @param {Number} depth Child path actually targeted for watch * @param {String=} target Child path actually targeted for watch * @returns {Promise} */async_addToNodeFs(path,initialAdd,priorWh,depth,target){constready=this.fsw._emitReady;if(this.fsw._isIgnored(path)||this.fsw.closed){ready();returnfalse;}constwh=this.fsw._getWatchHelpers(path,depth);if(!wh.hasGlob&&priorWh){wh.hasGlob=priorWh.hasGlob;wh.globFilter=priorWh.globFilter;wh.filterPath=entry=>priorWh.filterPath(entry);wh.filterDir=entry=>priorWh.filterDir(entry);}// evaluate what is at the path we're being asked to watchtry{conststats=awaitstatMethods[wh.statMethod](wh.watchPath);if(this.fsw.closed)return;if(this.fsw._isIgnored(wh.watchPath,stats)){ready();returnfalse;}constfollow=this.fsw.options.followSymlinks&&!path.includes(STAR)&&!path.includes(BRACE_START);letcloser;if(stats.isDirectory()){constabsPath=sysPath.resolve(path);consttargetPath=follow ? awaitfsrealpath(path) : path;if(this.fsw.closed)return;closer=awaitthis._handleDir(wh.watchPath,stats,initialAdd,depth,target,wh,targetPath);if(this.fsw.closed)return;// preserve this symlink's target pathif(absPath!==targetPath&&targetPath!==undefined){this.fsw._symlinkPaths.set(absPath,targetPath);}}elseif(stats.isSymbolicLink()){consttargetPath=follow ? awaitfsrealpath(path) : path;if(this.fsw.closed)return;constparent=sysPath.dirname(wh.watchPath);this.fsw._getWatchedDir(parent).add(wh.watchPath);this.fsw._emit(EV_ADD,wh.watchPath,stats);closer=awaitthis._handleDir(parent,stats,initialAdd,depth,path,wh,targetPath);if(this.fsw.closed)return;// preserve this symlink's target pathif(targetPath!==undefined){this.fsw._symlinkPaths.set(sysPath.resolve(path),targetPath);}}else{closer=this._handleFile(wh.watchPath,stats,initialAdd);}ready();this.fsw._addPathCloser(path,closer);returnfalse;}catch(error){if(this.fsw._handleError(error)){ready();returnpath;}}}}module.exports=NodeFsHandler;
The text was updated successfully, but these errors were encountered:
(may be shared across chokidar FSWatcher instances)
case the file's watcher misses it (and rely on throttling to de-dupe)
Workaround for nodejs/node-v0.x-archive#4337
instance if there are no more listeners left
if (cont.watcherUnusable) {
(may be shared across chokidar FSWatcher instances)
This creates some unlikely edge case issues if the user mixes
settings in a very weird way, but solving for those cases
doesn't seem worthwhile for the added complexity.
Todo/node_modules/chokidar/lib/nodefs-handler.js
Line 36 in 5ee5bf7
The text was updated successfully, but these errors were encountered: