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 must not " 671 "be trusted anymore" % (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(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 ' 976 'treshold is fixed to %d bytes' % (self._fd, 977 queue_size, 978 self._treshold)) 979 return 980 981 try: 982 # read content from file 983 r = os.read(self._fd, queue_size) 984 except Exception, msg: 985 raise NotifierError(msg) 986 log.debug('event queue size: %d' % queue_size) 987 rsum = 0 # counter 988 while rsum < queue_size: 989 s_size = 16 990 # retrieve wd, mask, cookie 991 s_ = struct.unpack('iIII', r[rsum:rsum+s_size]) 992 # length of name 993 fname_len = s_[3] 994 # field 'length' useless 995 s_ = s_[:-1] 996 # retrieve name 997 s_ += struct.unpack('%ds' % fname_len, 998 r[rsum + s_size:rsum + s_size + fname_len]) 999 self._eventq.append(_RawEvent(*s_)) 1000 rsum += s_size + fname_len
1001
1002 - def process_events(self):
1003 """ 1004 Routine for processing events from queue by calling their 1005 associated proccessing function (instance of ProcessEvent). 1006 It also do internal processings, to keep the system updated. 1007 """ 1008 while self._eventq: 1009 raw_event = self._eventq.popleft() # pop next event 1010 watch_ = self._watch_manager._wmd.get(raw_event.wd) 1011 revent = self._sys_proc_fun(raw_event) # system processings 1012 if watch_ and watch_.proc_fun: 1013 watch_.proc_fun(revent) # user processings 1014 else: 1015 self._default_proc_fun(revent) 1016 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1017 1018
1019 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull, 1020 stdout=os.devnull, stderr=os.devnull):
1021 """ 1022 pid_file: file to which pid will be written. 1023 force_kill: if True kill the process associated to pid_file. 1024 stdin, stdout, stderr: files associated to common streams. 1025 """ 1026 if pid_file is None: 1027 dirname = '/var/run/' 1028 basename = sys.argv[0] or 'pyinotify' 1029 pid_file = os.path.join(dirname, basename + '.pid') 1030 1031 if os.path.exists(pid_file): 1032 fo = file(pid_file, 'rb') 1033 try: 1034 try: 1035 pid = int(fo.read()) 1036 except ValueError: 1037 pid = None 1038 if pid is not None: 1039 try: 1040 os.kill(pid, 0) 1041 except OSError, err: 1042 pass 1043 else: 1044 if not force_kill: 1045 s = 'There is already a pid file %s with pid %d' 1046 raise NotifierError(s % (pid_file, pid)) 1047 else: 1048 os.kill(pid, 9) 1049 finally: 1050 fo.close() 1051 1052 1053 def fork_daemon(): 1054 # Adapted from Chad J. Schroeder's recipe 1055 pid = os.fork() 1056 if (pid == 0): 1057 # parent 2 1058 os.setsid() 1059 pid = os.fork() 1060 if (pid == 0): 1061 # child 1062 os.chdir('/') 1063 os.umask(0) 1064 else: 1065 # parent 2 1066 os._exit(0) 1067 else: 1068 # parent 1 1069 os._exit(0) 1070 1071 fd_inp = os.open(stdin, os.O_RDONLY) 1072 os.dup2(fd_inp, 0) 1073 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT) 1074 os.dup2(fd_out, 1) 1075 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT) 1076 os.dup2(fd_err, 2)
1077 1078 # Detach task 1079 fork_daemon() 1080 1081 # Write pid 1082 fo = file(pid_file, 'wb') 1083 try: 1084 fo.write(str(os.getpid()) + '\n') 1085 finally: 1086 fo.close() 1087 1088 atexit.register(lambda : os.unlink(pid_file))
1089 1090
1091 - def _sleep(self, ref_time):
1092 # Only consider sleeping if read_freq is > 0 1093 if self._read_freq > 0: 1094 cur_time = time.time() 1095 sleep_amount = self._read_freq - (cur_time - ref_time) 1096 if sleep_amount > 0: 1097 log.debug('Now sleeping %d seconds' % sleep_amount) 1098 time.sleep(sleep_amount)
1099 1100
1101 - def loop(self, callback=None, daemonize=False, **args):
1102 """ 1103 Events are read only once time every min(read_freq, timeout) 1104 seconds at best and only if the size to read is >= treshold. 1105 1106 @param callback: Functor called after each event processing. Expects 1107 to receive notifier object (self) as first parameter. 1108 @type callback: callable 1109 @param daemonize: This thread is daemonized if set to True. 1110 @type daemonize: boolean 1111 """ 1112 if daemonize: 1113 self.__daemonize(**args) 1114 1115 # Read and process events forever 1116 while 1: 1117 try: 1118 self.process_events() 1119 if callback is not None: 1120 callback(self) 1121 ref_time = time.time() 1122 # check_events is blocking 1123 if self.check_events(): 1124 self._sleep(ref_time) 1125 self.read_events() 1126 except KeyboardInterrupt: 1127 # Unless sigint is caught (Control-C) 1128 log.debug('Pyinotify stops monitoring.') 1129 # Stop monitoring 1130 self.stop() 1131 break
1132
1133 - def stop(self):
1134 """ 1135 Close the inotify's instance (close its file descriptor). 1136 It destroys all existing watches, pending events,... 1137 """ 1138 self._pollobj.unregister(self._fd) 1139 os.close(self._fd)
1140
1141 1142 -class ThreadedNotifier(threading.Thread, Notifier):
1143 """ 1144 This notifier inherits from threading.Thread for instantiating a separate 1145 thread, and also inherits from Notifier, because it is a threaded notifier. 1146 1147 Note that everything possible with this class is also possible through 1148 Notifier. Moreover Notifier is _better_ under many aspects: not threaded, 1149 can be easily daemonized. 1150 """
1151 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(), 1152 read_freq=0, treshold=0, timeout=None):
1153 """ 1154 Initialization, initialize base classes. read_freq, treshold and 1155 timeout parameters are used when looping. 1156 1157 @param watch_manager: Watch Manager. 1158 @type watch_manager: WatchManager instance 1159 @param default_proc_fun: Default processing method. 1160 @type default_proc_fun: instance of ProcessEvent 1161 @param read_freq: if read_freq == 0, events are read asap, 1162 if read_freq is > 0, this thread sleeps 1163 max(0, read_freq - timeout) seconds. 1164 @type read_freq: int 1165 @param treshold: File descriptor will be read only if its size to 1166 read is >= treshold. If != 0, you likely want to 1167 use it in combination with read_freq because 1168 without that you keep looping without really reading 1169 anything and that until the amount to read 1170 is >= treshold. At least with read_freq you may sleep. 1171 @type treshold: int 1172 @param timeout: 1173 see http://docs.python.org/lib/poll-objects.html#poll-objects 1174 Read the corresponding comment in the source code before changing 1175 it. 1176 @type timeout: int 1177 """ 1178 # Init threading base class 1179 threading.Thread.__init__(self) 1180 # Stop condition 1181 self._stop_event = threading.Event() 1182 # Init Notifier base class 1183 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, 1184 treshold, timeout) 1185 # Create a new pipe used for thread termination 1186 self._pipe = os.pipe() 1187 self._pollobj.register(self._pipe[0], select.POLLIN)
1188
1189 - def stop(self):
1190 """ 1191 Stop the notifier's loop. Stop notification. Join the thread. 1192 """ 1193 self._stop_event.set() 1194 os.write(self._pipe[1], 'stop') 1195 threading.Thread.join(self) 1196 Notifier.stop(self) 1197 self._pollobj.unregister(self._pipe[0]) 1198 os.close(self._pipe[0]) 1199 os.close(self._pipe[1])
1200
1201 - def loop(self):
1202 """ 1203 Thread's main loop. Don't meant to be called by user directly. 1204 Call start() instead. 1205 1206 Events are read only once time every min(read_freq, timeout) 1207 seconds at best and only if the size of events to read is >= treshold. 1208 """ 1209 # When the loop must be terminated .stop() is called, 'stop' 1210 # is written to pipe fd so poll() returns and .check_events() 1211 # returns False which make evaluate the While's stop condition 1212 # ._stop_event.isSet() wich put an end to the thread's execution. 1213 while not self._stop_event.isSet(): 1214 self.process_events() 1215 ref_time = time.time() 1216 if self.check_events(): 1217 self._sleep(ref_time) 1218 self.read_events()
1219
1220 - def run(self):
1221 """ 1222 Start the thread's loop: read and process events until the method 1223 stop() is called. 1224 Never call this method directly, instead call the start() method 1225 inherited from threading.Thread, which then will call run(). 1226 """ 1227 self.loop()
1228
1229 1230 -class Watch:
1231 """ 1232 Represent a watch, i.e. a file or directory being watched. 1233 1234 """
1235 - def __init__(self, **keys):
1236 """ 1237 Initializations. 1238 1239 @param wd: Watch descriptor. 1240 @type wd: int 1241 @param path: Path of the file or directory being watched. 1242 @type path: str 1243 @param mask: Mask. 1244 @type mask: int 1245 @param proc_fun: Processing callable object. 1246 @type proc_fun: 1247 @param auto_add: Automatically add watches on new directories. 1248 @type auto_add: bool 1249 """ 1250 for k, v in keys.iteritems(): 1251 setattr(self, k, v) 1252 self.dir = os.path.isdir(self.path)
1253
1254 - def __repr__(self):
1255 """ 1256 @return: String representation. 1257 @rtype: str 1258 """ 1259 s = ' '.join(['%s%s%s' % (Color.FieldName(attr), 1260 Color.Punctuation('='), 1261 Color.FieldValue(getattr(self, attr))) \ 1262 for attr in self.__dict__ if not attr.startswith('_')]) 1263 1264 s = '%s%s %s %s' % (Color.Punctuation('<'), 1265 Color.ClassName(self.__class__.__name__), 1266 s, 1267 Color.Punctuation('>')) 1268 return s
1269
1270 1271 -class ExcludeFilter:
1272 """ 1273 ExcludeFilter is an exclusion filter. 1274 """ 1275
1276 - def __init__(self, arg_lst):
1277 """ 1278 @param arg_lst: is either a list or dict of patterns: 1279 [pattern1, ..., patternn] 1280 {'filename1': (list1, listn), ...} where list1 is 1281 a list of patterns 1282 @type arg_lst: list or dict 1283 """ 1284 if isinstance(arg_lst, dict): 1285 lst = self._load_patterns(arg_lst) 1286 elif isinstance(arg_lst, list): 1287 lst = arg_lst 1288 else: 1289 raise TypeError 1290 1291 self._lregex = [] 1292 for regex in lst: 1293 self._lregex.append(re.compile(regex, re.UNICODE))
1294
1295 - def _load_patterns(self, dct):
1296 lst = [] 1297 for path, varnames in dct.iteritems(): 1298 loc = {} 1299 execfile(path, {}, loc) 1300 for varname in varnames: 1301 lst.extend(loc.get(varname, [])) 1302 return lst
1303
1304 - def _match(self, regex, path):
1305 return regex.match(path) is not None
1306
1307 - def __call__(self, path):
1308 """ 1309 @param path: path to match against regexps. 1310 @type path: str 1311 @return: return True is path has been matched and should 1312 be excluded, False otherwise. 1313 @rtype: bool 1314 """ 1315 for regex in self._lregex: 1316 if self._match(regex, path): 1317 return True 1318 return False
1319
1320 1321 -class WatchManagerError(Exception):
1322 """ 1323 WatchManager Exception. Raised on error encountered on watches 1324 operations. 1325 1326 """
1327 - def __init__(self, msg, wmd):
1328 """ 1329 @param msg: Exception string's description. 1330 @type msg: string 1331 @param wmd: Results of previous operations made by the same function 1332 on previous wd or paths. It also contains the item which 1333 raised this exception. 1334 @type wmd: dict 1335 """ 1336 self.wmd = wmd 1337 Exception.__init__(self, msg)
1338
1339 1340 -class WatchManager:
1341 """ 1342 Provide operations for watching files and directories. Integrated 1343 dictionary is used to reference watched items. 1344 """
1345 - def __init__(self, exclude_filter=lambda path: False):
1346 """ 1347 Initialization: init inotify, init watch manager dictionary. 1348 Raise OSError if initialization fails. 1349 1350 @param exclude_filter: boolean function, returns True if current 1351 path must be excluded from being watched. 1352 Convenient for providing a common exclusion 1353 filter for every call to add_watch. 1354 @type exclude_filter: bool 1355 """ 1356 self._exclude_filter = exclude_filter 1357 self._wmd = {} # watch dict key: watch descriptor, value: watch 1358 self._fd = LIBC.inotify_init() # inotify's init, file descriptor 1359 if self._fd < 0: 1360 raise OSError()
1361
1362 - def __add_watch(self, path, mask, proc_fun, auto_add):
1363 """ 1364 Add a watch on path, build a Watch object and insert it in the 1365 watch manager dictionary. Return the wd value. 1366 """ 1367 wd_ = LIBC.inotify_add_watch(self._fd, 1368 ctypes.create_string_buffer(path), 1369 mask) 1370 if wd_ < 0: 1371 return wd_ 1372 watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask, 1373 proc_fun=proc_fun, auto_add=auto_add) 1374 self._wmd[wd_] = watch_ 1375 log.debug('New %s' % watch_) 1376 return wd_
1377
1378 - def __glob(self, path, do_glob):
1379 if do_glob: 1380 return iglob(path) 1381 else: 1382 return [path]
1383
1384 - def add_watch(self, path, mask, proc_fun=None, rec=False, 1385 auto_add=False, do_glob=False, quiet=True, 1386 exclude_filter=None):
1387 """ 1388 Add watch(s) on given path(s) with the specified mask and 1389 optionnally with a processing function and recursive flag. 1390 1391 @param path: Path to watch, the path can either be a file or a 1392 directory. Also accepts a sequence (list) of paths. 1393 @type path: string or list of string 1394 @param mask: Bitmask of events. 1395 @type mask: int 1396 @param proc_fun: Processing object. 1397 @type proc_fun: function or ProcessEvent instance or instance of 1398 one of its subclasses or callable object. 1399 @param rec: Recursively add watches from path on all its 1400 subdirectories, set to False by default (doesn't 1401 follows symlinks). 1402 @type rec: bool 1403 @param auto_add: Automatically add watches on newly created 1404 directories in the watch's path. 1405 @type auto_add: bool 1406 @param do_glob: Do globbing on pathname. 1407 @type do_glob: bool 1408 @param quiet: if True raise an WatchManagerError exception on 1409 error. See example not_quiet.py 1410 @type quiet: bool 1411 @param exclude_filter: boolean function, returns True if current 1412 path must be excluded from being watched. 1413 Has precedence on exclude_filter defined 1414 into __init__. 1415 @type exclude_filter: bool 1416 @return: dict of paths associated to watch descriptors. A wd value 1417 is positive if the watch has been sucessfully added, 1418 otherwise the value is negative. If the path is invalid 1419 it will be not included into this dict. 1420 @rtype: dict of {str: int} 1421 """ 1422 ret_ = {} # return {path: wd, ...} 1423 1424 if exclude_filter is None: 1425 exclude_filter = self._exclude_filter 1426 1427 # normalize args as list elements 1428 for npath in self.__format_param(path): 1429 # unix pathname pattern expansion 1430 for apath in self.__glob(npath, do_glob): 1431 # recursively list subdirs according to rec param 1432 for rpath in self.__walk_rec(apath, rec): 1433 if not exclude_filter(rpath): 1434 wd = ret_[rpath] = self.__add_watch(rpath, mask, 1435 proc_fun, 1436 auto_add) 1437 if wd < 0: 1438 err = 'add_watch: cannot watch %s (WD=%d)' 1439 err = err % (rpath, wd) 1440 if quiet: 1441 log.error(err) 1442 else: 1443 raise WatchManagerError(err, ret_) 1444 else: 1445 # Let's say -2 means 'explicitely excluded 1446 # from watching'. 1447 ret_[rpath] = -2 1448 return ret_
1449
1450 - def __get_sub_rec(self, lpath):
1451 """ 1452 Get every wd from self._wmd if its path is under the path of 1453 one (at least) of those in lpath. Doesn't follow symlinks. 1454 1455 @param lpath: list of watch descriptor 1456 @type lpath: list of int 1457 @return: list of watch descriptor 1458 @rtype: list of int 1459 """ 1460 for d in lpath: 1461 root = self.get_path(d) 1462 if root: 1463 # always keep root 1464 yield d 1465 else: 1466 # if invalid 1467 continue 1468 1469 # nothing else to expect 1470 if not os.path.isdir(root): 1471 continue 1472 1473 # normalization 1474 root = os.path.normpath(root) 1475 # recursion 1476 lend = len(root) 1477 for iwd in self._wmd.items(): 1478 cur = iwd[1].path 1479 pref = os.path.commonprefix([root, cur]) 1480 if root == os.sep or (len(pref) == lend and \ 1481 len(cur) > lend and \ 1482 cur[lend] == os.sep): 1483 yield iwd[1].wd
1484
1485 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False, 1486 auto_add=False, quiet=True):
1487 """ 1488 Update existing watch(s). Both the mask and the processing 1489 object can be modified. 1490 1491 @param wd: Watch Descriptor to update. Also accepts a list of 1492 watch descriptors. 1493 @type wd: int or list of int 1494 @param mask: Optional new bitmask of events. 1495 @type mask: int 1496 @param proc_fun: Optional new processing function. 1497 @type proc_fun: function or ProcessEvent instance or instance of 1498 one of its subclasses or callable object. 1499 @param rec: Recursively update watches on every already watched 1500 subdirectories and subfiles. 1501 @type rec: bool 1502 @param auto_add: Automatically add watches on newly created 1503 directories in the watch's path. 1504 @type auto_add: bool 1505 @param quiet: if True raise an WatchManagerError exception on 1506 error. See example not_quiet.py 1507 @type quiet: bool 1508 @return: dict of watch descriptors associated to booleans values. 1509 True if the corresponding wd has been successfully 1510 updated, False otherwise. 1511 @rtype: dict of int: bool 1512 """ 1513 lwd = self.__format_param(wd) 1514 if rec: 1515 lwd = self.__get_sub_rec(lwd) 1516 1517 ret_ = {} # return {wd: bool, ...} 1518 for awd in lwd: 1519 apath = self.get_path(awd) 1520 if not apath or awd < 0: 1521 err = 'update_watch: invalid WD=%d' % awd 1522 if quiet: 1523 log.error(err) 1524 continue 1525 raise WatchManagerError(err, ret_) 1526 1527 if mask: 1528 addw = LIBC.inotify_add_watch 1529 wd_ = addw(self._fd, 1530 ctypes.create_string_buffer(apath), 1531 mask) 1532 if wd_ < 0: 1533 ret_[awd] = False 1534 err = 'update_watch: cannot update WD=%d (%s)' % (wd_, 1535 apath) 1536 if quiet: 1537 log.error(err) 1538 continue 1539 raise WatchManagerError(err, ret_) 1540 1541 assert(awd == wd_) 1542 1543 if proc_fun or auto_add: 1544 watch_ = self._wmd[awd] 1545 1546 if proc_fun: 1547 watch_.proc_fun = proc_fun 1548 1549 if auto_add: 1550 watch_.proc_fun = auto_add 1551 1552 ret_[awd] = True 1553 log.debug('Updated watch - %s' % self._wmd[awd]) 1554 return ret_
1555
1556 - def __format_param(self, param):
1557 """ 1558 @param param: Parameter. 1559 @type param: string or int 1560 @return: wrap param. 1561 @rtype: list of type(param) 1562 """ 1563 if isinstance(param, list): 1564 for p_ in param: 1565 yield p_ 1566 else: 1567 yield param
1568
1569 - def get_wd(self, path):
1570 """ 1571 Returns the watch descriptor associated to path. This method 1572 has an prohibitive cost, always prefer to keep the WD. 1573 If path is unknown None is returned. 1574 1575 @param path: path. 1576 @type path: str 1577 @return: WD or None. 1578 @rtype: int or None 1579 """ 1580 path = os.path.normpath(path) 1581 for iwd in self._wmd.iteritems(): 1582 if iwd[1].path == path: 1583 return iwd[0] 1584 log.debug('get_wd: unknown path %s' % path)
1585
1586 - def get_path(self, wd):
1587 """ 1588 Returns the path associated to WD, if WD is unknown 1589 None is returned. 1590 1591 @param wd: watch descriptor. 1592 @type wd: int 1593 @return: path or None. 1594 @rtype: string or None 1595 """ 1596 watch_ = self._wmd.get(wd) 1597 if watch_: 1598 return watch_.path 1599 log.debug('get_path: unknown WD %d' % wd)
1600
1601 - def __walk_rec(self, top, rec):
1602 """ 1603 Yields each subdirectories of top, doesn't follow symlinks. 1604 If rec is false, only yield top. 1605 1606 @param top: root directory. 1607 @type top: string 1608 @param rec: recursive flag. 1609 @type rec: bool 1610 @return: path of one subdirectory. 1611 @rtype: string 1612 """ 1613 if not rec or os.path.islink(top) or not os.path.isdir(top): 1614 yield top 1615 else: 1616 for root, dirs, files in os.walk(top): 1617 yield root
1618
1619 - def rm_watch(self, wd, rec=False, quiet=True):
1620 """ 1621 Removes watch(s). 1622 1623 @param wd: Watch Descriptor of the file or directory to unwatch. 1624 Also accepts a list of WDs. 1625 @type wd: int or list of int. 1626 @param rec: Recursively removes watches on every already watched 1627 subdirectories and subfiles. 1628 @type rec: bool 1629 @param quiet: if True raise an WatchManagerError exception on 1630 error. See example not_quiet.py 1631 @type quiet: bool 1632 @return: dict of watch descriptors associated to booleans values. 1633 True if the corresponding wd has been successfully 1634 removed, False otherwise. 1635 @rtype: dict of int: bool 1636 """ 1637 lwd = self.__format_param(wd) 1638 if rec: 1639 lwd = self.__get_sub_rec(lwd) 1640 1641 ret_ = {} # return {wd: bool, ...} 1642 for awd in lwd: 1643 # remove watch 1644 wd_ = LIBC.inotify_rm_watch(self._fd, awd) 1645 if wd_ < 0: 1646 ret_[awd] = False 1647 err = 'rm_watch: cannot remove WD=%d' % awd 1648 if quiet: 1649 log.error(err) 1650 continue 1651 raise WatchManagerError(err, ret_) 1652 1653 ret_[awd] = True 1654 log.debug('watch WD=%d (%s) removed' % (awd, self.get_path(awd))) 1655 return ret_
1656 1657
1658 - def watch_transient_file(self, filename, mask, proc_class):
1659 """ 1660 Watch a transient file, which will be created and deleted frequently 1661 over time (e.g. pid file). 1662 1663 @attention: Under the call to this function it will be impossible 1664 to correctly watch the events triggered into the same 1665 base directory than the directory where is located this watched 1666 transient file. For instance it would actually be wrong to make these 1667 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...) 1668 and wm.add_watch('/var/run/', ...) 1669 1670 @param filename: Filename. 1671 @type filename: string 1672 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE. 1673 @type mask: int 1674 @param proc_class: ProcessEvent (or of one of its subclass), beware of 1675 accepting a ProcessEvent's instance as argument into 1676 __init__, see transient_file.py example for more 1677 details. 1678 @type proc_class: ProcessEvent's instance or of one of its subclasses. 1679 @return: See add_watch(). 1680 @rtype: See add_watch(). 1681 """ 1682 dirname = os.path.dirname(filename) 1683 if dirname == '': 1684 return {} # Maintains coherence with add_watch() 1685 basename = os.path.basename(filename) 1686 # Assuming we are watching at least for IN_CREATE and IN_DELETE 1687 mask |= IN_CREATE | IN_DELETE 1688 1689 def cmp_name(event): 1690 return basename == event.name
1691 return self.add_watch(dirname, mask, 1692 proc_fun=proc_class(ChainIfTrue(func=cmp_name)), 1693 rec=False, 1694 auto_add=False, do_glob=False)
1695
1696 1697 -class Color:
1698 normal = "\033[0m" 1699 black = "\033[30m" 1700 red = "\033[31m" 1701 green = "\033[32m" 1702 yellow = "\033[33m" 1703 blue = "\033[34m" 1704 purple = "\033[35m" 1705 cyan = "\033[36m" 1706 bold = "\033[1m" 1707 uline = "\033[4m" 1708 blink = "\033[5m" 1709 invert = "\033[7m" 1710 1711 @staticmethod
1712 - def Punctuation(s):
1713 return Color.normal + s + Color.normal
1714 1715 @staticmethod
1716 - def FieldValue(s):
1717 if not isinstance(s, str): 1718 s = str(s) 1719 return Color.purple + s + Color.normal
1720 1721 @staticmethod
1722 - def FieldName(s):
1723 return Color.blue + s + Color.normal
1724 1725 @staticmethod
1726 - def ClassName(s):
1727 return Color.red + Color.bold + s + Color.normal
1728 1729 @staticmethod
1730 - def Simple(s, color):
1731 if not isinstance(s, str): 1732 s = str(s) 1733 try: 1734 color_attr = getattr(Color, color) 1735 except AttributeError: 1736 return s 1737 return color_attr + s + Color.normal
1738
1739 1740 -def command_line():
1741 # 1742 # - By default the watched path is '/tmp' for all events. 1743 # - The monitoring execution blocks and serve forever, type c^c 1744 # to stop it. 1745 # 1746 from optparse import OptionParser 1747 1748 usage = "usage: %prog [options] [path1] [path2] [pathn]" 1749 1750 parser = OptionParser(usage=usage) 1751 parser.add_option("-v", "--verbose", action="store_true", 1752 dest="verbose", help="Verbose mode") 1753 parser.add_option("-r", "--recursive", action="store_true", 1754 dest="recursive", 1755 help="Add watches recursively on paths") 1756 parser.add_option("-a", "--auto_add", action="store_true", 1757 dest="auto_add", 1758 help="Automatically add watches on new directories") 1759 parser.add_option("-e", "--events-list", metavar="EVENT[,...]", 1760 dest="events_list", 1761 help=("A comma-separated list of events to watch for - " 1762 "see the documentation for valid options (defaults" 1763 " to everything)")) 1764 parser.add_option("-s", "--stats", action="store_true", 1765 dest="stats", 1766 help="Display statistics") 1767 1768 (options, args) = parser.parse_args() 1769 1770 if options.verbose: 1771 log.setLevel(10) 1772 1773 if len(args) < 1: 1774 path = '/tmp' # default watched path 1775 else: 1776 path = args 1777 1778 # watch manager instance 1779 wm = WatchManager() 1780 # notifier instance and init 1781 if options.stats: 1782 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5) 1783 else: 1784 notifier = Notifier(wm) 1785 1786 # What mask to apply 1787 mask = 0 1788 if options.events_list: 1789 events_list = options.events_list.split(',') 1790 for ev in events_list: 1791 evcode = EventsCodes.ALL_FLAGS.get(ev, 0) 1792 if evcode: 1793 mask |= evcode 1794 else: 1795 parser.error("The event '%s' specified with option -e" 1796 " is not valid" % ev) 1797 else: 1798 mask = ALL_EVENTS 1799 1800 # stats 1801 cb_fun = None 1802 if options.stats: 1803 def cb(s): 1804 print('%s\n%s\n' % (repr(s.proc_fun()), 1805 s.proc_fun()))
1806 cb_fun = cb 1807 1808 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path) 1809 1810 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add) 1811 # Loop forever (until sigint signal get caught) 1812 notifier.loop(callback=cb_fun) 1813 1814 1815 if __name__ == '__main__': 1816 command_line() 1817