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) 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  # as published by the Free Software Foundation; either version 2 
  10  # of the License, or (at your option) any later version. 
  11  # 
  12  # This program is distributed in the hope that it will be useful, 
  13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  15  # GNU General Public License for more details. 
  16  # 
  17  # You should have received a copy of the GNU General Public License 
  18  # along with this program; if not, write to the Free Software 
  19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
  20  # 02110-1301, USA. 
  21   
  22  """ 
  23  pyinotify 
  24   
  25  @author: Sebastien Martini 
  26  @license: GPLv2+ 
  27  @contact: seb@dbzteam.org 
  28  """ 
29 30 -class PyinotifyError(Exception):
31 """Indicates exceptions raised by a Pyinotify class."""
32
33 34 -class UnsupportedPythonVersionError(PyinotifyError):
35 """ 36 Raised for unsupported Python version. 37 """
38 - def __init__(self, version):
39 """ 40 @param version: Current Python version 41 @type version: string 42 """ 43 PyinotifyError.__init__(self, 44 ('Python %s is unsupported, requires ' 45 'at least Python 2.4') % version)
46
47 48 -class UnsupportedLibcVersionError(PyinotifyError):
49 """ 50 Raised for unsupported libc version. 51 """
52 - def __init__(self, version):
53 """ 54 @param version: Current Libc version 55 @type version: string 56 """ 57 PyinotifyError.__init__(self, 58 ('Libc %s is unsupported, requires ' 59 'at least Libc 2.4') % version)
60 61 62 # Check Python version 63 import sys 64 if sys.version < '2.4': 65 raise UnsupportedPythonVersionError(sys.version) 66 67 68 # Import directives 69 import threading 70 import os 71 import select 72 import struct 73 import fcntl 74 import errno 75 import termios 76 import array 77 import logging 78 import atexit 79 from collections import deque 80 from datetime import datetime, timedelta 81 import time 82 import fnmatch 83 import re 84 import ctypes 85 import ctypes.util 86 87 88 __author__ = "seb@dbzteam.org (Sebastien Martini)" 89 90 __version__ = "0.8.6" 91 92 __metaclass__ = type # Use new-style classes by default 93 94 95 # load libc 96 LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c')) 97 98 # the libc version check. 99 # XXX: Maybe it is better to check if the libc has the needed functions inside? 100 # Because there are inotify patches for libc 2.3.6. 101 LIBC.gnu_get_libc_version.restype = ctypes.c_char_p 102 LIBC_VERSION = LIBC.gnu_get_libc_version() 103 if LIBC_VERSION < '2.4': 104 raise UnsupportedLibcVersionError(LIBC_VERSION) 105 106 107 # logging 108 log = logging.getLogger("pyinotify") 109 console_handler = logging.StreamHandler() 110 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 111 log.addHandler(console_handler) 112 log.setLevel(20) 113 114 115 # Try to speed-up execution with psyco 116 try: 117 if False: 118 import psyco 119 psyco.full() 120 except ImportError: 121 # Cannot import psyco 122 pass
123 124 125 ### inotify's variables ### 126 127 128 -class SysCtlINotify:
129 """ 130 Access (read, write) inotify's variables through sysctl. 131 132 Examples: 133 - Read variable: myvar = max_queued_events.value 134 - Update variable: max_queued_events.value = 42 135 """ 136 137 inotify_attrs = {'max_user_instances': 1, 138 'max_user_watches': 2, 139 'max_queued_events': 3} 140
141 - def __init__(self, attrname):
142 sino = ctypes.c_int * 3 143 self._attrname = attrname 144 self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
145
146 - def get_val(self):
147 """ 148 @return: stored value. 149 @rtype: int 150 """ 151 oldv = ctypes.c_int(0) 152 size = ctypes.c_int(ctypes.sizeof(oldv)) 153 LIBC.sysctl(self._attr, 3, 154 ctypes.c_voidp(ctypes.addressof(oldv)), 155 ctypes.addressof(size), 156 None, 0) 157 return oldv.value
158
159 - def set_val(self, nval):
160 """ 161 @param nval: set to nval. 162 @type nval: int 163 """ 164 oldv = ctypes.c_int(0) 165 sizeo = ctypes.c_int(ctypes.sizeof(oldv)) 166 newv = ctypes.c_int(nval) 167 sizen = ctypes.c_int(ctypes.sizeof(newv)) 168 LIBC.sysctl(self._attr, 3, 169 ctypes.c_voidp(ctypes.addressof(oldv)), 170 ctypes.addressof(sizeo), 171 ctypes.c_voidp(ctypes.addressof(newv)), 172 ctypes.addressof(sizen))
173 174 value = property(get_val, set_val) 175
176 - def __repr__(self):
177 return '<%s=%d>' % (self._attrname, self.get_val())
178 179 180 # singleton instances 181 # 182 # read int: myvar = max_queued_events.value 183 # update: max_queued_events.value = 42 184 # 185 for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): 186 globals()[attrname] = SysCtlINotify(attrname)
187 188 189 # fixme: put those tests elsewhere 190 # 191 # print max_queued_events 192 # print max_queued_events.value 193 # save = max_queued_events.value 194 # print save 195 # max_queued_events.value += 42 196 # print max_queued_events 197 # max_queued_events.value = save 198 # print max_queued_events 199 200 201 ### iglob ### 202 203 204 # Code taken from standart Python Lib, slightly modified in order to work 205 # with pyinotify (don't exclude dotted files/dirs like .foo). 206 # Original version: 207 # @see: http://svn.python.org/projects/python/trunk/Lib/glob.py 208 209 -def iglob(pathname):
210 if not has_magic(pathname): 211 if hasattr(os.path, 'lexists'): 212 if os.path.lexists(pathname): 213 yield pathname 214 else: 215 if os.path.islink(pathname) or os.path.exists(pathname): 216 yield pathname 217 return 218 dirname, basename = os.path.split(pathname) 219 # relative pathname 220 if not dirname: 221 return 222 # absolute pathname 223 if has_magic(dirname): 224 dirs = iglob(dirname) 225 else: 226 dirs = [dirname] 227 if has_magic(basename): 228 glob_in_dir = glob1 229 else: 230 glob_in_dir = glob0 231 for dirname in dirs: 232 for name in glob_in_dir(dirname, basename): 233 yield os.path.join(dirname, name)
234
235 -def glob1(dirname, pattern):
236 if not dirname: 237 dirname = os.curdir 238 try: 239 names = os.listdir(dirname) 240 except os.error: 241 return [] 242 return fnmatch.filter(names, pattern)
243
244 -def glob0(dirname, basename):
245 if basename == '' and os.path.isdir(dirname): 246 # `os.path.split()` returns an empty basename for paths ending with a 247 # directory separator. 'q*x/' should match only directories. 248 return [basename] 249 if hasattr(os.path, 'lexists'): 250 if os.path.lexists(os.path.join(dirname, basename)): 251 return [basename] 252 else: 253 if (os.path.islink(os.path.join(dirname, basename)) or 254 os.path.exists(os.path.join(dirname, basename))): 255 return [basename] 256 return []
257 258 magic_check = re.compile('[*?[]')
259 260 -def has_magic(s):
261 return magic_check.search(s) is not None
262
263 264 265 ### Core ### 266 267 268 -class EventsCodes:
269 """ 270 Set of codes corresponding to each kind of events. 271 Some of these flags are used to communicate with inotify, whereas 272 the others are sent to userspace by inotify notifying some events. 273 274 @cvar IN_ACCESS: File was accessed. 275 @type IN_ACCESS: int 276 @cvar IN_MODIFY: File was modified. 277 @type IN_MODIFY: int 278 @cvar IN_ATTRIB: Metadata changed. 279 @type IN_ATTRIB: int 280 @cvar IN_CLOSE_WRITE: Writtable file was closed. 281 @type IN_CLOSE_WRITE: int 282 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed. 283 @type IN_CLOSE_NOWRITE: int 284 @cvar IN_OPEN: File was opened. 285 @type IN_OPEN: int 286 @cvar IN_MOVED_FROM: File was moved from X. 287 @type IN_MOVED_FROM: int 288 @cvar IN_MOVED_TO: File was moved to Y. 289 @type IN_MOVED_TO: int 290 @cvar IN_CREATE: Subfile was created. 291 @type IN_CREATE: int 292 @cvar IN_DELETE: Subfile was deleted. 293 @type IN_DELETE: int 294 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted. 295 @type IN_DELETE_SELF: int 296 @cvar IN_MOVE_SELF: Self (watched item itself) was moved. 297 @type IN_MOVE_SELF: int 298 @cvar IN_UNMOUNT: Backing fs was unmounted. 299 @type IN_UNMOUNT: int 300 @cvar IN_Q_OVERFLOW: Event queued overflowed. 301 @type IN_Q_OVERFLOW: int 302 @cvar IN_IGNORED: File was ignored. 303 @type IN_IGNORED: int 304 @cvar IN_ONLYDIR: only watch the path if it is a directory (new 305 in kernel 2.6.15). 306 @type IN_ONLYDIR: int 307 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15). 308 IN_ONLYDIR we can make sure that we don't watch 309 the target of symlinks. 310 @type IN_DONT_FOLLOW: int 311 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new 312 in kernel 2.6.14). 313 @type IN_MASK_ADD: int 314 @cvar IN_ISDIR: Event occurred against dir. 315 @type IN_ISDIR: int 316 @cvar IN_ONESHOT: Only send event once. 317 @type IN_ONESHOT: int 318 @cvar ALL_EVENTS: Alias for considering all of the events. 319 @type ALL_EVENTS: int 320 """ 321 322 # The idea here is 'configuration-as-code' - this way, we get our nice class 323 # constants, but we also get nice human-friendly text mappings to do lookups 324 # against as well, for free: 325 FLAG_COLLECTIONS = {'OP_FLAGS': { 326 'IN_ACCESS' : 0x00000001, # File was accessed 327 'IN_MODIFY' : 0x00000002, # File was modified 328 'IN_ATTRIB' : 0x00000004, # Metadata changed 329 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed 330 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed 331 'IN_OPEN' : 0x00000020, # File was opened 332 'IN_MOVED_FROM' : 0x00000040, # File was moved from X 333 'IN_MOVED_TO' : 0x00000080, # File was moved to Y 334 'IN_CREATE' : 0x00000100, # Subfile was created 335 'IN_DELETE' : 0x00000200, # Subfile was deleted 336 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) 337 # was deleted 338 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved 339 }, 340 'EVENT_FLAGS': { 341 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted 342 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed 343 'IN_IGNORED' : 0x00008000, # File was ignored 344 }, 345 'SPECIAL_FLAGS': { 346 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a 347 # directory 348 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink 349 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already 350 # existing watch 351 'IN_ISDIR' : 0x40000000, # event occurred against dir 352 'IN_ONESHOT' : 0x80000000, # only send event once 353 }, 354 } 355
356 - def maskname(mask):
357 """ 358 Return the event name associated to mask. IN_ISDIR is appended when 359 appropriate. Note: only one event is returned, because only one is 360 raised once at a time. 361 362 @param mask: mask. 363 @type mask: int 364 @return: event name. 365 @rtype: str 366 """ 367 ms = mask 368 name = '%s' 369 if mask & IN_ISDIR: 370 ms = mask - IN_ISDIR 371 name = '%s|IN_ISDIR' 372 return name % EventsCodes.ALL_VALUES[ms]
373 374 maskname = staticmethod(maskname)
375 376 377 # So let's now turn the configuration into code 378 EventsCodes.ALL_FLAGS = {} 379 EventsCodes.ALL_VALUES = {} 380 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems(): 381 # Make the collections' members directly accessible through the 382 # class dictionary 383 setattr(EventsCodes, flagc, valc) 384 385 # Collect all the flags under a common umbrella 386 EventsCodes.ALL_FLAGS.update(valc) 387 388 # Make the individual masks accessible as 'constants' at globals() scope 389 # and masknames accessible by values. 390 for name, val in valc.iteritems(): 391 globals()[name] = val 392 EventsCodes.ALL_VALUES[val] = name 393 394 395 # all 'normal' events 396 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues()) 397 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS 398 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
399 400 401 -class _Event:
402 """ 403 Event structure, represent events raised by the system. This 404 is the base class and should be subclassed. 405 406 """
407 - def __init__(self, dict_):
408 """ 409 Attach attributes (contained in dict_) to self. 410 """ 411 for tpl in dict_.iteritems(): 412 setattr(self, *tpl)
413
414 - def __repr__(self):
415 """ 416 @return: String representation. 417 @rtype: str 418 """ 419 s = '' 420 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]): 421 if attr.startswith('_'): 422 continue 423 if attr == 'mask': 424 value = hex(getattr(self, attr)) 425 elif isinstance(value, str) and not value: 426 value ="''" 427 s += ' %s%s%s' % (Color.FieldName(attr), 428 Color.Punctuation('='), 429 Color.FieldValue(value)) 430 431 s = '%s%s%s %s' % (Color.Punctuation('<'), 432 Color.ClassName(self.__class__.__name__), 433 s, 434 Color.Punctuation('>')) 435 return s
436
437 438 -class _RawEvent(_Event):
439 """ 440 Raw event, it contains only the informations provided by the system. 441 It doesn't infer anything. 442 """
443 - def __init__(self, wd, mask, cookie, name):
444 """ 445 @param wd: Watch Descriptor. 446 @type wd: int 447 @param mask: Bitmask of events. 448 @type mask: int 449 @param cookie: Cookie. 450 @type cookie: int 451 @param name: Basename of the file or directory against which the 452 event was raised, in case where the watched directory 453 is the parent directory. None if the event was raised 454 on the watched item itself. 455 @type name: string or None 456 """ 457 # name: remove trailing '\0' 458 super(_RawEvent, self).__init__({'wd': wd, 459 'mask': mask, 460 'cookie': cookie, 461 'name': name.rstrip('\0')}) 462 log.debug(repr(self))
463
464 465 -class Event(_Event):
466 """ 467 This class contains all the useful informations about the observed 468 event. However, the incorporation of each field is not guaranteed and 469 depends on the type of event. In effect, some fields are irrelevant 470 for some kind of event (for example 'cookie' is meaningless for 471 IN_CREATE whereas it is useful for IN_MOVE_TO). 472 473 The possible fields are: 474 - wd (int): Watch Descriptor. 475 - mask (int): Mask. 476 - maskname (str): Readable event name. 477 - path (str): path of the file or directory being watched. 478 - name (str): Basename of the file or directory against which the 479 event was raised, in case where the watched directory 480 is the parent directory. None if the event was raised 481 on the watched item itself. This field is always provided 482 even if the string is ''. 483 - pathname (str): absolute path of: path + name 484 - cookie (int): Cookie. 485 - dir (bool): is the event raised against directory. 486 487 """
488 - def __init__(self, raw):
489 """ 490 Concretely, this is the raw event plus inferred infos. 491 """ 492 _Event.__init__(self, raw) 493 self.maskname = EventsCodes.maskname(self.mask) 494 try: 495 if self.name: 496 self.pathname = os.path.abspath(os.path.join(self.path, 497 self.name)) 498 else: 499 self.pathname = os.path.abspath(self.path) 500 except AttributeError: 501 pass
502
503 504 -class ProcessEventError(PyinotifyError):
505 """ 506 ProcessEventError Exception. Raised on ProcessEvent error. 507 """
508 - def __init__(self, err):
509 """ 510 @param err: Exception error description. 511 @type err: string 512 """ 513 PyinotifyError.__init__(self, err)
514
515 516 -class _ProcessEvent:
517 """ 518 Abstract processing event class. 519 """
520 - def __call__(self, event):
521 """ 522 To behave like a functor the object must be callable. 523 This method is a dispatch method. Lookup order: 524 1. process_MASKNAME method 525 2. process_FAMILY_NAME method 526 3. otherwise call process_default 527 528 @param event: Event to be processed. 529 @type event: Event object 530 @return: By convention when used from the ProcessEvent class: 531 - Returning False or None (default value) means keep on 532 executing next chained functors (see chain.py example). 533 - Returning True instead means do not execute next 534 processing functions. 535 @rtype: bool 536 @raise ProcessEventError: Event object undispatchable, 537 unknown event. 538 """ 539 stripped_mask = event.mask - (event.mask & IN_ISDIR) 540 maskname = EventsCodes.ALL_VALUES.get(stripped_mask) 541 if maskname is None: 542 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask) 543 544 # 1- look for process_MASKNAME 545 meth = getattr(self, 'process_' + maskname, None) 546 if meth is not None: 547 return meth(event) 548 # 2- look for process_FAMILY_NAME 549 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None) 550 if meth is not None: 551 return meth(event) 552 # 3- default call method process_default 553 return self.process_default(event)
554
555 - def __repr__(self):
556 return '<%s>' % self.__class__.__name__
557
558 559 -class _SysProcessEvent(_ProcessEvent):
560 """ 561 There is three kind of processing according to each event: 562 563 1. special handling (deletion from internal container, bug, ...). 564 2. default treatment: which is applied to most of events. 565 4. IN_ISDIR is never sent alone, he is piggybacked with a standart 566 event, he is not processed as the others events, instead, its 567 value is captured and appropriately aggregated to dst event. 568 """
569 - def __init__(self, wm, notifier):
570 """ 571 572 @param wm: Watch Manager. 573 @type wm: WatchManager instance 574 @param notifier: notifier. 575 @type notifier: Instance of Notifier. 576 """ 577 self._watch_manager = wm # watch manager 578 self._notifier = notifier # notifier 579 self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...} 580 self._mv = {} # {src_path(str): (dst_path(str), date), ...}
581
582 - def cleanup(self):
583 """ 584 Cleanup (delete) old (>1mn) records contained in self._mv_cookie 585 and self._mv. 586 """ 587 date_cur_ = datetime.now() 588 for seq in [self._mv_cookie, self._mv]: 589 for k in seq.keys(): 590 if (date_cur_ - seq[k][1]) > timedelta(minutes=1): 591 log.debug('cleanup: deleting entry %s' % seq[k][0]) 592 del seq[k]
593
594 - def process_IN_CREATE(self, raw_event):
595 """ 596 If the event concerns a directory and the auto_add flag of the 597 targetted watch is set to True, a new watch is added on this 598 new directory, with the same attributes's values than those of 599 this watch. 600 """ 601 if raw_event.mask & IN_ISDIR: 602 watch_ = self._watch_manager._wmd.get(raw_event.wd) 603 if watch_.auto_add: 604 addw = self._watch_manager.add_watch 605 newwd = addw(os.path.join(watch_.path, raw_event.name), 606 watch_.mask, proc_fun=watch_.proc_fun, 607 rec=False, auto_add=watch_.auto_add) 608 609 # Trick to handle mkdir -p /t1/t2/t3 where t1 is watched and 610 # t2 and t3 are created. 611 # Since the directory is new, then everything inside it 612 # must also be new. 613 base = os.path.join(watch_.path, raw_event.name) 614 if newwd[base] > 0: 615 for name in os.listdir(base): 616 inner = os.path.join(base, name) 617 if (os.path.isdir(inner) and 618 self._watch_manager.get_wd(inner) is None): 619 # Generate (simulate) creation event for sub 620 # directories. 621 rawevent = _RawEvent(newwd[base], 622 IN_CREATE | IN_ISDIR, 623 0, name) 624 self._notifier._eventq.append(rawevent) 625 return self.process_default(raw_event)
626
627 - def process_IN_MOVED_FROM(self, raw_event):
628 """ 629 Map the cookie with the source path (+ date for cleaning). 630 """ 631 watch_ = self._watch_manager._wmd.get(raw_event.wd) 632 path_ = watch_.path 633 src_path = os.path.normpath(os.path.join(path_, raw_event.name)) 634 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now()) 635 return self.process_default(raw_event, {'cookie': raw_event.cookie})
636
637 - def process_IN_MOVED_TO(self, raw_event):
638 """ 639 Map the source path with the destination path (+ date for 640 cleaning). 641 """ 642 watch_ = self._watch_manager._wmd.get(raw_event.wd) 643 path_ = watch_.path 644 dst_path = os.path.normpath(os.path.join(path_, raw_event.name)) 645 mv_ = self._mv_cookie.get(raw_event.cookie) 646 if mv_: 647 self._mv[mv_[0]] = (dst_path, datetime.now()) 648 return self.process_default(raw_event, {'cookie': raw_event.cookie})
649
650 - def process_IN_MOVE_SELF(self, raw_event):
651 """ 652 STATUS: the following bug has been fixed in the recent kernels (fixme: 653 which version ?). Now it raises IN_DELETE_SELF instead. 654 655 Old kernels are bugged, this event is raised when the watched item 656 was moved, so we must update its path, but under some circumstances it 657 can be impossible: if its parent directory and its destination 658 directory aren't watched. The kernel (see include/linux/fsnotify.h) 659 doesn't bring us enough informations like the destination path of 660 moved items. 661 """ 662 watch_ = self._watch_manager._wmd.get(raw_event.wd) 663 src_path = watch_.path 664 mv_ = self._mv.get(src_path) 665 if mv_: 666 watch_.path = mv_[0] 667 else: 668 log.error("The path %s of this watch %s must not " 669 "be trusted anymore" % (watch_.path, watch_)) 670 if not watch_.path.endswith('-wrong-path'): 671 watch_.path += '-wrong-path' 672 # FIXME: should we pass the cookie even if this is not standart? 673 return self.process_default(raw_event)
674
675 - def process_IN_Q_OVERFLOW(self, raw_event):
676 """ 677 Only signal overflow, most of the common flags are irrelevant 678 for this event (path, wd, name). 679 """ 680 return Event({'mask': raw_event.mask})
681
682 - def process_IN_IGNORED(self, raw_event):
683 """ 684 The watch descriptor raised by this event is now ignored (forever), 685 it can be safely deleted from watch manager dictionary. 686 After this event we can be sure that neither the event queue 687 neither the system will raise an event associated to this wd. 688 """ 689 event_ = self.process_default(raw_event) 690 try: 691 del self._watch_manager._wmd[raw_event.wd] 692 except KeyError, err: 693 log.error(err) 694 return event_
695
696 - def process_default(self, raw_event, to_append={}):
697 """ 698 Common handling for the following events: 699 700 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, 701 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT. 702 """ 703 ret = None 704 watch_ = self._watch_manager._wmd.get(raw_event.wd) 705 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF): 706 # unfornately information not provided by the kernel 707 dir_ = watch_.dir 708 else: 709 dir_ = bool(raw_event.mask & IN_ISDIR) 710 dict_ = {'wd': raw_event.wd, 711 'mask': raw_event.mask, 712 'path': watch_.path, 713 'name': raw_event.name, 714 'dir': dir_} 715 dict_.update(to_append) 716 return Event(dict_)
717
718 719 -class ProcessEvent(_ProcessEvent):
720 """ 721 Process events objects, can be specialized via subclassing, thus its 722 behavior can be overriden: 723 724 Note: you should not override __init__ in your subclass instead define 725 a my_init() method, this method will be called from the constructor of 726 this class with optional parameters. 727 728 1. Provide methods, e.g. process_IN_DELETE for processing a given kind 729 of event (eg. IN_DELETE in this case). 730 2. Or/and provide methods for processing events by 'family', e.g. 731 process_IN_CLOSE method will process both IN_CLOSE_WRITE and 732 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and 733 process_IN_CLOSE_NOWRITE aren't defined). 734 3. Or/and override process_default for processing the remaining kind of 735 events. 736 """ 737 pevent = None 738
739 - def __init__(self, pevent=None, **kargs):
740 """ 741 Enable chaining of ProcessEvent instances. 742 743 @param pevent: optional callable object, will be called on event 744 processing (before self). 745 @type pevent: callable 746 @param kargs: optional arguments delagated to template method my_init 747 @type kargs: dict 748 """ 749 self.pevent = pevent 750 self.my_init(**kargs)
751
752 - def my_init(self, **kargs):
753 """ 754 Override this method when subclassing if you want to achieve 755 custom initialization of your subclass' instance. You MUST pass 756 keyword arguments. This method does nothing by default. 757 758 @param kargs: optional arguments delagated to template method my_init 759 @type kargs: dict 760 """ 761 pass
762
763 - def __call__(self, event):
764 stop_chaining = False 765 if self.pevent is not None: 766 # By default methods return None so we fix as guideline 767 # that methods asking for stop chaining must explicitely 768 # return non None or False values, otherwise the default 769 # behavior is to chain call to the corresponding local 770 # method. 771 stop_chaining = self.pevent(event) 772 if not stop_chaining: 773 return _ProcessEvent.__call__(self, event)
774
775 - def nested_pevent(self):
776 return self.pevent
777
778 - def process_default(self, event):
779 """ 780 Default default processing event method. Print event 781 on standart output. 782 783 @param event: Event to be processed. 784 @type event: Event instance 785 """ 786 print(repr(event))
787
788 789 -class ChainIfTrue(ProcessEvent):
790 """ 791 Makes conditional chaining depending on the result of the nested 792 processing instance. 793 """
794 - def my_init(self, func):
795 self._func = func
796
797 - def process_default(self, event):
798 return not self._func(event)
799
800 801 -class Stats(ProcessEvent):
802 - def my_init(self):
803 self._start_time = time.time() 804 self._stats = {} 805 self._stats_lock = threading.Lock()
806
807 - def process_default(self, event):
808 self._stats_lock.acquire() 809 try: 810 events = event.maskname.split('|') 811 for event_name in events: 812 count = self._stats.get(event_name, 0) 813 self._stats[event_name] = count + 1 814 finally: 815 self._stats_lock.release()
816
817 - def _stats_copy(self):
818 self._stats_lock.acquire() 819 try: 820 return self._stats.copy() 821 finally: 822 self._stats_lock.release()
823
824 - def __repr__(self):
825 stats = self._stats_copy() 826 827 t = int(time.time() - self._start_time) 828 if t < 60: 829 ts = str(t) + 'sec' 830 elif 60 <= t < 3600: 831 ts = '%dmn%dsec' % (t / 60, t % 60) 832 elif 3600 <= t < 86400: 833 ts = '%dh%dmn' % (t / 3600, (t % 3600) / 60) 834 elif t >= 86400: 835 ts = '%dd%dh' % (t / 86400, (t % 86400) / 3600) 836 stats['ElapsedTime'] = ts 837 838 l = [] 839 for ev, value in sorted(stats.items(), key=lambda x: x[0]): 840 l.append(' %s=%s' % (Color.FieldName(ev), 841 Color.FieldValue(value))) 842 s = '<%s%s >' % (Color.ClassName(self.__class__.__name__), 843 ''.join(l)) 844 return s
845
846 - def dump(self, filename):
847 fo = file(filename, 'wb') 848 try: 849 fo.write(str(self)) 850 finally: 851 fo.close()
852
853 - def __str__(self, scale=45):
854 stats = self._stats_copy() 855 if not stats: 856 return '' 857 858 m = max(stats.values()) 859 unity = int(round(float(m) / scale)) or 1 860 fmt = '%%-26s%%-%ds%%s' % (len(Color.FieldValue('@' * scale)) 861 + 1) 862 def func(x): 863 return fmt % (Color.FieldName(x[0]), 864 Color.FieldValue('@' * (x[1] / unity)), 865 Color.Simple('%d' % x[1], 'yellow'))
866 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0]))) 867 return s
868
869 870 -class NotifierError(PyinotifyError):
871 """ 872 Notifier Exception. Raised on Notifier error. 873 874 """
875 - def __init__(self, err):
876 """ 877 @param err: Exception string's description. 878 @type err: string 879 """ 880 PyinotifyError.__init__(self, err)
881
882 883 -class Notifier:
884 """ 885 Read notifications, process events. 886 887 """
888 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 889 read_freq=0, treshold=0, timeout=None):
890 """ 891 Initialization. read_freq, treshold and timeout parameters are used 892 when looping. 893 894 @param watch_manager: Watch Manager. 895 @type watch_manager: WatchManager instance 896 @param default_proc_fun: Default processing method. 897 @type default_proc_fun: instance of ProcessEvent 898 @param read_freq: if read_freq == 0, events are read asap, 899 if read_freq is > 0, this thread sleeps 900 max(0, read_freq - timeout) seconds. But if 901 timeout is None it can be different because 902 poll is blocking waiting for something to read. 903 @type read_freq: int 904 @param treshold: File descriptor will be read only if its size to 905 read is >= treshold. If != 0, you likely want to 906 use it in combination with read_freq because 907 without that you keep looping without really reading 908 anything and that until the amount to read 909 is >= treshold. At least with read_freq you may sleep. 910 @type treshold: int 911 @param timeout: 912 http://docs.python.org/lib/poll-objects.html#poll-objects 913 @type timeout: int 914 """ 915 # watch manager instance 916 self._watch_manager = watch_manager 917 # file descriptor 918 self._fd = self._watch_manager._fd 919 # poll object and registration 920 self._pollobj = select.poll() 921 self._pollobj.register(self._fd, select.POLLIN) 922 # This pipe is correctely initialized and used by ThreadedNotifier 923 self._pipe = (-1, -1) 924 # event queue 925 self._eventq = deque() 926 # system processing functor, common to all events 927 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self) 928 # default processing method 929 self._default_proc_fun = default_proc_fun 930 # loop parameters 931 self._read_freq = read_freq 932 self._treshold = treshold 933 self._timeout = timeout
934
935 - def proc_fun(self):
936 return self._default_proc_fun
937
938 - def check_events(self):
939 """ 940 Check for new events available to read, blocks up to timeout 941 milliseconds. 942 943 @return: New events to read. 944 @rtype: bool 945 """ 946 while True: 947 try: 948 # blocks up to 'timeout' milliseconds 949 ret = self._pollobj.poll(self._timeout) 950 except select.error, err: 951 if err[0] == errno.EINTR: 952 continue # interrupted, retry 953 else: 954 raise 955 else: 956 break 957 958 if not ret or (self._pipe[0] == ret[0][0]): 959 return False 960 # only one fd is polled 961 return ret[0][1] & select.POLLIN
962
963 - def read_events(self):
964 """ 965 Read events from device, build _RawEvents, and enqueue them. 966 """ 967 buf_ = array.array('i', [0]) 968 # get event queue size 969 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1: 970 return 971 queue_size = buf_[0] 972 if queue_size < self._treshold: 973 log.debug('(fd: %d) %d bytes available to read but ' 974 'treshold is fixed to %d bytes' % (self._fd, 975 queue_size, 976 self._treshold)) 977 return 978 979 try: 980 # read content from file 981 r = os.read(self._fd, queue_size) 982 except Exception, msg: 983 raise NotifierError(msg) 984 log.debug('event queue size: %d' % queue_size) 985 rsum = 0 # counter 986 while rsum < queue_size: 987 s_size = 16 988 # retrieve wd, mask, cookie 989 s_ = struct.unpack('iIII', r[rsum:rsum+s_size]) 990 # length of name 991 fname_len = s_[3] 992 # field 'length' useless 993 s_ = s_[:-1] 994 # retrieve name 995 s_ += struct.unpack('%ds' % fname_len, 996 r[rsum + s_size:rsum + s_size + fname_len]) 997 self._eventq.append(_RawEvent(*s_)) 998 rsum += s_size + fname_len
999
1000 - def process_events(self):
1001 """ 1002 Routine for processing events from queue by calling their 1003 associated proccessing function (instance of ProcessEvent). 1004 It also do internal processings, to keep the system updated. 1005 """ 1006 while self._eventq: 1007 raw_event = self._eventq.popleft() # pop next event 1008 watch_ = self._watch_manager._wmd.get(raw_event.wd) 1009 revent = self._sys_proc_fun(raw_event) # system processings 1010 if watch_ and watch_.proc_fun: 1011 watch_.proc_fun(revent) # user processings 1012 else: 1013 self._default_proc_fun(revent) 1014 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1015 1016
1017 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull, 1018 stdout=os.devnull, stderr=os.devnull):
1019 """ 1020 pid_file: file to which pid will be written. 1021 force_kill: if True kill the process associated to pid_file. 1022 stdin, stdout, stderr: files associated to common streams. 1023 """ 1024 if pid_file is None: 1025 dirname = '/var/run/' 1026 basename = sys.argv[0] or 'pyinotify' 1027 pid_file = os.path.join(dirname, basename + '.pid') 1028 1029 if os.path.exists(pid_file): 1030 fo = file(pid_file, 'rb') 1031 try: 1032 try: 1033 pid = int(fo.read()) 1034 except ValueError: 1035 pid = None 1036 if pid is not None: 1037 try: 1038 os.kill(pid, 0) 1039 except OSError, err: 1040 pass 1041 else: 1042 if not force_kill: 1043 s = 'There is already a pid file %s with pid %d' 1044 raise NotifierError(s % (pid_file, pid)) 1045 else: 1046 os.kill(pid, 9) 1047 finally: 1048 fo.close() 1049 1050 1051 def fork_daemon(): 1052 # Adapted from Chad J. Schroeder's recipe 1053 pid = os.fork() 1054 if (pid == 0): 1055 # parent 2 1056 os.setsid() 1057 pid = os.fork() 1058 if (pid == 0): 1059 # child 1060 os.chdir('/') 1061 os.umask(0) 1062 else: 1063 # parent 2 1064 os._exit(0) 1065 else: 1066 # parent 1 1067 os._exit(0) 1068 1069 fd_inp = os.open(stdin, os.O_RDONLY) 1070 os.dup2(fd_inp, 0) 1071 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT) 1072 os.dup2(fd_out, 1) 1073 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT) 1074 os.dup2(fd_err, 2)
1075 1076 # Detach task 1077 fork_daemon() 1078 1079 # Write pid 1080 fo = file(pid_file, 'wb') 1081 try: 1082 fo.write(str(os.getpid()) + '\n') 1083 finally: 1084 fo.close() 1085 1086 atexit.register(lambda : os.unlink(pid_file))
1087 1088
1089 - def _sleep(self, ref_time):
1090 # Only consider sleeping if read_freq is > 0 1091 if self._read_freq > 0: 1092 cur_time = time.time() 1093 sleep_amount = self._read_freq - (cur_time - ref_time) 1094 if sleep_amount > 0: 1095 log.debug('Now sleeping %d seconds' % sleep_amount) 1096 time.sleep(sleep_amount)
1097 1098
1099 - def loop(self, callback=None, daemonize=False, **args):
1100 """ 1101 Events are read only once time every min(read_freq, timeout) 1102 seconds at best and only if the size to read is >= treshold. 1103 1104 @param callback: Functor called after each event processing. Expects 1105 to receive notifier object (self) as first parameter. 1106 @type callback: callable 1107 @param daemonize: This thread is daemonized if set to True. 1108 @type daemonize: boolean 1109 """ 1110 if daemonize: 1111 self.__daemonize(**args) 1112 1113 # Read and process events forever 1114 while 1: 1115 try: 1116 self.process_events() 1117 if callback is not None: 1118 callback(self) 1119 ref_time = time.time() 1120 # check_events is blocking 1121 if self.check_events(): 1122 self._sleep(ref_time) 1123 self.read_events() 1124 except KeyboardInterrupt: 1125 # Unless sigint is caught (Control-C) 1126 log.debug('Pyinotify stops monitoring.') 1127 # Stop monitoring 1128 self.stop() 1129 break
1130
1131 - def stop(self):
1132 """ 1133 Close the inotify's instance (close its file descriptor). 1134 It destroys all existing watches, pending events,... 1135 """ 1136 self._pollobj.unregister(self._fd) 1137 os.close(self._fd)
1138
1139 1140 -class ThreadedNotifier(threading.Thread, Notifier):
1141 """ 1142 This notifier inherits from threading.Thread for instantiating a separate 1143 thread, and also inherits from Notifier, because it is a threaded notifier. 1144 1145 Note that everything possible with this class is also possible through 1146 Notifier. Moreover Notifier is _better_ under many aspects: not threaded, 1147 can be easily daemonized. 1148 """
1149 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 1150 read_freq=0, treshold=0, timeout=None):
1151 """ 1152 Initialization, initialize base classes. read_freq, treshold and 1153 timeout parameters are used when looping. 1154 1155 @param watch_manager: Watch Manager. 1156 @type watch_manager: WatchManager instance 1157 @param default_proc_fun: Default processing method. 1158 @type default_proc_fun: instance of ProcessEvent 1159 @param read_freq: if read_freq == 0, events are read asap, 1160 if read_freq is > 0, this thread sleeps 1161 max(0, read_freq - timeout) seconds. 1162 @type read_freq: int 1163 @param treshold: File descriptor will be read only if its size to 1164 read is >= treshold. If != 0, you likely want to 1165 use it in combination with read_freq because 1166 without that you keep looping without really reading 1167 anything and that until the amount to read 1168 is >= treshold. At least with read_freq you may sleep. 1169 @type treshold: int 1170 @param timeout: 1171 see http://docs.python.org/lib/poll-objects.html#poll-objects 1172 Read the corresponding comment in the source code before changing 1173 it. 1174 @type timeout: int 1175 """ 1176 # Init threading base class 1177 threading.Thread.__init__(self) 1178 # Stop condition 1179 self._stop_event = threading.Event() 1180 # Init Notifier base class 1181 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1182 treshold, timeout) 1183 # Create a new pipe used for thread termination 1184 self._pipe = os.pipe() 1185 self._pollobj.register(self._pipe[0], select.POLLIN)
1186
1187 - def stop(self):
1188 """ 1189 Stop the notifier's loop. Stop notification. Join the thread. 1190 """ 1191 self._stop_event.set() 1192 os.write(self._pipe[1], 'stop') 1193 threading.Thread.join(self) 1194 Notifier.stop(self) 1195 self._pollobj.unregister(self._pipe[0]) 1196 os.close(self._pipe[0]) 1197 os.close(self._pipe[1])
1198
1199 - def loop(self):
1200 """ 1201 Thread's main loop. Don't meant to be called by user directly. 1202 Call start() instead. 1203 1204 Events are read only once time every min(read_freq, timeout) 1205 seconds at best and only if the size of events to read is >= treshold. 1206 """ 1207 # When the loop must be terminated .stop() is called, 'stop' 1208 # is written to pipe fd so poll() returns and .check_events() 1209 # returns False which make evaluate the While's stop condition 1210 # ._stop_event.isSet() wich put an end to the thread's execution. 1211 while not self._stop_event.isSet(): 1212 self.process_events() 1213 ref_time = time.time() 1214 if self.check_events(): 1215 self._sleep(ref_time) 1216 self.read_events()
1217
1218 - def run(self):
1219 """ 1220 Start the thread's loop: read and process events until the method 1221 stop() is called. 1222 Never call this method directly, instead call the start() method 1223 inherited from threading.Thread, which then will call run(). 1224 """ 1225 self.loop()
1226
1227 1228 -class Watch:
1229 """ 1230 Represent a watch, i.e. a file or directory being watched. 1231 1232 """
1233 - def __init__(self, **keys):
1234 """ 1235 Initializations. 1236 1237 @param wd: Watch descriptor. 1238 @type wd: int 1239 @param path: Path of the file or directory being watched. 1240 @type path: str 1241 @param mask: Mask. 1242 @type mask: int 1243 @param proc_fun: Processing callable object. 1244 @type proc_fun: 1245 @param auto_add: Automatically add watches on new directories. 1246 @type auto_add: bool 1247 """ 1248 for k, v in keys.iteritems(): 1249 setattr(self, k, v) 1250 self.dir = os.path.isdir(self.path)
1251
1252 - def __repr__(self):
1253 """ 1254 @return: String representation. 1255 @rtype: str 1256 """ 1257 s = ' '.join(['%s%s%s' % (Color.FieldName(attr), 1258 Color.Punctuation('='), 1259 Color.FieldValue(getattr(self, attr))) \ 1260 for attr in self.__dict__ if not attr.startswith('_')]) 1261 1262 s = '%s%s %s %s' % (Color.Punctuation('<'), 1263 Color.ClassName(self.__class__.__name__), 1264 s, 1265 Color.Punctuation('>')) 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 -class Color:
1696 normal = "\033[0m" 1697 black = "\033[30m" 1698 red = "\033[31m" 1699 green = "\033[32m" 1700 yellow = "\033[33m" 1701 blue = "\033[34m" 1702 purple = "\033[35m" 1703 cyan = "\033[36m" 1704 bold = "\033[1m" 1705 uline = "\033[4m" 1706 blink = "\033[5m" 1707 invert = "\033[7m" 1708 1709 @staticmethod
1710 - def Punctuation(s):
1711 return Color.normal + s + Color.normal
1712 1713 @staticmethod
1714 - def FieldValue(s):
1715 if not isinstance(s, str): 1716 s = str(s) 1717 return Color.purple + s + Color.normal
1718 1719 @staticmethod
1720 - def FieldName(s):
1721 return Color.blue + s + Color.normal
1722 1723 @staticmethod
1724 - def ClassName(s):
1725 return Color.red + Color.bold + s + Color.normal
1726 1727 @staticmethod
1728 - def Simple(s, color):
1729 if not isinstance(s, str): 1730 s = str(s) 1731 try: 1732 color_attr = getattr(Color, color) 1733 except AttributeError: 1734 return s 1735 return color_attr + s + Color.normal
1736
1737 1738 -def command_line():
1739 # 1740 # - By default the watched path is '/tmp' for all events. 1741 # - The monitoring execution blocks and serve forever, type c^c 1742 # to stop it. 1743 # 1744 from optparse import OptionParser 1745 1746 usage = "usage: %prog [options] [path1] [path2] [pathn]" 1747 1748 parser = OptionParser(usage=usage) 1749 parser.add_option("-v", "--verbose", action="store_true", 1750 dest="verbose", help="Verbose mode") 1751 parser.add_option("-r", "--recursive", action="store_true", 1752 dest="recursive", 1753 help="Add watches recursively on paths") 1754 parser.add_option("-a", "--auto_add", action="store_true", 1755 dest="auto_add", 1756 help="Automatically add watches on new directories") 1757 parser.add_option("-e", "--events-list", metavar="EVENT[,...]", 1758 dest="events_list", 1759 help=("A comma-separated list of events to watch for - " 1760 "see the documentation for valid options (defaults" 1761 " to everything)")) 1762 parser.add_option("-s", "--stats", action="store_true", 1763 dest="stats", 1764 help="Display statistics") 1765 1766 (options, args) = parser.parse_args() 1767 1768 if options.verbose: 1769 log.setLevel(10) 1770 1771 if len(args) < 1: 1772 path = '/tmp' # default watched path 1773 else: 1774 path = args 1775 1776 # watch manager instance 1777 wm = WatchManager() 1778 # notifier instance and init 1779 if options.stats: 1780 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5) 1781 else: 1782 notifier = Notifier(wm) 1783 1784 # What mask to apply 1785 mask = 0 1786 if options.events_list: 1787 events_list = options.events_list.split(',') 1788 for ev in events_list: 1789 evcode = EventsCodes.ALL_FLAGS.get(ev, 0) 1790 if evcode: 1791 mask |= evcode 1792 else: 1793 parser.error("The event '%s' specified with option -e" 1794 " is not valid" % ev) 1795 else: 1796 mask = ALL_EVENTS 1797 1798 # stats 1799 cb_fun = None 1800 if options.stats: 1801 def cb(s): 1802 print('%s\n%s\n' % (repr(s.proc_fun()), 1803 s.proc_fun()))
1804 cb_fun = cb 1805 1806 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path) 1807 1808 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add) 1809 # Loop forever (until sigint signal get caught) 1810 notifier.loop(callback=cb_fun) 1811 1812 1813 if __name__ == '__main__': 1814 command_line() 1815