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