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