Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F99333772
index.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 23, 14:14
Size
27 KB
Mime Type
text/x-c++
Expires
Sat, Jan 25, 14:14 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
23774102
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
index.js
View Options
'use strict'
;
const
{
EventEmitter
}
=
require
(
'events'
);
const
fs
=
require
(
'fs'
);
const
sysPath
=
require
(
'path'
);
const
{
promisify
}
=
require
(
'util'
);
const
readdirp
=
require
(
'readdirp'
);
const
anymatch
=
require
(
'anymatch'
).
default
;
const
globParent
=
require
(
'glob-parent'
);
const
isGlob
=
require
(
'is-glob'
);
const
braces
=
require
(
'braces'
);
const
normalizePath
=
require
(
'normalize-path'
);
const
NodeFsHandler
=
require
(
'./lib/nodefs-handler'
);
const
FsEventsHandler
=
require
(
'./lib/fsevents-handler'
);
const
{
EV_ALL
,
EV_READY
,
EV_ADD
,
EV_CHANGE
,
EV_UNLINK
,
EV_ADD_DIR
,
EV_UNLINK_DIR
,
EV_RAW
,
EV_ERROR
,
STR_CLOSE
,
STR_END
,
BACK_SLASH_RE
,
DOUBLE_SLASH_RE
,
SLASH_OR_BACK_SLASH_RE
,
DOT_RE
,
REPLACER_RE
,
SLASH
,
SLASH_SLASH
,
BRACE_START
,
BANG
,
ONE_DOT
,
TWO_DOTS
,
GLOBSTAR
,
SLASH_GLOBSTAR
,
ANYMATCH_OPTS
,
STRING_TYPE
,
FUNCTION_TYPE
,
EMPTY_STR
,
EMPTY_FN
,
isWindows
,
isMacos
,
isIBMi
}
=
require
(
'./lib/constants'
);
const
stat
=
promisify
(
fs
.
stat
);
const
readdir
=
promisify
(
fs
.
readdir
);
/**
* @typedef {String} Path
* @typedef {'all'|'add'|'addDir'|'change'|'unlink'|'unlinkDir'|'raw'|'error'|'ready'} EventName
* @typedef {'readdir'|'watch'|'add'|'remove'|'change'} ThrottleType
*/
/**
*
* @typedef {Object} WatchHelpers
* @property {Boolean} followSymlinks
* @property {'stat'|'lstat'} statMethod
* @property {Path} path
* @property {Path} watchPath
* @property {Function} entryPath
* @property {Boolean} hasGlob
* @property {Object} globFilter
* @property {Function} filterPath
* @property {Function} filterDir
*/
const
arrify
=
(
value
=
[])
=>
Array
.
isArray
(
value
)
?
value
:
[
value
];
const
flatten
=
(
list
,
result
=
[])
=>
{
list
.
forEach
(
item
=>
{
if
(
Array
.
isArray
(
item
))
{
flatten
(
item
,
result
);
}
else
{
result
.
push
(
item
);
}
});
return
result
;
};
const
unifyPaths
=
(
paths_
)
=>
{
/**
* @type {Array<String>}
*/
const
paths
=
flatten
(
arrify
(
paths_
));
if
(
!
paths
.
every
(
p
=>
typeof
p
===
STRING_TYPE
))
{
throw
new
TypeError
(
`
Non
-
string
provided
as
watch
path
:
$
{
paths
}
`
);
}
return
paths
.
map
(
normalizePathToUnix
);
};
// If SLASH_SLASH occurs at the beginning of path, it is not replaced
// because "//StoragePC/DrivePool/Movies" is a valid network path
const
toUnix
=
(
string
)
=>
{
let
str
=
string
.
replace
(
BACK_SLASH_RE
,
SLASH
);
let
prepend
=
false
;
if
(
str
.
startsWith
(
SLASH_SLASH
))
{
prepend
=
true
;
}
while
(
str
.
match
(
DOUBLE_SLASH_RE
))
{
str
=
str
.
replace
(
DOUBLE_SLASH_RE
,
SLASH
);
}
if
(
prepend
)
{
str
=
SLASH
+
str
;
}
return
str
;
};
// Our version of upath.normalize
// TODO: this is not equal to path-normalize module - investigate why
const
normalizePathToUnix
=
(
path
)
=>
toUnix
(
sysPath
.
normalize
(
toUnix
(
path
)));
const
normalizeIgnored
=
(
cwd
=
EMPTY_STR
)
=>
(
path
)
=>
{
if
(
typeof
path
!==
STRING_TYPE
)
return
path
;
return
normalizePathToUnix
(
sysPath
.
isAbsolute
(
path
)
?
path
:
sysPath
.
join
(
cwd
,
path
));
};
const
getAbsolutePath
=
(
path
,
cwd
)
=>
{
if
(
sysPath
.
isAbsolute
(
path
))
{
return
path
;
}
if
(
path
.
startsWith
(
BANG
))
{
return
BANG
+
sysPath
.
join
(
cwd
,
path
.
slice
(
1
));
}
return
sysPath
.
join
(
cwd
,
path
);
};
const
undef
=
(
opts
,
key
)
=>
opts
[
key
]
===
undefined
;
/**
* Directory entry.
* @property {Path} path
* @property {Set<Path>} items
*/
class
DirEntry
{
/**
* @param {Path} dir
* @param {Function} removeWatcher
*/
constructor
(
dir
,
removeWatcher
)
{
this
.
path
=
dir
;
this
.
_removeWatcher
=
removeWatcher
;
/** @type {Set<Path>} */
this
.
items
=
new
Set
();
}
add
(
item
)
{
const
{
items
}
=
this
;
if
(
!
items
)
return
;
if
(
item
!==
ONE_DOT
&&
item
!==
TWO_DOTS
)
items
.
add
(
item
);
}
async
remove
(
item
)
{
const
{
items
}
=
this
;
if
(
!
items
)
return
;
items
.
delete
(
item
);
if
(
items
.
size
>
0
)
return
;
const
dir
=
this
.
path
;
try
{
await
readdir
(
dir
);
}
catch
(
err
)
{
if
(
this
.
_removeWatcher
)
{
this
.
_removeWatcher
(
sysPath
.
dirname
(
dir
),
sysPath
.
basename
(
dir
));
}
}
}
has
(
item
)
{
const
{
items
}
=
this
;
if
(
!
items
)
return
;
return
items
.
has
(
item
);
}
/**
* @returns {Array<String>}
*/
getChildren
()
{
const
{
items
}
=
this
;
if
(
!
items
)
return
;
return
[...
items
.
values
()];
}
dispose
()
{
this
.
items
.
clear
();
delete
this
.
path
;
delete
this
.
_removeWatcher
;
delete
this
.
items
;
Object
.
freeze
(
this
);
}
}
const
STAT_METHOD_F
=
'stat'
;
const
STAT_METHOD_L
=
'lstat'
;
class
WatchHelper
{
constructor
(
path
,
watchPath
,
follow
,
fsw
)
{
this
.
fsw
=
fsw
;
this
.
path
=
path
=
path
.
replace
(
REPLACER_RE
,
EMPTY_STR
);
this
.
watchPath
=
watchPath
;
this
.
fullWatchPath
=
sysPath
.
resolve
(
watchPath
);
this
.
hasGlob
=
watchPath
!==
path
;
/** @type {object|boolean} */
if
(
path
===
EMPTY_STR
)
this
.
hasGlob
=
false
;
this
.
globSymlink
=
this
.
hasGlob
&&
follow
?
undefined
:
false
;
this
.
globFilter
=
this
.
hasGlob
?
anymatch
(
path
,
undefined
,
ANYMATCH_OPTS
)
:
false
;
this
.
dirParts
=
this
.
getDirParts
(
path
);
this
.
dirParts
.
forEach
((
parts
)
=>
{
if
(
parts
.
length
>
1
)
parts
.
pop
();
});
this
.
followSymlinks
=
follow
;
this
.
statMethod
=
follow
?
STAT_METHOD_F
:
STAT_METHOD_L
;
}
checkGlobSymlink
(
entry
)
{
// only need to resolve once
// first entry should always have entry.parentDir === EMPTY_STR
if
(
this
.
globSymlink
===
undefined
)
{
this
.
globSymlink
=
entry
.
fullParentDir
===
this
.
fullWatchPath
?
false
:
{
realPath
:
entry
.
fullParentDir
,
linkPath
:
this
.
fullWatchPath
};
}
if
(
this
.
globSymlink
)
{
return
entry
.
fullPath
.
replace
(
this
.
globSymlink
.
realPath
,
this
.
globSymlink
.
linkPath
);
}
return
entry
.
fullPath
;
}
entryPath
(
entry
)
{
return
sysPath
.
join
(
this
.
watchPath
,
sysPath
.
relative
(
this
.
watchPath
,
this
.
checkGlobSymlink
(
entry
))
);
}
filterPath
(
entry
)
{
const
{
stats
}
=
entry
;
if
(
stats
&&
stats
.
isSymbolicLink
())
return
this
.
filterDir
(
entry
);
const
resolvedPath
=
this
.
entryPath
(
entry
);
const
matchesGlob
=
this
.
hasGlob
&&
typeof
this
.
globFilter
===
FUNCTION_TYPE
?
this
.
globFilter
(
resolvedPath
)
:
true
;
return
matchesGlob
&&
this
.
fsw
.
_isntIgnored
(
resolvedPath
,
stats
)
&&
this
.
fsw
.
_hasReadPermissions
(
stats
);
}
getDirParts
(
path
)
{
if
(
!
this
.
hasGlob
)
return
[];
const
parts
=
[];
const
expandedPath
=
path
.
includes
(
BRACE_START
)
?
braces
.
expand
(
path
)
:
[
path
];
expandedPath
.
forEach
((
path
)
=>
{
parts
.
push
(
sysPath
.
relative
(
this
.
watchPath
,
path
).
split
(
SLASH_OR_BACK_SLASH_RE
));
});
return
parts
;
}
filterDir
(
entry
)
{
if
(
this
.
hasGlob
)
{
const
entryParts
=
this
.
getDirParts
(
this
.
checkGlobSymlink
(
entry
));
let
globstar
=
false
;
this
.
unmatchedGlob
=
!
this
.
dirParts
.
some
((
parts
)
=>
{
return
parts
.
every
((
part
,
i
)
=>
{
if
(
part
===
GLOBSTAR
)
globstar
=
true
;
return
globstar
||
!
entryParts
[
0
][
i
]
||
anymatch
(
part
,
entryParts
[
0
][
i
],
ANYMATCH_OPTS
);
});
});
}
return
!
this
.
unmatchedGlob
&&
this
.
fsw
.
_isntIgnored
(
this
.
entryPath
(
entry
),
entry
.
stats
);
}
}
/**
* Watches files & directories for changes. Emitted events:
* `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
*
* new FSWatcher()
* .add(directories)
* .on('add', path => log('File', path, 'was added'))
*/
class
FSWatcher
extends
EventEmitter
{
// Not indenting methods for history sake; for now.
constructor
(
_opts
)
{
super
();
const
opts
=
{};
if
(
_opts
)
Object
.
assign
(
opts
,
_opts
);
// for frozen objects
/** @type {Map<String, DirEntry>} */
this
.
_watched
=
new
Map
();
/** @type {Map<String, Array>} */
this
.
_closers
=
new
Map
();
/** @type {Set<String>} */
this
.
_ignoredPaths
=
new
Set
();
/** @type {Map<ThrottleType, Map>} */
this
.
_throttled
=
new
Map
();
/** @type {Map<Path, String|Boolean>} */
this
.
_symlinkPaths
=
new
Map
();
this
.
_streams
=
new
Set
();
this
.
closed
=
false
;
// Set up default options.
if
(
undef
(
opts
,
'persistent'
))
opts
.
persistent
=
true
;
if
(
undef
(
opts
,
'ignoreInitial'
))
opts
.
ignoreInitial
=
false
;
if
(
undef
(
opts
,
'ignorePermissionErrors'
))
opts
.
ignorePermissionErrors
=
false
;
if
(
undef
(
opts
,
'interval'
))
opts
.
interval
=
100
;
if
(
undef
(
opts
,
'binaryInterval'
))
opts
.
binaryInterval
=
300
;
if
(
undef
(
opts
,
'disableGlobbing'
))
opts
.
disableGlobbing
=
false
;
opts
.
enableBinaryInterval
=
opts
.
binaryInterval
!==
opts
.
interval
;
// Enable fsevents on OS X when polling isn't explicitly enabled.
if
(
undef
(
opts
,
'useFsEvents'
))
opts
.
useFsEvents
=
!
opts
.
usePolling
;
// If we can't use fsevents, ensure the options reflect it's disabled.
const
canUseFsEvents
=
FsEventsHandler
.
canUse
();
if
(
!
canUseFsEvents
)
opts
.
useFsEvents
=
false
;
// Use polling on Mac if not using fsevents.
// Other platforms use non-polling fs_watch.
if
(
undef
(
opts
,
'usePolling'
)
&&
!
opts
.
useFsEvents
)
{
opts
.
usePolling
=
isMacos
;
}
// Always default to polling on IBM i because fs.watch() is not available on IBM i.
if
(
isIBMi
)
{
opts
.
usePolling
=
true
;
}
// Global override (useful for end-developers that need to force polling for all
// instances of chokidar, regardless of usage/dependency depth)
const
envPoll
=
process
.
env
.
CHOKIDAR_USEPOLLING
;
if
(
envPoll
!==
undefined
)
{
const
envLower
=
envPoll
.
toLowerCase
();
if
(
envLower
===
'false'
||
envLower
===
'0'
)
{
opts
.
usePolling
=
false
;
}
else
if
(
envLower
===
'true'
||
envLower
===
'1'
)
{
opts
.
usePolling
=
true
;
}
else
{
opts
.
usePolling
=
!!
envLower
;
}
}
const
envInterval
=
process
.
env
.
CHOKIDAR_INTERVAL
;
if
(
envInterval
)
{
opts
.
interval
=
Number
.
parseInt
(
envInterval
,
10
);
}
// Editor atomic write normalization enabled by default with fs.watch
if
(
undef
(
opts
,
'atomic'
))
opts
.
atomic
=
!
opts
.
usePolling
&&
!
opts
.
useFsEvents
;
if
(
opts
.
atomic
)
this
.
_pendingUnlinks
=
new
Map
();
if
(
undef
(
opts
,
'followSymlinks'
))
opts
.
followSymlinks
=
true
;
if
(
undef
(
opts
,
'awaitWriteFinish'
))
opts
.
awaitWriteFinish
=
false
;
if
(
opts
.
awaitWriteFinish
===
true
)
opts
.
awaitWriteFinish
=
{};
const
awf
=
opts
.
awaitWriteFinish
;
if
(
awf
)
{
if
(
!
awf
.
stabilityThreshold
)
awf
.
stabilityThreshold
=
2000
;
if
(
!
awf
.
pollInterval
)
awf
.
pollInterval
=
100
;
this
.
_pendingWrites
=
new
Map
();
}
if
(
opts
.
ignored
)
opts
.
ignored
=
arrify
(
opts
.
ignored
);
let
readyCalls
=
0
;
this
.
_emitReady
=
()
=>
{
readyCalls
++
;
if
(
readyCalls
>=
this
.
_readyCount
)
{
this
.
_emitReady
=
EMPTY_FN
;
this
.
_readyEmitted
=
true
;
// use process.nextTick to allow time for listener to be bound
process
.
nextTick
(()
=>
this
.
emit
(
EV_READY
));
}
};
this
.
_emitRaw
=
(...
args
)
=>
this
.
emit
(
EV_RAW
,
...
args
);
this
.
_readyEmitted
=
false
;
this
.
options
=
opts
;
// Initialize with proper watcher.
if
(
opts
.
useFsEvents
)
{
this
.
_fsEventsHandler
=
new
FsEventsHandler
(
this
);
}
else
{
this
.
_nodeFsHandler
=
new
NodeFsHandler
(
this
);
}
// You’re frozen when your heart’s not open.
Object
.
freeze
(
opts
);
}
// Public methods
/**
* Adds paths to be watched on an existing FSWatcher instance
* @param {Path|Array<Path>} paths_
* @param {String=} _origAdd private; for handling non-existent paths to be watched
* @param {Boolean=} _internal private; indicates a non-user add
* @returns {FSWatcher} for chaining
*/
add
(
paths_
,
_origAdd
,
_internal
)
{
const
{
cwd
,
disableGlobbing
}
=
this
.
options
;
this
.
closed
=
false
;
let
paths
=
unifyPaths
(
paths_
);
if
(
cwd
)
{
paths
=
paths
.
map
((
path
)
=>
{
const
absPath
=
getAbsolutePath
(
path
,
cwd
);
// Check `path` instead of `absPath` because the cwd portion can't be a glob
if
(
disableGlobbing
||
!
isGlob
(
path
))
{
return
absPath
;
}
return
normalizePath
(
absPath
);
});
}
// set aside negated glob strings
paths
=
paths
.
filter
((
path
)
=>
{
if
(
path
.
startsWith
(
BANG
))
{
this
.
_ignoredPaths
.
add
(
path
.
slice
(
1
));
return
false
;
}
// if a path is being added that was previously ignored, stop ignoring it
this
.
_ignoredPaths
.
delete
(
path
);
this
.
_ignoredPaths
.
delete
(
path
+
SLASH_GLOBSTAR
);
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this
.
_userIgnored
=
undefined
;
return
true
;
});
if
(
this
.
options
.
useFsEvents
&&
this
.
_fsEventsHandler
)
{
if
(
!
this
.
_readyCount
)
this
.
_readyCount
=
paths
.
length
;
if
(
this
.
options
.
persistent
)
this
.
_readyCount
*=
2
;
paths
.
forEach
((
path
)
=>
this
.
_fsEventsHandler
.
_addToFsEvents
(
path
));
}
else
{
if
(
!
this
.
_readyCount
)
this
.
_readyCount
=
0
;
this
.
_readyCount
+=
paths
.
length
;
Promise
.
all
(
paths
.
map
(
async
path
=>
{
const
res
=
await
this
.
_nodeFsHandler
.
_addToNodeFs
(
path
,
!
_internal
,
0
,
0
,
_origAdd
);
if
(
res
)
this
.
_emitReady
();
return
res
;
})
).
then
(
results
=>
{
if
(
this
.
closed
)
return
;
results
.
filter
(
item
=>
item
).
forEach
(
item
=>
{
this
.
add
(
sysPath
.
dirname
(
item
),
sysPath
.
basename
(
_origAdd
||
item
));
});
});
}
return
this
;
}
/**
* Close watchers or start ignoring events from specified paths.
* @param {Path|Array<Path>} paths_ - string or array of strings, file/directory paths and/or globs
* @returns {FSWatcher} for chaining
*/
unwatch
(
paths_
)
{
if
(
this
.
closed
)
return
this
;
const
paths
=
unifyPaths
(
paths_
);
const
{
cwd
}
=
this
.
options
;
paths
.
forEach
((
path
)
=>
{
// convert to absolute path unless relative path already matches
if
(
!
sysPath
.
isAbsolute
(
path
)
&&
!
this
.
_closers
.
has
(
path
))
{
if
(
cwd
)
path
=
sysPath
.
join
(
cwd
,
path
);
path
=
sysPath
.
resolve
(
path
);
}
this
.
_closePath
(
path
);
this
.
_ignoredPaths
.
add
(
path
);
if
(
this
.
_watched
.
has
(
path
))
{
this
.
_ignoredPaths
.
add
(
path
+
SLASH_GLOBSTAR
);
}
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this
.
_userIgnored
=
undefined
;
});
return
this
;
}
/**
* Close watchers and remove all listeners from watched paths.
* @returns {Promise<void>}.
*/
close
()
{
if
(
this
.
closed
)
return
this
.
_closePromise
;
this
.
closed
=
true
;
// Memory management.
this
.
removeAllListeners
();
const
closers
=
[];
this
.
_closers
.
forEach
(
closerList
=>
closerList
.
forEach
(
closer
=>
{
const
promise
=
closer
();
if
(
promise
instanceof
Promise
)
closers
.
push
(
promise
);
}));
this
.
_streams
.
forEach
(
stream
=>
stream
.
destroy
());
this
.
_userIgnored
=
undefined
;
this
.
_readyCount
=
0
;
this
.
_readyEmitted
=
false
;
this
.
_watched
.
forEach
(
dirent
=>
dirent
.
dispose
());
[
'closers'
,
'watched'
,
'streams'
,
'symlinkPaths'
,
'throttled'
].
forEach
(
key
=>
{
this
[
`
_$
{
key
}
`
].
clear
();
});
this
.
_closePromise
=
closers
.
length
?
Promise
.
all
(
closers
).
then
(()
=>
undefined
)
:
Promise
.
resolve
();
return
this
.
_closePromise
;
}
/**
* Expose list of watched paths
* @returns {Object} for chaining
*/
getWatched
()
{
const
watchList
=
{};
this
.
_watched
.
forEach
((
entry
,
dir
)
=>
{
const
key
=
this
.
options
.
cwd
?
sysPath
.
relative
(
this
.
options
.
cwd
,
dir
)
:
dir
;
watchList
[
key
||
ONE_DOT
]
=
entry
.
getChildren
().
sort
();
});
return
watchList
;
}
emitWithAll
(
event
,
args
)
{
this
.
emit
(...
args
);
if
(
event
!==
EV_ERROR
)
this
.
emit
(
EV_ALL
,
...
args
);
}
// Common helpers
// --------------
/**
* Normalize and emit events.
* Calling _emit DOES NOT MEAN emit() would be called!
* @param {EventName} event Type of event
* @param {Path} path File or directory path
* @param {*=} val1 arguments to be passed with event
* @param {*=} val2
* @param {*=} val3
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
*/
async
_emit
(
event
,
path
,
val1
,
val2
,
val3
)
{
if
(
this
.
closed
)
return
;
const
opts
=
this
.
options
;
if
(
isWindows
)
path
=
sysPath
.
normalize
(
path
);
if
(
opts
.
cwd
)
path
=
sysPath
.
relative
(
opts
.
cwd
,
path
);
/** @type Array<any> */
const
args
=
[
event
,
path
];
if
(
val3
!==
undefined
)
args
.
push
(
val1
,
val2
,
val3
);
else
if
(
val2
!==
undefined
)
args
.
push
(
val1
,
val2
);
else
if
(
val1
!==
undefined
)
args
.
push
(
val1
);
const
awf
=
opts
.
awaitWriteFinish
;
let
pw
;
if
(
awf
&&
(
pw
=
this
.
_pendingWrites
.
get
(
path
)))
{
pw
.
lastChange
=
new
Date
();
return
this
;
}
if
(
opts
.
atomic
)
{
if
(
event
===
EV_UNLINK
)
{
this
.
_pendingUnlinks
.
set
(
path
,
args
);
setTimeout
(()
=>
{
this
.
_pendingUnlinks
.
forEach
((
entry
,
path
)
=>
{
this
.
emit
(...
entry
);
this
.
emit
(
EV_ALL
,
...
entry
);
this
.
_pendingUnlinks
.
delete
(
path
);
});
},
typeof
opts
.
atomic
===
'number'
?
opts
.
atomic
:
100
);
return
this
;
}
if
(
event
===
EV_ADD
&&
this
.
_pendingUnlinks
.
has
(
path
))
{
event
=
args
[
0
]
=
EV_CHANGE
;
this
.
_pendingUnlinks
.
delete
(
path
);
}
}
if
(
awf
&&
(
event
===
EV_ADD
||
event
===
EV_CHANGE
)
&&
this
.
_readyEmitted
)
{
const
awfEmit
=
(
err
,
stats
)
=>
{
if
(
err
)
{
event
=
args
[
0
]
=
EV_ERROR
;
args
[
1
]
=
err
;
this
.
emitWithAll
(
event
,
args
);
}
else
if
(
stats
)
{
// if stats doesn't exist the file must have been deleted
if
(
args
.
length
>
2
)
{
args
[
2
]
=
stats
;
}
else
{
args
.
push
(
stats
);
}
this
.
emitWithAll
(
event
,
args
);
}
};
this
.
_awaitWriteFinish
(
path
,
awf
.
stabilityThreshold
,
event
,
awfEmit
);
return
this
;
}
if
(
event
===
EV_CHANGE
)
{
const
isThrottled
=
!
this
.
_throttle
(
EV_CHANGE
,
path
,
50
);
if
(
isThrottled
)
return
this
;
}
if
(
opts
.
alwaysStat
&&
val1
===
undefined
&&
(
event
===
EV_ADD
||
event
===
EV_ADD_DIR
||
event
===
EV_CHANGE
)
)
{
const
fullPath
=
opts
.
cwd
?
sysPath
.
join
(
opts
.
cwd
,
path
)
:
path
;
let
stats
;
try
{
stats
=
await
stat
(
fullPath
);
}
catch
(
err
)
{}
// Suppress event when fs_stat fails, to avoid sending undefined 'stat'
if
(
!
stats
||
this
.
closed
)
return
;
args
.
push
(
stats
);
}
this
.
emitWithAll
(
event
,
args
);
return
this
;
}
/**
* Common handler for errors
* @param {Error} error
* @returns {Error|Boolean} The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
*/
_handleError
(
error
)
{
const
code
=
error
&&
error
.
code
;
if
(
error
&&
code
!==
'ENOENT'
&&
code
!==
'ENOTDIR'
&&
(
!
this
.
options
.
ignorePermissionErrors
||
(
code
!==
'EPERM'
&&
code
!==
'EACCES'
))
)
{
this
.
emit
(
EV_ERROR
,
error
);
}
return
error
||
this
.
closed
;
}
/**
* Helper utility for throttling
* @param {ThrottleType} actionType type being throttled
* @param {Path} path being acted upon
* @param {Number} timeout duration of time to suppress duplicate actions
* @returns {Object|false} tracking object or false if action should be suppressed
*/
_throttle
(
actionType
,
path
,
timeout
)
{
if
(
!
this
.
_throttled
.
has
(
actionType
))
{
this
.
_throttled
.
set
(
actionType
,
new
Map
());
}
/** @type {Map<Path, Object>} */
const
action
=
this
.
_throttled
.
get
(
actionType
);
/** @type {Object} */
const
actionPath
=
action
.
get
(
path
);
if
(
actionPath
)
{
actionPath
.
count
++
;
return
false
;
}
let
timeoutObject
;
const
clear
=
()
=>
{
const
item
=
action
.
get
(
path
);
const
count
=
item
?
item
.
count
:
0
;
action
.
delete
(
path
);
clearTimeout
(
timeoutObject
);
if
(
item
)
clearTimeout
(
item
.
timeoutObject
);
return
count
;
};
timeoutObject
=
setTimeout
(
clear
,
timeout
);
const
thr
=
{
timeoutObject
,
clear
,
count
:
0
};
action
.
set
(
path
,
thr
);
return
thr
;
}
_incrReadyCount
()
{
return
this
.
_readyCount
++
;
}
/**
* Awaits write operation to finish.
* Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
* @param {Path} path being acted upon
* @param {Number} threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
* @param {EventName} event
* @param {Function} awfEmit Callback to be called when ready for event to be emitted.
*/
_awaitWriteFinish
(
path
,
threshold
,
event
,
awfEmit
)
{
let
timeoutHandler
;
let
fullPath
=
path
;
if
(
this
.
options
.
cwd
&&
!
sysPath
.
isAbsolute
(
path
))
{
fullPath
=
sysPath
.
join
(
this
.
options
.
cwd
,
path
);
}
const
now
=
new
Date
();
const
awaitWriteFinish
=
(
prevStat
)
=>
{
fs
.
stat
(
fullPath
,
(
err
,
curStat
)
=>
{
if
(
err
||
!
this
.
_pendingWrites
.
has
(
path
))
{
if
(
err
&&
err
.
code
!==
'ENOENT'
)
awfEmit
(
err
);
return
;
}
const
now
=
Number
(
new
Date
());
if
(
prevStat
&&
curStat
.
size
!==
prevStat
.
size
)
{
this
.
_pendingWrites
.
get
(
path
).
lastChange
=
now
;
}
const
pw
=
this
.
_pendingWrites
.
get
(
path
);
const
df
=
now
-
pw
.
lastChange
;
if
(
df
>=
threshold
)
{
this
.
_pendingWrites
.
delete
(
path
);
awfEmit
(
undefined
,
curStat
);
}
else
{
timeoutHandler
=
setTimeout
(
awaitWriteFinish
,
this
.
options
.
awaitWriteFinish
.
pollInterval
,
curStat
);
}
});
};
if
(
!
this
.
_pendingWrites
.
has
(
path
))
{
this
.
_pendingWrites
.
set
(
path
,
{
lastChange
:
now
,
cancelWait
:
()
=>
{
this
.
_pendingWrites
.
delete
(
path
);
clearTimeout
(
timeoutHandler
);
return
event
;
}
});
timeoutHandler
=
setTimeout
(
awaitWriteFinish
,
this
.
options
.
awaitWriteFinish
.
pollInterval
);
}
}
_getGlobIgnored
()
{
return
[...
this
.
_ignoredPaths
.
values
()];
}
/**
* Determines whether user has asked to ignore this path.
* @param {Path} path filepath or dir
* @param {fs.Stats=} stats result of fs.stat
* @returns {Boolean}
*/
_isIgnored
(
path
,
stats
)
{
if
(
this
.
options
.
atomic
&&
DOT_RE
.
test
(
path
))
return
true
;
if
(
!
this
.
_userIgnored
)
{
const
{
cwd
}
=
this
.
options
;
const
ign
=
this
.
options
.
ignored
;
const
ignored
=
ign
&&
ign
.
map
(
normalizeIgnored
(
cwd
));
const
paths
=
arrify
(
ignored
)
.
filter
((
path
)
=>
typeof
path
===
STRING_TYPE
&&
!
isGlob
(
path
))
.
map
((
path
)
=>
path
+
SLASH_GLOBSTAR
);
const
list
=
this
.
_getGlobIgnored
().
map
(
normalizeIgnored
(
cwd
)).
concat
(
ignored
,
paths
);
this
.
_userIgnored
=
anymatch
(
list
,
undefined
,
ANYMATCH_OPTS
);
}
return
this
.
_userIgnored
([
path
,
stats
]);
}
_isntIgnored
(
path
,
stat
)
{
return
!
this
.
_isIgnored
(
path
,
stat
);
}
/**
* Provides a set of common helpers and properties relating to symlink and glob handling.
* @param {Path} path file, directory, or glob pattern being watched
* @param {Number=} depth at any depth > 0, this isn't a glob
* @returns {WatchHelper} object containing helpers for this path
*/
_getWatchHelpers
(
path
,
depth
)
{
const
watchPath
=
depth
||
this
.
options
.
disableGlobbing
||
!
isGlob
(
path
)
?
path
:
globParent
(
path
);
const
follow
=
this
.
options
.
followSymlinks
;
return
new
WatchHelper
(
path
,
watchPath
,
follow
,
this
);
}
// Directory helpers
// -----------------
/**
* Provides directory tracking objects
* @param {String} directory path of the directory
* @returns {DirEntry} the directory's tracking object
*/
_getWatchedDir
(
directory
)
{
if
(
!
this
.
_boundRemove
)
this
.
_boundRemove
=
this
.
_remove
.
bind
(
this
);
const
dir
=
sysPath
.
resolve
(
directory
);
if
(
!
this
.
_watched
.
has
(
dir
))
this
.
_watched
.
set
(
dir
,
new
DirEntry
(
dir
,
this
.
_boundRemove
));
return
this
.
_watched
.
get
(
dir
);
}
// File helpers
// ------------
/**
* Check for read permissions.
* Based on this answer on SO: https://stackoverflow.com/a/11781404/1358405
* @param {fs.Stats} stats - object, result of fs_stat
* @returns {Boolean} indicates whether the file can be read
*/
_hasReadPermissions
(
stats
)
{
if
(
this
.
options
.
ignorePermissionErrors
)
return
true
;
// stats.mode may be bigint
const
md
=
stats
&&
Number
.
parseInt
(
stats
.
mode
,
10
);
const
st
=
md
&
0
o777
;
const
it
=
Number
.
parseInt
(
st
.
toString
(
8
)[
0
],
10
);
return
Boolean
(
4
&
it
);
}
/**
* Handles emitting unlink events for
* files and directories, and via recursion, for
* files and directories within directories that are unlinked
* @param {String} directory within which the following item is located
* @param {String} item base path of item/directory
* @returns {void}
*/
_remove
(
directory
,
item
,
isDirectory
)
{
// if what is being deleted is a directory, get that directory's paths
// for recursive deleting and cleaning of watched object
// if it is not a directory, nestedDirectoryChildren will be empty array
const
path
=
sysPath
.
join
(
directory
,
item
);
const
fullPath
=
sysPath
.
resolve
(
path
);
isDirectory
=
isDirectory
!=
null
?
isDirectory
:
this
.
_watched
.
has
(
path
)
||
this
.
_watched
.
has
(
fullPath
);
// prevent duplicate handling in case of arriving here nearly simultaneously
// via multiple paths (such as _handleFile and _handleDir)
if
(
!
this
.
_throttle
(
'remove'
,
path
,
100
))
return
;
// if the only watched file is removed, watch for its return
if
(
!
isDirectory
&&
!
this
.
options
.
useFsEvents
&&
this
.
_watched
.
size
===
1
)
{
this
.
add
(
directory
,
item
,
true
);
}
// This will create a new entry in the watched object in either case
// so we got to do the directory check beforehand
const
wp
=
this
.
_getWatchedDir
(
path
);
const
nestedDirectoryChildren
=
wp
.
getChildren
();
// Recursively remove children directories / files.
nestedDirectoryChildren
.
forEach
(
nested
=>
this
.
_remove
(
path
,
nested
));
// Check if item was on the watched list and remove it
const
parent
=
this
.
_getWatchedDir
(
directory
);
const
wasTracked
=
parent
.
has
(
item
);
parent
.
remove
(
item
);
// Fixes issue #1042 -> Relative paths were detected and added as symlinks
// (https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L612),
// but never removed from the map in case the path was deleted.
// This leads to an incorrect state if the path was recreated:
// https://github.com/paulmillr/chokidar/blob/e1753ddbc9571bdc33b4a4af172d52cb6e611c10/lib/nodefs-handler.js#L553
if
(
this
.
_symlinkPaths
.
has
(
fullPath
))
{
this
.
_symlinkPaths
.
delete
(
fullPath
);
}
// If we wait for this file to be fully written, cancel the wait.
let
relPath
=
path
;
if
(
this
.
options
.
cwd
)
relPath
=
sysPath
.
relative
(
this
.
options
.
cwd
,
path
);
if
(
this
.
options
.
awaitWriteFinish
&&
this
.
_pendingWrites
.
has
(
relPath
))
{
const
event
=
this
.
_pendingWrites
.
get
(
relPath
).
cancelWait
();
if
(
event
===
EV_ADD
)
return
;
}
// The Entry will either be a directory that just got removed
// or a bogus entry to a file, in either case we have to remove it
this
.
_watched
.
delete
(
path
);
this
.
_watched
.
delete
(
fullPath
);
const
eventName
=
isDirectory
?
EV_UNLINK_DIR
:
EV_UNLINK
;
if
(
wasTracked
&&
!
this
.
_isIgnored
(
path
))
this
.
_emit
(
eventName
,
path
);
// Avoid conflicts if we later create another file with the same name
if
(
!
this
.
options
.
useFsEvents
)
{
this
.
_closePath
(
path
);
}
}
/**
* Closes all watchers for a path
* @param {Path} path
*/
_closePath
(
path
)
{
this
.
_closeFile
(
path
)
const
dir
=
sysPath
.
dirname
(
path
);
this
.
_getWatchedDir
(
dir
).
remove
(
sysPath
.
basename
(
path
));
}
/**
* Closes only file-specific watchers
* @param {Path} path
*/
_closeFile
(
path
)
{
const
closers
=
this
.
_closers
.
get
(
path
);
if
(
!
closers
)
return
;
closers
.
forEach
(
closer
=>
closer
());
this
.
_closers
.
delete
(
path
);
}
/**
*
* @param {Path} path
* @param {Function} closer
*/
_addPathCloser
(
path
,
closer
)
{
if
(
!
closer
)
return
;
let
list
=
this
.
_closers
.
get
(
path
);
if
(
!
list
)
{
list
=
[];
this
.
_closers
.
set
(
path
,
list
);
}
list
.
push
(
closer
);
}
_readdirp
(
root
,
opts
)
{
if
(
this
.
closed
)
return
;
const
options
=
{
type
:
EV_ALL
,
alwaysStat
:
true
,
lstat
:
true
,
...
opts
};
let
stream
=
readdirp
(
root
,
options
);
this
.
_streams
.
add
(
stream
);
stream
.
once
(
STR_CLOSE
,
()
=>
{
stream
=
undefined
;
});
stream
.
once
(
STR_END
,
()
=>
{
if
(
stream
)
{
this
.
_streams
.
delete
(
stream
);
stream
=
undefined
;
}
});
return
stream
;
}
}
// Export FSWatcher class
exports
.
FSWatcher
=
FSWatcher
;
/**
* Instantiates watcher with paths to be tracked.
* @param {String|Array<String>} paths file/directory paths and/or globs
* @param {Object=} options chokidar opts
* @returns an instance of FSWatcher for chaining.
*/
const
watch
=
(
paths
,
options
)
=>
{
const
watcher
=
new
FSWatcher
(
options
);
watcher
.
add
(
paths
);
return
watcher
;
};
exports
.
watch
=
watch
;
Event Timeline
Log In to Comment