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