2024-04-20 01:43:52 +00:00
|
|
|
import logging
|
|
|
|
from pathlib import Path
|
2024-04-27 23:33:08 +00:00
|
|
|
from typing import Callable
|
2024-04-20 01:43:52 +00:00
|
|
|
|
|
|
|
from execlog.router import Router
|
2024-04-27 23:33:08 +00:00
|
|
|
from execlog.event import FileEvent
|
2024-04-20 01:43:52 +00:00
|
|
|
from execlog.util.path import glob_match
|
2024-04-27 23:33:08 +00:00
|
|
|
from execlog.listeners.path import PathListener
|
2024-04-20 01:43:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2024-04-27 23:33:08 +00:00
|
|
|
class PathRouter(Router[FileEvent]):
|
|
|
|
listener_cls = PathListener
|
|
|
|
|
2024-04-20 01:43:52 +00:00
|
|
|
def register(
|
|
|
|
self,
|
2024-04-27 23:33:08 +00:00
|
|
|
path : Path,
|
|
|
|
func : Callable,
|
|
|
|
glob : str = '**/!(.*|*.tmp|*~)', # recursive, non-temp
|
|
|
|
debounce : int|float = 200,
|
|
|
|
delay : int|float = 30,
|
2024-04-20 01:43:52 +00:00
|
|
|
**listener_kwargs,
|
|
|
|
):
|
|
|
|
'''
|
|
|
|
Parameters:
|
|
|
|
path: Path (directory) to watch with `inotify`
|
|
|
|
func: Callback to run if FS event target matches glob
|
|
|
|
glob: Relative glob pattern to match files in provided path. The FS event's
|
|
|
|
filename must match this pattern for the callback to queued. (Default:
|
|
|
|
"*"; matching all files in path).
|
2024-04-27 23:33:08 +00:00
|
|
|
debounce:
|
|
|
|
delay:
|
2024-04-20 01:43:52 +00:00
|
|
|
listener_kwargs: Additional params for associated listener "listen" routes.
|
|
|
|
See `PathListener.listen`.
|
|
|
|
'''
|
|
|
|
super().register(
|
|
|
|
#endpoint=Path(path),
|
|
|
|
endpoint=path,
|
|
|
|
callback=func,
|
|
|
|
pattern=glob,
|
|
|
|
debounce=debounce,
|
|
|
|
delay=delay,
|
|
|
|
**listener_kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
def filter(self, event, glob, **listen_kwargs) -> bool:
|
|
|
|
'''
|
|
|
|
Note:
|
|
|
|
If `handle_events` is called externally, note that this loop will block in the
|
|
|
|
calling thread until the jobs have been submitted. It will _not_ block until
|
|
|
|
jobs have completed, however, as a list of futures is returned. The calling
|
|
|
|
Watcher instance may have already been started, in which case `run()` will
|
|
|
|
already be executing in a separate thread. Calling this method externally will
|
|
|
|
not interfere with this loop insofar as it adds jobs to the same thread pool.
|
|
|
|
|
|
|
|
Because this method only submits jobs associated with the provided `events`,
|
|
|
|
the calling thread can await the returned list of futures and be confident
|
|
|
|
that top-level callbacks associated with these file events have completed. Do
|
|
|
|
note that, if the Watcher has already been started, any propagating file
|
|
|
|
events will be picked up and possibly process simultaneously (although their
|
|
|
|
associated callbacks will have nothing to do with the return list of futures).
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
event : Event instance
|
|
|
|
glob : Single string or tuple of glob patterns to check against event endpoint
|
|
|
|
'''
|
|
|
|
not_tmp_glob = '**/!(.*|*.tmp|*~)'
|
|
|
|
if not glob_match(Path(event.name), not_tmp_glob):
|
|
|
|
return False
|
|
|
|
|
|
|
|
listen_flags = listen_kwargs.get('flags')
|
|
|
|
# only filter by flags if explicitly specified on registry
|
|
|
|
# (o/w route likely just wanting to use defaults)
|
|
|
|
if listen_flags is not None:
|
|
|
|
# negative filter if action not one of the listened flags
|
|
|
|
if not any(flag & listen_flags for flag in event.action):
|
|
|
|
logger.debug(
|
|
|
|
f'Event [{event.name}] caught in flag filter under [{glob}] for action [{event.action}]'
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return glob_match(Path(event.name), glob)
|