Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F98109335
fsevents-handler.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Jan 9, 21:25
Size
15 KB
Mime Type
text/x-c++
Expires
Sat, Jan 11, 21:25 (2 d)
Engine
blob
Format
Raw Data
Handle
23507095
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
fsevents-handler.js
View Options
'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
;
Event Timeline
Log In to Comment