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 > 2.4 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 (int(LIBC_VERSION.split('.')[0]) < 2 or 104 (int(LIBC_VERSION.split('.')[0]) == 2 and 105 int(LIBC_VERSION.split('.')[1]) < 4)): 106 raise UnsupportedLibcVersionError(LIBC_VERSION) 107 108 109 # logging 110 log = logging.getLogger("pyinotify") 111 console_handler = logging.StreamHandler() 112 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 113 log.addHandler(console_handler) 114 log.setLevel(20) 115 116 117 # Try to speed-up execution with psyco 118 try: 119 if False: 120 import psyco 121 psyco.full() 122 except ImportError: 123 # Cannot import psyco 124 pass
125 126 127 ### inotify's variables ### 128 129 130 -class SysCtlINotify:
131 """ 132 Access (read, write) inotify's variables through sysctl. 133 134 Examples: 135 - Read variable: myvar = max_queued_events.value 136 - Update variable: max_queued_events.value = 42 137 """ 138 139 inotify_attrs = {'max_user_instances': 1, 140 'max_user_watches': 2, 141 'max_queued_events': 3} 142
143 - def __init__(self, attrname):
144 sino = ctypes.c_int * 3 145 self._attrname = attrname 146 self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
147
148 - def get_val(self):
149 """ 150 @return: stored value. 151 @rtype: int 152 """ 153 oldv = ctypes.c_int(0) 154 size = ctypes.c_int(ctypes.sizeof(oldv)) 155 LIBC.sysctl(self._attr, 3, 156 ctypes.c_voidp(ctypes.addressof(oldv)), 157 ctypes.addressof(size), 158 None, 0) 159 return oldv.value
160
161 - def set_val(self, nval):
162 """ 163 @param nval: set to nval. 164 @type nval: int 165 """ 166 oldv = ctypes.c_int(0) 167 sizeo = ctypes.c_int(ctypes.sizeof(oldv)) 168 newv = ctypes.c_int(nval) 169 sizen = ctypes.c_int(ctypes.sizeof(newv)) 170 LIBC.sysctl(self._attr, 3, 171 ctypes.c_voidp(ctypes.addressof(oldv)), 172 ctypes.addressof(sizeo), 173 ctypes.c_voidp(ctypes.addressof(newv)), 174 ctypes.addressof(sizen))
175 176 value = property(get_val, set_val) 177
178 - def __repr__(self):
179 return '<%s=%d>' % (self._attrname, self.get_val())
180 181 182 # singleton instances 183 # 184 # read int: myvar = max_queued_events.value 185 # update: max_queued_events.value = 42 186 # 187 for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): 188 globals()[attrname] = SysCtlINotify(attrname)
189 190 191 # fixme: put those tests elsewhere 192 # 193 # print max_queued_events 194 # print max_queued_events.value 195 # save = max_queued_events.value 196 # print save 197 # max_queued_events.value += 42 198 # print max_queued_events 199 # max_queued_events.value = save 200 # print max_queued_events 201 202 203 ### iglob ### 204 205 206 # Code taken from standart Python Lib, slightly modified in order to work 207 # with pyinotify (don't exclude dotted files/dirs like .foo). 208 # Original version: 209 # @see: http://svn.python.org/projects/python/trunk/Lib/glob.py 210 211 -def iglob(pathname):
212 if not has_magic(pathname): 213 if hasattr(os.path, 'lexists'): 214 if os.path.lexists(pathname): 215 yield pathname 216 else: 217 if os.path.islink(pathname) or os.path.exists(pathname): 218 yield pathname 219 return 220 dirname, basename = os.path.split(pathname) 221 # relative pathname 222 if not dirname: 223 return 224 # absolute pathname 225 if has_magic(dirname): 226 dirs = iglob(dirname) 227 else: 228 dirs = [dirname] 229 if has_magic(basename): 230 glob_in_dir = glob1 231 else: 232 glob_in_dir = glob0 233 for dirname in dirs: 234 for name in glob_in_dir(dirname, basename): 235 yield os.path.join(dirname, name)
236
237 -def glob1(dirname, pattern):
238 if not dirname: 239 dirname = os.curdir 240 try: 241 names = os.listdir(dirname) 242 except os.error: 243 return [] 244 return fnmatch.filter(names, pattern)
245
246 -def glob0(dirname, basename):
247 if basename == '' and os.path.isdir(dirname): 248 # `os.path.split()` returns an empty basename for paths ending with a 249 # directory separator. 'q*x/' should match only directories. 250 return [basename] 251 if hasattr(os.path, 'lexists'): 252 if os.path.lexists(os.path.join(dirname, basename)): 253 return [basename] 254 else: 255 if (os.path.islink(os.path.join(dirname, basename)) or 256 os.path.exists(os.path.join(dirname, basename))): 257 return [basename] 258 return []
259 260 magic_check = re.compile('[*?[]')
261 262 -def has_magic(s):
263 return magic_check.search(s) is not None
264
265 266 267 ### Core ### 268 269 270 -class EventsCodes:
271 """ 272 Set of codes corresponding to each kind of events. 273 Some of these flags are used to communicate with inotify, whereas 274 the others are sent to userspace by inotify notifying some events. 275 276 @cvar IN_ACCESS: File was accessed. 277 @type IN_ACCESS: int 278 @cvar IN_MODIFY: File was modified. 279 @type IN_MODIFY: int 280 @cvar IN_ATTRIB: Metadata changed. 281 @type IN_ATTRIB: int 282 @cvar IN_CLOSE_WRITE: Writtable file was closed. 283 @type IN_CLOSE_WRITE: int 284 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed. 285 @type IN_CLOSE_NOWRITE: int 286 @cvar IN_OPEN: File was opened. 287 @type IN_OPEN: int 288 @cvar IN_MOVED_FROM: File was moved from X. 289 @type IN_MOVED_FROM: int 290 @cvar IN_MOVED_TO: File was moved to Y. 291 @type IN_MOVED_TO: int 292 @cvar IN_CREATE: Subfile was created. 293 @type IN_CREATE: int 294 @cvar IN_DELETE: Subfile was deleted. 295 @type IN_DELETE: int 296 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted. 297 @type IN_DELETE_SELF: int 298 @cvar IN_MOVE_SELF: Self (watched item itself) was moved. 299 @type IN_MOVE_SELF: int 300 @cvar IN_UNMOUNT: Backing fs was unmounted. 301 @type IN_UNMOUNT: int 302 @cvar IN_Q_OVERFLOW: Event queued overflowed. 303 @type IN_Q_OVERFLOW: int 304 @cvar IN_IGNORED: File was ignored. 305 @type IN_IGNORED: int 306 @cvar IN_ONLYDIR: only watch the path if it is a directory (new 307 in kernel 2.6.15). 308 @type IN_ONLYDIR: int 309 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15). 310 IN_ONLYDIR we can make sure that we don't watch 311 the target of symlinks. 312 @type IN_DONT_FOLLOW: int 313 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new 314 in kernel 2.6.14). 315 @type IN_MASK_ADD: int 316 @cvar IN_ISDIR: Event occurred against dir. 317 @type IN_ISDIR: int 318 @cvar IN_ONESHOT: Only send event once. 319 @type IN_ONESHOT: int 320 @cvar ALL_EVENTS: Alias for considering all of the events. 321 @type ALL_EVENTS: int 322 """ 323 324 # The idea here is 'configuration-as-code' - this way, we get our nice class 325 # constants, but we also get nice human-friendly text mappings to do lookups 326 # against as well, for free: 327 FLAG_COLLECTIONS = {'OP_FLAGS': { 328 'IN_ACCESS' : 0x00000001, # File was accessed 329 'IN_MODIFY' : 0x00000002, # File was modified 330 'IN_ATTRIB' : 0x00000004, # Metadata changed 331 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed 332 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed 333 'IN_OPEN' : 0x00000020, # File was opened 334 'IN_MOVED_FROM' : 0x00000040, # File was moved from X 335 'IN_MOVED_TO' : 0x00000080, # File was moved to Y 336 'IN_CREATE' : 0x00000100, # Subfile was created 337 'IN_DELETE' : 0x00000200, # Subfile was deleted 338 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) 339 # was deleted 340 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved 341 }, 342 'EVENT_FLAGS': { 343 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted 344 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed 345 'IN_IGNORED' : 0x00008000, # File was ignored 346 }, 347 'SPECIAL_FLAGS': { 348 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a 349 # directory 350 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink 351 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already 352 # existing watch 353 'IN_ISDIR' : 0x40000000, # event occurred against dir 354 'IN_ONESHOT' : 0x80000000, # only send event once 355 }, 356 } 357
358 - def maskname(mask):
359 """ 360 Return the event name associated to mask. IN_ISDIR is appended when 361 appropriate. Note: only one event is returned, because only one is 362 raised once at a time. 363 364 @param mask: mask. 365 @type mask: int 366 @return: event name. 367 @rtype: str 368 """ 369 ms = mask 370 name = '%s' 371 if mask & IN_ISDIR: 372 ms = mask - IN_ISDIR 373 name = '%s|IN_ISDIR' 374 return name % EventsCodes.ALL_VALUES[ms]
375 376 maskname = staticmethod(maskname)
377 378 379 # So let's now turn the configuration into code 380 EventsCodes.ALL_FLAGS = {} 381 EventsCodes.ALL_VALUES = {} 382 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems(): 383 # Make the collections' members directly accessible through the 384 # class dictionary 385 setattr(EventsCodes, flagc, valc) 386 387 # Collect all the flags under a common umbrella 388 EventsCodes.ALL_FLAGS.update(valc) 389 390 # Make the individual masks accessible as 'constants' at globals() scope 391 # and masknames accessible by values. 392 for name, val in valc.iteritems(): 393 globals()[name] = val 394 EventsCodes.ALL_VALUES[val] = name 395 396 397 # all 'normal' events 398 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues()) 399 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS 400 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
401 402 403 -class _Event:
404 """ 405 Event structure, represent events raised by the system. This 406 is the base class and should be subclassed. 407 408 """
409 - def __init__(self, dict_):
410 """ 411 Attach attributes (contained in dict_) to self. 412 """ 413 for tpl in dict_.iteritems(): 414 setattr(self, *tpl)
415
416 - def __repr__(self):
417 """ 418 @return: String representation. 419 @rtype: str 420 """ 421 s = '' 422 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]): 423 if attr.startswith('_'): 424 continue 425 if attr == 'mask': 426 value = hex(getattr(self, attr)) 427 elif isinstance(value, str) and not value: 428 value ="''" 429 s += ' %s%s%s' % (Color.FieldName(attr), 430 Color.Punctuation('='), 431 Color.FieldValue(value)) 432 433 s = '%s%s%s %s' % (Color.Punctuation('<'), 434 Color.ClassName(self.__class__.__name__), 435 s, 436 Color.Punctuation('>')) 437 return s
438
439 440 -class _RawEvent(_Event):
441 """ 442 Raw event, it contains only the informations provided by the system. 443 It doesn't infer anything. 444 """
445 - def __init__(self, wd, mask, cookie, name):
446 """ 447 @param wd: Watch Descriptor. 448 @type wd: int 449 @param mask: Bitmask of events. 450 @type mask: int 451 @param cookie: Cookie. 452 @type cookie: int 453 @param name: Basename of the file or directory against which the 454 event was raised, in case where the watched directory 455 is the parent directory. None if the event was raised 456 on the watched item itself. 457 @type name: string or None 458 """ 459 # name: remove trailing '\0' 460 super(_RawEvent, self).__init__({'wd': wd, 461 'mask': mask, 462 'cookie': cookie, 463 'name': name.rstrip('\0')}) 464 log.debug(repr(self))
465
466 467 -class Event(_Event):
468 """ 469 This class contains all the useful informations about the observed 470 event. However, the incorporation of each field is not guaranteed and 471 depends on the type of event. In effect, some fields are irrelevant 472 for some kind of event (for example 'cookie' is meaningless for 473 IN_CREATE whereas it is useful for IN_MOVE_TO). 474 475 The possible fields are: 476 - wd (int): Watch Descriptor. 477 - mask (int): Mask. 478 - maskname (str): Readable event name. 479 - path (str): path of the file or directory being watched. 480 - name (str): Basename of the file or directory against which the 481 event was raised, in case where the watched directory 482 is the parent directory. None if the event was raised 483 on the watched item itself. This field is always provided 484 even if the string is ''. 485 - pathname (str): absolute path of: path + name 486 - cookie (int): Cookie. 487 - dir (bool): is the event raised against directory. 488 489 """
490 - def __init__(self, raw):
491 """ 492 Concretely, this is the raw event plus inferred infos. 493 """ 494 _Event.__init__(self, raw) 495 self.maskname = EventsCodes.maskname(self.mask) 496 try: 497 if self.name: 498 self.pathname = os.path.abspath(os.path.join(self.path, 499 self.name)) 500 else: 501 self.pathname = os.path.abspath(self.path) 502 except AttributeError: 503 pass
504
505 506 -class ProcessEventError(PyinotifyError):
507 """ 508 ProcessEventError Exception. Raised on ProcessEvent error. 509 """
510 - def __init__(self, err):
511 """ 512 @param err: Exception error description. 513 @type err: string 514 """ 515 PyinotifyError.__init__(self, err)
516
517 518 -class _ProcessEvent:
519 """ 520 Abstract processing event class. 521 """
522 - def __call__(self, event):
523 """ 524 To behave like a functor the object must be callable. 525 This method is a dispatch method. Lookup order: 526 1. process_MASKNAME method 527 2. process_FAMILY_NAME method 528 3. otherwise call process_default 529 530 @param event: Event to be processed. 531 @type event: Event object 532 @return: By convention when used from the ProcessEvent class: 533 - Returning False or None (default value) means keep on 534 executing next chained functors (see chain.py example). 535 - Returning True instead means do not execute next 536 processing functions. 537 @rtype: bool 538 @raise ProcessEventError: Event object undispatchable, 539 unknown event. 540 """ 541 stripped_mask = event.mask - (event.mask & IN_ISDIR) 542 maskname = EventsCodes.ALL_VALUES.get(stripped_mask) 543 if maskname is None: 544 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask) 545 546 # 1- look for process_MASKNAME 547 meth = getattr(self, 'process_' + maskname, None) 548 if meth is not None: 549 return meth(event) 550 # 2- look for process_FAMILY_NAME 551 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None) 552 if meth is not None: 553 return meth(event) 554 # 3- default call method process_default 555 return self.process_default(event)
556
557 - def __repr__(self):
558 return '<%s>' % self.__class__.__name__
559
560 561 -class _SysProcessEvent(_ProcessEvent):
562 """ 563 There is three kind of processing according to each event: 564 565 1. special handling (deletion from internal container, bug, ...). 566 2. default treatment: which is applied to most of events. 567 4. IN_ISDIR is never sent alone, he is piggybacked with a standart 568 event, he is not processed as the others events, instead, its 569 value is captured and appropriately aggregated to dst event. 570 """
571 - def __init__(self, wm, notifier):
572 """ 573 574 @param wm: Watch Manager. 575 @type wm: WatchManager instance 576 @param notifier: notifier. 577 @type notifier: Instance of Notifier. 578 """ 579 self._watch_manager = wm # watch manager 580 self._notifier = notifier # notifier 581 self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...} 582 self._mv = {} # {src_path(str): (dst_path(str), date), ...}
583
584 - def cleanup(self):
585 """ 586 Cleanup (delete) old (>1mn) records contained in self._mv_cookie 587 and self._mv. 588 """ 589 date_cur_ = datetime.now() 590 for seq in [self._mv_cookie, self._mv]: 591 for k in seq.keys(): 592 if (date_cur_ - seq[k][1]) > timedelta(minutes=1): 593 log.debug('cleanup: deleting entry %s', seq[k][0]) 594 del seq[k]
595
596 - def process_IN_CREATE(self, raw_event):
597 """ 598 If the event concerns a directory and the auto_add flag of the 599 targetted watch is set to True, a new watch is added on this 600 new directory, with the same attributes's values than those of 601 this watch. 602 """ 603 if raw_event.mask & IN_ISDIR: 604 watch_ = self._watch_manager._wmd.get(raw_event.wd) 605 if watch_.auto_add: 606 addw = self._watch_manager.add_watch 607 newwd = addw(os.path.join(watch_.path, raw_event.name), 608 watch_.mask, proc_fun=watch_.proc_fun, 609 rec=False, auto_add=watch_.auto_add) 610 611 # Trick to handle mkdir -p /t1/t2/t3 where t1 is watched and 612 # t2 and t3 are created. 613 # Since the directory is new, then everything inside it 614 # must also be new. 615 base = os.path.join(watch_.path, raw_event.name) 616 if newwd[base] > 0: 617 for name in os.listdir(base): 618 inner = os.path.join(base, name) 619 if (os.path.isdir(inner) and 620 self._watch_manager.get_wd(inner) is None): 621 # Generate (simulate) creation event for sub 622 # directories. 623 rawevent = _RawEvent(newwd[base], 624 IN_CREATE | IN_ISDIR, 625 0, name) 626 self._notifier._eventq.append(rawevent) 627 return self.process_default(raw_event)
628
629 - def process_IN_MOVED_FROM(self, raw_event):
630 """ 631 Map the cookie with the source path (+ date for cleaning). 632 """ 633 watch_ = self._watch_manager._wmd.get(raw_event.wd) 634 path_ = watch_.path 635 src_path = os.path.normpath(os.path.join(path_, raw_event.name)) 636 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now()) 637 return self.process_default(raw_event, {'cookie': raw_event.cookie})
638
639 - def process_IN_MOVED_TO(self, raw_event):
640 """ 641 Map the source path with the destination path (+ date for 642 cleaning). 643 """ 644 watch_ = self._watch_manager._wmd.get(raw_event.wd) 645 path_ = watch_.path 646 dst_path = os.path.normpath(os.path.join(path_, raw_event.name)) 647 mv_ = self._mv_cookie.get(raw_event.cookie) 648 if mv_: 649 self._mv[mv_[0]] = (dst_path, datetime.now()) 650 return self.process_default(raw_event, {'cookie': raw_event.cookie})
651
652 - def process_IN_MOVE_SELF(self, raw_event):
653 """ 654 STATUS: the following bug has been fixed in the recent kernels (fixme: 655 which version ?). Now it raises IN_DELETE_SELF instead. 656 657 Old kernels are bugged, this event is raised when the watched item 658 was moved, so we must update its path, but under some circumstances it 659 can be impossible: if its parent directory and its destination 660 directory aren't watched. The kernel (see include/linux/fsnotify.h) 661 doesn't bring us enough informations like the destination path of 662 moved items. 663 """ 664 watch_ = self._watch_manager._wmd.get(raw_event.wd) 665 src_path = watch_.path 666 mv_ = self._mv.get(src_path) 667 if mv_: 668 watch_.path = mv_[0] 669 else: 670 log.error("The path %s of this watch %s is not reliable anymore", 671 watch_.path, watch_) 672 if not watch_.path.endswith('-wrong-path'): 673 watch_.path += '-wrong-path' 674 # FIXME: should we pass the cookie even if this is not standart? 675 return self.process_default(raw_event)
676
677 - def process_IN_Q_OVERFLOW(self, raw_event):
678 """ 679 Only signal overflow, most of the common flags are irrelevant 680 for this event (path, wd, name). 681 """ 682 return Event({'mask': raw_event.mask})
683
684 - def process_IN_IGNORED(self, raw_event):
685 """ 686 The watch descriptor raised by this event is now ignored (forever), 687 it can be safely deleted from watch manager dictionary. 688 After this event we can be sure that neither the event queue 689 neither the system will raise an event associated to this wd. 690 """ 691 event_ = self.process_default(raw_event) 692 try: 693 del self._watch_manager._wmd[raw_event.wd] 694 except KeyError, err: 695 log.error(str(err)) 696 return event_
697
698 - def process_default(self, raw_event, to_append={}):
699 """ 700 Common handling for the following events: 701 702 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, 703 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT. 704 """ 705 ret = None 706 watch_ = self._watch_manager._wmd.get(raw_event.wd) 707 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF): 708 # unfornately information not provided by the kernel 709 dir_ = watch_.dir 710 else: 711 dir_ = bool(raw_event.mask & IN_ISDIR) 712 dict_ = {'wd': raw_event.wd, 713 'mask': raw_event.mask, 714 'path': watch_.path, 715 'name': raw_event.name, 716 'dir': dir_} 717 dict_.update(to_append) 718 return Event(dict_)
719
720 721 -class ProcessEvent(_ProcessEvent):
722 """ 723 Process events objects, can be specialized via subclassing, thus its 724 behavior can be overriden: 725 726 Note: you should not override __init__ in your subclass instead define 727 a my_init() method, this method will be called from the constructor of 728 this class with optional parameters. 729 730 1. Provide methods, e.g. process_IN_DELETE for processing a given kind 731 of event (eg. IN_DELETE in this case). 732 2. Or/and provide methods for processing events by 'family', e.g. 733 process_IN_CLOSE method will process both IN_CLOSE_WRITE and 734 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and 735 process_IN_CLOSE_NOWRITE aren't defined). 736 3. Or/and override process_default for processing the remaining kind of 737 events. 738 """ 739 pevent = None 740
741 - def __init__(self, pevent=None, **kargs):
742 """ 743 Enable chaining of ProcessEvent instances. 744 745 @param pevent: optional callable object, will be called on event 746 processing (before self). 747 @type pevent: callable 748 @param kargs: optional arguments delagated to template method my_init 749 @type kargs: dict 750 """ 751 self.pevent = pevent 752 self.my_init(**kargs)
753
754 - def my_init(self, **kargs):
755 """ 756 Override this method when subclassing if you want to achieve 757 custom initialization of your subclass' instance. You MUST pass 758 keyword arguments. This method does nothing by default. 759 760 @param kargs: optional arguments delagated to template method my_init 761 @type kargs: dict 762 """ 763 pass
764
765 - def __call__(self, event):
766 stop_chaining = False 767 if self.pevent is not None: 768 # By default methods return None so we fix as guideline 769 # that methods asking for stop chaining must explicitely 770 # return non None or False values, otherwise the default 771 # behavior is to chain call to the corresponding local 772 # method. 773 stop_chaining = self.pevent(event) 774 if not stop_chaining: 775 return _ProcessEvent.__call__(self, event)
776
777 - def nested_pevent(self):
778 return self.pevent
779
780 - def process_default(self, event):
781 """ 782 Default default processing event method. Print event 783 on standart output. 784 785 @param event: Event to be processed. 786 @type event: Event instance 787 """ 788 print(repr(event))
789
790 791 -class ChainIfTrue(ProcessEvent):
792 """ 793 Makes conditional chaining depending on the result of the nested 794 processing instance. 795 """
796 - def my_init(self, func):
797 self._func = func
798
799 - def process_default(self, event):
800 return not self._func(event)
801
802 803 -class Stats(ProcessEvent):
804 - def my_init(self):
805 self._start_time = time.time() 806 self._stats = {} 807 self._stats_lock = threading.Lock()
808
809 - def process_default(self, event):
810 self._stats_lock.acquire() 811 try: 812 events = event.maskname.split('|') 813 for event_name in events: 814 count = self._stats.get(event_name, 0) 815 self._stats[event_name] = count + 1 816 finally: 817 self._stats_lock.release()
818
819 - def _stats_copy(self):
820 self._stats_lock.acquire() 821 try: 822 return self._stats.copy() 823 finally: 824 self._stats_lock.release()
825
826 - def __repr__(self):
827 stats = self._stats_copy() 828 829 t = int(time.time() - self._start_time) 830 if t < 60: 831 ts = str(t) + 'sec' 832 elif 60 <= t < 3600: 833 ts = '%dmn%dsec' % (t / 60, t % 60) 834 elif 3600 <= t < 86400: 835 ts = '%dh%dmn' % (t / 3600, (t % 3600) / 60) 836 elif t >= 86400: 837 ts = '%dd%dh' % (t / 86400, (t % 86400) / 3600) 838 stats['ElapsedTime'] = ts 839 840 l = [] 841 for ev, value in sorted(stats.items(), key=lambda x: x[0]): 842 l.append(' %s=%s' % (Color.FieldName(ev), 843 Color.FieldValue(value))) 844 s = '<%s%s >' % (Color.ClassName(self.__class__.__name__), 845 ''.join(l)) 846 return s
847
848 - def dump(self, filename):
849 fo = file(filename, 'wb') 850 try: 851 fo.write(str(self)) 852 finally: 853 fo.close()
854
855 - def __str__(self, scale=45):
856 stats = self._stats_copy() 857 if not stats: 858 return '' 859 860 m = max(stats.values()) 861 unity = int(round(float(m) / scale)) or 1 862 fmt = '%%-26s%%-%ds%%s' % (len(Color.FieldValue('@' * scale)) 863 + 1) 864 def func(x): 865 return fmt % (Color.FieldName(x[0]), 866 Color.FieldValue('@' * (x[1] / unity)), 867 Color.Simple('%d' % x[1], 'yellow'))
868 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0]))) 869 return s
870
871 872 -class NotifierError(PyinotifyError):
873 """ 874 Notifier Exception. Raised on Notifier error. 875 876 """
877 - def __init__(self, err):
878 """ 879 @param err: Exception string's description. 880 @type err: string 881 """ 882 PyinotifyError.__init__(self, err)
883
884 885 -class Notifier:
886 """ 887 Read notifications, process events. 888 889 """
890 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 891 read_freq=0, treshold=0, timeout=None):
892 """ 893 Initialization. read_freq, treshold and timeout parameters are used 894 when looping. 895 896 @param watch_manager: Watch Manager. 897 @type watch_manager: WatchManager instance 898 @param default_proc_fun: Default processing method. 899 @type default_proc_fun: instance of ProcessEvent 900 @param read_freq: if read_freq == 0, events are read asap, 901 if read_freq is > 0, this thread sleeps 902 max(0, read_freq - timeout) seconds. But if 903 timeout is None it can be different because 904 poll is blocking waiting for something to read. 905 @type read_freq: int 906 @param treshold: File descriptor will be read only if its size to 907 read is >= treshold. If != 0, you likely want to 908 use it in combination with read_freq because 909 without that you keep looping without really reading 910 anything and that until the amount to read 911 is >= treshold. At least with read_freq you may sleep. 912 @type treshold: int 913 @param timeout: 914 http://docs.python.org/lib/poll-objects.html#poll-objects 915 @type timeout: int 916 """ 917 # watch manager instance 918 self._watch_manager = watch_manager 919 # file descriptor 920 self._fd = self._watch_manager._fd 921 # poll object and registration 922 self._pollobj = select.poll() 923 self._pollobj.register(self._fd, select.POLLIN) 924 # This pipe is correctely initialized and used by ThreadedNotifier 925 self._pipe = (-1, -1) 926 # event queue 927 self._eventq = deque() 928 # system processing functor, common to all events 929 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self) 930 # default processing method 931 self._default_proc_fun = default_proc_fun 932 # loop parameters 933 self._read_freq = read_freq 934 self._treshold = treshold 935 self._timeout = timeout
936
937 - def proc_fun(self):
938 return self._default_proc_fun
939
940 - def check_events(self):
941 """ 942 Check for new events available to read, blocks up to timeout 943 milliseconds. 944 945 @return: New events to read. 946 @rtype: bool 947 """ 948 while True: 949 try: 950 # blocks up to 'timeout' milliseconds 951 ret = self._pollobj.poll(self._timeout) 952 except select.error, err: 953 if err[0] == errno.EINTR: 954 continue # interrupted, retry 955 else: 956 raise 957 else: 958 break 959 960 if not ret or (self._pipe[0] == ret[0][0]): 961 return False 962 # only one fd is polled 963 return ret[0][1] & select.POLLIN
964
965 - def read_events(self):
966 """ 967 Read events from device, build _RawEvents, and enqueue them. 968 """ 969 buf_ = array.array('i', [0]) 970 # get event queue size 971 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1: 972 return 973 queue_size = buf_[0] 974 if queue_size < self._treshold: 975 log.debug('(fd: %d) %d bytes available to read but treshold is ' 976 'fixed to %d bytes', self._fd, queue_size, 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 False raises a 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 False raises a 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 False raises a 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