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 src_path_len = len(src_path) 685 sep_len = len(os.path.sep) 686 # The next loop renames all watches with src_path as base path. 687 # It seems that IN_MOVE_SELF does not provide IN_ISDIR information 688 # therefore the next loop is iterated even if raw_event is a file. 689 for w in self._watch_manager.watches.values(): 690 if w.path.startswith(src_path): 691 # Note that dest_path is a normalized path. 692 w.path = os.path.join(dest_path, 693 w.path[src_path_len + sep_len:]) 694 else: 695 log.error("The pathname '%s' of this watch %s has probably changed " 696 "and couldn't be updated, so it cannot be trusted " 697 "anymore. To fix this error move directories/files only " 698 "between watched parents directories, in this case e.g. " 699 "put a watch on '%s'.", 700 watch_.path, watch_, 701 os.path.normpath(os.path.join(watch_.path, 702 os.path.pardir))) 703 if not watch_.path.endswith('-unknown-path'): 704 watch_.path += '-unknown-path' 705 return self.process_default(raw_event)
706
707 - def process_IN_Q_OVERFLOW(self, raw_event):
708 """ 709 Only signal an overflow, most of the common flags are irrelevant 710 for this event (path, wd, name). 711 """ 712 return Event({'mask': raw_event.mask})
713
714 - def process_IN_IGNORED(self, raw_event):
715 """ 716 The watch descriptor raised by this event is now ignored (forever), 717 it can be safely deleted from the watch manager dictionary. 718 After this event we can be sure that neither the event queue nor 719 the system will raise an event associated to this wd again. 720 """ 721 event_ = self.process_default(raw_event) 722 self._watch_manager.del_watch(raw_event.wd) 723 return event_
724
725 - def process_default(self, raw_event, to_append=None):
726 """ 727 Commons handling for the followings events: 728 729 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, 730 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT. 731 """ 732 watch_ = self._watch_manager.get_watch(raw_event.wd) 733 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF): 734 # Unfornulately this information is not provided by the kernel 735 dir_ = watch_.dir 736 else: 737 dir_ = bool(raw_event.mask & IN_ISDIR) 738 dict_ = {'wd': raw_event.wd, 739 'mask': raw_event.mask, 740 'path': watch_.path, 741 'name': raw_event.name, 742 'dir': dir_} 743 if COMPATIBILITY_MODE: 744 dict_['is_dir'] = dir_ 745 if to_append is not None: 746 dict_.update(to_append) 747 return Event(dict_)
748
749 750 -class ProcessEvent(_ProcessEvent):
751 """ 752 Process events objects, can be specialized via subclassing, thus its 753 behavior can be overriden: 754 755 Note: you should not override __init__ in your subclass instead define 756 a my_init() method, this method will be called automatically from the 757 constructor of this class with its optionals parameters. 758 759 1. Provide specialized individual methods, e.g. process_IN_DELETE for 760 processing a precise type of event (e.g. IN_DELETE in this case). 761 2. Or/and provide methods for processing events by 'family', e.g. 762 process_IN_CLOSE method will process both IN_CLOSE_WRITE and 763 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and 764 process_IN_CLOSE_NOWRITE aren't defined though). 765 3. Or/and override process_default for catching and processing all 766 the remaining types of events. 767 """ 768 pevent = None 769
770 - def __init__(self, pevent=None, **kargs):
771 """ 772 Enable chaining of ProcessEvent instances. 773 774 @param pevent: Optional callable object, will be called on event 775 processing (before self). 776 @type pevent: callable 777 @param kargs: This constructor is implemented as a template method 778 delegating its optionals keyworded arguments to the 779 method my_init(). 780 @type kargs: dict 781 """ 782 self.pevent = pevent 783 self.my_init(**kargs)
784
785 - def my_init(self, **kargs):
786 """ 787 This method is called from ProcessEvent.__init__(). This method is 788 empty here and must be redefined to be useful. In effect, if you 789 need to specifically initialize your subclass' instance then you 790 just have to override this method in your subclass. Then all the 791 keyworded arguments passed to ProcessEvent.__init__() will be 792 transmitted as parameters to this method. Beware you MUST pass 793 keyword arguments though. 794 795 @param kargs: optional delegated arguments from __init__(). 796 @type kargs: dict 797 """ 798 pass
799
800 - def __call__(self, event):
801 stop_chaining = False 802 if self.pevent is not None: 803 # By default methods return None so we set as guideline 804 # that methods asking for stop chaining must explicitely 805 # return non None or non False values, otherwise the default 806 # behavior will be to accept chain call to the corresponding 807 # local method. 808 stop_chaining = self.pevent(event) 809 if not stop_chaining: 810 return _ProcessEvent.__call__(self, event)
811
812 - def nested_pevent(self):
813 return self.pevent
814
815 - def process_IN_Q_OVERFLOW(self, event):
816 """ 817 By default this method only reports warning messages, you can overredide 818 it by subclassing ProcessEvent and implement your own 819 process_IN_Q_OVERFLOW method. The actions you can take on receiving this 820 event is either to update the variable max_queued_events in order to 821 handle more simultaneous events or to modify your code in order to 822 accomplish a better filtering diminishing the number of raised events. 823 Because this method is defined, IN_Q_OVERFLOW will never get 824 transmitted as arguments to process_default calls. 825 826 @param event: IN_Q_OVERFLOW event. 827 @type event: dict 828 """ 829 log.warning('Event queue overflowed.')
830
831 - def process_default(self, event):
832 """ 833 Default processing event method. By default does nothing. Subclass 834 ProcessEvent and redefine this method in order to modify its behavior. 835 836 @param event: Event to be processed. Can be of any type of events but 837 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW). 838 @type event: Event instance 839 """ 840 pass
841
842 843 -class PrintAllEvents(ProcessEvent):
844 """ 845 Dummy class used to print events strings representations. For instance this 846 class is used from command line to print all received events to stdout. 847 """
848 - def my_init(self, out=None):
849 """ 850 @param out: Where events will be written. 851 @type out: Object providing a valid file object interface. 852 """ 853 if out is None: 854 out = sys.stdout 855 self._out = out
856
857 - def process_default(self, event):
858 """ 859 Writes event string representation to file object provided to 860 my_init(). 861 862 @param event: Event to be processed. Can be of any type of events but 863 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW). 864 @type event: Event instance 865 """ 866 self._out.write(repr(event)) 867 self._out.write('\n')
868
869 870 -class ChainIfTrue(ProcessEvent):
871 """ 872 Makes conditional chaining depending on the result of the nested 873 processing instance. 874 """
875 - def my_init(self, func):
876 """ 877 Method automatically called from base class constructor. 878 """ 879 self._func = func
880
881 - def process_default(self, event):
882 return not self._func(event)
883
884 885 -class Stats(ProcessEvent):
886 """ 887 Compute and display trivial statistics about processed events. 888 """
889 - def my_init(self):
890 """ 891 Method automatically called from base class constructor. 892 """ 893 self._start_time = time.time() 894 self._stats = {} 895 self._stats_lock = threading.Lock()
896
897 - def process_default(self, event):
898 """ 899 Processes |event|. 900 """ 901 self._stats_lock.acquire() 902 try: 903 events = event.maskname.split('|') 904 for event_name in events: 905 count = self._stats.get(event_name, 0) 906 self._stats[event_name] = count + 1 907 finally: 908 self._stats_lock.release()
909
910 - def _stats_copy(self):
911 self._stats_lock.acquire() 912 try: 913 return self._stats.copy() 914 finally: 915 self._stats_lock.release()
916
917 - def __repr__(self):
918 stats = self._stats_copy() 919 920 elapsed = int(time.time() - self._start_time) 921 elapsed_str = '' 922 if elapsed < 60: 923 elapsed_str = str(elapsed) + 'sec' 924 elif 60 <= elapsed < 3600: 925 elapsed_str = '%dmn%dsec' % (elapsed / 60, elapsed % 60) 926 elif 3600 <= elapsed < 86400: 927 elapsed_str = '%dh%dmn' % (elapsed / 3600, (elapsed % 3600) / 60) 928 elif elapsed >= 86400: 929 elapsed_str = '%dd%dh' % (elapsed / 86400, (elapsed % 86400) / 3600) 930 stats['ElapsedTime'] = elapsed_str 931 932 l = [] 933 for ev, value in sorted(stats.items(), key=lambda x: x[0]): 934 l.append(' %s=%s' % (Color.field_name(ev), 935 Color.field_value(value))) 936 s = '<%s%s >' % (Color.class_name(self.__class__.__name__), 937 ''.join(l)) 938 return s
939
940 - def dump(self, filename):
941 """ 942 Dumps statistics to file |filename|. 943 944 @param filename: pathname. 945 @type filename: string 946 """ 947 file_obj = file(filename, 'wb') 948 try: 949 file_obj.write(str(self)) 950 finally: 951 file_obj.close()
952
953 - def __str__(self, scale=45):
954 stats = self._stats_copy() 955 if not stats: 956 return '' 957 958 m = max(stats.values()) 959 unity = float(scale) / m 960 fmt = '%%-26s%%-%ds%%s' % (len(Color.field_value('@' * scale)) 961 + 1) 962 def func(x): 963 return fmt % (Color.field_name(x[0]), 964 Color.field_value('@' * int(x[1] * unity)), 965 Color.simple('%d' % x[1], 'yellow'))
966 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0]))) 967 return s
968
969 970 -class NotifierError(PyinotifyError):
971 """ 972 Notifier Exception. Raised on Notifier error. 973 974 """
975 - def __init__(self, err):
976 """ 977 @param err: Exception string's description. 978 @type err: string 979 """ 980 PyinotifyError.__init__(self, err)
981
982 983 -class Notifier:
984 """ 985 Read notifications, process events. 986 987 """
988 - def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, 989 threshold=0, timeout=None):
990 """ 991 Initialization. read_freq, threshold and timeout parameters are used 992 when looping. 993 994 @param watch_manager: Watch Manager. 995 @type watch_manager: WatchManager instance 996 @param default_proc_fun: Default processing method. If None, a new 997 instance of PrintAllEvents will be assigned. 998 @type default_proc_fun: instance of ProcessEvent 999 @param read_freq: if read_freq == 0, events are read asap, 1000 if read_freq is > 0, this thread sleeps 1001 max(0, read_freq - timeout) seconds. But if 1002 timeout is None it can be different because 1003 poll is blocking waiting for something to read. 1004 @type read_freq: int 1005 @param threshold: File descriptor will be read only if the accumulated 1006 size to read becomes >= threshold. If != 0, you likely 1007 want to use it in combination with an appropriate 1008 value for read_freq because without that you would 1009 keep looping without really reading anything and that 1010 until the amount of events to read is >= threshold. 1011 At least with read_freq set you might sleep. 1012 @type threshold: int 1013 @param timeout: 1014 http://docs.python.org/lib/poll-objects.html#poll-objects 1015 @type timeout: int 1016 """ 1017 # Watch Manager instance 1018 self._watch_manager = watch_manager 1019 # File descriptor 1020 self._fd = self._watch_manager.get_fd() 1021 # Poll object and registration 1022 self._pollobj = select.poll() 1023 self._pollobj.register(self._fd, select.POLLIN) 1024 # This pipe is correctely initialized and used by ThreadedNotifier 1025 self._pipe = (-1, -1) 1026 # Event queue 1027 self._eventq = deque() 1028 # System processing functor, common to all events 1029 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self) 1030 # Default processing method 1031 self._default_proc_fun = default_proc_fun 1032 if default_proc_fun is None: 1033 self._default_proc_fun = PrintAllEvents() 1034 # Loop parameters 1035 self._read_freq = read_freq 1036 self._threshold = threshold 1037 self._timeout = timeout
1038
1039 - def append_event(self, event):
1040 """ 1041 Append a raw event to the event queue. 1042 1043 @param event: An event. 1044 @type event: _RawEvent instance. 1045 """ 1046 self._eventq.append(event)
1047
1048 - def proc_fun(self):
1049 return self._default_proc_fun
1050
1051 - def check_events(self, timeout=None):
1052 """ 1053 Check for new events available to read, blocks up to timeout 1054 milliseconds. 1055 1056 @param timeout: If specified it overrides the corresponding instance 1057 attribute _timeout. 1058 @type timeout: int 1059 1060 @return: New events to read. 1061 @rtype: bool 1062 """ 1063 while True: 1064 try: 1065 # blocks up to 'timeout' milliseconds 1066 if timeout is None: 1067 timeout = self._timeout 1068 ret = self._pollobj.poll(timeout) 1069 except select.error, err: 1070 if err[0] == errno.EINTR: 1071 continue # interrupted, retry 1072 else: 1073 raise 1074 else: 1075 break 1076 1077 if not ret or (self._pipe[0] == ret[0][0]): 1078 return False 1079 # only one fd is polled 1080 return ret[0][1] & select.POLLIN
1081
1082 - def read_events(self):
1083 """ 1084 Read events from device, build _RawEvents, and enqueue them. 1085 """ 1086 buf_ = array.array('i', [0]) 1087 # get event queue size 1088 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1: 1089 return 1090 queue_size = buf_[0] 1091 if queue_size < self._threshold: 1092 log.debug('(fd: %d) %d bytes available to read but threshold is ' 1093 'fixed to %d bytes', self._fd, queue_size, 1094 self._threshold) 1095 return 1096 1097 try: 1098 # Read content from file 1099 r = os.read(self._fd, queue_size) 1100 except Exception, msg: 1101 raise NotifierError(msg) 1102 log.debug('Event queue size: %d', queue_size) 1103 rsum = 0 # counter 1104 while rsum < queue_size: 1105 s_size = 16 1106 # Retrieve wd, mask, cookie and fname_len 1107 wd, mask, cookie, fname_len = struct.unpack('iIII', 1108 r[rsum:rsum+s_size]) 1109 # Retrieve name 1110 fname, = struct.unpack('%ds' % fname_len, 1111 r[rsum + s_size:rsum + s_size + fname_len]) 1112 self._eventq.append(_RawEvent(wd, mask, cookie, fname)) 1113 rsum += s_size + fname_len
1114
1115 - def process_events(self):
1116 """ 1117 Routine for processing events from queue by calling their 1118 associated proccessing method (an instance of ProcessEvent). 1119 It also does internal processings, to keep the system updated. 1120 """ 1121 while self._eventq: 1122 raw_event = self._eventq.popleft() # pop next event 1123 watch_ = self._watch_manager.get_watch(raw_event.wd) 1124 revent = self._sys_proc_fun(raw_event) # system processings 1125 if watch_ and watch_.proc_fun: 1126 watch_.proc_fun(revent) # user processings 1127 else: 1128 self._default_proc_fun(revent) 1129 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1130 1131
1132 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull, 1133 stdout=os.devnull, stderr=os.devnull):
1134 """ 1135 pid_file: file to which the pid will be written. 1136 force_kill: if True kill the process associated to pid_file. 1137 stdin, stdout, stderr: files associated to common streams. 1138 """ 1139 if pid_file is None: 1140 dirname = '/var/run/' 1141 basename = os.path.basename(sys.argv[0]) or 'pyinotify' 1142 pid_file = os.path.join(dirname, basename + '.pid') 1143 1144 if os.path.exists(pid_file): 1145 fo = file(pid_file, 'rb') 1146 try: 1147 try: 1148 pid = int(fo.read()) 1149 except ValueError: 1150 pid = None 1151 if pid is not None: 1152 try: 1153 os.kill(pid, 0) 1154 except OSError, err: 1155 if err.errno == errno.ESRCH: 1156 log.debug(err) 1157 else: 1158 log.error(err) 1159 else: 1160 if not force_kill: 1161 s = 'There is already a pid file %s with pid %d' 1162 raise NotifierError(s % (pid_file, pid)) 1163 else: 1164 os.kill(pid, 9) 1165 finally: 1166 fo.close() 1167 1168 1169 def fork_daemon(): 1170 # Adapted from Chad J. Schroeder's recipe 1171 # @see http://code.activestate.com/recipes/278731/ 1172 pid = os.fork() 1173 if (pid == 0): 1174 # parent 2 1175 os.setsid() 1176 pid = os.fork() 1177 if (pid == 0): 1178 # child 1179 os.chdir('/') 1180 os.umask(0) 1181 else: 1182 # parent 2 1183 os._exit(0) 1184 else: 1185 # parent 1 1186 os._exit(0) 1187 1188 fd_inp = os.open(stdin, os.O_RDONLY) 1189 os.dup2(fd_inp, 0) 1190 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT) 1191 os.dup2(fd_out, 1) 1192 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT) 1193 os.dup2(fd_err, 2)
1194 1195 # Detach task 1196 fork_daemon() 1197 1198 # Write pid 1199 file_obj = file(pid_file, 'wb') 1200 try: 1201 file_obj.write(str(os.getpid()) + '\n') 1202 finally: 1203 file_obj.close() 1204 1205 atexit.register(lambda : os.unlink(pid_file))
1206 1207
1208 - def _sleep(self, ref_time):
1209 # Only consider sleeping if read_freq is > 0 1210 if self._read_freq > 0: 1211 cur_time = time.time() 1212 sleep_amount = self._read_freq - (cur_time - ref_time) 1213 if sleep_amount > 0: 1214 log.debug('Now sleeping %d seconds', sleep_amount) 1215 time.sleep(sleep_amount)
1216 1217
1218 - def loop(self, callback=None, daemonize=False, **args):
1219 """ 1220 Events are read only once time every min(read_freq, timeout) 1221 seconds at best and only if the size to read is >= threshold. 1222 1223 @param callback: Functor called after each event processing. Expects 1224 to receive notifier object (self) as first parameter. 1225 @type callback: callable 1226 @param daemonize: This thread is daemonized if set to True. 1227 @type daemonize: boolean 1228 @param args: Optional and relevant only if daemonize is True. Remaining 1229 keyworded arguments are directly passed to daemonize see 1230 __daemonize() method. 1231 @type args: various 1232 """ 1233 if daemonize: 1234 self.__daemonize(**args) 1235 1236 # Read and process events forever 1237 while 1: 1238 try: 1239 self.process_events() 1240 if callback is not None: 1241 callback(self) 1242 ref_time = time.time() 1243 # check_events is blocking 1244 if self.check_events(): 1245 self._sleep(ref_time) 1246 self.read_events() 1247 except KeyboardInterrupt: 1248 # Unless sigint is caught (Control-C) 1249 log.debug('Pyinotify stops monitoring.') 1250 # Stop monitoring 1251 self.stop() 1252 break
1253
1254 - def stop(self):
1255 """ 1256 Close inotify's instance (close its file descriptor). 1257 It destroys all existing watches, pending events,... 1258 """ 1259 self._pollobj.unregister(self._fd) 1260 os.close(self._fd)
1261
1262 1263 -class ThreadedNotifier(threading.Thread, Notifier):
1264 """ 1265 This notifier inherits from threading.Thread for instanciating a separate 1266 thread, and also inherits from Notifier, because it is a threaded notifier. 1267 1268 Note that every functionality provided by this class is also provided 1269 through Notifier class. Moreover Notifier should be considered first because 1270 it is not threaded and could be easily daemonized. 1271 """
1272 - def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, 1273 threshold=0, timeout=None):
1274 """ 1275 Initialization, initialize base classes. read_freq, threshold and 1276 timeout parameters are used when looping. 1277 1278 @param watch_manager: Watch Manager. 1279 @type watch_manager: WatchManager instance 1280 @param default_proc_fun: Default processing method. See base class. 1281 @type default_proc_fun: instance of ProcessEvent 1282 @param read_freq: if read_freq == 0, events are read asap, 1283 if read_freq is > 0, this thread sleeps 1284 max(0, read_freq - timeout) seconds. 1285 @type read_freq: int 1286 @param threshold: File descriptor will be read only if the accumulated 1287 size to read becomes >= threshold. If != 0, you likely 1288 want to use it in combination with an appropriate 1289 value set for read_freq because without that you would 1290 keep looping without really reading anything and that 1291 until the amount of events to read is >= threshold. At 1292 least with read_freq you might sleep. 1293 @type threshold: int 1294 @param timeout: 1295 see http://docs.python.org/lib/poll-objects.html#poll-objects 1296 @type timeout: int 1297 """ 1298 # Init threading base class 1299 threading.Thread.__init__(self) 1300 # Stop condition 1301 self._stop_event = threading.Event() 1302 # Init Notifier base class 1303 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1304 threshold, timeout) 1305 # Create a new pipe used for thread termination 1306 self._pipe = os.pipe() 1307 self._pollobj.register(self._pipe[0], select.POLLIN)
1308
1309 - def stop(self):
1310 """ 1311 Stop notifier's loop. Stop notification. Join the thread. 1312 """ 1313 self._stop_event.set() 1314 os.write(self._pipe[1], 'stop') 1315 threading.Thread.join(self) 1316 Notifier.stop(self) 1317 self._pollobj.unregister(self._pipe[0]) 1318 os.close(self._pipe[0]) 1319 os.close(self._pipe[1])
1320
1321 - def loop(self):
1322 """ 1323 Thread's main loop. Don't meant to be called by user directly. 1324 Call inherited start() method instead. 1325 1326 Events are read only once time every min(read_freq, timeout) 1327 seconds at best and only if the size of events to read is >= threshold. 1328 """ 1329 # When the loop must be terminated .stop() is called, 'stop' 1330 # is written to pipe fd so poll() returns and .check_events() 1331 # returns False which make evaluate the While's stop condition 1332 # ._stop_event.isSet() wich put an end to the thread's execution. 1333 while not self._stop_event.isSet(): 1334 self.process_events() 1335 ref_time = time.time() 1336 if self.check_events(): 1337 self._sleep(ref_time) 1338 self.read_events()
1339
1340 - def run(self):
1341 """ 1342 Start thread's loop: read and process events until the method 1343 stop() is called. 1344 Never call this method directly, instead call the start() method 1345 inherited from threading.Thread, which then will call run() in 1346 its turn. 1347 """ 1348 self.loop()
1349
1350 1351 -class AsyncNotifier(asyncore.file_dispatcher, Notifier):
1352 """ 1353 This notifier inherits from asyncore.file_dispatcher in order to be able to 1354 use pyinotify along with the asyncore framework. 1355 1356 """
1357 - def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, 1358 threshold=0, timeout=None, channel_map=None):
1359 """ 1360 Initializes the async notifier. The only additional parameter is 1361 'channel_map' which is the optional asyncore private map. See 1362 Notifier class for the meaning of the others parameters. 1363 1364 """ 1365 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1366 threshold, timeout) 1367 asyncore.file_dispatcher.__init__(self, self._fd, channel_map)
1368
1369 - def handle_read(self):
1370 """ 1371 When asyncore tells us we can read from the fd, we proceed processing 1372 events. This method can be overridden for handling a notification 1373 differently. 1374 1375 """ 1376 self.read_events() 1377 self.process_events()
1378
1379 1380 -class Watch:
1381 """ 1382 Represent a watch, i.e. a file or directory being watched. 1383 1384 """
1385 - def __init__(self, wd, path, mask, proc_fun, auto_add, exclude_filter):
1386 """ 1387 Initializations. 1388 1389 @param wd: Watch descriptor. 1390 @type wd: int 1391 @param path: Path of the file or directory being watched. 1392 @type path: str 1393 @param mask: Mask. 1394 @type mask: int 1395 @param proc_fun: Processing callable object. 1396 @type proc_fun: 1397 @param auto_add: Automatically add watches on new directories. 1398 @type auto_add: bool 1399 @param exclude_filter: Boolean function, used to exclude new 1400 directories from being automatically watched. 1401 See WatchManager.__init__ 1402 @type exclude_filter: callable object 1403 """ 1404 self.wd = wd 1405 self.path = path 1406 self.mask = mask 1407 self.proc_fun = proc_fun 1408 self.auto_add = auto_add 1409 self.exclude_filter = exclude_filter 1410 self.dir = os.path.isdir(self.path)
1411
1412 - def __repr__(self):
1413 """ 1414 @return: String representation. 1415 @rtype: str 1416 """ 1417 s = ' '.join(['%s%s%s' % (Color.field_name(attr), 1418 Color.punctuation('='), 1419 Color.field_value(getattr(self, attr))) \ 1420 for attr in self.__dict__ if not attr.startswith('_')]) 1421 1422 s = '%s%s %s %s' % (Color.punctuation('<'), 1423 Color.class_name(self.__class__.__name__), 1424 s, 1425 Color.punctuation('>')) 1426 return s
1427
1428 1429 -class ExcludeFilter:
1430 """ 1431 ExcludeFilter is an exclusion filter. 1432 """
1433 - def __init__(self, arg_lst):
1434 """ 1435 Examples: 1436 ef1 = ExcludeFilter(["^/etc/rc.*", "^/etc/hostname"]) 1437 ef2 = ExcludeFilter("/my/path/exclude.lst") 1438 Where exclude.lst contains: 1439 ^/etc/rc.* 1440 ^/etc/hostname 1441 1442 @param arg_lst: is either a list or dict of patterns: 1443 [pattern1, ..., patternn] or a filename from which 1444 patterns will be loaded. 1445 @type arg_lst: list(str) or str 1446 """ 1447 if isinstance(arg_lst, str): 1448 lst = self._load_patterns_from_file(arg_lst) 1449 elif isinstance(arg_lst, list): 1450 lst = arg_lst 1451 else: 1452 raise TypeError 1453 1454 self._lregex = [] 1455 for regex in lst: 1456 self._lregex.append(re.compile(regex, re.UNICODE))
1457
1458 - def _load_patterns_from_file(self, filename):
1459 lst = [] 1460 file_obj = file(filename, 'r') 1461 try: 1462 for line in file_obj.readlines(): 1463 # Trim leading an trailing whitespaces 1464 pattern = line.strip() 1465 if not pattern or pattern.startswith('#'): 1466 continue 1467 lst.append(pattern) 1468 finally: 1469 file_obj.close() 1470 return lst
1471
1472 - def _match(self, regex, path):
1473 return regex.match(path) is not None
1474
1475 - def __call__(self, path):
1476 """ 1477 @param path: Path to match against provided regexps. 1478 @type path: str 1479 @return: Return True if path has been matched and should 1480 be excluded, False otherwise. 1481 @rtype: bool 1482 """ 1483 for regex in self._lregex: 1484 if self._match(regex, path): 1485 return True 1486 return False
1487
1488 1489 -class WatchManagerError(Exception):
1490 """ 1491 WatchManager Exception. Raised on error encountered on watches 1492 operations. 1493 1494 """
1495 - def __init__(self, msg, wmd):
1496 """ 1497 @param msg: Exception string's description. 1498 @type msg: string 1499 @param wmd: This dictionary contains the wd assigned to paths of the 1500 same call for which watches were successfully added. 1501 @type wmd: dict 1502 """ 1503 self.wmd = wmd 1504 Exception.__init__(self, msg)
1505
1506 1507 -class WatchManager:
1508 """ 1509 Provide operations for watching files and directories. Its internal 1510 dictionary is used to reference watched items. When used inside 1511 threaded code, one must instanciate as many WatchManager instances as 1512 there are ThreadedNotifier instances. 1513 1514 """
1515 - def __init__(self, exclude_filter=lambda path: False):
1516 """ 1517 Initialization: init inotify, init watch manager dictionary. 1518 Raise OSError if initialization fails. 1519 1520 @param exclude_filter: boolean function, returns True if current 1521 path must be excluded from being watched. 1522 Convenient for providing a common exclusion 1523 filter for every call to add_watch. 1524 @type exclude_filter: callable object 1525 """ 1526 self._exclude_filter = exclude_filter 1527 self._wmd = {} # watch dict key: watch descriptor, value: watch 1528 self._fd = LIBC.inotify_init() # inotify's init, file descriptor 1529 if self._fd < 0: 1530 raise OSError()
1531
1532 - def get_fd(self):
1533 """ 1534 Return assigned inotify's file descriptor. 1535 1536 @return: File descriptor. 1537 @rtype: int 1538 """ 1539 return self._fd
1540
1541 - def get_watch(self, wd):
1542 """ 1543 Get watch from provided watch descriptor wd. 1544 1545 @param wd: Watch descriptor. 1546 @type wd: int 1547 """ 1548 return self._wmd.get(wd)
1549
1550 - def del_watch(self, wd):
1551 """ 1552 Remove watch entry associated to watch descriptor wd. 1553 1554 @param wd: Watch descriptor. 1555 @type wd: int 1556 """ 1557 try: 1558 del self._wmd[wd] 1559 except KeyError, err: 1560 log.error(str(err))
1561 1562 @property
1563 - def watches(self):
1564 """ 1565 Get a reference on the internal watch manager dictionary. 1566 1567 @return: Internal watch manager dictionary. 1568 @rtype: dict 1569 """ 1570 return self._wmd
1571
1572 - def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter):
1573 """ 1574 Add a watch on path, build a Watch object and insert it in the 1575 watch manager dictionary. Return the wd value. 1576 """ 1577 # Unicode strings are converted to byte strings, it seems to be 1578 # required because LIBC.inotify_add_watch does not work well when 1579 # it receives an ctypes.create_unicode_buffer instance as argument. 1580 # Therefore even wd are indexed with bytes string and not with 1581 # unicode paths. 1582 if isinstance(path, unicode): 1583 byte_path = path.encode(sys.getfilesystemencoding()) 1584 else: 1585 byte_path = path 1586 1587 wd_ = LIBC.inotify_add_watch(self._fd, 1588 ctypes.create_string_buffer(byte_path), 1589 mask) 1590 if wd_ < 0: 1591 return wd_ 1592 watch_ = Watch(wd=wd_, path=os.path.normpath(byte_path), mask=mask, 1593 proc_fun=proc_fun, auto_add=auto_add, 1594 exclude_filter=exclude_filter) 1595 self._wmd[wd_] = watch_ 1596 log.debug('New %s', watch_) 1597 return wd_
1598
1599 - def __glob(self, path, do_glob):
1600 if do_glob: 1601 return glob.iglob(path) 1602 else: 1603 return [path]
1604
1605 - def add_watch(self, path, mask, proc_fun=None, rec=False, 1606 auto_add=False, do_glob=False, quiet=True, 1607 exclude_filter=None):
1608 """ 1609 Add watch(s) on the provided |path|(s) with associated |mask| flag 1610 value and optionally with a processing |proc_fun| function and 1611 recursive flag |rec| set to True. 1612 Ideally |path| components should not be unicode objects. Note that 1613 although unicode paths are accepted there are converted to byte 1614 strings before a watch is put on that path. The encoding used for 1615 converting the unicode object is given by sys.getfilesystemencoding(). 1616 1617 @param path: Path to watch, the path can either be a file or a 1618 directory. Also accepts a sequence (list) of paths. 1619 @type path: string or list of strings 1620 @param mask: Bitmask of events. 1621 @type mask: int 1622 @param proc_fun: Processing object. 1623 @type proc_fun: function or ProcessEvent instance or instance of 1624 one of its subclasses or callable object. 1625 @param rec: Recursively add watches from path on all its 1626 subdirectories, set to False by default (doesn't 1627 follows symlinks in any case). 1628 @type rec: bool 1629 @param auto_add: Automatically add watches on newly created 1630 directories in watched parent |path| directory. 1631 @type auto_add: bool 1632 @param do_glob: Do globbing on pathname (see standard globbing 1633 module for more informations). 1634 @type do_glob: bool 1635 @param quiet: if False raises a WatchManagerError exception on 1636 error. See example not_quiet.py. 1637 @type quiet: bool 1638 @param exclude_filter: predicate (boolean function), which returns 1639 True if the current path must be excluded 1640 from being watched. This argument has 1641 precedence over exclude_filter passed to 1642 the class' constructor. 1643 @type exclude_filter: callable object 1644 @return: dict of paths associated to watch descriptors. A wd value 1645 is positive if the watch was added sucessfully, 1646 otherwise the value is negative. If the path was invalid 1647 it is not included into this returned dictionary. 1648 @rtype: dict of {str: int} 1649 """ 1650 ret_ = {} # return {path: wd, ...} 1651 1652 if exclude_filter is None: 1653 exclude_filter = self._exclude_filter 1654 1655 # normalize args as list elements 1656 for npath in self.__format_param(path): 1657 # unix pathname pattern expansion 1658 for apath in self.__glob(npath, do_glob): 1659 # recursively list subdirs according to rec param 1660 for rpath in self.__walk_rec(apath, rec): 1661 if not exclude_filter(rpath): 1662 wd = ret_[rpath] = self.__add_watch(rpath, mask, 1663 proc_fun, 1664 auto_add, 1665 exclude_filter) 1666 if wd < 0: 1667 err = 'add_watch: cannot watch %s (WD=%d)' 1668 err = err % (rpath, wd) 1669 if quiet: 1670 log.error(err) 1671 else: 1672 raise WatchManagerError(err, ret_) 1673 else: 1674 # Let's say -2 means 'explicitely excluded 1675 # from watching'. 1676 ret_[rpath] = -2 1677 return ret_
1678
1679 - def __get_sub_rec(self, lpath):
1680 """ 1681 Get every wd from self._wmd if its path is under the path of 1682 one (at least) of those in lpath. Doesn't follow symlinks. 1683 1684 @param lpath: list of watch descriptor 1685 @type lpath: list of int 1686 @return: list of watch descriptor 1687 @rtype: list of int 1688 """ 1689 for d in lpath: 1690 root = self.get_path(d) 1691 if root: 1692 # always keep root 1693 yield d 1694 else: 1695 # if invalid 1696 continue 1697 1698 # nothing else to expect 1699 if not os.path.isdir(root): 1700 continue 1701 1702 # normalization 1703 root = os.path.normpath(root) 1704 # recursion 1705 lend = len(root) 1706 for iwd in self._wmd.items(): 1707 cur = iwd[1].path 1708 pref = os.path.commonprefix([root, cur]) 1709 if root == os.sep or (len(pref) == lend and \ 1710 len(cur) > lend and \ 1711 cur[lend] == os.sep): 1712 yield iwd[1].wd
1713
1714 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False, 1715 auto_add=False, quiet=True):
1716 """ 1717 Update existing watch descriptors |wd|. The |mask| value, the 1718 processing object |proc_fun|, the recursive param |rec| and the 1719 |auto_add| and |quiet| flags can all be updated. 1720 1721 @param wd: Watch Descriptor to update. Also accepts a list of 1722 watch descriptors. 1723 @type wd: int or list of int 1724 @param mask: Optional new bitmask of events. 1725 @type mask: int 1726 @param proc_fun: Optional new processing function. 1727 @type proc_fun: function or ProcessEvent instance or instance of 1728 one of its subclasses or callable object. 1729 @param rec: Optionally adds watches recursively on all 1730 subdirectories contained into |wd| directory. 1731 @type rec: bool 1732 @param auto_add: Automatically adds watches on newly created 1733 directories in the watch's path corresponding to 1734 |wd|. 1735 @type auto_add: bool 1736 @param quiet: If False raises a WatchManagerError exception on 1737 error. See example not_quiet.py 1738 @type quiet: bool 1739 @return: dict of watch descriptors associated to booleans values. 1740 True if the corresponding wd has been successfully 1741 updated, False otherwise. 1742 @rtype: dict of {int: bool} 1743 """ 1744 lwd = self.__format_param(wd) 1745 if rec: 1746 lwd = self.__get_sub_rec(lwd) 1747 1748 ret_ = {} # return {wd: bool, ...} 1749 for awd in lwd: 1750 apath = self.get_path(awd) 1751 if not apath or awd < 0: 1752 err = 'update_watch: invalid WD=%d' % awd 1753 if quiet: 1754 log.error(err) 1755 continue 1756 raise WatchManagerError(err, ret_) 1757 1758 if mask: 1759 addw = LIBC.inotify_add_watch 1760 wd_ = addw(self._fd, ctypes.create_string_buffer(apath), mask) 1761 if wd_ < 0: 1762 ret_[awd] = False 1763 err = 'update_watch: cannot update WD=%d (%s)' % (wd_, 1764 apath) 1765 if quiet: 1766 log.error(err) 1767 continue 1768 raise WatchManagerError(err, ret_) 1769 1770 assert(awd == wd_) 1771 1772 if proc_fun or auto_add: 1773 watch_ = self._wmd[awd] 1774 1775 if proc_fun: 1776 watch_.proc_fun = proc_fun 1777 1778 if auto_add: 1779 watch_.proc_fun = auto_add 1780 1781 ret_[awd] = True 1782 log.debug('Updated watch - %s', self._wmd[awd]) 1783 return ret_
1784
1785 - def __format_param(self, param):
1786 """ 1787 @param param: Parameter. 1788 @type param: string or int 1789 @return: wrap param. 1790 @rtype: list of type(param) 1791 """ 1792 if isinstance(param, list): 1793 for p_ in param: 1794 yield p_ 1795 else: 1796 yield param
1797
1798 - def get_wd(self, path):
1799 """ 1800 Returns the watch descriptor associated to path. This method 1801 presents a prohibitive cost, always prefer to keep the WD 1802 returned by add_watch(). If the path is unknown it returns None. 1803 1804 @param path: Path. 1805 @type path: str 1806 @return: WD or None. 1807 @rtype: int or None 1808 """ 1809 path = os.path.normpath(path) 1810 for iwd in self._wmd.items(): 1811 if iwd[1].path == path: 1812 return iwd[0] 1813 log.debug('get_wd: unknown path %s', path)
1814
1815 - def get_path(self, wd):
1816 """ 1817 Returns the path associated to WD, if WD is unknown it returns None. 1818 1819 @param wd: Watch descriptor. 1820 @type wd: int 1821 @return: Path or None. 1822 @rtype: string or None 1823 """ 1824 watch_ = self._wmd.get(wd) 1825 if watch_: 1826 return watch_.path 1827 log.debug('get_path: unknown WD %d', wd)
1828
1829 - def __walk_rec(self, top, rec):
1830 """ 1831 Yields each subdirectories of top, doesn't follow symlinks. 1832 If rec is false, only yield top. 1833 1834 @param top: root directory. 1835 @type top: string 1836 @param rec: recursive flag. 1837 @type rec: bool 1838 @return: path of one subdirectory. 1839 @rtype: string 1840 """ 1841 if not rec or os.path.islink(top) or not os.path.isdir(top): 1842 yield top 1843 else: 1844 for root, dirs, files in os.walk(top): 1845 yield root
1846
1847 - def rm_watch(self, wd, rec=False, quiet=True):
1848 """ 1849 Removes watch(s). 1850 1851 @param wd: Watch Descriptor of the file or directory to unwatch. 1852 Also accepts a list of WDs. 1853 @type wd: int or list of int. 1854 @param rec: Recursively removes watches on every already watched 1855 subdirectories and subfiles. 1856 @type rec: bool 1857 @param quiet: If False raises a WatchManagerError exception on 1858 error. See example not_quiet.py 1859 @type quiet: bool 1860 @return: dict of watch descriptors associated to booleans values. 1861 True if the corresponding wd has been successfully 1862 removed, False otherwise. 1863 @rtype: dict of {int: bool} 1864 """ 1865 lwd = self.__format_param(wd) 1866 if rec: 1867 lwd = self.__get_sub_rec(lwd) 1868 1869 ret_ = {} # return {wd: bool, ...} 1870 for awd in lwd: 1871 # remove watch 1872 wd_ = LIBC.inotify_rm_watch(self._fd, awd) 1873 if wd_ < 0: 1874 ret_[awd] = False 1875 err = 'rm_watch: cannot remove WD=%d' % awd 1876 if quiet: 1877 log.error(err) 1878 continue 1879 raise WatchManagerError(err, ret_) 1880 1881 ret_[awd] = True 1882 log.debug('Watch WD=%d (%s) removed', awd, self.get_path(awd)) 1883 return ret_
1884 1885
1886 - def watch_transient_file(self, filename, mask, proc_class):
1887 """ 1888 Watch a transient file, which will be created and deleted frequently 1889 over time (e.g. pid file). 1890 1891 @attention: Currently under the call to this function it is not 1892 possible to correctly watch the events triggered into the same 1893 base directory than the directory where is located this watched 1894 transient file. For instance it would be wrong to make these 1895 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...) 1896 and wm.add_watch('/var/run/', ...) 1897 1898 @param filename: Filename. 1899 @type filename: string 1900 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE. 1901 @type mask: int 1902 @param proc_class: ProcessEvent (or of one of its subclass), beware of 1903 accepting a ProcessEvent's instance as argument into 1904 __init__, see transient_file.py example for more 1905 details. 1906 @type proc_class: ProcessEvent's instance or of one of its subclasses. 1907 @return: Same as add_watch(). 1908 @rtype: Same as add_watch(). 1909 """ 1910 dirname = os.path.dirname(filename) 1911 if dirname == '': 1912 return {} # Maintains coherence with add_watch() 1913 basename = os.path.basename(filename) 1914 # Assuming we are watching at least for IN_CREATE and IN_DELETE 1915 mask |= IN_CREATE | IN_DELETE 1916 1917 def cmp_name(event): 1918 if getattr(event, 'name') is None: 1919 return False 1920 return basename == event.name
1921 return self.add_watch(dirname, mask, 1922 proc_fun=proc_class(ChainIfTrue(func=cmp_name)), 1923 rec=False, 1924 auto_add=False, do_glob=False, 1925 exclude_filter=lambda path: False)
1926
1927 1928 -class Color:
1929 """ 1930 Internal class. Provide fancy colors used by string representations. 1931 """ 1932 normal = "\033[0m" 1933 black = "\033[30m" 1934 red = "\033[31m" 1935 green = "\033[32m" 1936 yellow = "\033[33m" 1937 blue = "\033[34m" 1938 purple = "\033[35m" 1939 cyan = "\033[36m" 1940 bold = "\033[1m" 1941 uline = "\033[4m" 1942 blink = "\033[5m" 1943 invert = "\033[7m" 1944 1945 @staticmethod
1946 - def punctuation(s):
1947 """Punctuation color.""" 1948 return Color.normal + s + Color.normal
1949 1950 @staticmethod
1951 - def field_value(s):
1952 """Field value color.""" 1953 if not isinstance(s, basestring): 1954 s = str(s) 1955 return Color.purple + s + Color.normal
1956 1957 @staticmethod
1958 - def field_name(s):
1959 """Field name color.""" 1960 return Color.blue + s + Color.normal
1961 1962 @staticmethod
1963 - def class_name(s):
1964 """Class name color.""" 1965 return Color.red + Color.bold + s + Color.normal
1966 1967 @staticmethod
1968 - def simple(s, color):
1969 if not isinstance(s, basestring): 1970 s = str(s) 1971 try: 1972 color_attr = getattr(Color, color) 1973 except AttributeError: 1974 return s 1975 return color_attr + s + Color.normal
1976
1977 1978 -def compatibility_mode():
1979 """ 1980 Use this function to turn on the compatibility mode. The compatibility 1981 mode is used to improve compatibility with Pyinotify 0.7.1 (or older) 1982 programs. The compatibility mode provides additional variables 'is_dir', 1983 'event_name', 'EventsCodes.IN_*' and 'EventsCodes.ALL_EVENTS' as 1984 Pyinotify 0.7.1 provided. Do not call this function from new programs!! 1985 Especially if there are developped for Pyinotify >= 0.8.x. 1986 """ 1987 setattr(EventsCodes, 'ALL_EVENTS', ALL_EVENTS) 1988 for evname in globals(): 1989 if evname.startswith('IN_'): 1990 setattr(EventsCodes, evname, globals()[evname]) 1991 global COMPATIBILITY_MODE 1992 COMPATIBILITY_MODE = True
1993
1994 1995 -def command_line():
1996 """ 1997 By default the watched path is '/tmp' and all types of events are 1998 monitored. Events monitoring serves forever, type c^c to stop it. 1999 """ 2000 from optparse import OptionParser 2001 2002 usage = "usage: %prog [options] [path1] [path2] [pathn]" 2003 2004 parser = OptionParser(usage=usage) 2005 parser.add_option("-v", "--verbose", action="store_true", 2006 dest="verbose", help="Verbose mode") 2007 parser.add_option("-r", "--recursive", action="store_true", 2008 dest="recursive", 2009 help="Add watches recursively on paths") 2010 parser.add_option("-a", "--auto_add", action="store_true", 2011 dest="auto_add", 2012 help="Automatically add watches on new directories") 2013 parser.add_option("-e", "--events-list", metavar="EVENT[,...]", 2014 dest="events_list", 2015 help=("A comma-separated list of events to watch for - " 2016 "see the documentation for valid options (defaults" 2017 " to everything)")) 2018 parser.add_option("-s", "--stats", action="store_true", 2019 dest="stats", 2020 help="Display dummy statistics") 2021 2022 (options, args) = parser.parse_args() 2023 2024 if options.verbose: 2025 log.setLevel(10) 2026 2027 if len(args) < 1: 2028 path = '/tmp' # default watched path 2029 else: 2030 path = args 2031 2032 # watch manager instance 2033 wm = WatchManager() 2034 # notifier instance and init 2035 if options.stats: 2036 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5) 2037 else: 2038 notifier = Notifier(wm, default_proc_fun=PrintAllEvents()) 2039 2040 # What mask to apply 2041 mask = 0 2042 if options.events_list: 2043 events_list = options.events_list.split(',') 2044 for ev in events_list: 2045 evcode = EventsCodes.ALL_FLAGS.get(ev, 0) 2046 if evcode: 2047 mask |= evcode 2048 else: 2049 parser.error("The event '%s' specified with option -e" 2050 " is not valid" % ev) 2051 else: 2052 mask = ALL_EVENTS 2053 2054 # stats 2055 cb_fun = None 2056 if options.stats: 2057 def cb(s): 2058 print('%s\n%s\n' % (repr(s.proc_fun()), 2059 s.proc_fun()))
2060 cb_fun = cb 2061 2062 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path) 2063 2064 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add) 2065 # Loop forever (until sigint signal get caught) 2066 notifier.loop(callback=cb_fun) 2067 2068 2069 if __name__ == '__main__': 2070 command_line() 2071