Module pyinotify
[hide private]
[frames] | no frames]

Source Code for Module pyinotify

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