Package: pyinotifyd
pyinotifydA daemon for monitoring filesystem events with inotify on Linux and run tasks like filesystem operations (copy, move or delete), a shell command or custom async python methods.It is possible to schedule tasks with a delay, which can then be canceled again in case a canceling event occurs. A useful example for this is to run tasks only if a file has not changed within a certain amount of time.pyinotifyd offers great flexibility through its dev-op configuration approach, which enables you to do almost anything you want.RequirementspyinotifyInstallation# install pyinotifyd with pippipinstallpyinotifyd# install service files and configpyinotifyd--install# uninstall service files and unmodified configpyinotifyd--uninstallAutostartThe following init systems are supported.systemd# start the daemon at boot timesystemctlenablepyinotifyd.service# start the daemon immediatelysystemctlstartpyinotifyd.serviceOpenRC (Gentoo)# start the daemon at boot timerc-updateaddpyinotifyddefault# start the daemon immediatelyrc-servicepyinotifydstartConfigurationThe config file/etc/pyinotifyd/config.pyis written in python syntax. pyinotifyd reads and executes its content, that means you can write your custom async python methods directly into the config file.The basic idea is to instantiate one or multiple schedulers and map specific inotify events to schedulers with the help of event maps. Then, watch the given paths for events and run tasks as defined in the event maps.Schedulerspyinotifyd has 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. Furthermore, schedulers have the ability to differentiate between files and directories.TaskSchedulerSchedule a custom python methodjobwith an optionaldelayin seconds. Skip scheduling of tasks for files and/or directories according tofilesanddirsarguments. If there already is a scheduled task, re-schedule it withdelay. Uselognamein log messages. All additional modules, functions and variables that are defined in the config file and are needed within thejob, need to be passed as dictionary to the TaskManager throughglobal_vars. If you want to limit the scheduler to run only one job at a time, setsinglejobto True.All arguments except forjobare optional.# Please note that pyinotifyd uses pythons asyncio for asynchronous task execution.# Do not run anything inside the custom python method that blocks the daemon.## Bad:  time.sleep(10)# Good: await asyncio.sleep(10)importasyncioimportloggingasyncdefcustom_job(event,task_id):awaitasyncio.sleep(10)logging.info(f"{task_id}: execute example task:{event}")task_sched=TaskScheduler(job=custom_job,files=True,dirs=False,delay=0,logname="sched",global_vars=globals(),singlejob=False)ShellSchedulerSchedule a shell commandcmd. Replace{maskname},{pathname}and{src_pathname}incmdwith the actual values of occuring events. This scheduler is based on TaskScheduler and has the same optional arguments.# Please note that **{src_pathname}** is only present for IN_MOVED_TO events and only# in the case where the IN_MOVED_FROM events are watched too.# If it is not present, the command line argument will be an empty string.shell_sched=ShellScheduler(cmd="/usr/local/bin/task.sh{maskname}{pathname}{src_pathname}")FileManagerSchedulerMove, copy or delete files and/or directories following the list ofrules, the first matching rule is executed.This scheduler is based on TaskScheduler and has the same optional arguments.A rule holds anaction(move, copy or delete) and a regular expressionsrc_re. Theactionwill be executed ifsrc_rematches the path of an event. In case whereactionis copy or move, usedst_reas destination path. Subgroups and/or named-subgroups may be used insrc_reanddst_re.Automatically create possibly missing sub-directories ifauto_createis set to True. Set the mode and ownership of moved or copied files/directories and newly created sub-directories tofilemodeanddirmode. Override destination files ifoverrideis set to True.Ifactionis delete, delete non-empty directories ifrecis set to True.move_rule=FileManagerRule(action="move",src_re="^/src_path/(?P<path>.*).to_move$",dst_re="/dst_path/\g<path>",auto_create=False,rec=False,filemode=None,dirmode=None,user=None,group=None,override=False)delete_rule=FileManagerRule(action="delete",src_re="^/src_path/(?P<path>.*).to_delete$",rec=False)file_sched=FileManagerScheduler(rules=[move_rule,delete_rule])Event mapsMap specific events to one or multiple schedulers. Ignore the event if the scheduler is set to None. UseCancelto cancel a scheduled task within a scheduler.This is an example which schedules tasks for newly created files if they are not modified, moved or deleted within the delay time of the scheduler.event_map={"IN_ACCESS":None,"IN_ATTRIB":None,"IN_CLOSE_NOWRITE":None,"IN_CLOSE_WRITE":task_sched,"IN_CREATE":task_sched,"IN_DELETE":Cancel(task_sched),"IN_DELETE_SELF":Cancel(task_sched),"IN_IGNORED":None,"IN_MODIFY":Cancel(task_sched),"IN_MOVE_SELF":None,"IN_MOVED_FROM":Cancel(task_sched),"IN_MOVED_TO":task_sched,"IN_OPEN":None,"IN_Q_OVERFLOW":None,"IN_UNMOUNT":Cancel(task_sched)}# It is possible to instantiate an event map with a default scheduler set for every eventevent_map=EventMap(default_sched=task_sched)The following events are available:IN_ACCESS: a file was accessedIN_ATTRIB: a metadata changedIN_CLOSE_NOWRITE: an unwritable file was closedIN_CLOSE_WRITE: a writable file was closedIN_CREATE: a file/directory was createdIN_DELETE: a file/directory was deletedIN_DELETE_SELF: a watched item itself was deletedIN_IGNORED: raised when a watch is removed, probably useless for youIN_MODIFY: a file was modifiedIN_MOVE_SELF: a watched item was moved, currently its full pathname destination can only be known if its source and destination directories were both watched. Otherwise, the file is still being watched but you cannot rely anymore on the given path attributeevent.pathIN_MOVED_FROM: a file/directory in a watched directory was moved from another specified watched directory. Can trace the full move of an item when IN_MOVED_TO is available too, in this case if the moved item is itself watched, its path will be updated (see IN_MOVE_SELF)IN_MOVED_TO: a file/directory was moved to another specified watched directory (see IN_MOVE_FROM)IN_OPEN: a file was openedIN_Q_OVERFLOW: the event queue overflown. This event is not associated with any watch descriptorIN_UNMOUNT: when backing filesystem was unmounted. Notified to each watch of this filesystemPyinotifydpyinotifyd requires you to define a variable calledpyinotifydwithin the config file, which contains an instance of the Pyinotifyd class. Set the optional list ofwatchesand theshutdown_timeout. Pyinotifyd will waitshutdown_timeoutseconds for pending tasks to complete before shutdown. Uselognamein log messages.pyinotifyd=Pyinotifyd(watches=[],shutdown_timeout=30,logname="daemon")WatchesA watch connects thepathto anevent_map. Automatically add a watch on each sub-directories inpathifrecis set to True. Ifauto_addis True, a watch will be added automatically on newly created sub-directories inpath. All events for paths matching one of the regular expressions inexclude_filterare ignored. If the value ofexclude_filteris a string, it is assumed to be a path to a file from which the list of regular expressions will be read.# Add a watch directly to Pyinotifyd.pyinotifyd.add_watch(path="/src_path",event_map=event_map,rec=False,auto_add=False,exclude_filter=["^/src_path/subpath$"])# Or instantiate and add itw=Watch(path="/src_path",event_map=event_map,rec=False,auto_add=False,exclude_filter=["^/src_path/subpath$"])pyinotifyd.add_watch(watch=w)LoggingPythonsloggingframework is used to log messages (seehttps://docs.python.org/3/howto/logging.html).The following loglevels are available:DEBUGINFOWARNINGERRORCRITICAL# Configure global loglevelsetLoglevel(INFO)# Configure loglevel per logname.setLoglevel(INFO,logname="daemon")SyslogSend log messages to the local syslog server.# Enable logging to local syslog server (/dev/log).# Use *address* to specify a different target.enableSyslog(loglevel=INFO,address="/dev/log")# Enable syslog per lognameenableSyslog(lglevel=INFO,name="daemon")ExamplesSchedule python method for all events on files and directoriesimportloggingasyncdefcustom_job(event,task_id):logging.info(f"{task_id}: execute example task:{event}")task_sched=TaskScheduler(job=custom_job,files=True,dirs=True)event_map=EventMap(default_sched=task_sched)pyinotifyd=Pyinotifyd()pyinotifyd.add_watch(path="/src_path",event_map=event_map,rec=True,auto_add=True)Schedule shell commands for specific events on filesshell_sched=ShellScheduler(cmd="/usr/local/sbin/task.sh{pathname}",files=True,dirs=False)event_map={"IN_WRITE_CLOSE":shell_sched}pyinotifyd=Pyinotifyd()pyinotifyd.add_watch(path="/src_path",event_map=event_map,rec=True,auto_add=True)Move, copy or delete newly created files after a delaymove_rule=FileManagerRule(action="move",src_re="^/src_path/(?P<path>.*)\.to_move$",dst_re="/dst_path/\g<path>",auto_create=True,filemode=0o644,dirmode=0o755)copy_rule=FileManagerRule(action="copy",src_re="^/src_path/(?P<path>.*)\.to_copy$",dst_re="/dst_path/\g<path>",auto_create=True,filemode=0o644,dirmode=0o755)delete_rule=FileManagerRule(action="delete",src_re="^/src_path/(?P<path>.*)\.to_delete$",rec=False)file_sched=FileManagerScheduler(rules=[move_rule,copy_rule,delete_rule],delay=60,files=True,dirs=False)event_map={"IN_CLOSE_WRITE":file_sched,"IN_CREATE":file_sched,"IN_DELETE":Cancel(file_sched),"IN_DELETE_SELF":Cancel(file_sched),"IN_MODIFY":Cancel(file_sched),"IN_MOVED_FROM":Cancel(file_sched),"IN_MOVED_TO":file_sched,"IN_UNMOUNT":Cancel(file_sched)}# Please note that the shutdown timeout should be greater than the greatest scheduler delay,# otherwise pending tasks may get cancelled during shutdown.pyinotifyd=Pyinotifyd(shutdown_timeout=35)pyinotifyd.add_watch(path="/src_path",event_map=event_map,rec=True,auto_add=True)