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