1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# pyinotify.py - python interface to inotify 3*4882a593Smuzhiyun# Copyright (c) 2005-2015 Sebastien Martini <seb@dbzteam.org> 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: MIT 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun""" 8*4882a593Smuzhiyunpyinotify 9*4882a593Smuzhiyun 10*4882a593Smuzhiyun@author: Sebastien Martini 11*4882a593Smuzhiyun@license: MIT License 12*4882a593Smuzhiyun@contact: seb@dbzteam.org 13*4882a593Smuzhiyun""" 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunclass PyinotifyError(Exception): 16*4882a593Smuzhiyun """Indicates exceptions raised by a Pyinotify class.""" 17*4882a593Smuzhiyun pass 18*4882a593Smuzhiyun 19*4882a593Smuzhiyun 20*4882a593Smuzhiyunclass UnsupportedPythonVersionError(PyinotifyError): 21*4882a593Smuzhiyun """ 22*4882a593Smuzhiyun Raised on unsupported Python versions. 23*4882a593Smuzhiyun """ 24*4882a593Smuzhiyun def __init__(self, version): 25*4882a593Smuzhiyun """ 26*4882a593Smuzhiyun @param version: Current Python version 27*4882a593Smuzhiyun @type version: string 28*4882a593Smuzhiyun """ 29*4882a593Smuzhiyun PyinotifyError.__init__(self, 30*4882a593Smuzhiyun ('Python %s is unsupported, requires ' 31*4882a593Smuzhiyun 'at least Python 3.0') % version) 32*4882a593Smuzhiyun 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun# Check Python version 35*4882a593Smuzhiyunimport sys 36*4882a593Smuzhiyunif sys.version_info < (3, 0): 37*4882a593Smuzhiyun raise UnsupportedPythonVersionError(sys.version) 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun 40*4882a593Smuzhiyun# Import directives 41*4882a593Smuzhiyunimport threading 42*4882a593Smuzhiyunimport os 43*4882a593Smuzhiyunimport select 44*4882a593Smuzhiyunimport struct 45*4882a593Smuzhiyunimport fcntl 46*4882a593Smuzhiyunimport errno 47*4882a593Smuzhiyunimport termios 48*4882a593Smuzhiyunimport array 49*4882a593Smuzhiyunimport logging 50*4882a593Smuzhiyunimport atexit 51*4882a593Smuzhiyunfrom collections import deque 52*4882a593Smuzhiyunfrom datetime import datetime, timedelta 53*4882a593Smuzhiyunimport time 54*4882a593Smuzhiyunimport re 55*4882a593Smuzhiyunimport glob 56*4882a593Smuzhiyunimport locale 57*4882a593Smuzhiyunimport subprocess 58*4882a593Smuzhiyun 59*4882a593Smuzhiyuntry: 60*4882a593Smuzhiyun from functools import reduce 61*4882a593Smuzhiyunexcept ImportError: 62*4882a593Smuzhiyun pass # Will fail on Python 2.4 which has reduce() builtin anyway. 63*4882a593Smuzhiyun 64*4882a593Smuzhiyuntry: 65*4882a593Smuzhiyun import ctypes 66*4882a593Smuzhiyun import ctypes.util 67*4882a593Smuzhiyunexcept ImportError: 68*4882a593Smuzhiyun ctypes = None 69*4882a593Smuzhiyun 70*4882a593Smuzhiyuntry: 71*4882a593Smuzhiyun import inotify_syscalls 72*4882a593Smuzhiyunexcept ImportError: 73*4882a593Smuzhiyun inotify_syscalls = None 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun__author__ = "seb@dbzteam.org (Sebastien Martini)" 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun__version__ = "0.9.6" 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun# Compatibity mode: set to True to improve compatibility with 82*4882a593Smuzhiyun# Pyinotify 0.7.1. Do not set this variable yourself, call the 83*4882a593Smuzhiyun# function compatibility_mode() instead. 84*4882a593SmuzhiyunCOMPATIBILITY_MODE = False 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun 87*4882a593Smuzhiyunclass InotifyBindingNotFoundError(PyinotifyError): 88*4882a593Smuzhiyun """ 89*4882a593Smuzhiyun Raised when no inotify support couldn't be found. 90*4882a593Smuzhiyun """ 91*4882a593Smuzhiyun def __init__(self): 92*4882a593Smuzhiyun err = "Couldn't find any inotify binding" 93*4882a593Smuzhiyun PyinotifyError.__init__(self, err) 94*4882a593Smuzhiyun 95*4882a593Smuzhiyun 96*4882a593Smuzhiyunclass INotifyWrapper: 97*4882a593Smuzhiyun """ 98*4882a593Smuzhiyun Abstract class wrapping access to inotify's functions. This is an 99*4882a593Smuzhiyun internal class. 100*4882a593Smuzhiyun """ 101*4882a593Smuzhiyun @staticmethod 102*4882a593Smuzhiyun def create(): 103*4882a593Smuzhiyun """ 104*4882a593Smuzhiyun Factory method instanciating and returning the right wrapper. 105*4882a593Smuzhiyun """ 106*4882a593Smuzhiyun # First, try to use ctypes. 107*4882a593Smuzhiyun if ctypes: 108*4882a593Smuzhiyun inotify = _CtypesLibcINotifyWrapper() 109*4882a593Smuzhiyun if inotify.init(): 110*4882a593Smuzhiyun return inotify 111*4882a593Smuzhiyun # Second, see if C extension is compiled. 112*4882a593Smuzhiyun if inotify_syscalls: 113*4882a593Smuzhiyun inotify = _INotifySyscallsWrapper() 114*4882a593Smuzhiyun if inotify.init(): 115*4882a593Smuzhiyun return inotify 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun def get_errno(self): 118*4882a593Smuzhiyun """ 119*4882a593Smuzhiyun Return None is no errno code is available. 120*4882a593Smuzhiyun """ 121*4882a593Smuzhiyun return self._get_errno() 122*4882a593Smuzhiyun 123*4882a593Smuzhiyun def str_errno(self): 124*4882a593Smuzhiyun code = self.get_errno() 125*4882a593Smuzhiyun if code is None: 126*4882a593Smuzhiyun return 'Errno: no errno support' 127*4882a593Smuzhiyun return 'Errno=%s (%s)' % (os.strerror(code), errno.errorcode[code]) 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun def inotify_init(self): 130*4882a593Smuzhiyun return self._inotify_init() 131*4882a593Smuzhiyun 132*4882a593Smuzhiyun def inotify_add_watch(self, fd, pathname, mask): 133*4882a593Smuzhiyun # Unicode strings must be encoded to string prior to calling this 134*4882a593Smuzhiyun # method. 135*4882a593Smuzhiyun assert isinstance(pathname, str) 136*4882a593Smuzhiyun return self._inotify_add_watch(fd, pathname, mask) 137*4882a593Smuzhiyun 138*4882a593Smuzhiyun def inotify_rm_watch(self, fd, wd): 139*4882a593Smuzhiyun return self._inotify_rm_watch(fd, wd) 140*4882a593Smuzhiyun 141*4882a593Smuzhiyun 142*4882a593Smuzhiyunclass _INotifySyscallsWrapper(INotifyWrapper): 143*4882a593Smuzhiyun def __init__(self): 144*4882a593Smuzhiyun # Stores the last errno value. 145*4882a593Smuzhiyun self._last_errno = None 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun def init(self): 148*4882a593Smuzhiyun assert inotify_syscalls 149*4882a593Smuzhiyun return True 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun def _get_errno(self): 152*4882a593Smuzhiyun return self._last_errno 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun def _inotify_init(self): 155*4882a593Smuzhiyun try: 156*4882a593Smuzhiyun fd = inotify_syscalls.inotify_init() 157*4882a593Smuzhiyun except IOError as err: 158*4882a593Smuzhiyun self._last_errno = err.errno 159*4882a593Smuzhiyun return -1 160*4882a593Smuzhiyun return fd 161*4882a593Smuzhiyun 162*4882a593Smuzhiyun def _inotify_add_watch(self, fd, pathname, mask): 163*4882a593Smuzhiyun try: 164*4882a593Smuzhiyun wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask) 165*4882a593Smuzhiyun except IOError as err: 166*4882a593Smuzhiyun self._last_errno = err.errno 167*4882a593Smuzhiyun return -1 168*4882a593Smuzhiyun return wd 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun def _inotify_rm_watch(self, fd, wd): 171*4882a593Smuzhiyun try: 172*4882a593Smuzhiyun ret = inotify_syscalls.inotify_rm_watch(fd, wd) 173*4882a593Smuzhiyun except IOError as err: 174*4882a593Smuzhiyun self._last_errno = err.errno 175*4882a593Smuzhiyun return -1 176*4882a593Smuzhiyun return ret 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun 179*4882a593Smuzhiyunclass _CtypesLibcINotifyWrapper(INotifyWrapper): 180*4882a593Smuzhiyun def __init__(self): 181*4882a593Smuzhiyun self._libc = None 182*4882a593Smuzhiyun self._get_errno_func = None 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun def init(self): 185*4882a593Smuzhiyun assert ctypes 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun try_libc_name = 'c' 188*4882a593Smuzhiyun if sys.platform.startswith('freebsd'): 189*4882a593Smuzhiyun try_libc_name = 'inotify' 190*4882a593Smuzhiyun 191*4882a593Smuzhiyun libc_name = None 192*4882a593Smuzhiyun try: 193*4882a593Smuzhiyun libc_name = ctypes.util.find_library(try_libc_name) 194*4882a593Smuzhiyun except (OSError, IOError): 195*4882a593Smuzhiyun pass # Will attemp to load it with None anyway. 196*4882a593Smuzhiyun 197*4882a593Smuzhiyun self._libc = ctypes.CDLL(libc_name, use_errno=True) 198*4882a593Smuzhiyun self._get_errno_func = ctypes.get_errno 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun # Eventually check that libc has needed inotify bindings. 201*4882a593Smuzhiyun if (not hasattr(self._libc, 'inotify_init') or 202*4882a593Smuzhiyun not hasattr(self._libc, 'inotify_add_watch') or 203*4882a593Smuzhiyun not hasattr(self._libc, 'inotify_rm_watch')): 204*4882a593Smuzhiyun return False 205*4882a593Smuzhiyun 206*4882a593Smuzhiyun self._libc.inotify_init.argtypes = [] 207*4882a593Smuzhiyun self._libc.inotify_init.restype = ctypes.c_int 208*4882a593Smuzhiyun self._libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, 209*4882a593Smuzhiyun ctypes.c_uint32] 210*4882a593Smuzhiyun self._libc.inotify_add_watch.restype = ctypes.c_int 211*4882a593Smuzhiyun self._libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int] 212*4882a593Smuzhiyun self._libc.inotify_rm_watch.restype = ctypes.c_int 213*4882a593Smuzhiyun return True 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun def _get_errno(self): 216*4882a593Smuzhiyun assert self._get_errno_func 217*4882a593Smuzhiyun return self._get_errno_func() 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun def _inotify_init(self): 220*4882a593Smuzhiyun assert self._libc is not None 221*4882a593Smuzhiyun return self._libc.inotify_init() 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun def _inotify_add_watch(self, fd, pathname, mask): 224*4882a593Smuzhiyun assert self._libc is not None 225*4882a593Smuzhiyun # Encodes path to a bytes string. This conversion seems required because 226*4882a593Smuzhiyun # ctypes.create_string_buffer seems to manipulate bytes internally. 227*4882a593Smuzhiyun # Moreover it seems that inotify_add_watch does not work very well when 228*4882a593Smuzhiyun # it receives an ctypes.create_unicode_buffer instance as argument. 229*4882a593Smuzhiyun pathname = pathname.encode(sys.getfilesystemencoding()) 230*4882a593Smuzhiyun pathname = ctypes.create_string_buffer(pathname) 231*4882a593Smuzhiyun return self._libc.inotify_add_watch(fd, pathname, mask) 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun def _inotify_rm_watch(self, fd, wd): 234*4882a593Smuzhiyun assert self._libc is not None 235*4882a593Smuzhiyun return self._libc.inotify_rm_watch(fd, wd) 236*4882a593Smuzhiyun 237*4882a593Smuzhiyun 238*4882a593Smuzhiyun# Logging 239*4882a593Smuzhiyundef logger_init(): 240*4882a593Smuzhiyun """Initialize logger instance.""" 241*4882a593Smuzhiyun log = logging.getLogger("pyinotify") 242*4882a593Smuzhiyun console_handler = logging.StreamHandler() 243*4882a593Smuzhiyun console_handler.setFormatter( 244*4882a593Smuzhiyun logging.Formatter("[%(asctime)s %(name)s %(levelname)s] %(message)s")) 245*4882a593Smuzhiyun log.addHandler(console_handler) 246*4882a593Smuzhiyun log.setLevel(20) 247*4882a593Smuzhiyun return log 248*4882a593Smuzhiyun 249*4882a593Smuzhiyunlog = logger_init() 250*4882a593Smuzhiyun 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun# inotify's variables 253*4882a593Smuzhiyunclass ProcINotify: 254*4882a593Smuzhiyun """ 255*4882a593Smuzhiyun Access (read, write) inotify's variables through /proc/sys/. Note that 256*4882a593Smuzhiyun usually it requires administrator rights to update them. 257*4882a593Smuzhiyun 258*4882a593Smuzhiyun Examples: 259*4882a593Smuzhiyun - Read max_queued_events attribute: myvar = max_queued_events.value 260*4882a593Smuzhiyun - Update max_queued_events attribute: max_queued_events.value = 42 261*4882a593Smuzhiyun """ 262*4882a593Smuzhiyun def __init__(self, attr): 263*4882a593Smuzhiyun self._base = "/proc/sys/fs/inotify" 264*4882a593Smuzhiyun self._attr = attr 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun def get_val(self): 267*4882a593Smuzhiyun """ 268*4882a593Smuzhiyun Gets attribute's value. 269*4882a593Smuzhiyun 270*4882a593Smuzhiyun @return: stored value. 271*4882a593Smuzhiyun @rtype: int 272*4882a593Smuzhiyun @raise IOError: if corresponding file in /proc/sys cannot be read. 273*4882a593Smuzhiyun """ 274*4882a593Smuzhiyun with open(os.path.join(self._base, self._attr), 'r') as file_obj: 275*4882a593Smuzhiyun return int(file_obj.readline()) 276*4882a593Smuzhiyun 277*4882a593Smuzhiyun def set_val(self, nval): 278*4882a593Smuzhiyun """ 279*4882a593Smuzhiyun Sets new attribute's value. 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun @param nval: replaces current value by nval. 282*4882a593Smuzhiyun @type nval: int 283*4882a593Smuzhiyun @raise IOError: if corresponding file in /proc/sys cannot be written. 284*4882a593Smuzhiyun """ 285*4882a593Smuzhiyun with open(os.path.join(self._base, self._attr), 'w') as file_obj: 286*4882a593Smuzhiyun file_obj.write(str(nval) + '\n') 287*4882a593Smuzhiyun 288*4882a593Smuzhiyun value = property(get_val, set_val) 289*4882a593Smuzhiyun 290*4882a593Smuzhiyun def __repr__(self): 291*4882a593Smuzhiyun return '<%s=%d>' % (self._attr, self.get_val()) 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun 294*4882a593Smuzhiyun# Inotify's variables 295*4882a593Smuzhiyun# 296*4882a593Smuzhiyun# Note: may raise IOError if the corresponding value in /proc/sys 297*4882a593Smuzhiyun# cannot be accessed. 298*4882a593Smuzhiyun# 299*4882a593Smuzhiyun# Examples: 300*4882a593Smuzhiyun# - read: myvar = max_queued_events.value 301*4882a593Smuzhiyun# - update: max_queued_events.value = 42 302*4882a593Smuzhiyun# 303*4882a593Smuzhiyunfor attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): 304*4882a593Smuzhiyun globals()[attrname] = ProcINotify(attrname) 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun 307*4882a593Smuzhiyunclass EventsCodes: 308*4882a593Smuzhiyun """ 309*4882a593Smuzhiyun Set of codes corresponding to each kind of events. 310*4882a593Smuzhiyun Some of these flags are used to communicate with inotify, whereas 311*4882a593Smuzhiyun the others are sent to userspace by inotify notifying some events. 312*4882a593Smuzhiyun 313*4882a593Smuzhiyun @cvar IN_ACCESS: File was accessed. 314*4882a593Smuzhiyun @type IN_ACCESS: int 315*4882a593Smuzhiyun @cvar IN_MODIFY: File was modified. 316*4882a593Smuzhiyun @type IN_MODIFY: int 317*4882a593Smuzhiyun @cvar IN_ATTRIB: Metadata changed. 318*4882a593Smuzhiyun @type IN_ATTRIB: int 319*4882a593Smuzhiyun @cvar IN_CLOSE_WRITE: Writtable file was closed. 320*4882a593Smuzhiyun @type IN_CLOSE_WRITE: int 321*4882a593Smuzhiyun @cvar IN_CLOSE_NOWRITE: Unwrittable file closed. 322*4882a593Smuzhiyun @type IN_CLOSE_NOWRITE: int 323*4882a593Smuzhiyun @cvar IN_OPEN: File was opened. 324*4882a593Smuzhiyun @type IN_OPEN: int 325*4882a593Smuzhiyun @cvar IN_MOVED_FROM: File was moved from X. 326*4882a593Smuzhiyun @type IN_MOVED_FROM: int 327*4882a593Smuzhiyun @cvar IN_MOVED_TO: File was moved to Y. 328*4882a593Smuzhiyun @type IN_MOVED_TO: int 329*4882a593Smuzhiyun @cvar IN_CREATE: Subfile was created. 330*4882a593Smuzhiyun @type IN_CREATE: int 331*4882a593Smuzhiyun @cvar IN_DELETE: Subfile was deleted. 332*4882a593Smuzhiyun @type IN_DELETE: int 333*4882a593Smuzhiyun @cvar IN_DELETE_SELF: Self (watched item itself) was deleted. 334*4882a593Smuzhiyun @type IN_DELETE_SELF: int 335*4882a593Smuzhiyun @cvar IN_MOVE_SELF: Self (watched item itself) was moved. 336*4882a593Smuzhiyun @type IN_MOVE_SELF: int 337*4882a593Smuzhiyun @cvar IN_UNMOUNT: Backing fs was unmounted. 338*4882a593Smuzhiyun @type IN_UNMOUNT: int 339*4882a593Smuzhiyun @cvar IN_Q_OVERFLOW: Event queued overflowed. 340*4882a593Smuzhiyun @type IN_Q_OVERFLOW: int 341*4882a593Smuzhiyun @cvar IN_IGNORED: File was ignored. 342*4882a593Smuzhiyun @type IN_IGNORED: int 343*4882a593Smuzhiyun @cvar IN_ONLYDIR: only watch the path if it is a directory (new 344*4882a593Smuzhiyun in kernel 2.6.15). 345*4882a593Smuzhiyun @type IN_ONLYDIR: int 346*4882a593Smuzhiyun @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15). 347*4882a593Smuzhiyun IN_ONLYDIR we can make sure that we don't watch 348*4882a593Smuzhiyun the target of symlinks. 349*4882a593Smuzhiyun @type IN_DONT_FOLLOW: int 350*4882a593Smuzhiyun @cvar IN_EXCL_UNLINK: Events are not generated for children after they 351*4882a593Smuzhiyun have been unlinked from the watched directory. 352*4882a593Smuzhiyun (new in kernel 2.6.36). 353*4882a593Smuzhiyun @type IN_EXCL_UNLINK: int 354*4882a593Smuzhiyun @cvar IN_MASK_ADD: add to the mask of an already existing watch (new 355*4882a593Smuzhiyun in kernel 2.6.14). 356*4882a593Smuzhiyun @type IN_MASK_ADD: int 357*4882a593Smuzhiyun @cvar IN_ISDIR: Event occurred against dir. 358*4882a593Smuzhiyun @type IN_ISDIR: int 359*4882a593Smuzhiyun @cvar IN_ONESHOT: Only send event once. 360*4882a593Smuzhiyun @type IN_ONESHOT: int 361*4882a593Smuzhiyun @cvar ALL_EVENTS: Alias for considering all of the events. 362*4882a593Smuzhiyun @type ALL_EVENTS: int 363*4882a593Smuzhiyun """ 364*4882a593Smuzhiyun 365*4882a593Smuzhiyun # The idea here is 'configuration-as-code' - this way, we get our nice class 366*4882a593Smuzhiyun # constants, but we also get nice human-friendly text mappings to do lookups 367*4882a593Smuzhiyun # against as well, for free: 368*4882a593Smuzhiyun FLAG_COLLECTIONS = {'OP_FLAGS': { 369*4882a593Smuzhiyun 'IN_ACCESS' : 0x00000001, # File was accessed 370*4882a593Smuzhiyun 'IN_MODIFY' : 0x00000002, # File was modified 371*4882a593Smuzhiyun 'IN_ATTRIB' : 0x00000004, # Metadata changed 372*4882a593Smuzhiyun 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed 373*4882a593Smuzhiyun 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed 374*4882a593Smuzhiyun 'IN_OPEN' : 0x00000020, # File was opened 375*4882a593Smuzhiyun 'IN_MOVED_FROM' : 0x00000040, # File was moved from X 376*4882a593Smuzhiyun 'IN_MOVED_TO' : 0x00000080, # File was moved to Y 377*4882a593Smuzhiyun 'IN_CREATE' : 0x00000100, # Subfile was created 378*4882a593Smuzhiyun 'IN_DELETE' : 0x00000200, # Subfile was deleted 379*4882a593Smuzhiyun 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) 380*4882a593Smuzhiyun # was deleted 381*4882a593Smuzhiyun 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved 382*4882a593Smuzhiyun }, 383*4882a593Smuzhiyun 'EVENT_FLAGS': { 384*4882a593Smuzhiyun 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted 385*4882a593Smuzhiyun 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed 386*4882a593Smuzhiyun 'IN_IGNORED' : 0x00008000, # File was ignored 387*4882a593Smuzhiyun }, 388*4882a593Smuzhiyun 'SPECIAL_FLAGS': { 389*4882a593Smuzhiyun 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a 390*4882a593Smuzhiyun # directory 391*4882a593Smuzhiyun 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink 392*4882a593Smuzhiyun 'IN_EXCL_UNLINK' : 0x04000000, # exclude events on unlinked objects 393*4882a593Smuzhiyun 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already 394*4882a593Smuzhiyun # existing watch 395*4882a593Smuzhiyun 'IN_ISDIR' : 0x40000000, # event occurred against dir 396*4882a593Smuzhiyun 'IN_ONESHOT' : 0x80000000, # only send event once 397*4882a593Smuzhiyun }, 398*4882a593Smuzhiyun } 399*4882a593Smuzhiyun 400*4882a593Smuzhiyun def maskname(mask): 401*4882a593Smuzhiyun """ 402*4882a593Smuzhiyun Returns the event name associated to mask. IN_ISDIR is appended to 403*4882a593Smuzhiyun the result when appropriate. Note: only one event is returned, because 404*4882a593Smuzhiyun only one event can be raised at a given time. 405*4882a593Smuzhiyun 406*4882a593Smuzhiyun @param mask: mask. 407*4882a593Smuzhiyun @type mask: int 408*4882a593Smuzhiyun @return: event name. 409*4882a593Smuzhiyun @rtype: str 410*4882a593Smuzhiyun """ 411*4882a593Smuzhiyun ms = mask 412*4882a593Smuzhiyun name = '%s' 413*4882a593Smuzhiyun if mask & IN_ISDIR: 414*4882a593Smuzhiyun ms = mask - IN_ISDIR 415*4882a593Smuzhiyun name = '%s|IN_ISDIR' 416*4882a593Smuzhiyun return name % EventsCodes.ALL_VALUES[ms] 417*4882a593Smuzhiyun 418*4882a593Smuzhiyun maskname = staticmethod(maskname) 419*4882a593Smuzhiyun 420*4882a593Smuzhiyun 421*4882a593Smuzhiyun# So let's now turn the configuration into code 422*4882a593SmuzhiyunEventsCodes.ALL_FLAGS = {} 423*4882a593SmuzhiyunEventsCodes.ALL_VALUES = {} 424*4882a593Smuzhiyunfor flagc, valc in EventsCodes.FLAG_COLLECTIONS.items(): 425*4882a593Smuzhiyun # Make the collections' members directly accessible through the 426*4882a593Smuzhiyun # class dictionary 427*4882a593Smuzhiyun setattr(EventsCodes, flagc, valc) 428*4882a593Smuzhiyun 429*4882a593Smuzhiyun # Collect all the flags under a common umbrella 430*4882a593Smuzhiyun EventsCodes.ALL_FLAGS.update(valc) 431*4882a593Smuzhiyun 432*4882a593Smuzhiyun # Make the individual masks accessible as 'constants' at globals() scope 433*4882a593Smuzhiyun # and masknames accessible by values. 434*4882a593Smuzhiyun for name, val in valc.items(): 435*4882a593Smuzhiyun globals()[name] = val 436*4882a593Smuzhiyun EventsCodes.ALL_VALUES[val] = name 437*4882a593Smuzhiyun 438*4882a593Smuzhiyun 439*4882a593Smuzhiyun# all 'normal' events 440*4882a593SmuzhiyunALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values()) 441*4882a593SmuzhiyunEventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS 442*4882a593SmuzhiyunEventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS' 443*4882a593Smuzhiyun 444*4882a593Smuzhiyun 445*4882a593Smuzhiyunclass _Event: 446*4882a593Smuzhiyun """ 447*4882a593Smuzhiyun Event structure, represent events raised by the system. This 448*4882a593Smuzhiyun is the base class and should be subclassed. 449*4882a593Smuzhiyun 450*4882a593Smuzhiyun """ 451*4882a593Smuzhiyun def __init__(self, dict_): 452*4882a593Smuzhiyun """ 453*4882a593Smuzhiyun Attach attributes (contained in dict_) to self. 454*4882a593Smuzhiyun 455*4882a593Smuzhiyun @param dict_: Set of attributes. 456*4882a593Smuzhiyun @type dict_: dictionary 457*4882a593Smuzhiyun """ 458*4882a593Smuzhiyun for tpl in dict_.items(): 459*4882a593Smuzhiyun setattr(self, *tpl) 460*4882a593Smuzhiyun 461*4882a593Smuzhiyun def __repr__(self): 462*4882a593Smuzhiyun """ 463*4882a593Smuzhiyun @return: Generic event string representation. 464*4882a593Smuzhiyun @rtype: str 465*4882a593Smuzhiyun """ 466*4882a593Smuzhiyun s = '' 467*4882a593Smuzhiyun for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]): 468*4882a593Smuzhiyun if attr.startswith('_'): 469*4882a593Smuzhiyun continue 470*4882a593Smuzhiyun if attr == 'mask': 471*4882a593Smuzhiyun value = hex(getattr(self, attr)) 472*4882a593Smuzhiyun elif isinstance(value, str) and not value: 473*4882a593Smuzhiyun value = "''" 474*4882a593Smuzhiyun s += ' %s%s%s' % (output_format.field_name(attr), 475*4882a593Smuzhiyun output_format.punctuation('='), 476*4882a593Smuzhiyun output_format.field_value(value)) 477*4882a593Smuzhiyun 478*4882a593Smuzhiyun s = '%s%s%s %s' % (output_format.punctuation('<'), 479*4882a593Smuzhiyun output_format.class_name(self.__class__.__name__), 480*4882a593Smuzhiyun s, 481*4882a593Smuzhiyun output_format.punctuation('>')) 482*4882a593Smuzhiyun return s 483*4882a593Smuzhiyun 484*4882a593Smuzhiyun def __str__(self): 485*4882a593Smuzhiyun return repr(self) 486*4882a593Smuzhiyun 487*4882a593Smuzhiyun 488*4882a593Smuzhiyunclass _RawEvent(_Event): 489*4882a593Smuzhiyun """ 490*4882a593Smuzhiyun Raw event, it contains only the informations provided by the system. 491*4882a593Smuzhiyun It doesn't infer anything. 492*4882a593Smuzhiyun """ 493*4882a593Smuzhiyun def __init__(self, wd, mask, cookie, name): 494*4882a593Smuzhiyun """ 495*4882a593Smuzhiyun @param wd: Watch Descriptor. 496*4882a593Smuzhiyun @type wd: int 497*4882a593Smuzhiyun @param mask: Bitmask of events. 498*4882a593Smuzhiyun @type mask: int 499*4882a593Smuzhiyun @param cookie: Cookie. 500*4882a593Smuzhiyun @type cookie: int 501*4882a593Smuzhiyun @param name: Basename of the file or directory against which the 502*4882a593Smuzhiyun event was raised in case where the watched directory 503*4882a593Smuzhiyun is the parent directory. None if the event was raised 504*4882a593Smuzhiyun on the watched item itself. 505*4882a593Smuzhiyun @type name: string or None 506*4882a593Smuzhiyun """ 507*4882a593Smuzhiyun # Use this variable to cache the result of str(self), this object 508*4882a593Smuzhiyun # is immutable. 509*4882a593Smuzhiyun self._str = None 510*4882a593Smuzhiyun # name: remove trailing '\0' 511*4882a593Smuzhiyun d = {'wd': wd, 512*4882a593Smuzhiyun 'mask': mask, 513*4882a593Smuzhiyun 'cookie': cookie, 514*4882a593Smuzhiyun 'name': name.rstrip('\0')} 515*4882a593Smuzhiyun _Event.__init__(self, d) 516*4882a593Smuzhiyun log.debug(str(self)) 517*4882a593Smuzhiyun 518*4882a593Smuzhiyun def __str__(self): 519*4882a593Smuzhiyun if self._str is None: 520*4882a593Smuzhiyun self._str = _Event.__str__(self) 521*4882a593Smuzhiyun return self._str 522*4882a593Smuzhiyun 523*4882a593Smuzhiyun 524*4882a593Smuzhiyunclass Event(_Event): 525*4882a593Smuzhiyun """ 526*4882a593Smuzhiyun This class contains all the useful informations about the observed 527*4882a593Smuzhiyun event. However, the presence of each field is not guaranteed and 528*4882a593Smuzhiyun depends on the type of event. In effect, some fields are irrelevant 529*4882a593Smuzhiyun for some kind of event (for example 'cookie' is meaningless for 530*4882a593Smuzhiyun IN_CREATE whereas it is mandatory for IN_MOVE_TO). 531*4882a593Smuzhiyun 532*4882a593Smuzhiyun The possible fields are: 533*4882a593Smuzhiyun - wd (int): Watch Descriptor. 534*4882a593Smuzhiyun - mask (int): Mask. 535*4882a593Smuzhiyun - maskname (str): Readable event name. 536*4882a593Smuzhiyun - path (str): path of the file or directory being watched. 537*4882a593Smuzhiyun - name (str): Basename of the file or directory against which the 538*4882a593Smuzhiyun event was raised in case where the watched directory 539*4882a593Smuzhiyun is the parent directory. None if the event was raised 540*4882a593Smuzhiyun on the watched item itself. This field is always provided 541*4882a593Smuzhiyun even if the string is ''. 542*4882a593Smuzhiyun - pathname (str): Concatenation of 'path' and 'name'. 543*4882a593Smuzhiyun - src_pathname (str): Only present for IN_MOVED_TO events and only in 544*4882a593Smuzhiyun the case where IN_MOVED_FROM events are watched too. Holds the 545*4882a593Smuzhiyun source pathname from where pathname was moved from. 546*4882a593Smuzhiyun - cookie (int): Cookie. 547*4882a593Smuzhiyun - dir (bool): True if the event was raised against a directory. 548*4882a593Smuzhiyun 549*4882a593Smuzhiyun """ 550*4882a593Smuzhiyun def __init__(self, raw): 551*4882a593Smuzhiyun """ 552*4882a593Smuzhiyun Concretely, this is the raw event plus inferred infos. 553*4882a593Smuzhiyun """ 554*4882a593Smuzhiyun _Event.__init__(self, raw) 555*4882a593Smuzhiyun self.maskname = EventsCodes.maskname(self.mask) 556*4882a593Smuzhiyun if COMPATIBILITY_MODE: 557*4882a593Smuzhiyun self.event_name = self.maskname 558*4882a593Smuzhiyun try: 559*4882a593Smuzhiyun if self.name: 560*4882a593Smuzhiyun self.pathname = os.path.abspath(os.path.join(self.path, 561*4882a593Smuzhiyun self.name)) 562*4882a593Smuzhiyun else: 563*4882a593Smuzhiyun self.pathname = os.path.abspath(self.path) 564*4882a593Smuzhiyun except AttributeError as err: 565*4882a593Smuzhiyun # Usually it is not an error some events are perfectly valids 566*4882a593Smuzhiyun # despite the lack of these attributes. 567*4882a593Smuzhiyun log.debug(err) 568*4882a593Smuzhiyun 569*4882a593Smuzhiyun 570*4882a593Smuzhiyunclass ProcessEventError(PyinotifyError): 571*4882a593Smuzhiyun """ 572*4882a593Smuzhiyun ProcessEventError Exception. Raised on ProcessEvent error. 573*4882a593Smuzhiyun """ 574*4882a593Smuzhiyun def __init__(self, err): 575*4882a593Smuzhiyun """ 576*4882a593Smuzhiyun @param err: Exception error description. 577*4882a593Smuzhiyun @type err: string 578*4882a593Smuzhiyun """ 579*4882a593Smuzhiyun PyinotifyError.__init__(self, err) 580*4882a593Smuzhiyun 581*4882a593Smuzhiyun 582*4882a593Smuzhiyunclass _ProcessEvent: 583*4882a593Smuzhiyun """ 584*4882a593Smuzhiyun Abstract processing event class. 585*4882a593Smuzhiyun """ 586*4882a593Smuzhiyun def __call__(self, event): 587*4882a593Smuzhiyun """ 588*4882a593Smuzhiyun To behave like a functor the object must be callable. 589*4882a593Smuzhiyun This method is a dispatch method. Its lookup order is: 590*4882a593Smuzhiyun 1. process_MASKNAME method 591*4882a593Smuzhiyun 2. process_FAMILY_NAME method 592*4882a593Smuzhiyun 3. otherwise calls process_default 593*4882a593Smuzhiyun 594*4882a593Smuzhiyun @param event: Event to be processed. 595*4882a593Smuzhiyun @type event: Event object 596*4882a593Smuzhiyun @return: By convention when used from the ProcessEvent class: 597*4882a593Smuzhiyun - Returning False or None (default value) means keep on 598*4882a593Smuzhiyun executing next chained functors (see chain.py example). 599*4882a593Smuzhiyun - Returning True instead means do not execute next 600*4882a593Smuzhiyun processing functions. 601*4882a593Smuzhiyun @rtype: bool 602*4882a593Smuzhiyun @raise ProcessEventError: Event object undispatchable, 603*4882a593Smuzhiyun unknown event. 604*4882a593Smuzhiyun """ 605*4882a593Smuzhiyun stripped_mask = event.mask & ~IN_ISDIR 606*4882a593Smuzhiyun # Bitbake hack - we see event masks of 0x6, i.e., IN_MODIFY & IN_ATTRIB. 607*4882a593Smuzhiyun # The kernel inotify code can set more than one of the bits in the mask, 608*4882a593Smuzhiyun # fsnotify_change() in linux/fsnotify.h is quite clear that IN_ATTRIB, 609*4882a593Smuzhiyun # IN_MODIFY and IN_ACCESS can arrive together. 610*4882a593Smuzhiyun # This breaks the code below which assume only one mask bit is ever 611*4882a593Smuzhiyun # set in an event. We don't care about attrib or access in bitbake so 612*4882a593Smuzhiyun # drop those. 613*4882a593Smuzhiyun if stripped_mask & IN_MODIFY: 614*4882a593Smuzhiyun stripped_mask &= ~(IN_ATTRIB | IN_ACCESS) 615*4882a593Smuzhiyun 616*4882a593Smuzhiyun maskname = EventsCodes.ALL_VALUES.get(stripped_mask) 617*4882a593Smuzhiyun if maskname is None: 618*4882a593Smuzhiyun raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask) 619*4882a593Smuzhiyun 620*4882a593Smuzhiyun # 1- look for process_MASKNAME 621*4882a593Smuzhiyun meth = getattr(self, 'process_' + maskname, None) 622*4882a593Smuzhiyun if meth is not None: 623*4882a593Smuzhiyun return meth(event) 624*4882a593Smuzhiyun # 2- look for process_FAMILY_NAME 625*4882a593Smuzhiyun meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None) 626*4882a593Smuzhiyun if meth is not None: 627*4882a593Smuzhiyun return meth(event) 628*4882a593Smuzhiyun # 3- default call method process_default 629*4882a593Smuzhiyun return self.process_default(event) 630*4882a593Smuzhiyun 631*4882a593Smuzhiyun def __repr__(self): 632*4882a593Smuzhiyun return '<%s>' % self.__class__.__name__ 633*4882a593Smuzhiyun 634*4882a593Smuzhiyun 635*4882a593Smuzhiyunclass _SysProcessEvent(_ProcessEvent): 636*4882a593Smuzhiyun """ 637*4882a593Smuzhiyun There is three kind of processing according to each event: 638*4882a593Smuzhiyun 639*4882a593Smuzhiyun 1. special handling (deletion from internal container, bug, ...). 640*4882a593Smuzhiyun 2. default treatment: which is applied to the majority of events. 641*4882a593Smuzhiyun 3. IN_ISDIR is never sent alone, he is piggybacked with a standard 642*4882a593Smuzhiyun event, he is not processed as the others events, instead, its 643*4882a593Smuzhiyun value is captured and appropriately aggregated to dst event. 644*4882a593Smuzhiyun """ 645*4882a593Smuzhiyun def __init__(self, wm, notifier): 646*4882a593Smuzhiyun """ 647*4882a593Smuzhiyun 648*4882a593Smuzhiyun @param wm: Watch Manager. 649*4882a593Smuzhiyun @type wm: WatchManager instance 650*4882a593Smuzhiyun @param notifier: Notifier. 651*4882a593Smuzhiyun @type notifier: Notifier instance 652*4882a593Smuzhiyun """ 653*4882a593Smuzhiyun self._watch_manager = wm # watch manager 654*4882a593Smuzhiyun self._notifier = notifier # notifier 655*4882a593Smuzhiyun self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...} 656*4882a593Smuzhiyun self._mv = {} # {src_path(str): (dst_path(str), date), ...} 657*4882a593Smuzhiyun 658*4882a593Smuzhiyun def cleanup(self): 659*4882a593Smuzhiyun """ 660*4882a593Smuzhiyun Cleanup (delete) old (>1mn) records contained in self._mv_cookie 661*4882a593Smuzhiyun and self._mv. 662*4882a593Smuzhiyun """ 663*4882a593Smuzhiyun date_cur_ = datetime.now() 664*4882a593Smuzhiyun for seq in (self._mv_cookie, self._mv): 665*4882a593Smuzhiyun for k in list(seq.keys()): 666*4882a593Smuzhiyun if (date_cur_ - seq[k][1]) > timedelta(minutes=1): 667*4882a593Smuzhiyun log.debug('Cleanup: deleting entry %s', seq[k][0]) 668*4882a593Smuzhiyun del seq[k] 669*4882a593Smuzhiyun 670*4882a593Smuzhiyun def process_IN_CREATE(self, raw_event): 671*4882a593Smuzhiyun """ 672*4882a593Smuzhiyun If the event affects a directory and the auto_add flag of the 673*4882a593Smuzhiyun targetted watch is set to True, a new watch is added on this 674*4882a593Smuzhiyun new directory, with the same attribute values than those of 675*4882a593Smuzhiyun this watch. 676*4882a593Smuzhiyun """ 677*4882a593Smuzhiyun if raw_event.mask & IN_ISDIR: 678*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 679*4882a593Smuzhiyun created_dir = os.path.join(watch_.path, raw_event.name) 680*4882a593Smuzhiyun if watch_.auto_add and not watch_.exclude_filter(created_dir): 681*4882a593Smuzhiyun addw = self._watch_manager.add_watch 682*4882a593Smuzhiyun # The newly monitored directory inherits attributes from its 683*4882a593Smuzhiyun # parent directory. 684*4882a593Smuzhiyun addw_ret = addw(created_dir, watch_.mask, 685*4882a593Smuzhiyun proc_fun=watch_.proc_fun, 686*4882a593Smuzhiyun rec=False, auto_add=watch_.auto_add, 687*4882a593Smuzhiyun exclude_filter=watch_.exclude_filter) 688*4882a593Smuzhiyun 689*4882a593Smuzhiyun # Trick to handle mkdir -p /d1/d2/t3 where d1 is watched and 690*4882a593Smuzhiyun # d2 and t3 (directory or file) are created. 691*4882a593Smuzhiyun # Since the directory d2 is new, then everything inside it must 692*4882a593Smuzhiyun # also be new. 693*4882a593Smuzhiyun created_dir_wd = addw_ret.get(created_dir) 694*4882a593Smuzhiyun if ((created_dir_wd is not None) and (created_dir_wd > 0) and 695*4882a593Smuzhiyun os.path.isdir(created_dir)): 696*4882a593Smuzhiyun try: 697*4882a593Smuzhiyun for name in os.listdir(created_dir): 698*4882a593Smuzhiyun inner = os.path.join(created_dir, name) 699*4882a593Smuzhiyun if self._watch_manager.get_wd(inner) is not None: 700*4882a593Smuzhiyun continue 701*4882a593Smuzhiyun # Generate (simulate) creation events for sub- 702*4882a593Smuzhiyun # directories and files. 703*4882a593Smuzhiyun if os.path.isfile(inner): 704*4882a593Smuzhiyun # symlinks are handled as files. 705*4882a593Smuzhiyun flags = IN_CREATE 706*4882a593Smuzhiyun elif os.path.isdir(inner): 707*4882a593Smuzhiyun flags = IN_CREATE | IN_ISDIR 708*4882a593Smuzhiyun else: 709*4882a593Smuzhiyun # This path should not be taken. 710*4882a593Smuzhiyun continue 711*4882a593Smuzhiyun rawevent = _RawEvent(created_dir_wd, flags, 0, name) 712*4882a593Smuzhiyun self._notifier.append_event(rawevent) 713*4882a593Smuzhiyun except OSError as err: 714*4882a593Smuzhiyun msg = "process_IN_CREATE, invalid directory: %s" 715*4882a593Smuzhiyun log.debug(msg % str(err)) 716*4882a593Smuzhiyun return self.process_default(raw_event) 717*4882a593Smuzhiyun 718*4882a593Smuzhiyun def process_IN_MOVED_FROM(self, raw_event): 719*4882a593Smuzhiyun """ 720*4882a593Smuzhiyun Map the cookie with the source path (+ date for cleaning). 721*4882a593Smuzhiyun """ 722*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 723*4882a593Smuzhiyun path_ = watch_.path 724*4882a593Smuzhiyun src_path = os.path.normpath(os.path.join(path_, raw_event.name)) 725*4882a593Smuzhiyun self._mv_cookie[raw_event.cookie] = (src_path, datetime.now()) 726*4882a593Smuzhiyun return self.process_default(raw_event, {'cookie': raw_event.cookie}) 727*4882a593Smuzhiyun 728*4882a593Smuzhiyun def process_IN_MOVED_TO(self, raw_event): 729*4882a593Smuzhiyun """ 730*4882a593Smuzhiyun Map the source path with the destination path (+ date for 731*4882a593Smuzhiyun cleaning). 732*4882a593Smuzhiyun """ 733*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 734*4882a593Smuzhiyun path_ = watch_.path 735*4882a593Smuzhiyun dst_path = os.path.normpath(os.path.join(path_, raw_event.name)) 736*4882a593Smuzhiyun mv_ = self._mv_cookie.get(raw_event.cookie) 737*4882a593Smuzhiyun to_append = {'cookie': raw_event.cookie} 738*4882a593Smuzhiyun if mv_ is not None: 739*4882a593Smuzhiyun self._mv[mv_[0]] = (dst_path, datetime.now()) 740*4882a593Smuzhiyun # Let's assume that IN_MOVED_FROM event is always queued before 741*4882a593Smuzhiyun # that its associated (they share a common cookie) IN_MOVED_TO 742*4882a593Smuzhiyun # event is queued itself. It is then possible in that scenario 743*4882a593Smuzhiyun # to provide as additional information to the IN_MOVED_TO event 744*4882a593Smuzhiyun # the original pathname of the moved file/directory. 745*4882a593Smuzhiyun to_append['src_pathname'] = mv_[0] 746*4882a593Smuzhiyun elif (raw_event.mask & IN_ISDIR and watch_.auto_add and 747*4882a593Smuzhiyun not watch_.exclude_filter(dst_path)): 748*4882a593Smuzhiyun # We got a diretory that's "moved in" from an unknown source and 749*4882a593Smuzhiyun # auto_add is enabled. Manually add watches to the inner subtrees. 750*4882a593Smuzhiyun # The newly monitored directory inherits attributes from its 751*4882a593Smuzhiyun # parent directory. 752*4882a593Smuzhiyun self._watch_manager.add_watch(dst_path, watch_.mask, 753*4882a593Smuzhiyun proc_fun=watch_.proc_fun, 754*4882a593Smuzhiyun rec=True, auto_add=True, 755*4882a593Smuzhiyun exclude_filter=watch_.exclude_filter) 756*4882a593Smuzhiyun return self.process_default(raw_event, to_append) 757*4882a593Smuzhiyun 758*4882a593Smuzhiyun def process_IN_MOVE_SELF(self, raw_event): 759*4882a593Smuzhiyun """ 760*4882a593Smuzhiyun STATUS: the following bug has been fixed in recent kernels (FIXME: 761*4882a593Smuzhiyun which version ?). Now it raises IN_DELETE_SELF instead. 762*4882a593Smuzhiyun 763*4882a593Smuzhiyun Old kernels were bugged, this event raised when the watched item 764*4882a593Smuzhiyun were moved, so we had to update its path, but under some circumstances 765*4882a593Smuzhiyun it was impossible: if its parent directory and its destination 766*4882a593Smuzhiyun directory wasn't watched. The kernel (see include/linux/fsnotify.h) 767*4882a593Smuzhiyun doesn't bring us enough informations like the destination path of 768*4882a593Smuzhiyun moved items. 769*4882a593Smuzhiyun """ 770*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 771*4882a593Smuzhiyun src_path = watch_.path 772*4882a593Smuzhiyun mv_ = self._mv.get(src_path) 773*4882a593Smuzhiyun if mv_: 774*4882a593Smuzhiyun dest_path = mv_[0] 775*4882a593Smuzhiyun watch_.path = dest_path 776*4882a593Smuzhiyun # add the separator to the source path to avoid overlapping 777*4882a593Smuzhiyun # path issue when testing with startswith() 778*4882a593Smuzhiyun src_path += os.path.sep 779*4882a593Smuzhiyun src_path_len = len(src_path) 780*4882a593Smuzhiyun # The next loop renames all watches with src_path as base path. 781*4882a593Smuzhiyun # It seems that IN_MOVE_SELF does not provide IN_ISDIR information 782*4882a593Smuzhiyun # therefore the next loop is iterated even if raw_event is a file. 783*4882a593Smuzhiyun for w in self._watch_manager.watches.values(): 784*4882a593Smuzhiyun if w.path.startswith(src_path): 785*4882a593Smuzhiyun # Note that dest_path is a normalized path. 786*4882a593Smuzhiyun w.path = os.path.join(dest_path, w.path[src_path_len:]) 787*4882a593Smuzhiyun else: 788*4882a593Smuzhiyun log.error("The pathname '%s' of this watch %s has probably changed " 789*4882a593Smuzhiyun "and couldn't be updated, so it cannot be trusted " 790*4882a593Smuzhiyun "anymore. To fix this error move directories/files only " 791*4882a593Smuzhiyun "between watched parents directories, in this case e.g. " 792*4882a593Smuzhiyun "put a watch on '%s'.", 793*4882a593Smuzhiyun watch_.path, watch_, 794*4882a593Smuzhiyun os.path.normpath(os.path.join(watch_.path, 795*4882a593Smuzhiyun os.path.pardir))) 796*4882a593Smuzhiyun if not watch_.path.endswith('-unknown-path'): 797*4882a593Smuzhiyun watch_.path += '-unknown-path' 798*4882a593Smuzhiyun return self.process_default(raw_event) 799*4882a593Smuzhiyun 800*4882a593Smuzhiyun def process_IN_Q_OVERFLOW(self, raw_event): 801*4882a593Smuzhiyun """ 802*4882a593Smuzhiyun Only signal an overflow, most of the common flags are irrelevant 803*4882a593Smuzhiyun for this event (path, wd, name). 804*4882a593Smuzhiyun """ 805*4882a593Smuzhiyun return Event({'mask': raw_event.mask}) 806*4882a593Smuzhiyun 807*4882a593Smuzhiyun def process_IN_IGNORED(self, raw_event): 808*4882a593Smuzhiyun """ 809*4882a593Smuzhiyun The watch descriptor raised by this event is now ignored (forever), 810*4882a593Smuzhiyun it can be safely deleted from the watch manager dictionary. 811*4882a593Smuzhiyun After this event we can be sure that neither the event queue nor 812*4882a593Smuzhiyun the system will raise an event associated to this wd again. 813*4882a593Smuzhiyun """ 814*4882a593Smuzhiyun event_ = self.process_default(raw_event) 815*4882a593Smuzhiyun self._watch_manager.del_watch(raw_event.wd) 816*4882a593Smuzhiyun return event_ 817*4882a593Smuzhiyun 818*4882a593Smuzhiyun def process_default(self, raw_event, to_append=None): 819*4882a593Smuzhiyun """ 820*4882a593Smuzhiyun Commons handling for the followings events: 821*4882a593Smuzhiyun 822*4882a593Smuzhiyun IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, 823*4882a593Smuzhiyun IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT. 824*4882a593Smuzhiyun """ 825*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 826*4882a593Smuzhiyun if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF): 827*4882a593Smuzhiyun # Unfornulately this information is not provided by the kernel 828*4882a593Smuzhiyun dir_ = watch_.dir 829*4882a593Smuzhiyun else: 830*4882a593Smuzhiyun dir_ = bool(raw_event.mask & IN_ISDIR) 831*4882a593Smuzhiyun dict_ = {'wd': raw_event.wd, 832*4882a593Smuzhiyun 'mask': raw_event.mask, 833*4882a593Smuzhiyun 'path': watch_.path, 834*4882a593Smuzhiyun 'name': raw_event.name, 835*4882a593Smuzhiyun 'dir': dir_} 836*4882a593Smuzhiyun if COMPATIBILITY_MODE: 837*4882a593Smuzhiyun dict_['is_dir'] = dir_ 838*4882a593Smuzhiyun if to_append is not None: 839*4882a593Smuzhiyun dict_.update(to_append) 840*4882a593Smuzhiyun return Event(dict_) 841*4882a593Smuzhiyun 842*4882a593Smuzhiyun 843*4882a593Smuzhiyunclass ProcessEvent(_ProcessEvent): 844*4882a593Smuzhiyun """ 845*4882a593Smuzhiyun Process events objects, can be specialized via subclassing, thus its 846*4882a593Smuzhiyun behavior can be overriden: 847*4882a593Smuzhiyun 848*4882a593Smuzhiyun Note: you should not override __init__ in your subclass instead define 849*4882a593Smuzhiyun a my_init() method, this method will be called automatically from the 850*4882a593Smuzhiyun constructor of this class with its optionals parameters. 851*4882a593Smuzhiyun 852*4882a593Smuzhiyun 1. Provide specialized individual methods, e.g. process_IN_DELETE for 853*4882a593Smuzhiyun processing a precise type of event (e.g. IN_DELETE in this case). 854*4882a593Smuzhiyun 2. Or/and provide methods for processing events by 'family', e.g. 855*4882a593Smuzhiyun process_IN_CLOSE method will process both IN_CLOSE_WRITE and 856*4882a593Smuzhiyun IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and 857*4882a593Smuzhiyun process_IN_CLOSE_NOWRITE aren't defined though). 858*4882a593Smuzhiyun 3. Or/and override process_default for catching and processing all 859*4882a593Smuzhiyun the remaining types of events. 860*4882a593Smuzhiyun """ 861*4882a593Smuzhiyun pevent = None 862*4882a593Smuzhiyun 863*4882a593Smuzhiyun def __init__(self, pevent=None, **kargs): 864*4882a593Smuzhiyun """ 865*4882a593Smuzhiyun Enable chaining of ProcessEvent instances. 866*4882a593Smuzhiyun 867*4882a593Smuzhiyun @param pevent: Optional callable object, will be called on event 868*4882a593Smuzhiyun processing (before self). 869*4882a593Smuzhiyun @type pevent: callable 870*4882a593Smuzhiyun @param kargs: This constructor is implemented as a template method 871*4882a593Smuzhiyun delegating its optionals keyworded arguments to the 872*4882a593Smuzhiyun method my_init(). 873*4882a593Smuzhiyun @type kargs: dict 874*4882a593Smuzhiyun """ 875*4882a593Smuzhiyun self.pevent = pevent 876*4882a593Smuzhiyun self.my_init(**kargs) 877*4882a593Smuzhiyun 878*4882a593Smuzhiyun def my_init(self, **kargs): 879*4882a593Smuzhiyun """ 880*4882a593Smuzhiyun This method is called from ProcessEvent.__init__(). This method is 881*4882a593Smuzhiyun empty here and must be redefined to be useful. In effect, if you 882*4882a593Smuzhiyun need to specifically initialize your subclass' instance then you 883*4882a593Smuzhiyun just have to override this method in your subclass. Then all the 884*4882a593Smuzhiyun keyworded arguments passed to ProcessEvent.__init__() will be 885*4882a593Smuzhiyun transmitted as parameters to this method. Beware you MUST pass 886*4882a593Smuzhiyun keyword arguments though. 887*4882a593Smuzhiyun 888*4882a593Smuzhiyun @param kargs: optional delegated arguments from __init__(). 889*4882a593Smuzhiyun @type kargs: dict 890*4882a593Smuzhiyun """ 891*4882a593Smuzhiyun pass 892*4882a593Smuzhiyun 893*4882a593Smuzhiyun def __call__(self, event): 894*4882a593Smuzhiyun stop_chaining = False 895*4882a593Smuzhiyun if self.pevent is not None: 896*4882a593Smuzhiyun # By default methods return None so we set as guideline 897*4882a593Smuzhiyun # that methods asking for stop chaining must explicitely 898*4882a593Smuzhiyun # return non None or non False values, otherwise the default 899*4882a593Smuzhiyun # behavior will be to accept chain call to the corresponding 900*4882a593Smuzhiyun # local method. 901*4882a593Smuzhiyun stop_chaining = self.pevent(event) 902*4882a593Smuzhiyun if not stop_chaining: 903*4882a593Smuzhiyun return _ProcessEvent.__call__(self, event) 904*4882a593Smuzhiyun 905*4882a593Smuzhiyun def nested_pevent(self): 906*4882a593Smuzhiyun return self.pevent 907*4882a593Smuzhiyun 908*4882a593Smuzhiyun def process_IN_Q_OVERFLOW(self, event): 909*4882a593Smuzhiyun """ 910*4882a593Smuzhiyun By default this method only reports warning messages, you can overredide 911*4882a593Smuzhiyun it by subclassing ProcessEvent and implement your own 912*4882a593Smuzhiyun process_IN_Q_OVERFLOW method. The actions you can take on receiving this 913*4882a593Smuzhiyun event is either to update the variable max_queued_events in order to 914*4882a593Smuzhiyun handle more simultaneous events or to modify your code in order to 915*4882a593Smuzhiyun accomplish a better filtering diminishing the number of raised events. 916*4882a593Smuzhiyun Because this method is defined, IN_Q_OVERFLOW will never get 917*4882a593Smuzhiyun transmitted as arguments to process_default calls. 918*4882a593Smuzhiyun 919*4882a593Smuzhiyun @param event: IN_Q_OVERFLOW event. 920*4882a593Smuzhiyun @type event: dict 921*4882a593Smuzhiyun """ 922*4882a593Smuzhiyun log.warning('Event queue overflowed.') 923*4882a593Smuzhiyun 924*4882a593Smuzhiyun def process_default(self, event): 925*4882a593Smuzhiyun """ 926*4882a593Smuzhiyun Default processing event method. By default does nothing. Subclass 927*4882a593Smuzhiyun ProcessEvent and redefine this method in order to modify its behavior. 928*4882a593Smuzhiyun 929*4882a593Smuzhiyun @param event: Event to be processed. Can be of any type of events but 930*4882a593Smuzhiyun IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW). 931*4882a593Smuzhiyun @type event: Event instance 932*4882a593Smuzhiyun """ 933*4882a593Smuzhiyun pass 934*4882a593Smuzhiyun 935*4882a593Smuzhiyun 936*4882a593Smuzhiyunclass PrintAllEvents(ProcessEvent): 937*4882a593Smuzhiyun """ 938*4882a593Smuzhiyun Dummy class used to print events strings representations. For instance this 939*4882a593Smuzhiyun class is used from command line to print all received events to stdout. 940*4882a593Smuzhiyun """ 941*4882a593Smuzhiyun def my_init(self, out=None): 942*4882a593Smuzhiyun """ 943*4882a593Smuzhiyun @param out: Where events will be written. 944*4882a593Smuzhiyun @type out: Object providing a valid file object interface. 945*4882a593Smuzhiyun """ 946*4882a593Smuzhiyun if out is None: 947*4882a593Smuzhiyun out = sys.stdout 948*4882a593Smuzhiyun self._out = out 949*4882a593Smuzhiyun 950*4882a593Smuzhiyun def process_default(self, event): 951*4882a593Smuzhiyun """ 952*4882a593Smuzhiyun Writes event string representation to file object provided to 953*4882a593Smuzhiyun my_init(). 954*4882a593Smuzhiyun 955*4882a593Smuzhiyun @param event: Event to be processed. Can be of any type of events but 956*4882a593Smuzhiyun IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW). 957*4882a593Smuzhiyun @type event: Event instance 958*4882a593Smuzhiyun """ 959*4882a593Smuzhiyun self._out.write(str(event)) 960*4882a593Smuzhiyun self._out.write('\n') 961*4882a593Smuzhiyun self._out.flush() 962*4882a593Smuzhiyun 963*4882a593Smuzhiyun 964*4882a593Smuzhiyunclass ChainIfTrue(ProcessEvent): 965*4882a593Smuzhiyun """ 966*4882a593Smuzhiyun Makes conditional chaining depending on the result of the nested 967*4882a593Smuzhiyun processing instance. 968*4882a593Smuzhiyun """ 969*4882a593Smuzhiyun def my_init(self, func): 970*4882a593Smuzhiyun """ 971*4882a593Smuzhiyun Method automatically called from base class constructor. 972*4882a593Smuzhiyun """ 973*4882a593Smuzhiyun self._func = func 974*4882a593Smuzhiyun 975*4882a593Smuzhiyun def process_default(self, event): 976*4882a593Smuzhiyun return not self._func(event) 977*4882a593Smuzhiyun 978*4882a593Smuzhiyun 979*4882a593Smuzhiyunclass Stats(ProcessEvent): 980*4882a593Smuzhiyun """ 981*4882a593Smuzhiyun Compute and display trivial statistics about processed events. 982*4882a593Smuzhiyun """ 983*4882a593Smuzhiyun def my_init(self): 984*4882a593Smuzhiyun """ 985*4882a593Smuzhiyun Method automatically called from base class constructor. 986*4882a593Smuzhiyun """ 987*4882a593Smuzhiyun self._start_time = time.time() 988*4882a593Smuzhiyun self._stats = {} 989*4882a593Smuzhiyun self._stats_lock = threading.Lock() 990*4882a593Smuzhiyun 991*4882a593Smuzhiyun def process_default(self, event): 992*4882a593Smuzhiyun """ 993*4882a593Smuzhiyun Processes |event|. 994*4882a593Smuzhiyun """ 995*4882a593Smuzhiyun self._stats_lock.acquire() 996*4882a593Smuzhiyun try: 997*4882a593Smuzhiyun events = event.maskname.split('|') 998*4882a593Smuzhiyun for event_name in events: 999*4882a593Smuzhiyun count = self._stats.get(event_name, 0) 1000*4882a593Smuzhiyun self._stats[event_name] = count + 1 1001*4882a593Smuzhiyun finally: 1002*4882a593Smuzhiyun self._stats_lock.release() 1003*4882a593Smuzhiyun 1004*4882a593Smuzhiyun def _stats_copy(self): 1005*4882a593Smuzhiyun self._stats_lock.acquire() 1006*4882a593Smuzhiyun try: 1007*4882a593Smuzhiyun return self._stats.copy() 1008*4882a593Smuzhiyun finally: 1009*4882a593Smuzhiyun self._stats_lock.release() 1010*4882a593Smuzhiyun 1011*4882a593Smuzhiyun def __repr__(self): 1012*4882a593Smuzhiyun stats = self._stats_copy() 1013*4882a593Smuzhiyun 1014*4882a593Smuzhiyun elapsed = int(time.time() - self._start_time) 1015*4882a593Smuzhiyun elapsed_str = '' 1016*4882a593Smuzhiyun if elapsed < 60: 1017*4882a593Smuzhiyun elapsed_str = str(elapsed) + 'sec' 1018*4882a593Smuzhiyun elif 60 <= elapsed < 3600: 1019*4882a593Smuzhiyun elapsed_str = '%dmn%dsec' % (elapsed / 60, elapsed % 60) 1020*4882a593Smuzhiyun elif 3600 <= elapsed < 86400: 1021*4882a593Smuzhiyun elapsed_str = '%dh%dmn' % (elapsed / 3600, (elapsed % 3600) / 60) 1022*4882a593Smuzhiyun elif elapsed >= 86400: 1023*4882a593Smuzhiyun elapsed_str = '%dd%dh' % (elapsed / 86400, (elapsed % 86400) / 3600) 1024*4882a593Smuzhiyun stats['ElapsedTime'] = elapsed_str 1025*4882a593Smuzhiyun 1026*4882a593Smuzhiyun l = [] 1027*4882a593Smuzhiyun for ev, value in sorted(stats.items(), key=lambda x: x[0]): 1028*4882a593Smuzhiyun l.append(' %s=%s' % (output_format.field_name(ev), 1029*4882a593Smuzhiyun output_format.field_value(value))) 1030*4882a593Smuzhiyun s = '<%s%s >' % (output_format.class_name(self.__class__.__name__), 1031*4882a593Smuzhiyun ''.join(l)) 1032*4882a593Smuzhiyun return s 1033*4882a593Smuzhiyun 1034*4882a593Smuzhiyun def dump(self, filename): 1035*4882a593Smuzhiyun """ 1036*4882a593Smuzhiyun Dumps statistics. 1037*4882a593Smuzhiyun 1038*4882a593Smuzhiyun @param filename: filename where stats will be dumped, filename is 1039*4882a593Smuzhiyun created and must not exist prior to this call. 1040*4882a593Smuzhiyun @type filename: string 1041*4882a593Smuzhiyun """ 1042*4882a593Smuzhiyun flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL 1043*4882a593Smuzhiyun fd = os.open(filename, flags, 0o0600) 1044*4882a593Smuzhiyun os.write(fd, bytes(self.__str__(), locale.getpreferredencoding())) 1045*4882a593Smuzhiyun os.close(fd) 1046*4882a593Smuzhiyun 1047*4882a593Smuzhiyun def __str__(self, scale=45): 1048*4882a593Smuzhiyun stats = self._stats_copy() 1049*4882a593Smuzhiyun if not stats: 1050*4882a593Smuzhiyun return '' 1051*4882a593Smuzhiyun 1052*4882a593Smuzhiyun m = max(stats.values()) 1053*4882a593Smuzhiyun unity = scale / m 1054*4882a593Smuzhiyun fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale)) 1055*4882a593Smuzhiyun + 1) 1056*4882a593Smuzhiyun def func(x): 1057*4882a593Smuzhiyun return fmt % (output_format.field_name(x[0]), 1058*4882a593Smuzhiyun output_format.field_value('@' * int(x[1] * unity)), 1059*4882a593Smuzhiyun output_format.simple('%d' % x[1], 'yellow')) 1060*4882a593Smuzhiyun s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0]))) 1061*4882a593Smuzhiyun return s 1062*4882a593Smuzhiyun 1063*4882a593Smuzhiyun 1064*4882a593Smuzhiyunclass NotifierError(PyinotifyError): 1065*4882a593Smuzhiyun """ 1066*4882a593Smuzhiyun Notifier Exception. Raised on Notifier error. 1067*4882a593Smuzhiyun 1068*4882a593Smuzhiyun """ 1069*4882a593Smuzhiyun def __init__(self, err): 1070*4882a593Smuzhiyun """ 1071*4882a593Smuzhiyun @param err: Exception string's description. 1072*4882a593Smuzhiyun @type err: string 1073*4882a593Smuzhiyun """ 1074*4882a593Smuzhiyun PyinotifyError.__init__(self, err) 1075*4882a593Smuzhiyun 1076*4882a593Smuzhiyun 1077*4882a593Smuzhiyunclass Notifier: 1078*4882a593Smuzhiyun """ 1079*4882a593Smuzhiyun Read notifications, process events. 1080*4882a593Smuzhiyun 1081*4882a593Smuzhiyun """ 1082*4882a593Smuzhiyun def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, 1083*4882a593Smuzhiyun threshold=0, timeout=None): 1084*4882a593Smuzhiyun """ 1085*4882a593Smuzhiyun Initialization. read_freq, threshold and timeout parameters are used 1086*4882a593Smuzhiyun when looping. 1087*4882a593Smuzhiyun 1088*4882a593Smuzhiyun @param watch_manager: Watch Manager. 1089*4882a593Smuzhiyun @type watch_manager: WatchManager instance 1090*4882a593Smuzhiyun @param default_proc_fun: Default processing method. If None, a new 1091*4882a593Smuzhiyun instance of PrintAllEvents will be assigned. 1092*4882a593Smuzhiyun @type default_proc_fun: instance of ProcessEvent 1093*4882a593Smuzhiyun @param read_freq: if read_freq == 0, events are read asap, 1094*4882a593Smuzhiyun if read_freq is > 0, this thread sleeps 1095*4882a593Smuzhiyun max(0, read_freq - (timeout / 1000)) seconds. But if 1096*4882a593Smuzhiyun timeout is None it may be different because 1097*4882a593Smuzhiyun poll is blocking waiting for something to read. 1098*4882a593Smuzhiyun @type read_freq: int 1099*4882a593Smuzhiyun @param threshold: File descriptor will be read only if the accumulated 1100*4882a593Smuzhiyun size to read becomes >= threshold. If != 0, you likely 1101*4882a593Smuzhiyun want to use it in combination with an appropriate 1102*4882a593Smuzhiyun value for read_freq because without that you would 1103*4882a593Smuzhiyun keep looping without really reading anything and that 1104*4882a593Smuzhiyun until the amount of events to read is >= threshold. 1105*4882a593Smuzhiyun At least with read_freq set you might sleep. 1106*4882a593Smuzhiyun @type threshold: int 1107*4882a593Smuzhiyun @param timeout: see read_freq above. If provided, it must be set in 1108*4882a593Smuzhiyun milliseconds. See 1109*4882a593Smuzhiyun https://docs.python.org/3/library/select.html#select.poll.poll 1110*4882a593Smuzhiyun @type timeout: int 1111*4882a593Smuzhiyun """ 1112*4882a593Smuzhiyun # Watch Manager instance 1113*4882a593Smuzhiyun self._watch_manager = watch_manager 1114*4882a593Smuzhiyun # File descriptor 1115*4882a593Smuzhiyun self._fd = self._watch_manager.get_fd() 1116*4882a593Smuzhiyun # Poll object and registration 1117*4882a593Smuzhiyun self._pollobj = select.poll() 1118*4882a593Smuzhiyun self._pollobj.register(self._fd, select.POLLIN) 1119*4882a593Smuzhiyun # This pipe is correctely initialized and used by ThreadedNotifier 1120*4882a593Smuzhiyun self._pipe = (-1, -1) 1121*4882a593Smuzhiyun # Event queue 1122*4882a593Smuzhiyun self._eventq = deque() 1123*4882a593Smuzhiyun # System processing functor, common to all events 1124*4882a593Smuzhiyun self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self) 1125*4882a593Smuzhiyun # Default processing method 1126*4882a593Smuzhiyun self._default_proc_fun = default_proc_fun 1127*4882a593Smuzhiyun if default_proc_fun is None: 1128*4882a593Smuzhiyun self._default_proc_fun = PrintAllEvents() 1129*4882a593Smuzhiyun # Loop parameters 1130*4882a593Smuzhiyun self._read_freq = read_freq 1131*4882a593Smuzhiyun self._threshold = threshold 1132*4882a593Smuzhiyun self._timeout = timeout 1133*4882a593Smuzhiyun # Coalesce events option 1134*4882a593Smuzhiyun self._coalesce = False 1135*4882a593Smuzhiyun # set of str(raw_event), only used when coalesce option is True 1136*4882a593Smuzhiyun self._eventset = set() 1137*4882a593Smuzhiyun 1138*4882a593Smuzhiyun def append_event(self, event): 1139*4882a593Smuzhiyun """ 1140*4882a593Smuzhiyun Append a raw event to the event queue. 1141*4882a593Smuzhiyun 1142*4882a593Smuzhiyun @param event: An event. 1143*4882a593Smuzhiyun @type event: _RawEvent instance. 1144*4882a593Smuzhiyun """ 1145*4882a593Smuzhiyun self._eventq.append(event) 1146*4882a593Smuzhiyun 1147*4882a593Smuzhiyun def proc_fun(self): 1148*4882a593Smuzhiyun return self._default_proc_fun 1149*4882a593Smuzhiyun 1150*4882a593Smuzhiyun def coalesce_events(self, coalesce=True): 1151*4882a593Smuzhiyun """ 1152*4882a593Smuzhiyun Coalescing events. Events are usually processed by batchs, their size 1153*4882a593Smuzhiyun depend on various factors. Thus, before processing them, events received 1154*4882a593Smuzhiyun from inotify are aggregated in a fifo queue. If this coalescing 1155*4882a593Smuzhiyun option is enabled events are filtered based on their unicity, only 1156*4882a593Smuzhiyun unique events are enqueued, doublons are discarded. An event is unique 1157*4882a593Smuzhiyun when the combination of its fields (wd, mask, cookie, name) is unique 1158*4882a593Smuzhiyun among events of a same batch. After a batch of events is processed any 1159*4882a593Smuzhiyun events is accepted again. By default this option is disabled, you have 1160*4882a593Smuzhiyun to explictly call this function to turn it on. 1161*4882a593Smuzhiyun 1162*4882a593Smuzhiyun @param coalesce: Optional new coalescing value. True by default. 1163*4882a593Smuzhiyun @type coalesce: Bool 1164*4882a593Smuzhiyun """ 1165*4882a593Smuzhiyun self._coalesce = coalesce 1166*4882a593Smuzhiyun if not coalesce: 1167*4882a593Smuzhiyun self._eventset.clear() 1168*4882a593Smuzhiyun 1169*4882a593Smuzhiyun def check_events(self, timeout=None): 1170*4882a593Smuzhiyun """ 1171*4882a593Smuzhiyun Check for new events available to read, blocks up to timeout 1172*4882a593Smuzhiyun milliseconds. 1173*4882a593Smuzhiyun 1174*4882a593Smuzhiyun @param timeout: If specified it overrides the corresponding instance 1175*4882a593Smuzhiyun attribute _timeout. timeout must be sepcified in 1176*4882a593Smuzhiyun milliseconds. 1177*4882a593Smuzhiyun @type timeout: int 1178*4882a593Smuzhiyun 1179*4882a593Smuzhiyun @return: New events to read. 1180*4882a593Smuzhiyun @rtype: bool 1181*4882a593Smuzhiyun """ 1182*4882a593Smuzhiyun while True: 1183*4882a593Smuzhiyun try: 1184*4882a593Smuzhiyun # blocks up to 'timeout' milliseconds 1185*4882a593Smuzhiyun if timeout is None: 1186*4882a593Smuzhiyun timeout = self._timeout 1187*4882a593Smuzhiyun ret = self._pollobj.poll(timeout) 1188*4882a593Smuzhiyun except select.error as err: 1189*4882a593Smuzhiyun if err.args[0] == errno.EINTR: 1190*4882a593Smuzhiyun continue # interrupted, retry 1191*4882a593Smuzhiyun else: 1192*4882a593Smuzhiyun raise 1193*4882a593Smuzhiyun else: 1194*4882a593Smuzhiyun break 1195*4882a593Smuzhiyun 1196*4882a593Smuzhiyun if not ret or (self._pipe[0] == ret[0][0]): 1197*4882a593Smuzhiyun return False 1198*4882a593Smuzhiyun # only one fd is polled 1199*4882a593Smuzhiyun return ret[0][1] & select.POLLIN 1200*4882a593Smuzhiyun 1201*4882a593Smuzhiyun def read_events(self): 1202*4882a593Smuzhiyun """ 1203*4882a593Smuzhiyun Read events from device, build _RawEvents, and enqueue them. 1204*4882a593Smuzhiyun """ 1205*4882a593Smuzhiyun buf_ = array.array('i', [0]) 1206*4882a593Smuzhiyun # get event queue size 1207*4882a593Smuzhiyun if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1: 1208*4882a593Smuzhiyun return 1209*4882a593Smuzhiyun queue_size = buf_[0] 1210*4882a593Smuzhiyun if queue_size < self._threshold: 1211*4882a593Smuzhiyun log.debug('(fd: %d) %d bytes available to read but threshold is ' 1212*4882a593Smuzhiyun 'fixed to %d bytes', self._fd, queue_size, 1213*4882a593Smuzhiyun self._threshold) 1214*4882a593Smuzhiyun return 1215*4882a593Smuzhiyun 1216*4882a593Smuzhiyun try: 1217*4882a593Smuzhiyun # Read content from file 1218*4882a593Smuzhiyun r = os.read(self._fd, queue_size) 1219*4882a593Smuzhiyun except Exception as msg: 1220*4882a593Smuzhiyun raise NotifierError(msg) 1221*4882a593Smuzhiyun log.debug('Event queue size: %d', queue_size) 1222*4882a593Smuzhiyun rsum = 0 # counter 1223*4882a593Smuzhiyun while rsum < queue_size: 1224*4882a593Smuzhiyun s_size = 16 1225*4882a593Smuzhiyun # Retrieve wd, mask, cookie and fname_len 1226*4882a593Smuzhiyun wd, mask, cookie, fname_len = struct.unpack('iIII', 1227*4882a593Smuzhiyun r[rsum:rsum+s_size]) 1228*4882a593Smuzhiyun # Retrieve name 1229*4882a593Smuzhiyun bname, = struct.unpack('%ds' % fname_len, 1230*4882a593Smuzhiyun r[rsum + s_size:rsum + s_size + fname_len]) 1231*4882a593Smuzhiyun # FIXME: should we explictly call sys.getdefaultencoding() here ?? 1232*4882a593Smuzhiyun uname = bname.decode() 1233*4882a593Smuzhiyun rawevent = _RawEvent(wd, mask, cookie, uname) 1234*4882a593Smuzhiyun if self._coalesce: 1235*4882a593Smuzhiyun # Only enqueue new (unique) events. 1236*4882a593Smuzhiyun raweventstr = str(rawevent) 1237*4882a593Smuzhiyun if raweventstr not in self._eventset: 1238*4882a593Smuzhiyun self._eventset.add(raweventstr) 1239*4882a593Smuzhiyun self._eventq.append(rawevent) 1240*4882a593Smuzhiyun else: 1241*4882a593Smuzhiyun self._eventq.append(rawevent) 1242*4882a593Smuzhiyun rsum += s_size + fname_len 1243*4882a593Smuzhiyun 1244*4882a593Smuzhiyun def process_events(self): 1245*4882a593Smuzhiyun """ 1246*4882a593Smuzhiyun Routine for processing events from queue by calling their 1247*4882a593Smuzhiyun associated proccessing method (an instance of ProcessEvent). 1248*4882a593Smuzhiyun It also does internal processings, to keep the system updated. 1249*4882a593Smuzhiyun """ 1250*4882a593Smuzhiyun while self._eventq: 1251*4882a593Smuzhiyun raw_event = self._eventq.popleft() # pop next event 1252*4882a593Smuzhiyun if self._watch_manager.ignore_events: 1253*4882a593Smuzhiyun log.debug("Event ignored: %s" % repr(raw_event)) 1254*4882a593Smuzhiyun continue 1255*4882a593Smuzhiyun watch_ = self._watch_manager.get_watch(raw_event.wd) 1256*4882a593Smuzhiyun if (watch_ is None) and not (raw_event.mask & IN_Q_OVERFLOW): 1257*4882a593Smuzhiyun if not (raw_event.mask & IN_IGNORED): 1258*4882a593Smuzhiyun # Not really sure how we ended up here, nor how we should 1259*4882a593Smuzhiyun # handle these types of events and if it is appropriate to 1260*4882a593Smuzhiyun # completly skip them (like we are doing here). 1261*4882a593Smuzhiyun log.warning("Unable to retrieve Watch object associated to %s", 1262*4882a593Smuzhiyun repr(raw_event)) 1263*4882a593Smuzhiyun continue 1264*4882a593Smuzhiyun revent = self._sys_proc_fun(raw_event) # system processings 1265*4882a593Smuzhiyun if watch_ and watch_.proc_fun: 1266*4882a593Smuzhiyun watch_.proc_fun(revent) # user processings 1267*4882a593Smuzhiyun else: 1268*4882a593Smuzhiyun self._default_proc_fun(revent) 1269*4882a593Smuzhiyun self._sys_proc_fun.cleanup() # remove olds MOVED_* events records 1270*4882a593Smuzhiyun if self._coalesce: 1271*4882a593Smuzhiyun self._eventset.clear() 1272*4882a593Smuzhiyun 1273*4882a593Smuzhiyun def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull, 1274*4882a593Smuzhiyun stderr=os.devnull): 1275*4882a593Smuzhiyun """ 1276*4882a593Smuzhiyun pid_file: file where the pid will be written. If pid_file=None the pid 1277*4882a593Smuzhiyun is written to /var/run/<sys.argv[0]|pyinotify>.pid, if 1278*4882a593Smuzhiyun pid_file=False no pid_file is written. 1279*4882a593Smuzhiyun stdin, stdout, stderr: files associated to common streams. 1280*4882a593Smuzhiyun """ 1281*4882a593Smuzhiyun if pid_file is None: 1282*4882a593Smuzhiyun dirname = '/var/run/' 1283*4882a593Smuzhiyun basename = os.path.basename(sys.argv[0]) or 'pyinotify' 1284*4882a593Smuzhiyun pid_file = os.path.join(dirname, basename + '.pid') 1285*4882a593Smuzhiyun 1286*4882a593Smuzhiyun if pid_file and os.path.lexists(pid_file): 1287*4882a593Smuzhiyun err = 'Cannot daemonize: pid file %s already exists.' % pid_file 1288*4882a593Smuzhiyun raise NotifierError(err) 1289*4882a593Smuzhiyun 1290*4882a593Smuzhiyun def fork_daemon(): 1291*4882a593Smuzhiyun # Adapted from Chad J. Schroeder's recipe 1292*4882a593Smuzhiyun # @see http://code.activestate.com/recipes/278731/ 1293*4882a593Smuzhiyun pid = os.fork() 1294*4882a593Smuzhiyun if (pid == 0): 1295*4882a593Smuzhiyun # parent 2 1296*4882a593Smuzhiyun os.setsid() 1297*4882a593Smuzhiyun pid = os.fork() 1298*4882a593Smuzhiyun if (pid == 0): 1299*4882a593Smuzhiyun # child 1300*4882a593Smuzhiyun os.chdir('/') 1301*4882a593Smuzhiyun os.umask(0o022) 1302*4882a593Smuzhiyun else: 1303*4882a593Smuzhiyun # parent 2 1304*4882a593Smuzhiyun os._exit(0) 1305*4882a593Smuzhiyun else: 1306*4882a593Smuzhiyun # parent 1 1307*4882a593Smuzhiyun os._exit(0) 1308*4882a593Smuzhiyun 1309*4882a593Smuzhiyun fd_inp = os.open(stdin, os.O_RDONLY) 1310*4882a593Smuzhiyun os.dup2(fd_inp, 0) 1311*4882a593Smuzhiyun fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0o0600) 1312*4882a593Smuzhiyun os.dup2(fd_out, 1) 1313*4882a593Smuzhiyun fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0o0600) 1314*4882a593Smuzhiyun os.dup2(fd_err, 2) 1315*4882a593Smuzhiyun 1316*4882a593Smuzhiyun # Detach task 1317*4882a593Smuzhiyun fork_daemon() 1318*4882a593Smuzhiyun 1319*4882a593Smuzhiyun # Write pid 1320*4882a593Smuzhiyun if pid_file: 1321*4882a593Smuzhiyun flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL 1322*4882a593Smuzhiyun fd_pid = os.open(pid_file, flags, 0o0600) 1323*4882a593Smuzhiyun os.write(fd_pid, bytes(str(os.getpid()) + '\n', 1324*4882a593Smuzhiyun locale.getpreferredencoding())) 1325*4882a593Smuzhiyun os.close(fd_pid) 1326*4882a593Smuzhiyun # Register unlink function 1327*4882a593Smuzhiyun atexit.register(lambda : os.unlink(pid_file)) 1328*4882a593Smuzhiyun 1329*4882a593Smuzhiyun def _sleep(self, ref_time): 1330*4882a593Smuzhiyun # Only consider sleeping if read_freq is > 0 1331*4882a593Smuzhiyun if self._read_freq > 0: 1332*4882a593Smuzhiyun cur_time = time.time() 1333*4882a593Smuzhiyun sleep_amount = self._read_freq - (cur_time - ref_time) 1334*4882a593Smuzhiyun if sleep_amount > 0: 1335*4882a593Smuzhiyun log.debug('Now sleeping %d seconds', sleep_amount) 1336*4882a593Smuzhiyun time.sleep(sleep_amount) 1337*4882a593Smuzhiyun 1338*4882a593Smuzhiyun def loop(self, callback=None, daemonize=False, **args): 1339*4882a593Smuzhiyun """ 1340*4882a593Smuzhiyun Events are read only one time every min(read_freq, timeout) 1341*4882a593Smuzhiyun seconds at best and only if the size to read is >= threshold. 1342*4882a593Smuzhiyun After this method returns it must not be called again for the same 1343*4882a593Smuzhiyun instance. 1344*4882a593Smuzhiyun 1345*4882a593Smuzhiyun @param callback: Functor called after each event processing iteration. 1346*4882a593Smuzhiyun Expects to receive the notifier object (self) as first 1347*4882a593Smuzhiyun parameter. If this function returns True the loop is 1348*4882a593Smuzhiyun immediately terminated otherwise the loop method keeps 1349*4882a593Smuzhiyun looping. 1350*4882a593Smuzhiyun @type callback: callable object or function 1351*4882a593Smuzhiyun @param daemonize: This thread is daemonized if set to True. 1352*4882a593Smuzhiyun @type daemonize: boolean 1353*4882a593Smuzhiyun @param args: Optional and relevant only if daemonize is True. Remaining 1354*4882a593Smuzhiyun keyworded arguments are directly passed to daemonize see 1355*4882a593Smuzhiyun __daemonize() method. If pid_file=None or is set to a 1356*4882a593Smuzhiyun pathname the caller must ensure the file does not exist 1357*4882a593Smuzhiyun before this method is called otherwise an exception 1358*4882a593Smuzhiyun pyinotify.NotifierError will be raised. If pid_file=False 1359*4882a593Smuzhiyun it is still daemonized but the pid is not written in any 1360*4882a593Smuzhiyun file. 1361*4882a593Smuzhiyun @type args: various 1362*4882a593Smuzhiyun """ 1363*4882a593Smuzhiyun if daemonize: 1364*4882a593Smuzhiyun self.__daemonize(**args) 1365*4882a593Smuzhiyun 1366*4882a593Smuzhiyun # Read and process events forever 1367*4882a593Smuzhiyun while 1: 1368*4882a593Smuzhiyun try: 1369*4882a593Smuzhiyun self.process_events() 1370*4882a593Smuzhiyun if (callback is not None) and (callback(self) is True): 1371*4882a593Smuzhiyun break 1372*4882a593Smuzhiyun ref_time = time.time() 1373*4882a593Smuzhiyun # check_events is blocking 1374*4882a593Smuzhiyun if self.check_events(): 1375*4882a593Smuzhiyun self._sleep(ref_time) 1376*4882a593Smuzhiyun self.read_events() 1377*4882a593Smuzhiyun except KeyboardInterrupt: 1378*4882a593Smuzhiyun # Stop monitoring if sigint is caught (Control-C). 1379*4882a593Smuzhiyun log.debug('Pyinotify stops monitoring.') 1380*4882a593Smuzhiyun break 1381*4882a593Smuzhiyun # Close internals 1382*4882a593Smuzhiyun self.stop() 1383*4882a593Smuzhiyun 1384*4882a593Smuzhiyun def stop(self): 1385*4882a593Smuzhiyun """ 1386*4882a593Smuzhiyun Close inotify's instance (close its file descriptor). 1387*4882a593Smuzhiyun It destroys all existing watches, pending events,... 1388*4882a593Smuzhiyun This method is automatically called at the end of loop(). 1389*4882a593Smuzhiyun Afterward it is invalid to access this instance. 1390*4882a593Smuzhiyun """ 1391*4882a593Smuzhiyun if self._fd is not None: 1392*4882a593Smuzhiyun self._pollobj.unregister(self._fd) 1393*4882a593Smuzhiyun os.close(self._fd) 1394*4882a593Smuzhiyun self._fd = None 1395*4882a593Smuzhiyun self._sys_proc_fun = None 1396*4882a593Smuzhiyun 1397*4882a593Smuzhiyun 1398*4882a593Smuzhiyunclass ThreadedNotifier(threading.Thread, Notifier): 1399*4882a593Smuzhiyun """ 1400*4882a593Smuzhiyun This notifier inherits from threading.Thread for instanciating a separate 1401*4882a593Smuzhiyun thread, and also inherits from Notifier, because it is a threaded notifier. 1402*4882a593Smuzhiyun 1403*4882a593Smuzhiyun Note that every functionality provided by this class is also provided 1404*4882a593Smuzhiyun through Notifier class. Moreover Notifier should be considered first because 1405*4882a593Smuzhiyun it is not threaded and could be easily daemonized. 1406*4882a593Smuzhiyun """ 1407*4882a593Smuzhiyun def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, 1408*4882a593Smuzhiyun threshold=0, timeout=None): 1409*4882a593Smuzhiyun """ 1410*4882a593Smuzhiyun Initialization, initialize base classes. read_freq, threshold and 1411*4882a593Smuzhiyun timeout parameters are used when looping. 1412*4882a593Smuzhiyun 1413*4882a593Smuzhiyun @param watch_manager: Watch Manager. 1414*4882a593Smuzhiyun @type watch_manager: WatchManager instance 1415*4882a593Smuzhiyun @param default_proc_fun: Default processing method. See base class. 1416*4882a593Smuzhiyun @type default_proc_fun: instance of ProcessEvent 1417*4882a593Smuzhiyun @param read_freq: if read_freq == 0, events are read asap, 1418*4882a593Smuzhiyun if read_freq is > 0, this thread sleeps 1419*4882a593Smuzhiyun max(0, read_freq - (timeout / 1000)) seconds. 1420*4882a593Smuzhiyun @type read_freq: int 1421*4882a593Smuzhiyun @param threshold: File descriptor will be read only if the accumulated 1422*4882a593Smuzhiyun size to read becomes >= threshold. If != 0, you likely 1423*4882a593Smuzhiyun want to use it in combination with an appropriate 1424*4882a593Smuzhiyun value set for read_freq because without that you would 1425*4882a593Smuzhiyun keep looping without really reading anything and that 1426*4882a593Smuzhiyun until the amount of events to read is >= threshold. At 1427*4882a593Smuzhiyun least with read_freq you might sleep. 1428*4882a593Smuzhiyun @type threshold: int 1429*4882a593Smuzhiyun @param timeout: see read_freq above. If provided, it must be set in 1430*4882a593Smuzhiyun milliseconds. See 1431*4882a593Smuzhiyun https://docs.python.org/3/library/select.html#select.poll.poll 1432*4882a593Smuzhiyun @type timeout: int 1433*4882a593Smuzhiyun """ 1434*4882a593Smuzhiyun # Init threading base class 1435*4882a593Smuzhiyun threading.Thread.__init__(self) 1436*4882a593Smuzhiyun # Stop condition 1437*4882a593Smuzhiyun self._stop_event = threading.Event() 1438*4882a593Smuzhiyun # Init Notifier base class 1439*4882a593Smuzhiyun Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1440*4882a593Smuzhiyun threshold, timeout) 1441*4882a593Smuzhiyun # Create a new pipe used for thread termination 1442*4882a593Smuzhiyun self._pipe = os.pipe() 1443*4882a593Smuzhiyun self._pollobj.register(self._pipe[0], select.POLLIN) 1444*4882a593Smuzhiyun 1445*4882a593Smuzhiyun def stop(self): 1446*4882a593Smuzhiyun """ 1447*4882a593Smuzhiyun Stop notifier's loop. Stop notification. Join the thread. 1448*4882a593Smuzhiyun """ 1449*4882a593Smuzhiyun self._stop_event.set() 1450*4882a593Smuzhiyun os.write(self._pipe[1], b'stop') 1451*4882a593Smuzhiyun threading.Thread.join(self) 1452*4882a593Smuzhiyun Notifier.stop(self) 1453*4882a593Smuzhiyun self._pollobj.unregister(self._pipe[0]) 1454*4882a593Smuzhiyun os.close(self._pipe[0]) 1455*4882a593Smuzhiyun os.close(self._pipe[1]) 1456*4882a593Smuzhiyun 1457*4882a593Smuzhiyun def loop(self): 1458*4882a593Smuzhiyun """ 1459*4882a593Smuzhiyun Thread's main loop. Don't meant to be called by user directly. 1460*4882a593Smuzhiyun Call inherited start() method instead. 1461*4882a593Smuzhiyun 1462*4882a593Smuzhiyun Events are read only once time every min(read_freq, timeout) 1463*4882a593Smuzhiyun seconds at best and only if the size of events to read is >= threshold. 1464*4882a593Smuzhiyun """ 1465*4882a593Smuzhiyun # When the loop must be terminated .stop() is called, 'stop' 1466*4882a593Smuzhiyun # is written to pipe fd so poll() returns and .check_events() 1467*4882a593Smuzhiyun # returns False which make evaluate the While's stop condition 1468*4882a593Smuzhiyun # ._stop_event.isSet() wich put an end to the thread's execution. 1469*4882a593Smuzhiyun while not self._stop_event.isSet(): 1470*4882a593Smuzhiyun self.process_events() 1471*4882a593Smuzhiyun ref_time = time.time() 1472*4882a593Smuzhiyun if self.check_events(): 1473*4882a593Smuzhiyun self._sleep(ref_time) 1474*4882a593Smuzhiyun self.read_events() 1475*4882a593Smuzhiyun 1476*4882a593Smuzhiyun def run(self): 1477*4882a593Smuzhiyun """ 1478*4882a593Smuzhiyun Start thread's loop: read and process events until the method 1479*4882a593Smuzhiyun stop() is called. 1480*4882a593Smuzhiyun Never call this method directly, instead call the start() method 1481*4882a593Smuzhiyun inherited from threading.Thread, which then will call run() in 1482*4882a593Smuzhiyun its turn. 1483*4882a593Smuzhiyun """ 1484*4882a593Smuzhiyun self.loop() 1485*4882a593Smuzhiyun 1486*4882a593Smuzhiyun 1487*4882a593Smuzhiyunclass TornadoAsyncNotifier(Notifier): 1488*4882a593Smuzhiyun """ 1489*4882a593Smuzhiyun Tornado ioloop adapter. 1490*4882a593Smuzhiyun 1491*4882a593Smuzhiyun """ 1492*4882a593Smuzhiyun def __init__(self, watch_manager, ioloop, callback=None, 1493*4882a593Smuzhiyun default_proc_fun=None, read_freq=0, threshold=0, timeout=None, 1494*4882a593Smuzhiyun channel_map=None): 1495*4882a593Smuzhiyun """ 1496*4882a593Smuzhiyun Note that if later you must call ioloop.close() be sure to let the 1497*4882a593Smuzhiyun default parameter to all_fds=False. 1498*4882a593Smuzhiyun 1499*4882a593Smuzhiyun See example tornado_notifier.py for an example using this notifier. 1500*4882a593Smuzhiyun 1501*4882a593Smuzhiyun @param ioloop: Tornado's IO loop. 1502*4882a593Smuzhiyun @type ioloop: tornado.ioloop.IOLoop instance. 1503*4882a593Smuzhiyun @param callback: Functor called at the end of each call to handle_read 1504*4882a593Smuzhiyun (IOLoop's read handler). Expects to receive the 1505*4882a593Smuzhiyun notifier object (self) as single parameter. 1506*4882a593Smuzhiyun @type callback: callable object or function 1507*4882a593Smuzhiyun """ 1508*4882a593Smuzhiyun self.io_loop = ioloop 1509*4882a593Smuzhiyun self.handle_read_callback = callback 1510*4882a593Smuzhiyun Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1511*4882a593Smuzhiyun threshold, timeout) 1512*4882a593Smuzhiyun ioloop.add_handler(self._fd, self.handle_read, ioloop.READ) 1513*4882a593Smuzhiyun 1514*4882a593Smuzhiyun def stop(self): 1515*4882a593Smuzhiyun self.io_loop.remove_handler(self._fd) 1516*4882a593Smuzhiyun Notifier.stop(self) 1517*4882a593Smuzhiyun 1518*4882a593Smuzhiyun def handle_read(self, *args, **kwargs): 1519*4882a593Smuzhiyun """ 1520*4882a593Smuzhiyun See comment in AsyncNotifier. 1521*4882a593Smuzhiyun 1522*4882a593Smuzhiyun """ 1523*4882a593Smuzhiyun self.read_events() 1524*4882a593Smuzhiyun self.process_events() 1525*4882a593Smuzhiyun if self.handle_read_callback is not None: 1526*4882a593Smuzhiyun self.handle_read_callback(self) 1527*4882a593Smuzhiyun 1528*4882a593Smuzhiyun 1529*4882a593Smuzhiyunclass AsyncioNotifier(Notifier): 1530*4882a593Smuzhiyun """ 1531*4882a593Smuzhiyun 1532*4882a593Smuzhiyun asyncio/trollius event loop adapter. 1533*4882a593Smuzhiyun 1534*4882a593Smuzhiyun """ 1535*4882a593Smuzhiyun def __init__(self, watch_manager, loop, callback=None, 1536*4882a593Smuzhiyun default_proc_fun=None, read_freq=0, threshold=0, timeout=None): 1537*4882a593Smuzhiyun """ 1538*4882a593Smuzhiyun 1539*4882a593Smuzhiyun See examples/asyncio_notifier.py for an example usage. 1540*4882a593Smuzhiyun 1541*4882a593Smuzhiyun @param loop: asyncio or trollius event loop instance. 1542*4882a593Smuzhiyun @type loop: asyncio.BaseEventLoop or trollius.BaseEventLoop instance. 1543*4882a593Smuzhiyun @param callback: Functor called at the end of each call to handle_read. 1544*4882a593Smuzhiyun Expects to receive the notifier object (self) as 1545*4882a593Smuzhiyun single parameter. 1546*4882a593Smuzhiyun @type callback: callable object or function 1547*4882a593Smuzhiyun 1548*4882a593Smuzhiyun """ 1549*4882a593Smuzhiyun self.loop = loop 1550*4882a593Smuzhiyun self.handle_read_callback = callback 1551*4882a593Smuzhiyun Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1552*4882a593Smuzhiyun threshold, timeout) 1553*4882a593Smuzhiyun loop.add_reader(self._fd, self.handle_read) 1554*4882a593Smuzhiyun 1555*4882a593Smuzhiyun def stop(self): 1556*4882a593Smuzhiyun self.loop.remove_reader(self._fd) 1557*4882a593Smuzhiyun Notifier.stop(self) 1558*4882a593Smuzhiyun 1559*4882a593Smuzhiyun def handle_read(self, *args, **kwargs): 1560*4882a593Smuzhiyun self.read_events() 1561*4882a593Smuzhiyun self.process_events() 1562*4882a593Smuzhiyun if self.handle_read_callback is not None: 1563*4882a593Smuzhiyun self.handle_read_callback(self) 1564*4882a593Smuzhiyun 1565*4882a593Smuzhiyun 1566*4882a593Smuzhiyunclass Watch: 1567*4882a593Smuzhiyun """ 1568*4882a593Smuzhiyun Represent a watch, i.e. a file or directory being watched. 1569*4882a593Smuzhiyun 1570*4882a593Smuzhiyun """ 1571*4882a593Smuzhiyun __slots__ = ('wd', 'path', 'mask', 'proc_fun', 'auto_add', 1572*4882a593Smuzhiyun 'exclude_filter', 'dir') 1573*4882a593Smuzhiyun 1574*4882a593Smuzhiyun def __init__(self, wd, path, mask, proc_fun, auto_add, exclude_filter): 1575*4882a593Smuzhiyun """ 1576*4882a593Smuzhiyun Initializations. 1577*4882a593Smuzhiyun 1578*4882a593Smuzhiyun @param wd: Watch descriptor. 1579*4882a593Smuzhiyun @type wd: int 1580*4882a593Smuzhiyun @param path: Path of the file or directory being watched. 1581*4882a593Smuzhiyun @type path: str 1582*4882a593Smuzhiyun @param mask: Mask. 1583*4882a593Smuzhiyun @type mask: int 1584*4882a593Smuzhiyun @param proc_fun: Processing callable object. 1585*4882a593Smuzhiyun @type proc_fun: 1586*4882a593Smuzhiyun @param auto_add: Automatically add watches on new directories. 1587*4882a593Smuzhiyun @type auto_add: bool 1588*4882a593Smuzhiyun @param exclude_filter: Boolean function, used to exclude new 1589*4882a593Smuzhiyun directories from being automatically watched. 1590*4882a593Smuzhiyun See WatchManager.__init__ 1591*4882a593Smuzhiyun @type exclude_filter: callable object 1592*4882a593Smuzhiyun """ 1593*4882a593Smuzhiyun self.wd = wd 1594*4882a593Smuzhiyun self.path = path 1595*4882a593Smuzhiyun self.mask = mask 1596*4882a593Smuzhiyun self.proc_fun = proc_fun 1597*4882a593Smuzhiyun self.auto_add = auto_add 1598*4882a593Smuzhiyun self.exclude_filter = exclude_filter 1599*4882a593Smuzhiyun self.dir = os.path.isdir(self.path) 1600*4882a593Smuzhiyun 1601*4882a593Smuzhiyun def __repr__(self): 1602*4882a593Smuzhiyun """ 1603*4882a593Smuzhiyun @return: String representation. 1604*4882a593Smuzhiyun @rtype: str 1605*4882a593Smuzhiyun """ 1606*4882a593Smuzhiyun s = ' '.join(['%s%s%s' % (output_format.field_name(attr), 1607*4882a593Smuzhiyun output_format.punctuation('='), 1608*4882a593Smuzhiyun output_format.field_value(getattr(self, 1609*4882a593Smuzhiyun attr))) \ 1610*4882a593Smuzhiyun for attr in self.__slots__ if not attr.startswith('_')]) 1611*4882a593Smuzhiyun 1612*4882a593Smuzhiyun s = '%s%s %s %s' % (output_format.punctuation('<'), 1613*4882a593Smuzhiyun output_format.class_name(self.__class__.__name__), 1614*4882a593Smuzhiyun s, 1615*4882a593Smuzhiyun output_format.punctuation('>')) 1616*4882a593Smuzhiyun return s 1617*4882a593Smuzhiyun 1618*4882a593Smuzhiyun 1619*4882a593Smuzhiyunclass ExcludeFilter: 1620*4882a593Smuzhiyun """ 1621*4882a593Smuzhiyun ExcludeFilter is an exclusion filter. 1622*4882a593Smuzhiyun """ 1623*4882a593Smuzhiyun def __init__(self, arg_lst): 1624*4882a593Smuzhiyun """ 1625*4882a593Smuzhiyun Examples: 1626*4882a593Smuzhiyun ef1 = ExcludeFilter(["/etc/rc.*", "/etc/hostname"]) 1627*4882a593Smuzhiyun ef2 = ExcludeFilter("/my/path/exclude.lst") 1628*4882a593Smuzhiyun Where exclude.lst contains: 1629*4882a593Smuzhiyun /etc/rc.* 1630*4882a593Smuzhiyun /etc/hostname 1631*4882a593Smuzhiyun 1632*4882a593Smuzhiyun Note: it is not possible to exclude a file if its encapsulating 1633*4882a593Smuzhiyun directory is itself watched. See this issue for more details 1634*4882a593Smuzhiyun https://github.com/seb-m/pyinotify/issues/31 1635*4882a593Smuzhiyun 1636*4882a593Smuzhiyun @param arg_lst: is either a list of patterns or a filename from which 1637*4882a593Smuzhiyun patterns will be loaded. 1638*4882a593Smuzhiyun @type arg_lst: list of str or str 1639*4882a593Smuzhiyun """ 1640*4882a593Smuzhiyun if isinstance(arg_lst, str): 1641*4882a593Smuzhiyun lst = self._load_patterns_from_file(arg_lst) 1642*4882a593Smuzhiyun elif isinstance(arg_lst, list): 1643*4882a593Smuzhiyun lst = arg_lst 1644*4882a593Smuzhiyun else: 1645*4882a593Smuzhiyun raise TypeError 1646*4882a593Smuzhiyun 1647*4882a593Smuzhiyun self._lregex = [] 1648*4882a593Smuzhiyun for regex in lst: 1649*4882a593Smuzhiyun self._lregex.append(re.compile(regex, re.UNICODE)) 1650*4882a593Smuzhiyun 1651*4882a593Smuzhiyun def _load_patterns_from_file(self, filename): 1652*4882a593Smuzhiyun lst = [] 1653*4882a593Smuzhiyun with open(filename, 'r') as file_obj: 1654*4882a593Smuzhiyun for line in file_obj.readlines(): 1655*4882a593Smuzhiyun # Trim leading an trailing whitespaces 1656*4882a593Smuzhiyun pattern = line.strip() 1657*4882a593Smuzhiyun if not pattern or pattern.startswith('#'): 1658*4882a593Smuzhiyun continue 1659*4882a593Smuzhiyun lst.append(pattern) 1660*4882a593Smuzhiyun return lst 1661*4882a593Smuzhiyun 1662*4882a593Smuzhiyun def _match(self, regex, path): 1663*4882a593Smuzhiyun return regex.match(path) is not None 1664*4882a593Smuzhiyun 1665*4882a593Smuzhiyun def __call__(self, path): 1666*4882a593Smuzhiyun """ 1667*4882a593Smuzhiyun @param path: Path to match against provided regexps. 1668*4882a593Smuzhiyun @type path: str 1669*4882a593Smuzhiyun @return: Return True if path has been matched and should 1670*4882a593Smuzhiyun be excluded, False otherwise. 1671*4882a593Smuzhiyun @rtype: bool 1672*4882a593Smuzhiyun """ 1673*4882a593Smuzhiyun for regex in self._lregex: 1674*4882a593Smuzhiyun if self._match(regex, path): 1675*4882a593Smuzhiyun return True 1676*4882a593Smuzhiyun return False 1677*4882a593Smuzhiyun 1678*4882a593Smuzhiyun 1679*4882a593Smuzhiyunclass WatchManagerError(Exception): 1680*4882a593Smuzhiyun """ 1681*4882a593Smuzhiyun WatchManager Exception. Raised on error encountered on watches 1682*4882a593Smuzhiyun operations. 1683*4882a593Smuzhiyun """ 1684*4882a593Smuzhiyun def __init__(self, msg, wmd): 1685*4882a593Smuzhiyun """ 1686*4882a593Smuzhiyun @param msg: Exception string's description. 1687*4882a593Smuzhiyun @type msg: string 1688*4882a593Smuzhiyun @param wmd: This dictionary contains the wd assigned to paths of the 1689*4882a593Smuzhiyun same call for which watches were successfully added. 1690*4882a593Smuzhiyun @type wmd: dict 1691*4882a593Smuzhiyun """ 1692*4882a593Smuzhiyun self.wmd = wmd 1693*4882a593Smuzhiyun Exception.__init__(self, msg) 1694*4882a593Smuzhiyun 1695*4882a593Smuzhiyun 1696*4882a593Smuzhiyunclass WatchManager: 1697*4882a593Smuzhiyun """ 1698*4882a593Smuzhiyun Provide operations for watching files and directories. Its internal 1699*4882a593Smuzhiyun dictionary is used to reference watched items. When used inside 1700*4882a593Smuzhiyun threaded code, one must instanciate as many WatchManager instances as 1701*4882a593Smuzhiyun there are ThreadedNotifier instances. 1702*4882a593Smuzhiyun 1703*4882a593Smuzhiyun """ 1704*4882a593Smuzhiyun def __init__(self, exclude_filter=lambda path: False): 1705*4882a593Smuzhiyun """ 1706*4882a593Smuzhiyun Initialization: init inotify, init watch manager dictionary. 1707*4882a593Smuzhiyun Raise OSError if initialization fails, raise InotifyBindingNotFoundError 1708*4882a593Smuzhiyun if no inotify binding was found (through ctypes or from direct access to 1709*4882a593Smuzhiyun syscalls). 1710*4882a593Smuzhiyun 1711*4882a593Smuzhiyun @param exclude_filter: boolean function, returns True if current 1712*4882a593Smuzhiyun path must be excluded from being watched. 1713*4882a593Smuzhiyun Convenient for providing a common exclusion 1714*4882a593Smuzhiyun filter for every call to add_watch. 1715*4882a593Smuzhiyun @type exclude_filter: callable object 1716*4882a593Smuzhiyun """ 1717*4882a593Smuzhiyun self._ignore_events = False 1718*4882a593Smuzhiyun self._exclude_filter = exclude_filter 1719*4882a593Smuzhiyun self._wmd = {} # watch dict key: watch descriptor, value: watch 1720*4882a593Smuzhiyun 1721*4882a593Smuzhiyun self._inotify_wrapper = INotifyWrapper.create() 1722*4882a593Smuzhiyun if self._inotify_wrapper is None: 1723*4882a593Smuzhiyun raise InotifyBindingNotFoundError() 1724*4882a593Smuzhiyun 1725*4882a593Smuzhiyun self._fd = self._inotify_wrapper.inotify_init() # file descriptor 1726*4882a593Smuzhiyun if self._fd < 0: 1727*4882a593Smuzhiyun err = 'Cannot initialize new instance of inotify, %s' 1728*4882a593Smuzhiyun raise OSError(err % self._inotify_wrapper.str_errno()) 1729*4882a593Smuzhiyun 1730*4882a593Smuzhiyun def close(self): 1731*4882a593Smuzhiyun """ 1732*4882a593Smuzhiyun Close inotify's file descriptor, this action will also automatically 1733*4882a593Smuzhiyun remove (i.e. stop watching) all its associated watch descriptors. 1734*4882a593Smuzhiyun After a call to this method the WatchManager's instance become useless 1735*4882a593Smuzhiyun and cannot be reused, a new instance must then be instanciated. It 1736*4882a593Smuzhiyun makes sense to call this method in few situations for instance if 1737*4882a593Smuzhiyun several independant WatchManager must be instanciated or if all watches 1738*4882a593Smuzhiyun must be removed and no other watches need to be added. 1739*4882a593Smuzhiyun """ 1740*4882a593Smuzhiyun os.close(self._fd) 1741*4882a593Smuzhiyun 1742*4882a593Smuzhiyun def get_fd(self): 1743*4882a593Smuzhiyun """ 1744*4882a593Smuzhiyun Return assigned inotify's file descriptor. 1745*4882a593Smuzhiyun 1746*4882a593Smuzhiyun @return: File descriptor. 1747*4882a593Smuzhiyun @rtype: int 1748*4882a593Smuzhiyun """ 1749*4882a593Smuzhiyun return self._fd 1750*4882a593Smuzhiyun 1751*4882a593Smuzhiyun def get_watch(self, wd): 1752*4882a593Smuzhiyun """ 1753*4882a593Smuzhiyun Get watch from provided watch descriptor wd. 1754*4882a593Smuzhiyun 1755*4882a593Smuzhiyun @param wd: Watch descriptor. 1756*4882a593Smuzhiyun @type wd: int 1757*4882a593Smuzhiyun """ 1758*4882a593Smuzhiyun return self._wmd.get(wd) 1759*4882a593Smuzhiyun 1760*4882a593Smuzhiyun def del_watch(self, wd): 1761*4882a593Smuzhiyun """ 1762*4882a593Smuzhiyun Remove watch entry associated to watch descriptor wd. 1763*4882a593Smuzhiyun 1764*4882a593Smuzhiyun @param wd: Watch descriptor. 1765*4882a593Smuzhiyun @type wd: int 1766*4882a593Smuzhiyun """ 1767*4882a593Smuzhiyun try: 1768*4882a593Smuzhiyun del self._wmd[wd] 1769*4882a593Smuzhiyun except KeyError as err: 1770*4882a593Smuzhiyun log.error('Cannot delete unknown watch descriptor %s' % str(err)) 1771*4882a593Smuzhiyun 1772*4882a593Smuzhiyun @property 1773*4882a593Smuzhiyun def watches(self): 1774*4882a593Smuzhiyun """ 1775*4882a593Smuzhiyun Get a reference on the internal watch manager dictionary. 1776*4882a593Smuzhiyun 1777*4882a593Smuzhiyun @return: Internal watch manager dictionary. 1778*4882a593Smuzhiyun @rtype: dict 1779*4882a593Smuzhiyun """ 1780*4882a593Smuzhiyun return self._wmd 1781*4882a593Smuzhiyun 1782*4882a593Smuzhiyun def __format_path(self, path): 1783*4882a593Smuzhiyun """ 1784*4882a593Smuzhiyun Format path to its internal (stored in watch manager) representation. 1785*4882a593Smuzhiyun """ 1786*4882a593Smuzhiyun # path must be a unicode string (str) and is just normalized. 1787*4882a593Smuzhiyun return os.path.normpath(path) 1788*4882a593Smuzhiyun 1789*4882a593Smuzhiyun def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter): 1790*4882a593Smuzhiyun """ 1791*4882a593Smuzhiyun Add a watch on path, build a Watch object and insert it in the 1792*4882a593Smuzhiyun watch manager dictionary. Return the wd value. 1793*4882a593Smuzhiyun """ 1794*4882a593Smuzhiyun path = self.__format_path(path) 1795*4882a593Smuzhiyun if auto_add and not mask & IN_CREATE: 1796*4882a593Smuzhiyun mask |= IN_CREATE 1797*4882a593Smuzhiyun wd = self._inotify_wrapper.inotify_add_watch(self._fd, path, mask) 1798*4882a593Smuzhiyun if wd < 0: 1799*4882a593Smuzhiyun return wd 1800*4882a593Smuzhiyun watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun, 1801*4882a593Smuzhiyun auto_add=auto_add, exclude_filter=exclude_filter) 1802*4882a593Smuzhiyun # wd are _always_ indexed with their original unicode paths in wmd. 1803*4882a593Smuzhiyun self._wmd[wd] = watch 1804*4882a593Smuzhiyun log.debug('New %s', watch) 1805*4882a593Smuzhiyun return wd 1806*4882a593Smuzhiyun 1807*4882a593Smuzhiyun def __glob(self, path, do_glob): 1808*4882a593Smuzhiyun if do_glob: 1809*4882a593Smuzhiyun return glob.iglob(path) 1810*4882a593Smuzhiyun else: 1811*4882a593Smuzhiyun return [path] 1812*4882a593Smuzhiyun 1813*4882a593Smuzhiyun def add_watch(self, path, mask, proc_fun=None, rec=False, 1814*4882a593Smuzhiyun auto_add=False, do_glob=False, quiet=True, 1815*4882a593Smuzhiyun exclude_filter=None): 1816*4882a593Smuzhiyun """ 1817*4882a593Smuzhiyun Add watch(s) on the provided |path|(s) with associated |mask| flag 1818*4882a593Smuzhiyun value and optionally with a processing |proc_fun| function and 1819*4882a593Smuzhiyun recursive flag |rec| set to True. 1820*4882a593Smuzhiyun All |path| components _must_ be str (i.e. unicode) objects. 1821*4882a593Smuzhiyun If |path| is already watched it is ignored, but if it is called with 1822*4882a593Smuzhiyun option rec=True a watch is put on each one of its not-watched 1823*4882a593Smuzhiyun subdirectory. 1824*4882a593Smuzhiyun 1825*4882a593Smuzhiyun @param path: Path to watch, the path can either be a file or a 1826*4882a593Smuzhiyun directory. Also accepts a sequence (list) of paths. 1827*4882a593Smuzhiyun @type path: string or list of strings 1828*4882a593Smuzhiyun @param mask: Bitmask of events. 1829*4882a593Smuzhiyun @type mask: int 1830*4882a593Smuzhiyun @param proc_fun: Processing object. 1831*4882a593Smuzhiyun @type proc_fun: function or ProcessEvent instance or instance of 1832*4882a593Smuzhiyun one of its subclasses or callable object. 1833*4882a593Smuzhiyun @param rec: Recursively add watches from path on all its 1834*4882a593Smuzhiyun subdirectories, set to False by default (doesn't 1835*4882a593Smuzhiyun follows symlinks in any case). 1836*4882a593Smuzhiyun @type rec: bool 1837*4882a593Smuzhiyun @param auto_add: Automatically add watches on newly created 1838*4882a593Smuzhiyun directories in watched parent |path| directory. 1839*4882a593Smuzhiyun If |auto_add| is True, IN_CREATE is ored with |mask| 1840*4882a593Smuzhiyun when the watch is added. 1841*4882a593Smuzhiyun @type auto_add: bool 1842*4882a593Smuzhiyun @param do_glob: Do globbing on pathname (see standard globbing 1843*4882a593Smuzhiyun module for more informations). 1844*4882a593Smuzhiyun @type do_glob: bool 1845*4882a593Smuzhiyun @param quiet: if False raises a WatchManagerError exception on 1846*4882a593Smuzhiyun error. See example not_quiet.py. 1847*4882a593Smuzhiyun @type quiet: bool 1848*4882a593Smuzhiyun @param exclude_filter: predicate (boolean function), which returns 1849*4882a593Smuzhiyun True if the current path must be excluded 1850*4882a593Smuzhiyun from being watched. This argument has 1851*4882a593Smuzhiyun precedence over exclude_filter passed to 1852*4882a593Smuzhiyun the class' constructor. 1853*4882a593Smuzhiyun @type exclude_filter: callable object 1854*4882a593Smuzhiyun @return: dict of paths associated to watch descriptors. A wd value 1855*4882a593Smuzhiyun is positive if the watch was added sucessfully, otherwise 1856*4882a593Smuzhiyun the value is negative. If the path was invalid or was already 1857*4882a593Smuzhiyun watched it is not included into this returned dictionary. 1858*4882a593Smuzhiyun @rtype: dict of {str: int} 1859*4882a593Smuzhiyun """ 1860*4882a593Smuzhiyun ret_ = {} # return {path: wd, ...} 1861*4882a593Smuzhiyun 1862*4882a593Smuzhiyun if exclude_filter is None: 1863*4882a593Smuzhiyun exclude_filter = self._exclude_filter 1864*4882a593Smuzhiyun 1865*4882a593Smuzhiyun # normalize args as list elements 1866*4882a593Smuzhiyun for npath in self.__format_param(path): 1867*4882a593Smuzhiyun # Require that path be a unicode string 1868*4882a593Smuzhiyun if not isinstance(npath, str): 1869*4882a593Smuzhiyun ret_[path] = -3 1870*4882a593Smuzhiyun continue 1871*4882a593Smuzhiyun 1872*4882a593Smuzhiyun # unix pathname pattern expansion 1873*4882a593Smuzhiyun for apath in self.__glob(npath, do_glob): 1874*4882a593Smuzhiyun # recursively list subdirs according to rec param 1875*4882a593Smuzhiyun for rpath in self.__walk_rec(apath, rec): 1876*4882a593Smuzhiyun if not exclude_filter(rpath): 1877*4882a593Smuzhiyun wd = ret_[rpath] = self.__add_watch(rpath, mask, 1878*4882a593Smuzhiyun proc_fun, 1879*4882a593Smuzhiyun auto_add, 1880*4882a593Smuzhiyun exclude_filter) 1881*4882a593Smuzhiyun if wd < 0: 1882*4882a593Smuzhiyun err = ('add_watch: cannot watch %s WD=%d, %s' % \ 1883*4882a593Smuzhiyun (rpath, wd, 1884*4882a593Smuzhiyun self._inotify_wrapper.str_errno())) 1885*4882a593Smuzhiyun if quiet: 1886*4882a593Smuzhiyun log.error(err) 1887*4882a593Smuzhiyun else: 1888*4882a593Smuzhiyun raise WatchManagerError(err, ret_) 1889*4882a593Smuzhiyun else: 1890*4882a593Smuzhiyun # Let's say -2 means 'explicitely excluded 1891*4882a593Smuzhiyun # from watching'. 1892*4882a593Smuzhiyun ret_[rpath] = -2 1893*4882a593Smuzhiyun return ret_ 1894*4882a593Smuzhiyun 1895*4882a593Smuzhiyun def __get_sub_rec(self, lpath): 1896*4882a593Smuzhiyun """ 1897*4882a593Smuzhiyun Get every wd from self._wmd if its path is under the path of 1898*4882a593Smuzhiyun one (at least) of those in lpath. Doesn't follow symlinks. 1899*4882a593Smuzhiyun 1900*4882a593Smuzhiyun @param lpath: list of watch descriptor 1901*4882a593Smuzhiyun @type lpath: list of int 1902*4882a593Smuzhiyun @return: list of watch descriptor 1903*4882a593Smuzhiyun @rtype: list of int 1904*4882a593Smuzhiyun """ 1905*4882a593Smuzhiyun for d in lpath: 1906*4882a593Smuzhiyun root = self.get_path(d) 1907*4882a593Smuzhiyun if root is not None: 1908*4882a593Smuzhiyun # always keep root 1909*4882a593Smuzhiyun yield d 1910*4882a593Smuzhiyun else: 1911*4882a593Smuzhiyun # if invalid 1912*4882a593Smuzhiyun continue 1913*4882a593Smuzhiyun 1914*4882a593Smuzhiyun # nothing else to expect 1915*4882a593Smuzhiyun if not os.path.isdir(root): 1916*4882a593Smuzhiyun continue 1917*4882a593Smuzhiyun 1918*4882a593Smuzhiyun # normalization 1919*4882a593Smuzhiyun root = os.path.normpath(root) 1920*4882a593Smuzhiyun # recursion 1921*4882a593Smuzhiyun lend = len(root) 1922*4882a593Smuzhiyun for iwd in self._wmd.items(): 1923*4882a593Smuzhiyun cur = iwd[1].path 1924*4882a593Smuzhiyun pref = os.path.commonprefix([root, cur]) 1925*4882a593Smuzhiyun if root == os.sep or (len(pref) == lend and \ 1926*4882a593Smuzhiyun len(cur) > lend and \ 1927*4882a593Smuzhiyun cur[lend] == os.sep): 1928*4882a593Smuzhiyun yield iwd[1].wd 1929*4882a593Smuzhiyun 1930*4882a593Smuzhiyun def update_watch(self, wd, mask=None, proc_fun=None, rec=False, 1931*4882a593Smuzhiyun auto_add=False, quiet=True): 1932*4882a593Smuzhiyun """ 1933*4882a593Smuzhiyun Update existing watch descriptors |wd|. The |mask| value, the 1934*4882a593Smuzhiyun processing object |proc_fun|, the recursive param |rec| and the 1935*4882a593Smuzhiyun |auto_add| and |quiet| flags can all be updated. 1936*4882a593Smuzhiyun 1937*4882a593Smuzhiyun @param wd: Watch Descriptor to update. Also accepts a list of 1938*4882a593Smuzhiyun watch descriptors. 1939*4882a593Smuzhiyun @type wd: int or list of int 1940*4882a593Smuzhiyun @param mask: Optional new bitmask of events. 1941*4882a593Smuzhiyun @type mask: int 1942*4882a593Smuzhiyun @param proc_fun: Optional new processing function. 1943*4882a593Smuzhiyun @type proc_fun: function or ProcessEvent instance or instance of 1944*4882a593Smuzhiyun one of its subclasses or callable object. 1945*4882a593Smuzhiyun @param rec: Optionally adds watches recursively on all 1946*4882a593Smuzhiyun subdirectories contained into |wd| directory. 1947*4882a593Smuzhiyun @type rec: bool 1948*4882a593Smuzhiyun @param auto_add: Automatically adds watches on newly created 1949*4882a593Smuzhiyun directories in the watch's path corresponding to |wd|. 1950*4882a593Smuzhiyun If |auto_add| is True, IN_CREATE is ored with |mask| 1951*4882a593Smuzhiyun when the watch is updated. 1952*4882a593Smuzhiyun @type auto_add: bool 1953*4882a593Smuzhiyun @param quiet: If False raises a WatchManagerError exception on 1954*4882a593Smuzhiyun error. See example not_quiet.py 1955*4882a593Smuzhiyun @type quiet: bool 1956*4882a593Smuzhiyun @return: dict of watch descriptors associated to booleans values. 1957*4882a593Smuzhiyun True if the corresponding wd has been successfully 1958*4882a593Smuzhiyun updated, False otherwise. 1959*4882a593Smuzhiyun @rtype: dict of {int: bool} 1960*4882a593Smuzhiyun """ 1961*4882a593Smuzhiyun lwd = self.__format_param(wd) 1962*4882a593Smuzhiyun if rec: 1963*4882a593Smuzhiyun lwd = self.__get_sub_rec(lwd) 1964*4882a593Smuzhiyun 1965*4882a593Smuzhiyun ret_ = {} # return {wd: bool, ...} 1966*4882a593Smuzhiyun for awd in lwd: 1967*4882a593Smuzhiyun apath = self.get_path(awd) 1968*4882a593Smuzhiyun if not apath or awd < 0: 1969*4882a593Smuzhiyun err = 'update_watch: invalid WD=%d' % awd 1970*4882a593Smuzhiyun if quiet: 1971*4882a593Smuzhiyun log.error(err) 1972*4882a593Smuzhiyun continue 1973*4882a593Smuzhiyun raise WatchManagerError(err, ret_) 1974*4882a593Smuzhiyun 1975*4882a593Smuzhiyun if mask: 1976*4882a593Smuzhiyun wd_ = self._inotify_wrapper.inotify_add_watch(self._fd, apath, 1977*4882a593Smuzhiyun mask) 1978*4882a593Smuzhiyun if wd_ < 0: 1979*4882a593Smuzhiyun ret_[awd] = False 1980*4882a593Smuzhiyun err = ('update_watch: cannot update %s WD=%d, %s' % \ 1981*4882a593Smuzhiyun (apath, wd_, self._inotify_wrapper.str_errno())) 1982*4882a593Smuzhiyun if quiet: 1983*4882a593Smuzhiyun log.error(err) 1984*4882a593Smuzhiyun continue 1985*4882a593Smuzhiyun raise WatchManagerError(err, ret_) 1986*4882a593Smuzhiyun 1987*4882a593Smuzhiyun assert(awd == wd_) 1988*4882a593Smuzhiyun 1989*4882a593Smuzhiyun if proc_fun or auto_add: 1990*4882a593Smuzhiyun watch_ = self._wmd[awd] 1991*4882a593Smuzhiyun 1992*4882a593Smuzhiyun if proc_fun: 1993*4882a593Smuzhiyun watch_.proc_fun = proc_fun 1994*4882a593Smuzhiyun 1995*4882a593Smuzhiyun if auto_add: 1996*4882a593Smuzhiyun watch_.auto_add = auto_add 1997*4882a593Smuzhiyun 1998*4882a593Smuzhiyun ret_[awd] = True 1999*4882a593Smuzhiyun log.debug('Updated watch - %s', self._wmd[awd]) 2000*4882a593Smuzhiyun return ret_ 2001*4882a593Smuzhiyun 2002*4882a593Smuzhiyun def __format_param(self, param): 2003*4882a593Smuzhiyun """ 2004*4882a593Smuzhiyun @param param: Parameter. 2005*4882a593Smuzhiyun @type param: string or int 2006*4882a593Smuzhiyun @return: wrap param. 2007*4882a593Smuzhiyun @rtype: list of type(param) 2008*4882a593Smuzhiyun """ 2009*4882a593Smuzhiyun if isinstance(param, list): 2010*4882a593Smuzhiyun for p_ in param: 2011*4882a593Smuzhiyun yield p_ 2012*4882a593Smuzhiyun else: 2013*4882a593Smuzhiyun yield param 2014*4882a593Smuzhiyun 2015*4882a593Smuzhiyun def get_wd(self, path): 2016*4882a593Smuzhiyun """ 2017*4882a593Smuzhiyun Returns the watch descriptor associated to path. This method 2018*4882a593Smuzhiyun presents a prohibitive cost, always prefer to keep the WD 2019*4882a593Smuzhiyun returned by add_watch(). If the path is unknown it returns None. 2020*4882a593Smuzhiyun 2021*4882a593Smuzhiyun @param path: Path. 2022*4882a593Smuzhiyun @type path: str 2023*4882a593Smuzhiyun @return: WD or None. 2024*4882a593Smuzhiyun @rtype: int or None 2025*4882a593Smuzhiyun """ 2026*4882a593Smuzhiyun path = self.__format_path(path) 2027*4882a593Smuzhiyun for iwd in self._wmd.items(): 2028*4882a593Smuzhiyun if iwd[1].path == path: 2029*4882a593Smuzhiyun return iwd[0] 2030*4882a593Smuzhiyun 2031*4882a593Smuzhiyun def get_path(self, wd): 2032*4882a593Smuzhiyun """ 2033*4882a593Smuzhiyun Returns the path associated to WD, if WD is unknown it returns None. 2034*4882a593Smuzhiyun 2035*4882a593Smuzhiyun @param wd: Watch descriptor. 2036*4882a593Smuzhiyun @type wd: int 2037*4882a593Smuzhiyun @return: Path or None. 2038*4882a593Smuzhiyun @rtype: string or None 2039*4882a593Smuzhiyun """ 2040*4882a593Smuzhiyun watch_ = self._wmd.get(wd) 2041*4882a593Smuzhiyun if watch_ is not None: 2042*4882a593Smuzhiyun return watch_.path 2043*4882a593Smuzhiyun 2044*4882a593Smuzhiyun def __walk_rec(self, top, rec): 2045*4882a593Smuzhiyun """ 2046*4882a593Smuzhiyun Yields each subdirectories of top, doesn't follow symlinks. 2047*4882a593Smuzhiyun If rec is false, only yield top. 2048*4882a593Smuzhiyun 2049*4882a593Smuzhiyun @param top: root directory. 2050*4882a593Smuzhiyun @type top: string 2051*4882a593Smuzhiyun @param rec: recursive flag. 2052*4882a593Smuzhiyun @type rec: bool 2053*4882a593Smuzhiyun @return: path of one subdirectory. 2054*4882a593Smuzhiyun @rtype: string 2055*4882a593Smuzhiyun """ 2056*4882a593Smuzhiyun if not rec or os.path.islink(top) or not os.path.isdir(top): 2057*4882a593Smuzhiyun yield top 2058*4882a593Smuzhiyun else: 2059*4882a593Smuzhiyun for root, dirs, files in os.walk(top): 2060*4882a593Smuzhiyun yield root 2061*4882a593Smuzhiyun 2062*4882a593Smuzhiyun def rm_watch(self, wd, rec=False, quiet=True): 2063*4882a593Smuzhiyun """ 2064*4882a593Smuzhiyun Removes watch(s). 2065*4882a593Smuzhiyun 2066*4882a593Smuzhiyun @param wd: Watch Descriptor of the file or directory to unwatch. 2067*4882a593Smuzhiyun Also accepts a list of WDs. 2068*4882a593Smuzhiyun @type wd: int or list of int. 2069*4882a593Smuzhiyun @param rec: Recursively removes watches on every already watched 2070*4882a593Smuzhiyun subdirectories and subfiles. 2071*4882a593Smuzhiyun @type rec: bool 2072*4882a593Smuzhiyun @param quiet: If False raises a WatchManagerError exception on 2073*4882a593Smuzhiyun error. See example not_quiet.py 2074*4882a593Smuzhiyun @type quiet: bool 2075*4882a593Smuzhiyun @return: dict of watch descriptors associated to booleans values. 2076*4882a593Smuzhiyun True if the corresponding wd has been successfully 2077*4882a593Smuzhiyun removed, False otherwise. 2078*4882a593Smuzhiyun @rtype: dict of {int: bool} 2079*4882a593Smuzhiyun """ 2080*4882a593Smuzhiyun lwd = self.__format_param(wd) 2081*4882a593Smuzhiyun if rec: 2082*4882a593Smuzhiyun lwd = self.__get_sub_rec(lwd) 2083*4882a593Smuzhiyun 2084*4882a593Smuzhiyun ret_ = {} # return {wd: bool, ...} 2085*4882a593Smuzhiyun for awd in lwd: 2086*4882a593Smuzhiyun # remove watch 2087*4882a593Smuzhiyun wd_ = self._inotify_wrapper.inotify_rm_watch(self._fd, awd) 2088*4882a593Smuzhiyun if wd_ < 0: 2089*4882a593Smuzhiyun ret_[awd] = False 2090*4882a593Smuzhiyun err = ('rm_watch: cannot remove WD=%d, %s' % \ 2091*4882a593Smuzhiyun (awd, self._inotify_wrapper.str_errno())) 2092*4882a593Smuzhiyun if quiet: 2093*4882a593Smuzhiyun log.error(err) 2094*4882a593Smuzhiyun continue 2095*4882a593Smuzhiyun raise WatchManagerError(err, ret_) 2096*4882a593Smuzhiyun 2097*4882a593Smuzhiyun # Remove watch from our dictionary 2098*4882a593Smuzhiyun if awd in self._wmd: 2099*4882a593Smuzhiyun del self._wmd[awd] 2100*4882a593Smuzhiyun ret_[awd] = True 2101*4882a593Smuzhiyun log.debug('Watch WD=%d (%s) removed', awd, self.get_path(awd)) 2102*4882a593Smuzhiyun return ret_ 2103*4882a593Smuzhiyun 2104*4882a593Smuzhiyun 2105*4882a593Smuzhiyun def watch_transient_file(self, filename, mask, proc_class): 2106*4882a593Smuzhiyun """ 2107*4882a593Smuzhiyun Watch a transient file, which will be created and deleted frequently 2108*4882a593Smuzhiyun over time (e.g. pid file). 2109*4882a593Smuzhiyun 2110*4882a593Smuzhiyun @attention: Currently under the call to this function it is not 2111*4882a593Smuzhiyun possible to correctly watch the events triggered into the same 2112*4882a593Smuzhiyun base directory than the directory where is located this watched 2113*4882a593Smuzhiyun transient file. For instance it would be wrong to make these 2114*4882a593Smuzhiyun two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...) 2115*4882a593Smuzhiyun and wm.add_watch('/var/run/', ...) 2116*4882a593Smuzhiyun 2117*4882a593Smuzhiyun @param filename: Filename. 2118*4882a593Smuzhiyun @type filename: string 2119*4882a593Smuzhiyun @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE. 2120*4882a593Smuzhiyun @type mask: int 2121*4882a593Smuzhiyun @param proc_class: ProcessEvent (or of one of its subclass), beware of 2122*4882a593Smuzhiyun accepting a ProcessEvent's instance as argument into 2123*4882a593Smuzhiyun __init__, see transient_file.py example for more 2124*4882a593Smuzhiyun details. 2125*4882a593Smuzhiyun @type proc_class: ProcessEvent's instance or of one of its subclasses. 2126*4882a593Smuzhiyun @return: Same as add_watch(). 2127*4882a593Smuzhiyun @rtype: Same as add_watch(). 2128*4882a593Smuzhiyun """ 2129*4882a593Smuzhiyun dirname = os.path.dirname(filename) 2130*4882a593Smuzhiyun if dirname == '': 2131*4882a593Smuzhiyun return {} # Maintains coherence with add_watch() 2132*4882a593Smuzhiyun basename = os.path.basename(filename) 2133*4882a593Smuzhiyun # Assuming we are watching at least for IN_CREATE and IN_DELETE 2134*4882a593Smuzhiyun mask |= IN_CREATE | IN_DELETE 2135*4882a593Smuzhiyun 2136*4882a593Smuzhiyun def cmp_name(event): 2137*4882a593Smuzhiyun if getattr(event, 'name') is None: 2138*4882a593Smuzhiyun return False 2139*4882a593Smuzhiyun return basename == event.name 2140*4882a593Smuzhiyun return self.add_watch(dirname, mask, 2141*4882a593Smuzhiyun proc_fun=proc_class(ChainIfTrue(func=cmp_name)), 2142*4882a593Smuzhiyun rec=False, 2143*4882a593Smuzhiyun auto_add=False, do_glob=False, 2144*4882a593Smuzhiyun exclude_filter=lambda path: False) 2145*4882a593Smuzhiyun 2146*4882a593Smuzhiyun def get_ignore_events(self): 2147*4882a593Smuzhiyun return self._ignore_events 2148*4882a593Smuzhiyun 2149*4882a593Smuzhiyun def set_ignore_events(self, nval): 2150*4882a593Smuzhiyun self._ignore_events = nval 2151*4882a593Smuzhiyun 2152*4882a593Smuzhiyun ignore_events = property(get_ignore_events, set_ignore_events, 2153*4882a593Smuzhiyun "Make watch manager ignoring new events.") 2154*4882a593Smuzhiyun 2155*4882a593Smuzhiyun 2156*4882a593Smuzhiyunclass RawOutputFormat: 2157*4882a593Smuzhiyun """ 2158*4882a593Smuzhiyun Format string representations. 2159*4882a593Smuzhiyun """ 2160*4882a593Smuzhiyun def __init__(self, format=None): 2161*4882a593Smuzhiyun self.format = format or {} 2162*4882a593Smuzhiyun 2163*4882a593Smuzhiyun def simple(self, s, attribute): 2164*4882a593Smuzhiyun if not isinstance(s, str): 2165*4882a593Smuzhiyun s = str(s) 2166*4882a593Smuzhiyun return (self.format.get(attribute, '') + s + 2167*4882a593Smuzhiyun self.format.get('normal', '')) 2168*4882a593Smuzhiyun 2169*4882a593Smuzhiyun def punctuation(self, s): 2170*4882a593Smuzhiyun """Punctuation color.""" 2171*4882a593Smuzhiyun return self.simple(s, 'normal') 2172*4882a593Smuzhiyun 2173*4882a593Smuzhiyun def field_value(self, s): 2174*4882a593Smuzhiyun """Field value color.""" 2175*4882a593Smuzhiyun return self.simple(s, 'purple') 2176*4882a593Smuzhiyun 2177*4882a593Smuzhiyun def field_name(self, s): 2178*4882a593Smuzhiyun """Field name color.""" 2179*4882a593Smuzhiyun return self.simple(s, 'blue') 2180*4882a593Smuzhiyun 2181*4882a593Smuzhiyun def class_name(self, s): 2182*4882a593Smuzhiyun """Class name color.""" 2183*4882a593Smuzhiyun return self.format.get('red', '') + self.simple(s, 'bold') 2184*4882a593Smuzhiyun 2185*4882a593Smuzhiyunoutput_format = RawOutputFormat() 2186*4882a593Smuzhiyun 2187*4882a593Smuzhiyunclass ColoredOutputFormat(RawOutputFormat): 2188*4882a593Smuzhiyun """ 2189*4882a593Smuzhiyun Format colored string representations. 2190*4882a593Smuzhiyun """ 2191*4882a593Smuzhiyun def __init__(self): 2192*4882a593Smuzhiyun f = {'normal': '\033[0m', 2193*4882a593Smuzhiyun 'black': '\033[30m', 2194*4882a593Smuzhiyun 'red': '\033[31m', 2195*4882a593Smuzhiyun 'green': '\033[32m', 2196*4882a593Smuzhiyun 'yellow': '\033[33m', 2197*4882a593Smuzhiyun 'blue': '\033[34m', 2198*4882a593Smuzhiyun 'purple': '\033[35m', 2199*4882a593Smuzhiyun 'cyan': '\033[36m', 2200*4882a593Smuzhiyun 'bold': '\033[1m', 2201*4882a593Smuzhiyun 'uline': '\033[4m', 2202*4882a593Smuzhiyun 'blink': '\033[5m', 2203*4882a593Smuzhiyun 'invert': '\033[7m'} 2204*4882a593Smuzhiyun RawOutputFormat.__init__(self, f) 2205*4882a593Smuzhiyun 2206*4882a593Smuzhiyun 2207*4882a593Smuzhiyundef compatibility_mode(): 2208*4882a593Smuzhiyun """ 2209*4882a593Smuzhiyun Use this function to turn on the compatibility mode. The compatibility 2210*4882a593Smuzhiyun mode is used to improve compatibility with Pyinotify 0.7.1 (or older) 2211*4882a593Smuzhiyun programs. The compatibility mode provides additional variables 'is_dir', 2212*4882a593Smuzhiyun 'event_name', 'EventsCodes.IN_*' and 'EventsCodes.ALL_EVENTS' as 2213*4882a593Smuzhiyun Pyinotify 0.7.1 provided. Do not call this function from new programs!! 2214*4882a593Smuzhiyun Especially if there are developped for Pyinotify >= 0.8.x. 2215*4882a593Smuzhiyun """ 2216*4882a593Smuzhiyun setattr(EventsCodes, 'ALL_EVENTS', ALL_EVENTS) 2217*4882a593Smuzhiyun for evname in globals(): 2218*4882a593Smuzhiyun if evname.startswith('IN_'): 2219*4882a593Smuzhiyun setattr(EventsCodes, evname, globals()[evname]) 2220*4882a593Smuzhiyun global COMPATIBILITY_MODE 2221*4882a593Smuzhiyun COMPATIBILITY_MODE = True 2222*4882a593Smuzhiyun 2223*4882a593Smuzhiyun 2224*4882a593Smuzhiyundef command_line(): 2225*4882a593Smuzhiyun """ 2226*4882a593Smuzhiyun By default the watched path is '/tmp' and all types of events are 2227*4882a593Smuzhiyun monitored. Events monitoring serves forever, type c^c to stop it. 2228*4882a593Smuzhiyun """ 2229*4882a593Smuzhiyun from optparse import OptionParser 2230*4882a593Smuzhiyun 2231*4882a593Smuzhiyun usage = "usage: %prog [options] [path1] [path2] [pathn]" 2232*4882a593Smuzhiyun 2233*4882a593Smuzhiyun parser = OptionParser(usage=usage) 2234*4882a593Smuzhiyun parser.add_option("-v", "--verbose", action="store_true", 2235*4882a593Smuzhiyun dest="verbose", help="Verbose mode") 2236*4882a593Smuzhiyun parser.add_option("-r", "--recursive", action="store_true", 2237*4882a593Smuzhiyun dest="recursive", 2238*4882a593Smuzhiyun help="Add watches recursively on paths") 2239*4882a593Smuzhiyun parser.add_option("-a", "--auto_add", action="store_true", 2240*4882a593Smuzhiyun dest="auto_add", 2241*4882a593Smuzhiyun help="Automatically add watches on new directories") 2242*4882a593Smuzhiyun parser.add_option("-g", "--glob", action="store_true", 2243*4882a593Smuzhiyun dest="glob", 2244*4882a593Smuzhiyun help="Treat paths as globs") 2245*4882a593Smuzhiyun parser.add_option("-e", "--events-list", metavar="EVENT[,...]", 2246*4882a593Smuzhiyun dest="events_list", 2247*4882a593Smuzhiyun help=("A comma-separated list of events to watch for - " 2248*4882a593Smuzhiyun "see the documentation for valid options (defaults" 2249*4882a593Smuzhiyun " to everything)")) 2250*4882a593Smuzhiyun parser.add_option("-s", "--stats", action="store_true", 2251*4882a593Smuzhiyun dest="stats", 2252*4882a593Smuzhiyun help="Display dummy statistics") 2253*4882a593Smuzhiyun parser.add_option("-V", "--version", action="store_true", 2254*4882a593Smuzhiyun dest="version", help="Pyinotify version") 2255*4882a593Smuzhiyun parser.add_option("-f", "--raw-format", action="store_true", 2256*4882a593Smuzhiyun dest="raw_format", 2257*4882a593Smuzhiyun help="Disable enhanced output format.") 2258*4882a593Smuzhiyun parser.add_option("-c", "--command", action="store", 2259*4882a593Smuzhiyun dest="command", 2260*4882a593Smuzhiyun help="Shell command to run upon event") 2261*4882a593Smuzhiyun 2262*4882a593Smuzhiyun (options, args) = parser.parse_args() 2263*4882a593Smuzhiyun 2264*4882a593Smuzhiyun if options.verbose: 2265*4882a593Smuzhiyun log.setLevel(10) 2266*4882a593Smuzhiyun 2267*4882a593Smuzhiyun if options.version: 2268*4882a593Smuzhiyun print(__version__) 2269*4882a593Smuzhiyun 2270*4882a593Smuzhiyun if not options.raw_format: 2271*4882a593Smuzhiyun global output_format 2272*4882a593Smuzhiyun output_format = ColoredOutputFormat() 2273*4882a593Smuzhiyun 2274*4882a593Smuzhiyun if len(args) < 1: 2275*4882a593Smuzhiyun path = '/tmp' # default watched path 2276*4882a593Smuzhiyun else: 2277*4882a593Smuzhiyun path = args 2278*4882a593Smuzhiyun 2279*4882a593Smuzhiyun # watch manager instance 2280*4882a593Smuzhiyun wm = WatchManager() 2281*4882a593Smuzhiyun # notifier instance and init 2282*4882a593Smuzhiyun if options.stats: 2283*4882a593Smuzhiyun notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5) 2284*4882a593Smuzhiyun else: 2285*4882a593Smuzhiyun notifier = Notifier(wm, default_proc_fun=PrintAllEvents()) 2286*4882a593Smuzhiyun 2287*4882a593Smuzhiyun # What mask to apply 2288*4882a593Smuzhiyun mask = 0 2289*4882a593Smuzhiyun if options.events_list: 2290*4882a593Smuzhiyun events_list = options.events_list.split(',') 2291*4882a593Smuzhiyun for ev in events_list: 2292*4882a593Smuzhiyun evcode = EventsCodes.ALL_FLAGS.get(ev, 0) 2293*4882a593Smuzhiyun if evcode: 2294*4882a593Smuzhiyun mask |= evcode 2295*4882a593Smuzhiyun else: 2296*4882a593Smuzhiyun parser.error("The event '%s' specified with option -e" 2297*4882a593Smuzhiyun " is not valid" % ev) 2298*4882a593Smuzhiyun else: 2299*4882a593Smuzhiyun mask = ALL_EVENTS 2300*4882a593Smuzhiyun 2301*4882a593Smuzhiyun # stats 2302*4882a593Smuzhiyun cb_fun = None 2303*4882a593Smuzhiyun if options.stats: 2304*4882a593Smuzhiyun def cb(s): 2305*4882a593Smuzhiyun sys.stdout.write(repr(s.proc_fun())) 2306*4882a593Smuzhiyun sys.stdout.write('\n') 2307*4882a593Smuzhiyun sys.stdout.write(str(s.proc_fun())) 2308*4882a593Smuzhiyun sys.stdout.write('\n') 2309*4882a593Smuzhiyun sys.stdout.flush() 2310*4882a593Smuzhiyun cb_fun = cb 2311*4882a593Smuzhiyun 2312*4882a593Smuzhiyun # External command 2313*4882a593Smuzhiyun if options.command: 2314*4882a593Smuzhiyun def cb(s): 2315*4882a593Smuzhiyun subprocess.Popen(options.command, shell=True) 2316*4882a593Smuzhiyun cb_fun = cb 2317*4882a593Smuzhiyun 2318*4882a593Smuzhiyun log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path) 2319*4882a593Smuzhiyun 2320*4882a593Smuzhiyun wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add, do_glob=options.glob) 2321*4882a593Smuzhiyun # Loop forever (until sigint signal get caught) 2322*4882a593Smuzhiyun notifier.loop(callback=cb_fun) 2323*4882a593Smuzhiyun 2324*4882a593Smuzhiyun 2325*4882a593Smuzhiyunif __name__ == '__main__': 2326*4882a593Smuzhiyun command_line() 2327