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

Source Code for Module pyinotify

   1  #!/usr/bin/env python 
   2  # -*- coding: iso-8859-1 -*- 
   3  # 
   4  # pyinotify.py - python interface to inotify 
   5  # Copyright (C) 2005-2008 Sébastien Martini <sebastien.martini@gmail.com> 
   6  # 
   7  # This program is free software; you can redistribute it and/or 
   8  # modify it under the terms of the GNU General Public License 
   9  # version 2 as published by the Free Software Foundation; version 2. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, 
  12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  # GNU General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 
  19  # 02111-1307, USA. 
  20   
  21  """ 
  22  pyinotify 
  23   
  24  @author: Sebastien Martini 
  25  @license: GPL 2 
  26  @contact: seb@dbzteam.org 
  27  """ 
  28   
29 -class PyinotifyError(Exception):
30 """Indicates exceptions raised by a Pyinotify class."""
31 32
33 -class UnsupportedPythonVersionError(PyinotifyError):
34 """ 35 Raised for unsupported Python version. 36 """
37 - def __init__(self, version):
38 """ 39 @param version: Current Python version 40 @type version: string 41 """ 42 PyinotifyError.__init__(self, 43 ('Python %s is unsupported, requires ' 44 'at least Python 2.4') % version)
45 46
47 -class UnsupportedLibcVersionError(PyinotifyError):
48 """ 49 Raised for unsupported libc version. 50 """
51 - def __init__(self, version):
52 """ 53 @param version: Current Libc version 54 @type version: string 55 """ 56 PyinotifyError.__init__(self, 57 ('Libc %s is unsupported, requires ' 58 'at least Libc 2.4') % version)
59 60 61 # Check Python version 62 import sys 63 if sys.version < '2.4': 64 raise UnsupportedPythonVersionError(sys.version) 65 66 67 # Import directives 68 import threading 69 import os 70 import select 71 import struct 72 import fcntl 73 import errno 74 import termios 75 import array 76 import logging 77 import atexit 78 from collections import deque 79 from datetime import datetime, timedelta 80 import time 81 import fnmatch 82 import re 83 import ctypes 84 import ctypes.util 85 86 87 __author__ = "seb@dbzteam.org (Sebastien Martini)" 88 89 __version__ = "0.8.5" 90 91 __metaclass__ = type # Use new-style classes by default 92 93 94 # load libc 95 LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) 96 97 # the libc version check. 98 # XXX: Maybe it is better to check if the libc has the needed functions inside? 99 # Because there are inotify patches for libc 2.3.6. 100 LIBC.gnu_get_libc_version.restype = ctypes.c_char_p 101 LIBC_VERSION = LIBC.gnu_get_libc_version() 102 if LIBC_VERSION < '2.4': 103 raise UnsupportedLibcVersionError(LIBC_VERSION) 104 105 106 # logging 107 log = logging.getLogger("pyinotify") 108 console_handler = logging.StreamHandler() 109 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 110 log.addHandler(console_handler) 111 log.setLevel(20) 112 113 114 # Try to speed-up execution with psyco 115 try: 116 if False: 117 import psyco 118 psyco.full() 119 except ImportError: 120 # Cannot import psyco 121 pass 122 123 124 ### inotify's variables ### 125 126
127 -class SysCtlINotify:
128 """ 129 Access (read, write) inotify's variables through sysctl. 130 131 Examples: 132 - Read variable: myvar = max_queued_events.value 133 - Update variable: max_queued_events.value = 42 134 """ 135 136 inotify_attrs = {'max_user_instances': 1, 137 'max_user_watches': 2, 138 'max_queued_events': 3} 139
140 - def __init__(self, attrname):
141 sino = ctypes.c_int * 3 142 self._attrname = attrname 143 self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
144
145 - def get_val(self):
146 """ 147 @return: stored value. 148 @rtype: int 149 """ 150 oldv = ctypes.c_int(0) 151 size = ctypes.c_int(ctypes.sizeof(oldv)) 152 LIBC.sysctl(self._attr, 3, 153 ctypes.c_voidp(ctypes.addressof(oldv)), 154 ctypes.addressof(size), 155 None, 0) 156 return oldv.value
157
158 - def set_val(self, nval):
159 """ 160 @param nval: set to nval. 161 @type nval: int 162 """ 163 oldv = ctypes.c_int(0) 164 sizeo = ctypes.c_int(ctypes.sizeof(oldv)) 165 newv = ctypes.c_int(nval) 166 sizen = ctypes.c_int(ctypes.sizeof(newv)) 167 LIBC.sysctl(self._attr, 3, 168 ctypes.c_voidp(ctypes.addressof(oldv)), 169 ctypes.addressof(sizeo), 170 ctypes.c_voidp(ctypes.addressof(newv)), 171 ctypes.addressof(sizen))
172 173 value = property(get_val, set_val) 174
175 - def __repr__(self):
176 return '<%s=%d>' % (self._attrname, self.get_val())
177 178 179 # singleton instances 180 # 181 # read int: myvar = max_queued_events.value 182 # update: max_queued_events.value = 42 183 # 184 for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): 185 globals()[attrname] = SysCtlINotify(attrname) 186 187 188 # fixme: put those tests elsewhere 189 # 190 # print max_queued_events 191 # print max_queued_events.value 192 # save = max_queued_events.value 193 # print save 194 # max_queued_events.value += 42 195 # print max_queued_events 196 # max_queued_events.value = save 197 # print max_queued_events 198 199 200 ### iglob ### 201 202 203 # Code taken from standart Python Lib, slightly modified in order to work 204 # with pyinotify (don't exclude dotted files/dirs like .foo). 205 # Original version: 206 # @see: http://svn.python.org/projects/python/trunk/Lib/glob.py 207
208 -def iglob(pathname):
209 if not has_magic(pathname): 210 if hasattr(os.path, 'lexists'): 211 if os.path.lexists(pathname): 212 yield pathname 213 else: 214 if os.path.islink(pathname) or os.path.exists(pathname): 215 yield pathname 216 return 217 dirname, basename = os.path.split(pathname) 218 # relative pathname 219 if not dirname: 220 return 221 # absolute pathname 222 if has_magic(dirname): 223 dirs = iglob(dirname) 224 else: 225 dirs = [dirname] 226 if has_magic(basename): 227 glob_in_dir = glob1 228 else: 229 glob_in_dir = glob0 230 for dirname in dirs: 231 for name in glob_in_dir(dirname, basename): 232 yield os.path.join(dirname, name)
233
234 -def glob1(dirname, pattern):
235 if not dirname: 236 dirname = os.curdir 237 try: 238 names = os.listdir(dirname) 239 except os.error: 240 return [] 241 return fnmatch.filter(names, pattern)
242
243 -def glob0(dirname, basename):
244 if basename == '' and os.path.isdir(dirname): 245 # `os.path.split()` returns an empty basename for paths ending with a 246 # directory separator. 'q*x/' should match only directories. 247 return [basename] 248 if hasattr(os.path, 'lexists'): 249 if os.path.lexists(os.path.join(dirname, basename)): 250 return [basename] 251 else: 252 if (os.path.islink(os.path.join(dirname, basename)) or 253 os.path.exists(os.path.join(dirname, basename))): 254 return [basename] 255 return []
256 257 magic_check = re.compile('[*?[]') 258
259 -def has_magic(s):
260 return magic_check.search(s) is not None
261 262 263 264 ### Core ### 265 266
267 -class EventsCodes:
268 """ 269 Set of codes corresponding to each kind of events. 270 Some of these flags are used to communicate with inotify, whereas 271 the others are sent to userspace by inotify notifying some events. 272 273 @cvar IN_ACCESS: File was accessed. 274 @type IN_ACCESS: int 275 @cvar IN_MODIFY: File was modified. 276 @type IN_MODIFY: int 277 @cvar IN_ATTRIB: Metadata changed. 278 @type IN_ATTRIB: int 279 @cvar IN_CLOSE_WRITE: Writtable file was closed. 280 @type IN_CLOSE_WRITE: int 281 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed. 282 @type IN_CLOSE_NOWRITE: int 283 @cvar IN_OPEN: File was opened. 284 @type IN_OPEN: int 285 @cvar IN_MOVED_FROM: File was moved from X. 286 @type IN_MOVED_FROM: int 287 @cvar IN_MOVED_TO: File was moved to Y. 288 @type IN_MOVED_TO: int 289 @cvar IN_CREATE: Subfile was created. 290 @type IN_CREATE: int 291 @cvar IN_DELETE: Subfile was deleted. 292 @type IN_DELETE: int 293 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted. 294 @type IN_DELETE_SELF: int 295 @cvar IN_MOVE_SELF: Self (watched item itself) was moved. 296 @type IN_MOVE_SELF: int 297 @cvar IN_UNMOUNT: Backing fs was unmounted. 298 @type IN_UNMOUNT: int 299 @cvar IN_Q_OVERFLOW: Event queued overflowed. 300 @type IN_Q_OVERFLOW: int 301 @cvar IN_IGNORED: File was ignored. 302 @type IN_IGNORED: int 303 @cvar IN_ONLYDIR: only watch the path if it is a directory (new 304 in kernel 2.6.15). 305 @type IN_ONLYDIR: int 306 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15). 307 IN_ONLYDIR we can make sure that we don't watch 308 the target of symlinks. 309 @type IN_DONT_FOLLOW: int 310 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new 311 in kernel 2.6.14). 312 @type IN_MASK_ADD: int 313 @cvar IN_ISDIR: Event occurred against dir. 314 @type IN_ISDIR: int 315 @cvar IN_ONESHOT: Only send event once. 316 @type IN_ONESHOT: int 317 @cvar ALL_EVENTS: Alias for considering all of the events. 318 @type ALL_EVENTS: int 319 """ 320 321 # The idea here is 'configuration-as-code' - this way, we get our nice class 322 # constants, but we also get nice human-friendly text mappings to do lookups 323 # against as well, for free: 324 FLAG_COLLECTIONS = {'OP_FLAGS': { 325 'IN_ACCESS' : 0x00000001, # File was accessed 326 'IN_MODIFY' : 0x00000002, # File was modified 327 'IN_ATTRIB' : 0x00000004, # Metadata changed 328 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed 329 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed 330 'IN_OPEN' : 0x00000020, # File was opened 331 'IN_MOVED_FROM' : 0x00000040, # File was moved from X 332 'IN_MOVED_TO' : 0x00000080, # File was moved to Y 333 'IN_CREATE' : 0x00000100, # Subfile was created 334 'IN_DELETE' : 0x00000200, # Subfile was deleted 335 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) 336 # was deleted 337 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved 338 }, 339 'EVENT_FLAGS': { 340 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted 341 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed 342 'IN_IGNORED' : 0x00008000, # File was ignored 343 }, 344 'SPECIAL_FLAGS': { 345 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a 346 # directory 347 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink 348 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already 349 # existing watch 350 'IN_ISDIR' : 0x40000000, # event occurred against dir 351 'IN_ONESHOT' : 0x80000000, # only send event once 352 }, 353 } 354
355 - def maskname(mask):
356 """ 357 Return the event name associated to mask. IN_ISDIR is appended when 358 appropriate. Note: only one event is returned, because only one is 359 raised once at a time. 360 361 @param mask: mask. 362 @type mask: int 363 @return: event name. 364 @rtype: str 365 """ 366 ms = mask 367 name = '%s' 368 if mask & IN_ISDIR: 369 ms = mask - IN_ISDIR 370 name = '%s|IN_ISDIR' 371 return name % EventsCodes.ALL_VALUES[ms]
372 373 maskname = staticmethod(maskname)
374 375 376 # So let's now turn the configuration into code 377 EventsCodes.ALL_FLAGS = {} 378 EventsCodes.ALL_VALUES = {} 379 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems(): 380 # Make the collections' members directly accessible through the 381 # class dictionary 382 setattr(EventsCodes, flagc, valc) 383 384 # Collect all the flags under a common umbrella 385 EventsCodes.ALL_FLAGS.update(valc) 386 387 # Make the individual masks accessible as 'constants' at globals() scope 388 # and masknames accessible by values. 389 for name, val in valc.iteritems(): 390 globals()[name] = val 391 EventsCodes.ALL_VALUES[val] = name 392 393 394 # all 'normal' events 395 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues()) 396 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS 397 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS' 398 399
400 -class _Event:
401 """ 402 Event structure, represent events raised by the system. This 403 is the base class and should be subclassed. 404 405 """
406 - def __init__(self, dict_):
407 """ 408 Attach attributes (contained in dict_) to self. 409 """ 410 for tpl in dict_.iteritems(): 411 setattr(self, *tpl)
412
413 - def __repr__(self):
414 """ 415 @return: String representation. 416 @rtype: str 417 """ 418 s = '' 419 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]): 420 if attr.startswith('_'): 421 continue 422 if attr == 'mask': 423 value = hex(getattr(self, attr)) 424 elif isinstance(value, str) and not value: 425 value ="''" 426 s += ' %s%s%s' % (color_theme.field_name(attr), 427 color_theme.punct('='), 428 color_theme.field_value(value)) 429 430 s = '%s%s%s %s' % (color_theme.punct('<'), 431 color_theme.class_name(self.__class__.__name__), 432 s, 433 color_theme.punct('>')) 434 return s
435 436
437 -class _RawEvent(_Event):
438 """ 439 Raw event, it contains only the informations provided by the system. 440 It doesn't infer anything. 441 """
442 - def __init__(self, wd, mask, cookie, name):
443 """ 444 @param wd: Watch Descriptor. 445 @type wd: int 446 @param mask: Bitmask of events. 447 @type mask: int 448 @param cookie: Cookie. 449 @type cookie: int 450 @param name: Basename of the file or directory against which the 451 event was raised, in case where the watched directory 452 is the parent directory. None if the event was raised 453 on the watched item itself. 454 @type name: string or None 455 """ 456 # name: remove trailing '\0' 457 super(_RawEvent, self).__init__({'wd': wd, 458 'mask': mask, 459 'cookie': cookie, 460 'name': name.rstrip('\0')}) 461 log.debug(repr(self))
462 463
464 -class Event(_Event):
465 """ 466 This class contains all the useful informations about the observed 467 event. However, the incorporation of each field is not guaranteed and 468 depends on the type of event. In effect, some fields are irrelevant 469 for some kind of event (for example 'cookie' is meaningless for 470 IN_CREATE whereas it is useful for IN_MOVE_TO). 471 472 The possible fields are: 473 - wd (int): Watch Descriptor. 474 - mask (int): Mask. 475 - maskname (str): Readable event name. 476 - path (str): path of the file or directory being watched. 477 - name (str): Basename of the file or directory against which the 478 event was raised, in case where the watched directory 479 is the parent directory. None if the event was raised 480 on the watched item itself. This field is always provided 481 even if the string is ''. 482 - pathname (str): absolute path of: path + name 483 - cookie (int): Cookie. 484 - dir (bool): is the event raised against directory. 485 486 """
487 - def __init__(self, raw):
488 """ 489 Concretely, this is the raw event plus inferred infos. 490 """ 491 _Event.__init__(self, raw) 492 self.maskname = EventsCodes.maskname(self.mask) 493 try: 494 if self.name: 495 self.pathname = os.path.abspath(os.path.join(self.path, 496 self.name)) 497 else: 498 self.pathname = os.path.abspath(self.path) 499 except AttributeError: 500 pass
501 502
503 -class ProcessEventError(PyinotifyError):
504 """ 505 ProcessEventError Exception. Raised on ProcessEvent error. 506 """
507 - def __init__(self, err):
508 """ 509 @param err: Exception error description. 510 @type err: string 511 """ 512 PyinotifyError.__init__(self, err)
513 514
515 -class _ProcessEvent:
516 """ 517 Abstract processing event class. 518 """
519 - def __call__(self, event):
520 """ 521 To behave like a functor the object must be callable. 522 This method is a dispatch method. Lookup order: 523 1. process_MASKNAME method 524 2. process_FAMILY_NAME method 525 3. otherwise call process_default 526 527 @param event: Event to be processed. 528 @type event: Event object 529 @return: By convention when used from the ProcessEvent class: 530 - Returning False or None (default value) means keep on 531 executing next chained functors (see chain.py example). 532 - Returning True instead means do not execute next 533 processing functions. 534 @rtype: bool 535 @raise ProcessEventError: Event object undispatchable, 536 unknown event. 537 """ 538 stripped_mask = event.mask - (event.mask & IN_ISDIR) 539 maskname = EventsCodes.ALL_VALUES.get(stripped_mask) 540 if maskname is None: 541 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask) 542 543 # 1- look for process_MASKNAME 544 meth = getattr(self, 'process_' + maskname, None) 545 if meth is not None: 546 return meth(event) 547 # 2- look for process_FAMILY_NAME 548 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None) 549 if meth is not None: 550 return meth(event) 551 # 3- default call method process_default 552 return self.process_default(event)
553
554 - def __repr__(self):
555 return '<%s>' % self.__class__.__name__
556 557
558 -class _SysProcessEvent(_ProcessEvent):
559 """ 560 There is three kind of processing according to each event: 561 562 1. special handling (deletion from internal container, bug, ...). 563 2. default treatment: which is applied to most of events. 564 4. IN_ISDIR is never sent alone, he is piggybacked with a standart 565 event, he is not processed as the others events, instead, its 566 value is captured and appropriately aggregated to dst event. 567 """
568 - def __init__(self, wm, notifier):
569 """ 570 571 @param wm: Watch Manager. 572 @type wm: WatchManager instance 573 @param notifier: notifier. 574 @type notifier: Instance of Notifier. 575 """ 576 self._watch_manager = wm # watch manager 577 self._notifier = notifier # notifier 578 self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...} 579 self._mv = {} # {src_path(str): (dst_path(str), date), ...}
580
581 - def cleanup(self):
582 """ 583 Cleanup (delete) old (>1mn) records contained in self._mv_cookie 584 and self._mv. 585 """ 586 date_cur_ = datetime.now() 587 for seq in [self._mv_cookie, self._mv]: 588 for k in seq.keys(): 589 if (date_cur_ - seq[k][1]) > timedelta(minutes=1): 590 log.debug('cleanup: deleting entry %s' % seq[k][0]) 591 del seq[k]
592
593 - def process_IN_CREATE(self, raw_event):
594 """ 595 If the event concerns a directory and the auto_add flag of the 596 targetted watch is set to True, a new watch is added on this 597 new directory, with the same attributes's values than those of 598 this watch. 599 """ 600 if raw_event.mask & IN_ISDIR: 601 watch_ = self._watch_manager._wmd.get(raw_event.wd) 602 if watch_.auto_add: 603 addw = self._watch_manager.add_watch 604 newwd = addw(os.path.join(watch_.path, raw_event.name), 605 watch_.mask, proc_fun=watch_.proc_fun, 606 rec=False, auto_add=watch_.auto_add) 607 608 # Trick to handle mkdir -p /t1/t2/t3 where t1 is watched and 609 # t2 and t3 are created. 610 # Since the directory is new, then everything inside it 611 # must also be new. 612 base = os.path.join(watch_.path, raw_event.name) 613 if newwd[base] > 0: 614 for name in os.listdir(base): 615 inner = os.path.join(base, 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[base], 621 IN_CREATE | IN_ISDIR, 622 0, name) 623 self._notifier._eventq.append(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._wmd.get(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._wmd.get(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 if mv_: 646 self._mv[mv_[0]] = (dst_path, datetime.now()) 647 return self.process_default(raw_event, {'cookie': raw_event.cookie})
648
649 - def process_IN_MOVE_SELF(self, raw_event):
650 """ 651 STATUS: the following bug has been fixed in the recent kernels (fixme: 652 which version ?). Now it raises IN_DELETE_SELF instead. 653 654 Old kernels are bugged, this event is raised when the watched item 655 was moved, so we must update its path, but under some circumstances it 656 can be impossible: if its parent directory and its destination 657 directory aren't watched. The kernel (see include/linux/fsnotify.h) 658 doesn't bring us enough informations like the destination path of 659 moved items. 660 """ 661 watch_ = self._watch_manager._wmd.get(raw_event.wd) 662 src_path = watch_.path 663 mv_ = self._mv.get(src_path) 664 if mv_: 665 watch_.path = mv_[0] 666 else: 667 log.error("The path %s of this watch %s must not " 668 "be trusted anymore" % (watch_.path, watch_)) 669 if not watch_.path.endswith('-wrong-path'): 670 watch_.path += '-wrong-path' 671 # FIXME: should we pass the cookie even if this is not standart? 672 return self.process_default(raw_event)
673
674 - def process_IN_Q_OVERFLOW(self, raw_event):
675 """ 676 Only signal overflow, most of the common flags are irrelevant 677 for this event (path, wd, name). 678 """ 679 return Event({'mask': raw_event.mask})
680
681 - def process_IN_IGNORED(self, raw_event):
682 """ 683 The watch descriptor raised by this event is now ignored (forever), 684 it can be safely deleted from watch manager dictionary. 685 After this event we can be sure that neither the event queue 686 neither the system will raise an event associated to this wd. 687 """ 688 event_ = self.process_default(raw_event) 689 try: 690 del self._watch_manager._wmd[raw_event.wd] 691 except KeyError, err: 692 log.error(err) 693 return event_
694
695 - def process_default(self, raw_event, to_append={}):
696 """ 697 Common handling for the following events: 698 699 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, 700 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT. 701 """ 702 ret = None 703 watch_ = self._watch_manager._wmd.get(raw_event.wd) 704 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF): 705 # unfornately information not provided by the kernel 706 dir_ = watch_.dir 707 else: 708 dir_ = bool(raw_event.mask & IN_ISDIR) 709 dict_ = {'wd': raw_event.wd, 710 'mask': raw_event.mask, 711 'path': watch_.path, 712 'name': raw_event.name, 713 'dir': dir_} 714 dict_.update(to_append) 715 return Event(dict_)
716 717
718 -class ProcessEvent(_ProcessEvent):
719 """ 720 Process events objects, can be specialized via subclassing, thus its 721 behavior can be overriden: 722 723 Note: you should not override __init__ in your subclass instead define 724 a my_init() method, this method will be called from the constructor of 725 this class with optional parameters. 726 727 1. Provide methods, e.g. process_IN_DELETE for processing a given kind 728 of event (eg. IN_DELETE in this case). 729 2. Or/and provide methods for processing events by 'family', e.g. 730 process_IN_CLOSE method will process both IN_CLOSE_WRITE and 731 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and 732 process_IN_CLOSE_NOWRITE aren't defined). 733 3. Or/and override process_default for processing the remaining kind of 734 events. 735 """ 736 pevent = None 737
738 - def __init__(self, pevent=None, **kargs):
739 """ 740 Enable chaining of ProcessEvent instances. 741 742 @param pevent: optional callable object, will be called on event 743 processing (before self). 744 @type pevent: callable 745 @param kargs: optional arguments delagated to template method my_init 746 @type kargs: dict 747 """ 748 self.pevent = pevent 749 self.my_init(**kargs)
750
751 - def my_init(self, **kargs):
752 """ 753 Override this method when subclassing if you want to achieve 754 custom initialization of your subclass' instance. You MUST pass 755 keyword arguments. This method does nothing by default. 756 757 @param kargs: optional arguments delagated to template method my_init 758 @type kargs: dict 759 """ 760 pass
761
762 - def __call__(self, event):
763 stop_chaining = False 764 if self.pevent is not None: 765 # By default methods return None so we fix as guideline 766 # that methods asking for stop chaining must explicitely 767 # return non None or False values, otherwise the default 768 # behavior is to chain call to the corresponding local 769 # method. 770 stop_chaining = self.pevent(event) 771 if not stop_chaining: 772 return _ProcessEvent.__call__(self, event)
773
774 - def nested_pevent(self):
775 return self.pevent
776
777 - def process_default(self, event):
778 """ 779 Default default processing event method. Print event 780 on standart output. 781 782 @param event: Event to be processed. 783 @type event: Event instance 784 """ 785 print(repr(event))
786 787
788 -class ChainIfTrue(ProcessEvent):
789 """ 790 Makes conditional chaining depending on the result of the nested 791 processing instance. 792 """
793 - def my_init(self, func):
794 self._func = func
795
796 - def process_default(self, event):
797 return not self._func(event)
798 799
800 -class Stats(ProcessEvent):
801 - def my_init(self):
802 self._start_time = time.time() 803 self._stats = {} 804 self._stats_lock = threading.Lock()
805
806 - def process_default(self, event):
807 self._stats_lock.acquire() 808 try: 809 events = event.maskname.split('|') 810 for event_name in events: 811 count = self._stats.get(event_name, 0) 812 self._stats[event_name] = count + 1 813 finally: 814 self._stats_lock.release()
815
816 - def _stats_copy(self):
817 self._stats_lock.acquire() 818 try: 819 return self._stats.copy() 820 finally: 821 self._stats_lock.release()
822
823 - def __repr__(self):
824 stats = self._stats_copy() 825 826 t = int(time.time() - self._start_time) 827 if t < 60: 828 ts = str(t) + 'sec' 829 elif 60 <= t < 3600: 830 ts = '%dmn%dsec' % (t / 60, t % 60) 831 elif 3600 <= t < 86400: 832 ts = '%dh%dmn' % (t / 3600, (t % 3600) / 60) 833 elif t >= 86400: 834 ts = '%dd%dh' % (t / 86400, (t % 86400) / 3600) 835 stats['ElapsedTime'] = ts 836 837 l = [] 838 for ev, value in sorted(stats.items(), key=lambda x: x[0]): 839 l.append(' %s=%s' % (color_theme.field_name(ev), 840 color_theme.field_value(value))) 841 s = '<%s%s >' % (color_theme.class_name(self.__class__.__name__), 842 ''.join(l)) 843 return s
844
845 - def dump(self, filename):
846 fo = file(filename, 'wb') 847 try: 848 fo.write(str(self)) 849 finally: 850 fo.close()
851
852 - def __str__(self, scale=45):
853 stats = self._stats_copy() 854 if not stats: 855 return '' 856 857 m = max(stats.values()) 858 unity = int(round(float(m) / scale)) or 1 859 fmt = '%%-26s%%-%ds%%s' % (len(color_theme.field_value('@' * scale)) 860 + 1) 861 def func(x): 862 return fmt % (color_theme.field_name(x[0]), 863 color_theme.field_value('@' * (x[1] / unity)), 864 color_theme.yellow('%d' % x[1]))
865 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0]))) 866 return s
867 868
869 -class NotifierError(PyinotifyError):
870 """ 871 Notifier Exception. Raised on Notifier error. 872 873 """
874 - def __init__(self, err):
875 """ 876 @param err: Exception string's description. 877 @type err: string 878 """ 879 PyinotifyError.__init__(self, err)
880 881
882 -class Notifier:
883 """ 884 Read notifications, process events. 885 886 """
887 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 888 read_freq=0, treshold=0, timeout=None):
889 """ 890 Initialization. read_freq, treshold and timeout parameters are used 891 when looping. 892 893 @param watch_manager: Watch Manager. 894 @type watch_manager: WatchManager instance 895 @param default_proc_fun: Default processing method. 896 @type default_proc_fun: instance of ProcessEvent 897 @param read_freq: if read_freq == 0, events are read asap, 898 if read_freq is > 0, this thread sleeps 899 max(0, read_freq - timeout) seconds. But if 900 timeout is None it can be different because 901 poll is blocking waiting for something to read. 902 @type read_freq: int 903 @param treshold: File descriptor will be read only if its size to 904 read is >= treshold. If != 0, you likely want to 905 use it in combination with read_freq because 906 without that you keep looping without really reading 907 anything and that until the amount to read 908 is >= treshold. At least with read_freq you may sleep. 909 @type treshold: int 910 @param timeout: 911 http://docs.python.org/lib/poll-objects.html#poll-objects 912 @type timeout: int 913 """ 914 # watch manager instance 915 self._watch_manager = watch_manager 916 # file descriptor 917 self._fd = self._watch_manager._fd 918 # poll object and registration 919 self._pollobj = select.poll() 920 self._pollobj.register(self._fd, select.POLLIN) 921 # This pipe is correctely initialized and used by ThreadedNotifier 922 self._pipe = (-1, -1) 923 # event queue 924 self._eventq = deque() 925 # system processing functor, common to all events 926 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self) 927 # default processing method 928 self._default_proc_fun = default_proc_fun 929 # loop parameters 930 self._read_freq = read_freq 931 self._treshold = treshold 932 self._timeout = timeout
933
934 - def proc_fun(self):
935 return self._default_proc_fun
936
937 - def check_events(self):
938 """ 939 Check for new events available to read, blocks up to timeout 940 milliseconds. 941 942 @return: New events to read. 943 @rtype: bool 944 """ 945 while True: 946 try: 947 # blocks up to 'timeout' milliseconds 948 ret = self._pollobj.poll(self._timeout) 949 except select.error, err: 950 if err[0] == errno.EINTR: 951 continue # interrupted, retry 952 else: 953 raise 954 else: 955 break 956 957 if not ret or (self._pipe[0] == ret[0][0]): 958 return False 959 # only one fd is polled 960 return ret[0][1] & select.POLLIN
961
962 - def read_events(self):
963 """ 964 Read events from device, build _RawEvents, and enqueue them. 965 """ 966 buf_ = array.array('i', [0]) 967 # get event queue size 968 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1: 969 return 970 queue_size = buf_[0] 971 if queue_size < self._treshold: 972 log.debug('(fd: %d) %d bytes available to read but ' 973 'treshold is fixed to %d bytes' % (self._fd, 974 queue_size, 975 self._treshold)) 976 return 977 978 try: 979 # read content from file 980 r = os.read(self._fd, queue_size) 981 except Exception, msg: 982 raise NotifierError(msg) 983 log.debug('event queue size: %d' % queue_size) 984 rsum = 0 # counter 985 while rsum < queue_size: 986 s_size = 16 987 # retrieve wd, mask, cookie 988 s_ = struct.unpack('iIII', r[rsum:rsum+s_size]) 989 # length of name 990 fname_len = s_[3] 991 # field 'length' useless 992 s_ = s_[:-1] 993 # retrieve name 994 s_ += struct.unpack('%ds' % fname_len, 995 r[rsum + s_size:rsum + s_size + fname_len]) 996 self._eventq.append(_RawEvent(*s_)) 997 rsum += s_size + fname_len
998
999 - def process_events(self):
1000 """ 1001 Routine for processing events from queue by calling their 1002 associated proccessing function (instance of ProcessEvent). 1003 It also do internal processings, to keep the system updated. 1004 """ 1005 while self._eventq: 1006 raw_event = self._eventq.popleft() # pop next event 1007 watch_ = self._watch_manager._wmd.get(raw_event.wd) 1008 revent = self._sys_proc_fun(raw_event) # system processings 1009 if watch_ and watch_.proc_fun: 1010 watch_.proc_fun(revent) # user processings 1011 else: 1012 self._default_proc_fun(revent) 1013 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1014 1015
1016 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull, 1017 stdout=os.devnull, stderr=os.devnull):
1018 """ 1019 pid_file: file to which pid will be written. 1020 force_kill: if True kill the process associated to pid_file. 1021 stdin, stdout, stderr: files associated to common streams. 1022 """ 1023 if pid_file is None: 1024 dirname = '/var/run/' 1025 basename = sys.argv[0] or 'pyinotify' 1026 pid_file = os.path.join(dirname, basename + '.pid') 1027 1028 if os.path.exists(pid_file): 1029 fo = file(pid_file, 'rb') 1030 try: 1031 try: 1032 pid = int(fo.read()) 1033 except ValueError: 1034 pid = None 1035 if pid is not None: 1036 try: 1037 os.kill(pid, 0) 1038 except OSError, err: 1039 pass 1040 else: 1041 if not force_kill: 1042 s = 'There is already a pid file %s with pid %d' 1043 raise NotifierError(s % (pid_file, pid)) 1044 else: 1045 os.kill(pid, 9) 1046 finally: 1047 fo.close() 1048 1049 1050 def fork_daemon(): 1051 # Adapted from Chad J. Schroeder's recipe 1052 pid = os.fork() 1053 if (pid == 0): 1054 # parent 2 1055 os.setsid() 1056 pid = os.fork() 1057 if (pid == 0): 1058 # child 1059 os.chdir('/') 1060 os.umask(0) 1061 else: 1062 # parent 2 1063 os._exit(0) 1064 else: 1065 # parent 1 1066 os._exit(0) 1067 1068 fd_inp = os.open(stdin, os.O_RDONLY) 1069 os.dup2(fd_inp, 0) 1070 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT) 1071 os.dup2(fd_out, 1) 1072 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT) 1073 os.dup2(fd_err, 2)
1074 1075 # Detach task 1076 fork_daemon() 1077 1078 # Write pid 1079 fo = file(pid_file, 'wb') 1080 try: 1081 fo.write(str(os.getpid()) + '\n') 1082 finally: 1083 fo.close() 1084 1085 atexit.register(lambda : os.unlink(pid_file))
1086 1087
1088 - def _sleep(self, ref_time):
1089 # Only consider sleeping if read_freq is > 0 1090 if self._read_freq > 0: 1091 cur_time = time.time() 1092 sleep_amount = self._read_freq - (cur_time - ref_time) 1093 if sleep_amount > 0: 1094 log.debug('Now sleeping %d seconds' % sleep_amount) 1095 time.sleep(sleep_amount)
1096 1097
1098 - def loop(self, callback=None, daemonize=False, **args):
1099 """ 1100 Events are read only once time every min(read_freq, timeout) 1101 seconds at best and only if the size to read is >= treshold. 1102 1103 @param callback: Functor called after each event processing. Expects 1104 to receive notifier object (self) as first parameter. 1105 @type callback: callable 1106 @param daemonize: This thread is daemonized if set to True. 1107 @type daemonize: boolean 1108 """ 1109 if daemonize: 1110 self.__daemonize(**args) 1111 1112 # Read and process events forever 1113 while 1: 1114 try: 1115 self.process_events() 1116 if callback is not None: 1117 callback(self) 1118 ref_time = time.time() 1119 # check_events is blocking 1120 if self.check_events(): 1121 self._sleep(ref_time) 1122 self.read_events() 1123 except KeyboardInterrupt: 1124 # Unless sigint is caught (Control-C) 1125 log.debug('Pyinotify stops monitoring.') 1126 # Stop monitoring 1127 self.stop() 1128 break
1129
1130 - def stop(self):
1131 """ 1132 Close the inotify's instance (close its file descriptor). 1133 It destroys all existing watches, pending events,... 1134 """ 1135 self._pollobj.unregister(self._fd) 1136 os.close(self._fd)
1137 1138
1139 -class ThreadedNotifier(threading.Thread, Notifier):
1140 """ 1141 This notifier inherits from threading.Thread for instantiating a separate 1142 thread, and also inherits from Notifier, because it is a threaded notifier. 1143 1144 Note that everything possible with this class is also possible through 1145 Notifier. Moreover Notifier is _better_ under many aspects: not threaded, 1146 can be easily daemonized. 1147 """
1148 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 1149 read_freq=0, treshold=0, timeout=None):
1150 """ 1151 Initialization, initialize base classes. read_freq, treshold and 1152 timeout parameters are used when looping. 1153 1154 @param watch_manager: Watch Manager. 1155 @type watch_manager: WatchManager instance 1156 @param default_proc_fun: Default processing method. 1157 @type default_proc_fun: instance of ProcessEvent 1158 @param read_freq: if read_freq == 0, events are read asap, 1159 if read_freq is > 0, this thread sleeps 1160 max(0, read_freq - timeout) seconds. 1161 @type read_freq: int 1162 @param treshold: File descriptor will be read only if its size to 1163 read is >= treshold. If != 0, you likely want to 1164 use it in combination with read_freq because 1165 without that you keep looping without really reading 1166 anything and that until the amount to read 1167 is >= treshold. At least with read_freq you may sleep. 1168 @type treshold: int 1169 @param timeout: 1170 see http://docs.python.org/lib/poll-objects.html#poll-objects 1171 Read the corresponding comment in the source code before changing 1172 it. 1173 @type timeout: int 1174 """ 1175 # Init threading base class 1176 threading.Thread.__init__(self) 1177 # Stop condition 1178 self._stop_event = threading.Event() 1179 # Init Notifier base class 1180 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1181 treshold, timeout) 1182 # Create a new pipe used for thread termination 1183 self._pipe = os.pipe() 1184 self._pollobj.register(self._pipe[0], select.POLLIN)
1185
1186 - def stop(self):
1187 """ 1188 Stop the notifier's loop. Stop notification. Join the thread. 1189 """ 1190 self._stop_event.set() 1191 os.write(self._pipe[1], 'stop') 1192 threading.Thread.join(self) 1193 Notifier.stop(self) 1194 self._pollobj.unregister(self._pipe[0]) 1195 os.close(self._pipe[0]) 1196 os.close(self._pipe[1])
1197
1198 - def loop(self):
1199 """ 1200 Thread's main loop. Don't meant to be called by user directly. 1201 Call start() instead. 1202 1203 Events are read only once time every min(read_freq, timeout) 1204 seconds at best and only if the size of events to read is >= treshold. 1205 """ 1206 # When the loop must be terminated .stop() is called, 'stop' 1207 # is written to pipe fd so poll() returns and .check_events() 1208 # returns False which make evaluate the While's stop condition 1209 # ._stop_event.isSet() wich put an end to the thread's execution. 1210 while not self._stop_event.isSet(): 1211 self.process_events() 1212 ref_time = time.time() 1213 if self.check_events(): 1214 self._sleep(ref_time) 1215 self.read_events()
1216
1217 - def run(self):
1218 """ 1219 Start the thread's loop: read and process events until the method 1220 stop() is called. 1221 Never call this method directly, instead call the start() method 1222 inherited from threading.Thread, which then will call run(). 1223 """ 1224 self.loop()
1225 1226
1227 -class Watch:
1228 """ 1229 Represent a watch, i.e. a file or directory being watched. 1230 1231 """
1232 - def __init__(self, **keys):
1233 """ 1234 Initializations. 1235 1236 @param wd: Watch descriptor. 1237 @type wd: int 1238 @param path: Path of the file or directory being watched. 1239 @type path: str 1240 @param mask: Mask. 1241 @type mask: int 1242 @param proc_fun: Processing callable object. 1243 @type proc_fun: 1244 @param auto_add: Automatically add watches on new directories. 1245 @type auto_add: bool 1246 """ 1247 for k, v in keys.iteritems(): 1248 setattr(self, k, v) 1249 self.dir = os.path.isdir(self.path)
1250
1251 - def __repr__(self):
1252 """ 1253 @return: String representation. 1254 @rtype: str 1255 """ 1256 s = ' '.join(['%s%s%s' % (color_theme.field_name(attr), 1257 color_theme.punct('='), 1258 color_theme.field_value(getattr(self, 1259 attr))) \ 1260 for attr in self.__dict__ if not attr.startswith('_')]) 1261 1262 s = '%s%s %s %s' % (color_theme.punct('<'), 1263 color_theme.class_name(self.__class__.__name__), 1264 s, 1265 color_theme.punct('>')) 1266 return s
1267 1268
1269 -class ExcludeFilter:
1270 """ 1271 ExcludeFilter is an exclusion filter. 1272 """ 1273
1274 - def __init__(self, arg_lst):
1275 """ 1276 @param arg_lst: is either a list or dict of patterns: 1277 [pattern1, ..., patternn] 1278 {'filename1': (list1, listn), ...} where list1 is 1279 a list of patterns 1280 @type arg_lst: list or dict 1281 """ 1282 if isinstance(arg_lst, dict): 1283 lst = self._load_patterns(arg_lst) 1284 elif isinstance(arg_lst, list): 1285 lst = arg_lst 1286 else: 1287 raise TypeError 1288 1289 self._lregex = [] 1290 for regex in lst: 1291 self._lregex.append(re.compile(regex, re.UNICODE))
1292
1293 - def _load_patterns(self, dct):
1294 lst = [] 1295 for path, varnames in dct.iteritems(): 1296 loc = {} 1297 execfile(path, {}, loc) 1298 for varname in varnames: 1299 lst.extend(loc.get(varname, [])) 1300 return lst
1301
1302 - def _match(self, regex, path):
1303 return regex.match(path) is not None
1304
1305 - def __call__(self, path):
1306 """ 1307 @param path: path to match against regexps. 1308 @type path: str 1309 @return: return True is path has been matched and should 1310 be excluded, False otherwise. 1311 @rtype: bool 1312 """ 1313 for regex in self._lregex: 1314 if self._match(regex, path): 1315 return True 1316 return False
1317 1318
1319 -class WatchManagerError(Exception):
1320 """ 1321 WatchManager Exception. Raised on error encountered on watches 1322 operations. 1323 1324 """
1325 - def __init__(self, msg, wmd):
1326 """ 1327 @param msg: Exception string's description. 1328 @type msg: string 1329 @param wmd: Results of previous operations made by the same function 1330 on previous wd or paths. It also contains the item which 1331 raised this exception. 1332 @type wmd: dict 1333 """ 1334 self.wmd = wmd 1335 Exception.__init__(self, msg)
1336 1337
1338 -class WatchManager:
1339 """ 1340 Provide operations for watching files and directories. Integrated 1341 dictionary is used to reference watched items. 1342 """
1343 - def __init__(self, exclude_filter=lambda path: False):
1344 """ 1345 Initialization: init inotify, init watch manager dictionary. 1346 Raise OSError if initialization fails. 1347 1348 @param exclude_filter: boolean function, returns True if current 1349 path must be excluded from being watched. 1350 Convenient for providing a common exclusion 1351 filter for every call to add_watch. 1352 @type exclude_filter: bool 1353 """ 1354 self._exclude_filter = exclude_filter 1355 self._wmd = {} # watch dict key: watch descriptor, value: watch 1356 self._fd = LIBC.inotify_init() # inotify's init, file descriptor 1357 if self._fd < 0: 1358 raise OSError()
1359
1360 - def __add_watch(self, path, mask, proc_fun, auto_add):
1361 """ 1362 Add a watch on path, build a Watch object and insert it in the 1363 watch manager dictionary. Return the wd value. 1364 """ 1365 wd_ = LIBC.inotify_add_watch(self._fd, 1366 ctypes.create_string_buffer(path), 1367 mask) 1368 if wd_ < 0: 1369 return wd_ 1370 watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask, 1371 proc_fun=proc_fun, auto_add=auto_add) 1372 self._wmd[wd_] = watch_ 1373 log.debug('New %s' % watch_) 1374 return wd_
1375
1376 - def __glob(self, path, do_glob):
1377 if do_glob: 1378 return iglob(path) 1379 else: 1380 return [path]
1381
1382 - def add_watch(self, path, mask, proc_fun=None, rec=False, 1383 auto_add=False, do_glob=False, quiet=True, 1384 exclude_filter=None):
1385 """ 1386 Add watch(s) on given path(s) with the specified mask and 1387 optionnally with a processing function and recursive flag. 1388 1389 @param path: Path to watch, the path can either be a file or a 1390 directory. Also accepts a sequence (list) of paths. 1391 @type path: string or list of string 1392 @param mask: Bitmask of events. 1393 @type mask: int 1394 @param proc_fun: Processing object. 1395 @type proc_fun: function or ProcessEvent instance or instance of 1396 one of its subclasses or callable object. 1397 @param rec: Recursively add watches from path on all its 1398 subdirectories, set to False by default (doesn't 1399 follows symlinks). 1400 @type rec: bool 1401 @param auto_add: Automatically add watches on newly created 1402 directories in the watch's path. 1403 @type auto_add: bool 1404 @param do_glob: Do globbing on pathname. 1405 @type do_glob: bool 1406 @param quiet: if True raise an WatchManagerError exception on 1407 error. See example not_quiet.py 1408 @type quiet: bool 1409 @param exclude_filter: boolean function, returns True if current 1410 path must be excluded from being watched. 1411 Has precedence on exclude_filter defined 1412 into __init__. 1413 @type exclude_filter: bool 1414 @return: dict of paths associated to watch descriptors. A wd value 1415 is positive if the watch has been sucessfully added, 1416 otherwise the value is negative. If the path is invalid 1417 it will be not included into this dict. 1418 @rtype: dict of {str: int} 1419 """ 1420 ret_ = {} # return {path: wd, ...} 1421 1422 if exclude_filter is None: 1423 exclude_filter = self._exclude_filter 1424 1425 # normalize args as list elements 1426 for npath in self.__format_param(path): 1427 # unix pathname pattern expansion 1428 for apath in self.__glob(npath, do_glob): 1429 # recursively list subdirs according to rec param 1430 for rpath in self.__walk_rec(apath, rec): 1431 if not exclude_filter(rpath): 1432 wd = ret_[rpath] = self.__add_watch(rpath, mask, 1433 proc_fun, 1434 auto_add) 1435 if wd < 0: 1436 err = 'add_watch: cannot watch %s (WD=%d)' 1437 err = err % (rpath, wd) 1438 if quiet: 1439 log.error(err) 1440 else: 1441 raise WatchManagerError(err, ret_) 1442 else: 1443 # Let's say -2 means 'explicitely excluded 1444 # from watching'. 1445 ret_[rpath] = -2 1446 return ret_
1447
1448 - def __get_sub_rec(self, lpath):
1449 """ 1450 Get every wd from self._wmd if its path is under the path of 1451 one (at least) of those in lpath. Doesn't follow symlinks. 1452 1453 @param lpath: list of watch descriptor 1454 @type lpath: list of int 1455 @return: list of watch descriptor 1456 @rtype: list of int 1457 """ 1458 for d in lpath: 1459 root = self.get_path(d) 1460 if root: 1461 # always keep root 1462 yield d 1463 else: 1464 # if invalid 1465 continue 1466 1467 # nothing else to expect 1468 if not os.path.isdir(root): 1469 continue 1470 1471 # normalization 1472 root = os.path.normpath(root) 1473 # recursion 1474 lend = len(root) 1475 for iwd in self._wmd.items(): 1476 cur = iwd[1].path 1477 pref = os.path.commonprefix([root, cur]) 1478 if root == os.sep or (len(pref) == lend and \ 1479 len(cur) > lend and \ 1480 cur[lend] == os.sep): 1481 yield iwd[1].wd
1482
1483 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False, 1484 auto_add=False, quiet=True):
1485 """ 1486 Update existing watch(s). Both the mask and the processing 1487 object can be modified. 1488 1489 @param wd: Watch Descriptor to update. Also accepts a list of 1490 watch descriptors. 1491 @type wd: int or list of int 1492 @param mask: Optional new bitmask of events. 1493 @type mask: int 1494 @param proc_fun: Optional new processing function. 1495 @type proc_fun: function or ProcessEvent instance or instance of 1496 one of its subclasses or callable object. 1497 @param rec: Recursively update watches on every already watched 1498 subdirectories and subfiles. 1499 @type rec: bool 1500 @param auto_add: Automatically add watches on newly created 1501 directories in the watch's path. 1502 @type auto_add: bool 1503 @param quiet: if True raise an WatchManagerError exception on 1504 error. See example not_quiet.py 1505 @type quiet: bool 1506 @return: dict of watch descriptors associated to booleans values. 1507 True if the corresponding wd has been successfully 1508 updated, False otherwise. 1509 @rtype: dict of int: bool 1510 """ 1511 lwd = self.__format_param(wd) 1512 if rec: 1513 lwd = self.__get_sub_rec(lwd) 1514 1515 ret_ = {} # return {wd: bool, ...} 1516 for awd in lwd: 1517 apath = self.get_path(awd) 1518 if not apath or awd < 0: 1519 err = 'update_watch: invalid WD=%d' % awd 1520 if quiet: 1521 log.error(err) 1522 continue 1523 raise WatchManagerError(err, ret_) 1524 1525 if mask: 1526 addw = LIBC.inotify_add_watch 1527 wd_ = addw(self._fd, 1528 ctypes.create_string_buffer(apath), 1529 mask) 1530 if wd_ < 0: 1531 ret_[awd] = False 1532 err = 'update_watch: cannot update WD=%d (%s)' % (wd_, 1533 apath) 1534 if quiet: 1535 log.error(err) 1536 continue 1537 raise WatchManagerError(err, ret_) 1538 1539 assert(awd == wd_) 1540 1541 if proc_fun or auto_add: 1542 watch_ = self._wmd[awd] 1543 1544 if proc_fun: 1545 watch_.proc_fun = proc_fun 1546 1547 if auto_add: 1548 watch_.proc_fun = auto_add 1549 1550 ret_[awd] = True 1551 log.debug('Updated watch - %s' % self._wmd[awd]) 1552 return ret_
1553
1554 - def __format_param(self, param):
1555 """ 1556 @param param: Parameter. 1557 @type param: string or int 1558 @return: wrap param. 1559 @rtype: list of type(param) 1560 """ 1561 if isinstance(param, list): 1562 for p_ in param: 1563 yield p_ 1564 else: 1565 yield param
1566
1567 - def get_wd(self, path):
1568 """ 1569 Returns the watch descriptor associated to path. This method 1570 has an prohibitive cost, always prefer to keep the WD. 1571 If path is unknown None is returned. 1572 1573 @param path: path. 1574 @type path: str 1575 @return: WD or None. 1576 @rtype: int or None 1577 """ 1578 path = os.path.normpath(path) 1579 for iwd in self._wmd.iteritems(): 1580 if iwd[1].path == path: 1581 return iwd[0] 1582 log.debug('get_wd: unknown path %s' % path)
1583
1584 - def get_path(self, wd):
1585 """ 1586 Returns the path associated to WD, if WD is unknown 1587 None is returned. 1588 1589 @param wd: watch descriptor. 1590 @type wd: int 1591 @return: path or None. 1592 @rtype: string or None 1593 """ 1594 watch_ = self._wmd.get(wd) 1595 if watch_: 1596 return watch_.path 1597 log.debug('get_path: unknown WD %d' % wd)
1598
1599 - def __walk_rec(self, top, rec):
1600 """ 1601 Yields each subdirectories of top, doesn't follow symlinks. 1602 If rec is false, only yield top. 1603 1604 @param top: root directory. 1605 @type top: string 1606 @param rec: recursive flag. 1607 @type rec: bool 1608 @return: path of one subdirectory. 1609 @rtype: string 1610 """ 1611 if not rec or os.path.islink(top) or not os.path.isdir(top): 1612 yield top 1613 else: 1614 for root, dirs, files in os.walk(top): 1615 yield root
1616
1617 - def rm_watch(self, wd, rec=False, quiet=True):
1618 """ 1619 Removes watch(s). 1620 1621 @param wd: Watch Descriptor of the file or directory to unwatch. 1622 Also accepts a list of WDs. 1623 @type wd: int or list of int. 1624 @param rec: Recursively removes watches on every already watched 1625 subdirectories and subfiles. 1626 @type rec: bool 1627 @param quiet: if True raise an WatchManagerError exception on 1628 error. See example not_quiet.py 1629 @type quiet: bool 1630 @return: dict of watch descriptors associated to booleans values. 1631 True if the corresponding wd has been successfully 1632 removed, False otherwise. 1633 @rtype: dict of int: bool 1634 """ 1635 lwd = self.__format_param(wd) 1636 if rec: 1637 lwd = self.__get_sub_rec(lwd) 1638 1639 ret_ = {} # return {wd: bool, ...} 1640 for awd in lwd: 1641 # remove watch 1642 wd_ = LIBC.inotify_rm_watch(self._fd, awd) 1643 if wd_ < 0: 1644 ret_[awd] = False 1645 err = 'rm_watch: cannot remove WD=%d' % awd 1646 if quiet: 1647 log.error(err) 1648 continue 1649 raise WatchManagerError(err, ret_) 1650 1651 ret_[awd] = True 1652 log.debug('watch WD=%d (%s) removed' % (awd, self.get_path(awd))) 1653 return ret_
1654 1655
1656 - def watch_transient_file(self, filename, mask, proc_class):
1657 """ 1658 Watch a transient file, which will be created and deleted frequently 1659 over time (e.g. pid file). 1660 1661 @attention: Under the call to this function it will be impossible 1662 to correctly watch the events triggered into the same 1663 base directory than the directory where is located this watched 1664 transient file. For instance it would actually be wrong to make these 1665 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...) 1666 and wm.add_watch('/var/run/', ...) 1667 1668 @param filename: Filename. 1669 @type filename: string 1670 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE. 1671 @type mask: int 1672 @param proc_class: ProcessEvent (or of one of its subclass), beware of 1673 accepting a ProcessEvent's instance as argument into 1674 __init__, see transient_file.py example for more 1675 details. 1676 @type proc_class: ProcessEvent's instance or of one of its subclasses. 1677 @return: See add_watch(). 1678 @rtype: See add_watch(). 1679 """ 1680 dirname = os.path.dirname(filename) 1681 if dirname == '': 1682 return {} # Maintains coherence with add_watch() 1683 basename = os.path.basename(filename) 1684 # Assuming we are watching at least for IN_CREATE and IN_DELETE 1685 mask |= IN_CREATE | IN_DELETE 1686 1687 def cmp_name(event): 1688 return basename == event.name
1689 return self.add_watch(dirname, mask, 1690 proc_fun=proc_class(ChainIfTrue(func=cmp_name)), 1691 rec=False, 1692 auto_add=False, do_glob=False)
1693 1694 1695 # 1696 # The color mechanism is taken from Scapy: 1697 # @see: http://www.secdev.org/projects/scapy/ 1698 # Thanks to Philippe Biondi for his awesome tool and design. 1699 # 1700
1701 -class Color:
1702 normal = "\033[0m" 1703 black = "\033[30m" 1704 red = "\033[31m" 1705 green = "\033[32m" 1706 yellow = "\033[33m" 1707 blue = "\033[34m" 1708 purple = "\033[35m" 1709 cyan = "\033[36m" 1710 grey = "\033[37m" 1711 1712 bold = "\033[1m" 1713 uline = "\033[4m" 1714 blink = "\033[5m" 1715 invert = "\033[7m"
1716
1717 -class ColorTheme:
1718 - def __repr__(self):
1719 return "<%s>" % self.__class__.__name__
1720 - def __getattr__(self, attr):
1721 return lambda x:x
1722
1723 -class NoTheme(ColorTheme):
1724 pass
1725
1726 -class AnsiColorTheme(ColorTheme):
1727 - def __getattr__(self, attr):
1728 if attr.startswith("__"): 1729 raise AttributeError(attr) 1730 s = "style_%s" % attr 1731 if s in self.__class__.__dict__: 1732 before = getattr(self, s) 1733 after = self.style_normal 1734 else: 1735 before = after = "" 1736 1737 def do_style(val, fmt=None, before=before, after=after): 1738 if fmt is None: 1739 if type(val) is not str: 1740 val = str(val) 1741 else: 1742 val = fmt % val 1743 return before+val+after
1744 return do_style
1745 1746 1747 style_normal = "" 1748 style_prompt = "" # '>>>' 1749 style_punct = "" 1750 style_id = "" 1751 style_not_printable = "" 1752 style_class_name = "" 1753 style_field_name = "" 1754 style_field_value = "" 1755 style_emph_field_name = "" 1756 style_emph_field_value = "" 1757 style_watchlist_name = "" 1758 style_watchlist_type = "" 1759 style_watchlist_value = "" 1760 style_fail = "" 1761 style_success = "" 1762 style_odd = "" 1763 style_even = "" 1764 style_yellow = "" 1765 style_active = "" 1766 style_closed = "" 1767 style_left = "" 1768 style_right = "" 1769
1770 -class BlackAndWhite(AnsiColorTheme):
1771 pass
1772
1773 -class DefaultTheme(AnsiColorTheme):
1774 style_normal = Color.normal 1775 style_prompt = Color.blue+Color.bold 1776 style_punct = Color.normal 1777 style_id = Color.blue+Color.bold 1778 style_not_printable = Color.grey 1779 style_class_name = Color.red+Color.bold 1780 style_field_name = Color.blue 1781 style_field_value = Color.purple 1782 style_emph_field_name = Color.blue+Color.uline+Color.bold 1783 style_emph_field_value = Color.purple+Color.uline+Color.bold 1784 style_watchlist_type = Color.blue 1785 style_watchlist_value = Color.purple 1786 style_fail = Color.red+Color.bold 1787 style_success = Color.blue+Color.bold 1788 style_even = Color.black+Color.bold 1789 style_odd = Color.black 1790 style_yellow = Color.yellow 1791 style_active = Color.black 1792 style_closed = Color.grey 1793 style_left = Color.blue+Color.invert 1794 style_right = Color.red+Color.invert
1795 1796 color_theme = DefaultTheme() 1797 1798 1799
1800 -def command_line():
1801 # 1802 # - By default the watched path is '/tmp' for all events. 1803 # - The monitoring execution blocks and serve forever, type c^c 1804 # to stop it. 1805 # 1806 from optparse import OptionParser 1807 1808 usage = "usage: %prog [options] [path1] [path2] [pathn]" 1809 1810 parser = OptionParser(usage=usage) 1811 parser.add_option("-v", "--verbose", action="store_true", 1812 dest="verbose", help="Verbose mode") 1813 parser.add_option("-r", "--recursive", action="store_true", 1814 dest="recursive", 1815 help="Add watches recursively on paths") 1816 parser.add_option("-a", "--auto_add", action="store_true", 1817 dest="auto_add", 1818 help="Automatically add watches on new directories") 1819 parser.add_option("-e", "--events-list", metavar="EVENT[,...]", 1820 dest="events_list", 1821 help=("A comma-separated list of events to watch for - " 1822 "see the documentation for valid options (defaults" 1823 " to everything)")) 1824 parser.add_option("-s", "--stats", action="store_true", 1825 dest="stats", 1826 help="Display statistics") 1827 1828 (options, args) = parser.parse_args() 1829 1830 if options.verbose: 1831 log.setLevel(10) 1832 1833 if len(args) < 1: 1834 path = '/tmp' # default watched path 1835 else: 1836 path = args 1837 1838 # watch manager instance 1839 wm = WatchManager() 1840 # notifier instance and init 1841 if options.stats: 1842 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5) 1843 else: 1844 notifier = Notifier(wm) 1845 1846 # What mask to apply 1847 mask = 0 1848 if options.events_list: 1849 events_list = options.events_list.split(',') 1850 for ev in events_list: 1851 evcode = EventsCodes.ALL_FLAGS.get(ev, 0) 1852 if evcode: 1853 mask |= evcode 1854 else: 1855 parser.error("The event '%s' specified with option -e" 1856 " is not valid" % ev) 1857 else: 1858 mask = ALL_EVENTS 1859 1860 # stats 1861 cb_fun = None 1862 if options.stats: 1863 def cb(s): 1864 print('%s\n%s\n' % (repr(s.proc_fun()), 1865 s.proc_fun()))
1866 cb_fun = cb 1867 1868 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path) 1869 1870 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add) 1871 # Loop forever (until sigint signal get caught) 1872 notifier.loop(callback=cb_fun) 1873 1874 1875 if __name__ == '__main__': 1876 command_line() 1877