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