Module pyinotify
[hide private]
[frames] | no frames]

Source Code for Module pyinotify

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