diff --git a/README.md b/README.md index 5720087..f629671 100644 --- a/README.md +++ b/README.md @@ -11,26 +11,60 @@ pip install pyinotifyd ``` * Modify /etc/pyinotifyd/config.py according to your needs. -## Global configuration -The config file is written in Python syntax. pyinotifyd reads config options from a dictionary named **pyinotifyd_config**. -This is the default configuration: +# Configuration +The config file is written in Python syntax. pyinotifyd reads and executes its content, which means you can add custom Python code to the config file. + +To pass config options to pyinotifyd, define a dictionary named **pyinotifyd_config**. +This is the default: ```python pyinotifyd_config = { - # List of watches, description below + # List of watches, see description below "watches": [], - # Set the loglevel (see https://docs.python.org/3/library/logging.html#levels) + + # Loglevel (see https://docs.python.org/3/library/logging.html#levels) "loglevel": logging.INFO, - # Set the timeout to wait for pending tasks to complete during shutdown + + # Timeout to wait for pending tasks to complete during shutdown "shutdown_timeout": 30 } ``` -## Watch configuration -A Watch is defined as a dictionary which contains the config options: + +## Schedulers +pyinotifyd comes with different schedulers to schedule tasks with an optional delay. The advantages of using a scheduler are consistent logging and the possibility to cancel delayed tasks. + +### TaskScheduler +This scheduler is used to run Python functions. + +class TaskScheduler(*job, delay=0, files=True, dirs=False, logname="TaskScheduler"*) + Return a TaskScheduler object configured to call the Python function *job* with a delay of *delay* seconds. Use *files* and *dirs* to define if *job* is called for events on files and/or directories. Log messages with *logname*. + +### ShellScheduler + +## Watches +A Watch is defined as a dictionary. +This is the default: ```python { - "path": "/tmp", - "rec": True, - "auto_add": True, + # path to watch, globbing is allowed + "path": "", + + # set to True to add a watch on each subdirectory + "rec": False, + + # set to True to automatically add watches on newly created directories in watched parent path + "auto_add": False, + + # dictionary which contains the event map, see description below "event_map": {} } ``` + +### Event maps +An event map is defined as a dictionary. It is used to map different event types to Python functions. Those functions are called with the event-object a task-id as positional arguments if an event is received. It is possible to set a list of functions to run multiple tasks on a single event. If an event type is not present in the map or None is given, the event type is ignored. +This is an example: +```python +{ + "IN_CLOSE_NOWRITE": [s1.schedule, s2.schedule], + "IN_CLOSE_WRITE": s1.schedule +} +``` diff --git a/docs/.config.py.swp b/docs/.config.py.swp index 5b59b1c..30fd80e 100644 Binary files a/docs/.config.py.swp and b/docs/.config.py.swp differ diff --git a/pyinotifyd.py b/pyinotifyd.py index 61f0256..523e909 100755 --- a/pyinotifyd.py +++ b/pyinotifyd.py @@ -69,10 +69,14 @@ class Task: class TaskScheduler: def __init__(self, job, delay=0, files=True, dirs=False, logname="TaskScheduler"): - self._delay = delay - self._files = files - self._dirs = dirs + assert callable(job), f"job: expected callable, got {type(job)}" self._job = job + assert isinstance(delay, int), f"delay: expected {type(int)}, got {type(delay)}" + self._delay = delay + assert isinstance(files, bool), f"files: expected {type(bool)}, got {type(files)}" + self._files = files + assert isinstance(dirs, bool), f"dirs: expected {type(bool)}, got {type(dirs)}" + self._dirs = dirs self._tasks = {} self._log = logging.getLogger(logname) @@ -121,6 +125,7 @@ class TaskScheduler: class ShellScheduler(TaskScheduler): def __init__(self, cmd, job=None, logname="ShellScheduler", *args, **kwargs): + assert isinstance(cmd, str), f"cmd: expected {type('')}, got {type(cmd)}" self._cmd = cmd super().__init__(*args, job=self.job, logname=logname, **kwargs) @@ -285,15 +290,19 @@ def main(): print(f"pyinotifyd ({__version__})") sys.exit(0) - cfg = { - "watches": [], - "loglevel": logging.INFO, - "shutdown_timeout": 30} + cfg = {"watches": [], + "loglevel": logging.INFO, + "shutdown_timeout": 30} - cfg_vars = {"pyinotifyd_config": cfg} - with open(args.config, "r") as c: - exec(c.read(), globals(), cfg_vars) - cfg.update(cfg_vars["pyinotifyd_config"]) + try: + cfg_vars = {"pyinotifyd_config": cfg} + with open(args.config, "r") as c: + exec(c.read(), globals(), cfg_vars) + + cfg.update(cfg_vars["pyinotifyd_config"]) + except Exception as e: + print(f"error in config file: {e}") + sys.exit(1) console = logging.StreamHandler() formatter = logging.Formatter( @@ -313,7 +322,15 @@ def main(): wm = pyinotify.WatchManager() loop = asyncio.get_event_loop() notifiers = [] - for watch in cfg["watches"]: + for watchcfg in cfg["watches"]: + watch = {"path": "", + "rec": False, + "auto_add": False, + "event_map": {}} + watch.update(watchcfg) + if not watch["path"]: + continue + mask = False handler = pyinotify.ProcessEvent() for flag, values in watch["event_map"].items(): @@ -350,6 +367,7 @@ def main(): loop.run_until_complete(shutdown(timeout=cfg["shutdown_timeout"])) loop.close() + sys.exit(0)