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