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