2023-08-27 01:35:51 +02:00
// What is this module called?
module monitor ;
// What does this module require to function?
2016-02-04 15:36:25 +01:00
import core.stdc.errno ;
2020-11-25 20:35:20 +01:00
import core.stdc.stdlib ;
2023-08-27 01:35:51 +02:00
import core.sys.linux.sys.inotify ;
import core.sys.posix.poll ;
import core.sys.posix.unistd ;
import std.algorithm ;
import std.exception ;
import std.file ;
import std.path ;
import std.regex ;
import std.stdio ;
import std.string ;
2023-08-30 22:29:24 +02:00
import std.conv ;
2023-08-27 01:35:51 +02:00
// What other modules that we have created do we need to import?
2017-03-24 22:30:03 +01:00
import config ;
import util ;
2023-08-27 01:35:51 +02:00
import log ;
import clientSideFiltering ;
2015-09-10 23:05:15 +02:00
2023-08-27 01:35:51 +02:00
// Relevant inotify events
2020-05-29 11:30:03 +02:00
private immutable uint32_t mask = IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_MOVE | IN_IGNORED | IN_Q_OVERFLOW ;
2015-09-10 23:05:15 +02:00
2023-08-27 01:35:51 +02:00
class MonitorException : ErrnoException {
@safe this ( string msg , string file = __FILE__ , size_t line = __LINE__ ) {
2015-09-17 00:16:23 +02:00
super ( msg , file , line ) ;
2015-09-10 23:05:15 +02:00
}
}
2023-08-27 01:35:51 +02:00
final class Monitor {
// Class variables
ApplicationConfig appConfig ;
ClientSideFiltering selectiveSync ;
// Are we verbose in logging output
bool verbose = false ;
// skip symbolic links
bool skip_symlinks = false ;
// check for .nosync if enabled
bool check_nosync = false ;
// Configure Private Class Variables
2015-09-10 23:05:15 +02:00
// inotify file descriptor
private int fd ;
2015-09-17 00:16:23 +02:00
// map every inotify watch descriptor to its directory
private string [ int ] wdToDirName ;
2015-09-10 23:05:15 +02:00
// map the inotify cookies of move_from events to their path
2015-09-11 11:02:07 +02:00
private string [ int ] cookieToPath ;
2015-09-10 23:05:15 +02:00
// buffer to receive the inotify events
private void [ ] buffer ;
2018-08-17 00:03:21 +02:00
2023-08-27 01:35:51 +02:00
// Configure function delegates
2015-09-11 18:33:22 +02:00
void delegate ( string path ) onDirCreated ;
void delegate ( string path ) onFileChanged ;
void delegate ( string path ) onDelete ;
void delegate ( string from , string to ) onMove ;
2023-08-27 01:35:51 +02:00
// Configure the class varaible to consume the application configuration including selective sync
this ( ApplicationConfig appConfig , ClientSideFiltering selectiveSync ) {
this . appConfig = appConfig ;
2017-03-24 22:30:03 +01:00
this . selectiveSync = selectiveSync ;
}
2023-08-27 01:35:51 +02:00
// Initialise the monitor class
void initialise ( ) {
// Configure the variables
skip_symlinks = appConfig . getValueBool ( "skip_symlinks" ) ;
check_nosync = appConfig . getValueBool ( "check_nosync" ) ;
if ( appConfig . getValueLong ( "verbose" ) > 0 ) {
verbose = true ;
}
2018-09-13 00:43:29 +02:00
assert ( onDirCreated & & onFileChanged & & onDelete & & onMove ) ;
2015-09-10 23:05:15 +02:00
fd = inotify_init ( ) ;
2018-09-13 00:43:29 +02:00
if ( fd < 0 ) throw new MonitorException ( "inotify_init failed" ) ;
2015-09-17 00:16:23 +02:00
if ( ! buffer ) buffer = new void [ 4096 ] ;
2019-11-02 00:41:18 +01:00
// from which point do we start watching for changes?
string monitorPath ;
2023-08-27 01:35:51 +02:00
if ( appConfig . getValueString ( "single_directory" ) ! = "" ) {
// single directory in use, monitor only this path
monitorPath = "./" ~ appConfig . getValueString ( "single_directory" ) ;
2019-11-02 00:41:18 +01:00
} else {
// default
monitorPath = "." ;
}
addRecursive ( monitorPath ) ;
2015-09-10 23:05:15 +02:00
}
2023-08-27 01:35:51 +02:00
// Shutdown the monitor class
void shutdown ( ) {
2015-09-10 23:05:15 +02:00
if ( fd > 0 ) close ( fd ) ;
2015-09-17 00:16:23 +02:00
wdToDirName = null ;
2015-09-10 23:05:15 +02:00
}
2023-08-27 01:35:51 +02:00
// Recursivly add this path to be monitored
private void addRecursive ( string dirname ) {
2018-12-06 10:28:03 +01:00
// skip non existing/disappeared items
if ( ! exists ( dirname ) ) {
log . vlog ( "Not adding non-existing/disappeared directory: " , dirname ) ;
return ;
}
2020-07-07 09:39:09 +02:00
// Skip the monitoring of any user filtered items
2017-03-14 17:20:18 +01:00
if ( dirname ! = "." ) {
2020-07-07 09:39:09 +02:00
// Is the directory name a match to a skip_dir entry?
// The path that needs to be checked needs to include the '/'
// This due to if the user has specified in skip_dir an exclusive path: '/path' - that is what must be matched
if ( isDir ( dirname ) ) {
if ( selectiveSync . isDirNameExcluded ( dirname . strip ( '.' ) ) ) {
// dont add a watch for this item
log . vdebug ( "Skipping monitoring due to skip_dir match: " , dirname ) ;
return ;
}
2019-03-14 20:55:05 +01:00
}
2020-07-07 09:39:09 +02:00
if ( isFile ( dirname ) ) {
// Is the filename a match to a skip_file entry?
// The path that needs to be checked needs to include the '/'
// This due to if the user has specified in skip_file an exclusive path: '/path/file' - that is what must be matched
if ( selectiveSync . isFileNameExcluded ( dirname . strip ( '.' ) ) ) {
// dont add a watch for this item
log . vdebug ( "Skipping monitoring due to skip_file match: " , dirname ) ;
return ;
}
2017-03-14 17:20:18 +01:00
}
2020-04-06 12:05:06 +02:00
// is the path exluded by sync_list?
2019-10-08 08:34:35 +02:00
if ( selectiveSync . isPathExcludedViaSyncList ( buildNormalizedPath ( dirname ) ) ) {
2020-04-06 12:05:06 +02:00
// dont add a watch for this item
log . vdebug ( "Skipping monitoring due to sync_list match: " , dirname ) ;
2017-03-14 17:20:18 +01:00
return ;
}
}
2020-04-06 12:05:06 +02:00
2018-08-17 00:03:21 +02:00
// skip symlinks if configured
if ( isSymlink ( dirname ) ) {
// if config says so we skip all symlinked items
if ( skip_symlinks ) {
// dont add a watch for this directory
return ;
}
}
2019-03-02 19:58:36 +01:00
// Do we need to check for .nosync? Only if check_nosync is true
if ( check_nosync ) {
if ( exists ( buildNormalizedPath ( dirname ) ~ "/.nosync" ) ) {
log . vlog ( "Skipping watching path - .nosync found & --check-for-nosync enabled: " , buildNormalizedPath ( dirname ) ) ;
return ;
}
}
2020-04-06 12:05:06 +02:00
// passed all potential exclusions
2020-11-25 20:35:20 +01:00
// add inotify watch for this path / directory / file
log . vdebug ( "Calling add() for this dirname: " , dirname ) ;
2017-03-14 17:20:18 +01:00
add ( dirname ) ;
2020-11-25 20:35:20 +01:00
// if this is a directory, recursivly add this path
if ( isDir ( dirname ) ) {
// try and get all the directory entities for this path
try {
auto pathList = dirEntries ( dirname , SpanMode . shallow , false ) ;
foreach ( DirEntry entry ; pathList ) {
if ( entry . isDir ) {
log . vdebug ( "Calling addRecursive() for this directory: " , entry . name ) ;
addRecursive ( entry . name ) ;
}
}
// catch any error which is generated
} catch ( std . file . FileException e ) {
// Standard filesystem error
2020-12-09 04:18:16 +01:00
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-11-25 20:35:20 +01:00
return ;
} catch ( Exception e ) {
// Issue #1154 handling
// Need to check for: Failed to stat file in error message
if ( canFind ( e . msg , "Failed to stat file" ) ) {
// File system access issue
log . error ( "ERROR: The local file system returned an error with the following message:" ) ;
log . error ( " Error Message: " , e . msg ) ;
log . error ( "ACCESS ERROR: Please check your UID and GID access to this file, as the permissions on this file is preventing this application to read it" ) ;
log . error ( "\nFATAL: Exiting application to avoid deleting data due to local file system access issues\n" ) ;
// Must exit here
exit ( - 1 ) ;
} else {
// some other error
2020-12-09 04:18:16 +01:00
displayFileSystemErrorMessage ( e . msg , getFunctionName ! ( { } ) ) ;
2020-11-25 20:35:20 +01:00
return ;
2019-06-15 01:23:32 +02:00
}
2015-09-17 17:34:58 +02:00
}
2015-09-17 00:16:23 +02:00
}
2015-09-10 23:05:15 +02:00
}
2023-08-27 01:35:51 +02:00
// Add this path to be monitored
private void add ( string pathname ) {
2018-09-13 00:43:29 +02:00
int wd = inotify_add_watch ( fd , toStringz ( pathname ) , mask ) ;
if ( wd < 0 ) {
if ( errno ( ) = = ENOSPC ) {
2023-08-30 22:29:24 +02:00
// Get the current value
ulong maxInotifyWatches = to ! int ( strip ( readText ( "/proc/sys/user/max_inotify_watches" ) ) ) ;
2018-09-13 00:43:29 +02:00
log . log ( "The user limit on the total number of inotify watches has been reached." ) ;
2023-08-30 22:29:24 +02:00
log . log ( "Your current limit of inotify watches is: " , maxInotifyWatches ) ;
log . log ( "It is recommended that you change the max number of inotify watches to at least double your existing value." ) ;
log . log ( "To change the current max number of watches to " , ( maxInotifyWatches * 2 ) , " run:" ) ;
log . log ( "EXAMPLE: sudo sysctl fs.inotify.max_user_watches=" , ( maxInotifyWatches * 2 ) ) ;
2018-09-13 00:43:29 +02:00
}
2019-06-15 01:23:32 +02:00
if ( errno ( ) = = 13 ) {
2023-08-27 01:35:51 +02:00
if ( ( selectiveSync . getSkipDotfiles ( ) ) & & ( isDotFile ( pathname ) ) ) {
2020-04-15 06:07:58 +02:00
// no misleading output that we could not add a watch due to permission denied
return ;
} else {
log . vlog ( "WARNING: inotify_add_watch failed - permission denied: " , pathname ) ;
return ;
}
2019-06-15 01:23:32 +02:00
}
// Flag any other errors
log . error ( "ERROR: inotify_add_watch failed: " , pathname ) ;
return ;
2018-09-13 00:43:29 +02:00
}
2020-04-06 12:05:06 +02:00
// Add path to inotify watch - required regardless if a '.folder' or 'folder'
2018-09-13 00:43:29 +02:00
wdToDirName [ wd ] = buildNormalizedPath ( pathname ) ~ "/" ;
2020-11-25 20:35:20 +01:00
log . vdebug ( "inotify_add_watch successfully added for: " , pathname ) ;
2020-04-06 12:05:06 +02:00
// Do we log that we are monitoring this directory?
if ( isDir ( pathname ) ) {
// This is a directory
// is the path exluded if skip_dotfiles configured and path is a .folder?
2023-08-27 01:35:51 +02:00
if ( ( selectiveSync . getSkipDotfiles ( ) ) & & ( isDotFile ( pathname ) ) ) {
2020-04-15 06:07:58 +02:00
// no misleading output that we are monitoring this directory
return ;
2020-04-06 12:05:06 +02:00
}
// Log that this is directory is being monitored
2023-08-27 01:35:51 +02:00
log . vlog ( "Monitoring directory: " , pathname ) ;
2020-04-06 12:05:06 +02:00
}
2015-09-10 23:05:15 +02:00
}
2023-08-27 01:35:51 +02:00
// Remove a watch descriptor
private void remove ( int wd ) {
2015-09-17 00:16:23 +02:00
assert ( wd in wdToDirName ) ;
2015-09-10 23:05:15 +02:00
int ret = inotify_rm_watch ( fd , wd ) ;
2018-09-13 00:43:29 +02:00
if ( ret < 0 ) throw new MonitorException ( "inotify_rm_watch failed" ) ;
2016-08-04 23:35:58 +02:00
log . vlog ( "Monitored directory removed: " , wdToDirName [ wd ] ) ;
2015-09-17 00:16:23 +02:00
wdToDirName . remove ( wd ) ;
2015-09-10 23:05:15 +02:00
}
2023-08-27 01:35:51 +02:00
// Remove the watch descriptors associated to the given path
private void remove ( const ( char ) [ ] path ) {
2015-09-19 14:25:39 +02:00
path ~ = "/" ;
foreach ( wd , dirname ; wdToDirName ) {
if ( dirname . startsWith ( path ) ) {
int ret = inotify_rm_watch ( fd , wd ) ;
2018-09-13 00:43:29 +02:00
if ( ret < 0 ) throw new MonitorException ( "inotify_rm_watch failed" ) ;
2015-09-19 14:25:39 +02:00
wdToDirName . remove ( wd ) ;
2016-08-04 23:35:58 +02:00
log . vlog ( "Monitored directory removed: " , dirname ) ;
2015-09-19 14:25:39 +02:00
}
}
}
2023-08-27 01:35:51 +02:00
// Return the file path from an inotify event
private string getPath ( const ( inotify_event ) * event ) {
2015-09-17 00:16:23 +02:00
string path = wdToDirName [ event . wd ] ;
2015-09-10 23:05:15 +02:00
if ( event . len > 0 ) path ~ = fromStringz ( event . name . ptr ) ;
2020-04-06 12:05:06 +02:00
log . vdebug ( "inotify path event for: " , path ) ;
2015-09-10 23:05:15 +02:00
return path ;
}
2023-08-27 01:35:51 +02:00
// Update
void update ( bool useCallbacks = true ) {
2018-09-13 00:43:29 +02:00
pollfd fds = {
fd : fd ,
events : POLLIN
} ;
2015-09-10 23:05:15 +02:00
2015-09-17 00:16:23 +02:00
while ( true ) {
2018-09-13 00:43:29 +02:00
int ret = poll ( & fds , 1 , 0 ) ;
2015-09-17 00:16:23 +02:00
if ( ret = = - 1 ) throw new MonitorException ( "poll failed" ) ;
else if ( ret = = 0 ) break ; // no events available
2018-09-13 00:43:29 +02:00
size_t length = read ( fd , buffer . ptr , buffer . length ) ;
2015-09-17 00:16:23 +02:00
if ( length = = - 1 ) throw new MonitorException ( "read failed" ) ;
int i = 0 ;
while ( i < length ) {
inotify_event * event = cast ( inotify_event * ) & buffer [ i ] ;
2015-09-19 14:25:39 +02:00
string path ;
2020-07-21 22:54:35 +02:00
string evalPath ;
2020-05-29 11:30:03 +02:00
// inotify event debug
log . vdebug ( "inotify event wd: " , event . wd ) ;
log . vdebug ( "inotify event mask: " , event . mask ) ;
log . vdebug ( "inotify event cookie: " , event . cookie ) ;
log . vdebug ( "inotify event len: " , event . len ) ;
log . vdebug ( "inotify event name: " , event . name ) ;
if ( event . mask & IN_ACCESS ) log . vdebug ( "inotify event flag: IN_ACCESS" ) ;
if ( event . mask & IN_MODIFY ) log . vdebug ( "inotify event flag: IN_MODIFY" ) ;
if ( event . mask & IN_ATTRIB ) log . vdebug ( "inotify event flag: IN_ATTRIB" ) ;
if ( event . mask & IN_CLOSE_WRITE ) log . vdebug ( "inotify event flag: IN_CLOSE_WRITE" ) ;
if ( event . mask & IN_CLOSE_NOWRITE ) log . vdebug ( "inotify event flag: IN_CLOSE_NOWRITE" ) ;
if ( event . mask & IN_MOVED_FROM ) log . vdebug ( "inotify event flag: IN_MOVED_FROM" ) ;
if ( event . mask & IN_MOVED_TO ) log . vdebug ( "inotify event flag: IN_MOVED_TO" ) ;
if ( event . mask & IN_CREATE ) log . vdebug ( "inotify event flag: IN_CREATE" ) ;
if ( event . mask & IN_DELETE ) log . vdebug ( "inotify event flag: IN_DELETE" ) ;
if ( event . mask & IN_DELETE_SELF ) log . vdebug ( "inotify event flag: IN_DELETE_SELF" ) ;
if ( event . mask & IN_MOVE_SELF ) log . vdebug ( "inotify event flag: IN_MOVE_SELF" ) ;
if ( event . mask & IN_UNMOUNT ) log . vdebug ( "inotify event flag: IN_UNMOUNT" ) ;
if ( event . mask & IN_Q_OVERFLOW ) log . vdebug ( "inotify event flag: IN_Q_OVERFLOW" ) ;
if ( event . mask & IN_IGNORED ) log . vdebug ( "inotify event flag: IN_IGNORED" ) ;
if ( event . mask & IN_CLOSE ) log . vdebug ( "inotify event flag: IN_CLOSE" ) ;
if ( event . mask & IN_MOVE ) log . vdebug ( "inotify event flag: IN_MOVE" ) ;
if ( event . mask & IN_ONLYDIR ) log . vdebug ( "inotify event flag: IN_ONLYDIR" ) ;
if ( event . mask & IN_DONT_FOLLOW ) log . vdebug ( "inotify event flag: IN_DONT_FOLLOW" ) ;
if ( event . mask & IN_EXCL_UNLINK ) log . vdebug ( "inotify event flag: IN_EXCL_UNLINK" ) ;
if ( event . mask & IN_MASK_ADD ) log . vdebug ( "inotify event flag: IN_MASK_ADD" ) ;
if ( event . mask & IN_ISDIR ) log . vdebug ( "inotify event flag: IN_ISDIR" ) ;
if ( event . mask & IN_ONESHOT ) log . vdebug ( "inotify event flag: IN_ONESHOT" ) ;
if ( event . mask & IN_ALL_EVENTS ) log . vdebug ( "inotify event flag: IN_ALL_EVENTS" ) ;
// skip events that need to be ignored
2015-09-17 00:16:23 +02:00
if ( event . mask & IN_IGNORED ) {
// forget the directory associated to the watch descriptor
wdToDirName . remove ( event . wd ) ;
2015-09-18 22:56:09 +02:00
goto skip ;
2015-09-17 00:16:23 +02:00
} else if ( event . mask & IN_Q_OVERFLOW ) {
throw new MonitorException ( "Inotify overflow, events missing" ) ;
2015-09-18 22:56:09 +02:00
}
2020-06-16 22:28:55 +02:00
// if the event is not to be ignored, obtain path
path = getPath ( event ) ;
2020-07-21 22:54:35 +02:00
// configure the skip_dir & skip skip_file comparison item
evalPath = path . strip ( '.' ) ;
// Skip events that should be excluded based on application configuration
// We cant use isDir or isFile as this information is missing from the inotify event itself
// Thus this causes a segfault when attempting to query this - https://github.com/abraunegg/onedrive/issues/995
// Based on the 'type' of event & object type (directory or file) check that path against the 'right' user exclusions
// Directory events should only be compared against skip_dir and file events should only be compared against skip_file
if ( event . mask & IN_ISDIR ) {
// The event in question contains IN_ISDIR event mask, thus highly likely this is an event on a directory
2020-07-07 09:39:09 +02:00
// This due to if the user has specified in skip_dir an exclusive path: '/path' - that is what must be matched
2020-07-21 22:54:35 +02:00
if ( selectiveSync . isDirNameExcluded ( evalPath ) ) {
// The path to evaluate matches a path that the user has configured to skip
2020-07-07 09:39:09 +02:00
goto skip ;
}
2020-07-21 22:54:35 +02:00
} else {
// The event in question missing the IN_ISDIR event mask, thus highly likely this is an event on a file
2020-07-07 09:39:09 +02:00
// This due to if the user has specified in skip_file an exclusive path: '/path/file' - that is what must be matched
2020-07-21 22:54:35 +02:00
if ( selectiveSync . isFileNameExcluded ( evalPath ) ) {
// The path to evaluate matches a file that the user has configured to skip
2020-07-07 09:39:09 +02:00
goto skip ;
}
2017-03-14 17:20:18 +01:00
}
2020-07-21 22:54:35 +02:00
// is the path, excluded via sync_list
2019-10-08 08:34:35 +02:00
if ( selectiveSync . isPathExcludedViaSyncList ( path ) ) {
2020-07-21 22:54:35 +02:00
// The path to evaluate matches a directory or file that the user has configured not to include in the sync
2017-03-14 17:20:18 +01:00
goto skip ;
2015-09-18 22:56:09 +02:00
}
2020-04-06 12:05:06 +02:00
2020-07-21 22:54:35 +02:00
// handle the inotify events
2015-09-18 22:56:09 +02:00
if ( event . mask & IN_MOVED_FROM ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event IN_MOVED_FROM: " , path ) ;
2015-09-17 00:16:23 +02:00
cookieToPath [ event . cookie ] = path ;
} else if ( event . mask & IN_MOVED_TO ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event IN_MOVED_TO: " , path ) ;
2015-09-17 00:16:23 +02:00
if ( event . mask & IN_ISDIR ) addRecursive ( path ) ;
auto from = event . cookie in cookieToPath ;
if ( from ) {
cookieToPath . remove ( event . cookie ) ;
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onMove ( * from , path ) ;
2015-09-11 11:02:07 +02:00
} else {
2015-09-17 00:16:23 +02:00
// item moved from the outside
if ( event . mask & IN_ISDIR ) {
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onDirCreated ( path ) ;
2015-09-17 00:16:23 +02:00
} else {
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onFileChanged ( path ) ;
2015-09-17 00:16:23 +02:00
}
2015-09-11 11:02:07 +02:00
}
2015-09-17 00:16:23 +02:00
} else if ( event . mask & IN_CREATE ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event IN_CREATE: " , path ) ;
2015-09-17 00:16:23 +02:00
if ( event . mask & IN_ISDIR ) {
2015-09-11 11:02:07 +02:00
addRecursive ( path ) ;
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onDirCreated ( path ) ;
2015-09-10 23:05:15 +02:00
}
2015-09-17 00:16:23 +02:00
} else if ( event . mask & IN_DELETE ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event IN_DELETE: " , path ) ;
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onDelete ( path ) ;
2018-09-13 00:43:29 +02:00
} else if ( ( event . mask & IN_CLOSE_WRITE ) & & ! ( event . mask & IN_ISDIR ) ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event IN_CLOSE_WRITE and ...: " , path ) ;
2018-09-13 00:43:29 +02:00
if ( useCallbacks ) onFileChanged ( path ) ;
2015-09-17 00:16:23 +02:00
} else {
2018-12-07 19:01:22 +01:00
log . vdebug ( "event unhandled: " , path ) ;
2018-09-13 00:43:29 +02:00
assert ( 0 ) ;
2015-09-10 23:05:15 +02:00
}
2015-09-18 22:56:09 +02:00
skip :
2015-09-17 00:16:23 +02:00
i + = inotify_event . sizeof + event . len ;
}
2018-09-13 00:43:29 +02:00
// assume that the items moved outside the watched directory have been deleted
2015-09-17 00:16:23 +02:00
foreach ( cookie , path ; cookieToPath ) {
2018-12-07 19:01:22 +01:00
log . vdebug ( "deleting (post loop): " , path ) ;
2015-10-10 22:18:33 +02:00
if ( useCallbacks ) onDelete ( path ) ;
2015-09-19 14:25:39 +02:00
remove ( path ) ;
2015-09-17 00:16:23 +02:00
cookieToPath . remove ( cookie ) ;
2015-09-10 23:05:15 +02:00
}
2023-08-27 06:22:06 +02:00
2023-08-30 22:29:24 +02:00
log . log ( "inotify events flushed" ) ;
2015-09-10 23:05:15 +02:00
}
}
}