1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 pyinotify
24
25 @author: Sebastien Martini
26 @license: GPLv2+
27 @contact: seb@dbzteam.org
28 """
31 """Indicates exceptions raised by a Pyinotify class."""
32
35 """
36 Raised for unsupported Python version.
37 """
39 """
40 @param version: Current Python version
41 @type version: string
42 """
43 PyinotifyError.__init__(self,
44 ('Python %s is unsupported, requires '
45 'at least Python 2.4') % version)
46
49 """
50 Raised for unsupported libc version.
51 """
53 """
54 @param version: Current Libc version
55 @type version: string
56 """
57 PyinotifyError.__init__(self,
58 ('Libc %s is unsupported, requires '
59 'at least Libc 2.4') % version)
60
61
62
63 import sys
64 if sys.version < '2.4':
65 raise UnsupportedPythonVersionError(sys.version)
66
67
68
69 import threading
70 import os
71 import select
72 import struct
73 import fcntl
74 import errno
75 import termios
76 import array
77 import logging
78 import atexit
79 from collections import deque
80 from datetime import datetime, timedelta
81 import time
82 import fnmatch
83 import re
84 import ctypes
85 import ctypes.util
86
87
88 __author__ = "seb@dbzteam.org (Sebastien Martini)"
89
90 __version__ = "0.8.6"
91
92 __metaclass__ = type
93
94
95
96 LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
97
98
99
100
101 LIBC.gnu_get_libc_version.restype = ctypes.c_char_p
102 LIBC_VERSION = LIBC.gnu_get_libc_version()
103 if LIBC_VERSION < '2.4':
104 raise UnsupportedLibcVersionError(LIBC_VERSION)
105
106
107
108 log = logging.getLogger("pyinotify")
109 console_handler = logging.StreamHandler()
110 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
111 log.addHandler(console_handler)
112 log.setLevel(20)
113
114
115
116 try:
117 if False:
118 import psyco
119 psyco.full()
120 except ImportError:
121
122 pass
129 """
130 Access (read, write) inotify's variables through sysctl.
131
132 Examples:
133 - Read variable: myvar = max_queued_events.value
134 - Update variable: max_queued_events.value = 42
135 """
136
137 inotify_attrs = {'max_user_instances': 1,
138 'max_user_watches': 2,
139 'max_queued_events': 3}
140
145
147 """
148 @return: stored value.
149 @rtype: int
150 """
151 oldv = ctypes.c_int(0)
152 size = ctypes.c_int(ctypes.sizeof(oldv))
153 LIBC.sysctl(self._attr, 3,
154 ctypes.c_voidp(ctypes.addressof(oldv)),
155 ctypes.addressof(size),
156 None, 0)
157 return oldv.value
158
160 """
161 @param nval: set to nval.
162 @type nval: int
163 """
164 oldv = ctypes.c_int(0)
165 sizeo = ctypes.c_int(ctypes.sizeof(oldv))
166 newv = ctypes.c_int(nval)
167 sizen = ctypes.c_int(ctypes.sizeof(newv))
168 LIBC.sysctl(self._attr, 3,
169 ctypes.c_voidp(ctypes.addressof(oldv)),
170 ctypes.addressof(sizeo),
171 ctypes.c_voidp(ctypes.addressof(newv)),
172 ctypes.addressof(sizen))
173
174 value = property(get_val, set_val)
175
177 return '<%s=%d>' % (self._attrname, self.get_val())
178
179
180
181
182
183
184
185 for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
186 globals()[attrname] = SysCtlINotify(attrname)
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 -def iglob(pathname):
210 if not has_magic(pathname):
211 if hasattr(os.path, 'lexists'):
212 if os.path.lexists(pathname):
213 yield pathname
214 else:
215 if os.path.islink(pathname) or os.path.exists(pathname):
216 yield pathname
217 return
218 dirname, basename = os.path.split(pathname)
219
220 if not dirname:
221 return
222
223 if has_magic(dirname):
224 dirs = iglob(dirname)
225 else:
226 dirs = [dirname]
227 if has_magic(basename):
228 glob_in_dir = glob1
229 else:
230 glob_in_dir = glob0
231 for dirname in dirs:
232 for name in glob_in_dir(dirname, basename):
233 yield os.path.join(dirname, name)
234
235 -def glob1(dirname, pattern):
236 if not dirname:
237 dirname = os.curdir
238 try:
239 names = os.listdir(dirname)
240 except os.error:
241 return []
242 return fnmatch.filter(names, pattern)
243
244 -def glob0(dirname, basename):
245 if basename == '' and os.path.isdir(dirname):
246
247
248 return [basename]
249 if hasattr(os.path, 'lexists'):
250 if os.path.lexists(os.path.join(dirname, basename)):
251 return [basename]
252 else:
253 if (os.path.islink(os.path.join(dirname, basename)) or
254 os.path.exists(os.path.join(dirname, basename))):
255 return [basename]
256 return []
257
258 magic_check = re.compile('[*?[]')
262
269 """
270 Set of codes corresponding to each kind of events.
271 Some of these flags are used to communicate with inotify, whereas
272 the others are sent to userspace by inotify notifying some events.
273
274 @cvar IN_ACCESS: File was accessed.
275 @type IN_ACCESS: int
276 @cvar IN_MODIFY: File was modified.
277 @type IN_MODIFY: int
278 @cvar IN_ATTRIB: Metadata changed.
279 @type IN_ATTRIB: int
280 @cvar IN_CLOSE_WRITE: Writtable file was closed.
281 @type IN_CLOSE_WRITE: int
282 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
283 @type IN_CLOSE_NOWRITE: int
284 @cvar IN_OPEN: File was opened.
285 @type IN_OPEN: int
286 @cvar IN_MOVED_FROM: File was moved from X.
287 @type IN_MOVED_FROM: int
288 @cvar IN_MOVED_TO: File was moved to Y.
289 @type IN_MOVED_TO: int
290 @cvar IN_CREATE: Subfile was created.
291 @type IN_CREATE: int
292 @cvar IN_DELETE: Subfile was deleted.
293 @type IN_DELETE: int
294 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
295 @type IN_DELETE_SELF: int
296 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
297 @type IN_MOVE_SELF: int
298 @cvar IN_UNMOUNT: Backing fs was unmounted.
299 @type IN_UNMOUNT: int
300 @cvar IN_Q_OVERFLOW: Event queued overflowed.
301 @type IN_Q_OVERFLOW: int
302 @cvar IN_IGNORED: File was ignored.
303 @type IN_IGNORED: int
304 @cvar IN_ONLYDIR: only watch the path if it is a directory (new
305 in kernel 2.6.15).
306 @type IN_ONLYDIR: int
307 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
308 IN_ONLYDIR we can make sure that we don't watch
309 the target of symlinks.
310 @type IN_DONT_FOLLOW: int
311 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
312 in kernel 2.6.14).
313 @type IN_MASK_ADD: int
314 @cvar IN_ISDIR: Event occurred against dir.
315 @type IN_ISDIR: int
316 @cvar IN_ONESHOT: Only send event once.
317 @type IN_ONESHOT: int
318 @cvar ALL_EVENTS: Alias for considering all of the events.
319 @type ALL_EVENTS: int
320 """
321
322
323
324
325 FLAG_COLLECTIONS = {'OP_FLAGS': {
326 'IN_ACCESS' : 0x00000001,
327 'IN_MODIFY' : 0x00000002,
328 'IN_ATTRIB' : 0x00000004,
329 'IN_CLOSE_WRITE' : 0x00000008,
330 'IN_CLOSE_NOWRITE' : 0x00000010,
331 'IN_OPEN' : 0x00000020,
332 'IN_MOVED_FROM' : 0x00000040,
333 'IN_MOVED_TO' : 0x00000080,
334 'IN_CREATE' : 0x00000100,
335 'IN_DELETE' : 0x00000200,
336 'IN_DELETE_SELF' : 0x00000400,
337
338 'IN_MOVE_SELF' : 0x00000800,
339 },
340 'EVENT_FLAGS': {
341 'IN_UNMOUNT' : 0x00002000,
342 'IN_Q_OVERFLOW' : 0x00004000,
343 'IN_IGNORED' : 0x00008000,
344 },
345 'SPECIAL_FLAGS': {
346 'IN_ONLYDIR' : 0x01000000,
347
348 'IN_DONT_FOLLOW' : 0x02000000,
349 'IN_MASK_ADD' : 0x20000000,
350
351 'IN_ISDIR' : 0x40000000,
352 'IN_ONESHOT' : 0x80000000,
353 },
354 }
355
357 """
358 Return the event name associated to mask. IN_ISDIR is appended when
359 appropriate. Note: only one event is returned, because only one is
360 raised once at a time.
361
362 @param mask: mask.
363 @type mask: int
364 @return: event name.
365 @rtype: str
366 """
367 ms = mask
368 name = '%s'
369 if mask & IN_ISDIR:
370 ms = mask - IN_ISDIR
371 name = '%s|IN_ISDIR'
372 return name % EventsCodes.ALL_VALUES[ms]
373
374 maskname = staticmethod(maskname)
375
376
377
378 EventsCodes.ALL_FLAGS = {}
379 EventsCodes.ALL_VALUES = {}
380 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems():
381
382
383 setattr(EventsCodes, flagc, valc)
384
385
386 EventsCodes.ALL_FLAGS.update(valc)
387
388
389
390 for name, val in valc.iteritems():
391 globals()[name] = val
392 EventsCodes.ALL_VALUES[val] = name
393
394
395
396 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues())
397 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
398 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
402 """
403 Event structure, represent events raised by the system. This
404 is the base class and should be subclassed.
405
406 """
408 """
409 Attach attributes (contained in dict_) to self.
410 """
411 for tpl in dict_.iteritems():
412 setattr(self, *tpl)
413
436
439 """
440 Raw event, it contains only the informations provided by the system.
441 It doesn't infer anything.
442 """
443 - def __init__(self, wd, mask, cookie, name):
444 """
445 @param wd: Watch Descriptor.
446 @type wd: int
447 @param mask: Bitmask of events.
448 @type mask: int
449 @param cookie: Cookie.
450 @type cookie: int
451 @param name: Basename of the file or directory against which the
452 event was raised, in case where the watched directory
453 is the parent directory. None if the event was raised
454 on the watched item itself.
455 @type name: string or None
456 """
457
458 super(_RawEvent, self).__init__({'wd': wd,
459 'mask': mask,
460 'cookie': cookie,
461 'name': name.rstrip('\0')})
462 log.debug(repr(self))
463
466 """
467 This class contains all the useful informations about the observed
468 event. However, the incorporation of each field is not guaranteed and
469 depends on the type of event. In effect, some fields are irrelevant
470 for some kind of event (for example 'cookie' is meaningless for
471 IN_CREATE whereas it is useful for IN_MOVE_TO).
472
473 The possible fields are:
474 - wd (int): Watch Descriptor.
475 - mask (int): Mask.
476 - maskname (str): Readable event name.
477 - path (str): path of the file or directory being watched.
478 - name (str): Basename of the file or directory against which the
479 event was raised, in case where the watched directory
480 is the parent directory. None if the event was raised
481 on the watched item itself. This field is always provided
482 even if the string is ''.
483 - pathname (str): absolute path of: path + name
484 - cookie (int): Cookie.
485 - dir (bool): is the event raised against directory.
486
487 """
489 """
490 Concretely, this is the raw event plus inferred infos.
491 """
492 _Event.__init__(self, raw)
493 self.maskname = EventsCodes.maskname(self.mask)
494 try:
495 if self.name:
496 self.pathname = os.path.abspath(os.path.join(self.path,
497 self.name))
498 else:
499 self.pathname = os.path.abspath(self.path)
500 except AttributeError:
501 pass
502
505 """
506 ProcessEventError Exception. Raised on ProcessEvent error.
507 """
509 """
510 @param err: Exception error description.
511 @type err: string
512 """
513 PyinotifyError.__init__(self, err)
514
517 """
518 Abstract processing event class.
519 """
521 """
522 To behave like a functor the object must be callable.
523 This method is a dispatch method. Lookup order:
524 1. process_MASKNAME method
525 2. process_FAMILY_NAME method
526 3. otherwise call process_default
527
528 @param event: Event to be processed.
529 @type event: Event object
530 @return: By convention when used from the ProcessEvent class:
531 - Returning False or None (default value) means keep on
532 executing next chained functors (see chain.py example).
533 - Returning True instead means do not execute next
534 processing functions.
535 @rtype: bool
536 @raise ProcessEventError: Event object undispatchable,
537 unknown event.
538 """
539 stripped_mask = event.mask - (event.mask & IN_ISDIR)
540 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
541 if maskname is None:
542 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
543
544
545 meth = getattr(self, 'process_' + maskname, None)
546 if meth is not None:
547 return meth(event)
548
549 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
550 if meth is not None:
551 return meth(event)
552
553 return self.process_default(event)
554
556 return '<%s>' % self.__class__.__name__
557
560 """
561 There is three kind of processing according to each event:
562
563 1. special handling (deletion from internal container, bug, ...).
564 2. default treatment: which is applied to most of events.
565 4. IN_ISDIR is never sent alone, he is piggybacked with a standart
566 event, he is not processed as the others events, instead, its
567 value is captured and appropriately aggregated to dst event.
568 """
570 """
571
572 @param wm: Watch Manager.
573 @type wm: WatchManager instance
574 @param notifier: notifier.
575 @type notifier: Instance of Notifier.
576 """
577 self._watch_manager = wm
578 self._notifier = notifier
579 self._mv_cookie = {}
580 self._mv = {}
581
583 """
584 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
585 and self._mv.
586 """
587 date_cur_ = datetime.now()
588 for seq in [self._mv_cookie, self._mv]:
589 for k in seq.keys():
590 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
591 log.debug('cleanup: deleting entry %s' % seq[k][0])
592 del seq[k]
593
595 """
596 If the event concerns a directory and the auto_add flag of the
597 targetted watch is set to True, a new watch is added on this
598 new directory, with the same attributes's values than those of
599 this watch.
600 """
601 if raw_event.mask & IN_ISDIR:
602 watch_ = self._watch_manager._wmd.get(raw_event.wd)
603 if watch_.auto_add:
604 addw = self._watch_manager.add_watch
605 newwd = addw(os.path.join(watch_.path, raw_event.name),
606 watch_.mask, proc_fun=watch_.proc_fun,
607 rec=False, auto_add=watch_.auto_add)
608
609
610
611
612
613 base = os.path.join(watch_.path, raw_event.name)
614 if newwd[base] > 0:
615 for name in os.listdir(base):
616 inner = os.path.join(base, name)
617 if (os.path.isdir(inner) and
618 self._watch_manager.get_wd(inner) is None):
619
620
621 rawevent = _RawEvent(newwd[base],
622 IN_CREATE | IN_ISDIR,
623 0, name)
624 self._notifier._eventq.append(rawevent)
625 return self.process_default(raw_event)
626
628 """
629 Map the cookie with the source path (+ date for cleaning).
630 """
631 watch_ = self._watch_manager._wmd.get(raw_event.wd)
632 path_ = watch_.path
633 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
634 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
635 return self.process_default(raw_event, {'cookie': raw_event.cookie})
636
638 """
639 Map the source path with the destination path (+ date for
640 cleaning).
641 """
642 watch_ = self._watch_manager._wmd.get(raw_event.wd)
643 path_ = watch_.path
644 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
645 mv_ = self._mv_cookie.get(raw_event.cookie)
646 if mv_:
647 self._mv[mv_[0]] = (dst_path, datetime.now())
648 return self.process_default(raw_event, {'cookie': raw_event.cookie})
649
651 """
652 STATUS: the following bug has been fixed in the recent kernels (fixme:
653 which version ?). Now it raises IN_DELETE_SELF instead.
654
655 Old kernels are bugged, this event is raised when the watched item
656 was moved, so we must update its path, but under some circumstances it
657 can be impossible: if its parent directory and its destination
658 directory aren't watched. The kernel (see include/linux/fsnotify.h)
659 doesn't bring us enough informations like the destination path of
660 moved items.
661 """
662 watch_ = self._watch_manager._wmd.get(raw_event.wd)
663 src_path = watch_.path
664 mv_ = self._mv.get(src_path)
665 if mv_:
666 watch_.path = mv_[0]
667 else:
668 log.error("The path %s of this watch %s must not "
669 "be trusted anymore" % (watch_.path, watch_))
670 if not watch_.path.endswith('-wrong-path'):
671 watch_.path += '-wrong-path'
672
673 return self.process_default(raw_event)
674
676 """
677 Only signal overflow, most of the common flags are irrelevant
678 for this event (path, wd, name).
679 """
680 return Event({'mask': raw_event.mask})
681
683 """
684 The watch descriptor raised by this event is now ignored (forever),
685 it can be safely deleted from watch manager dictionary.
686 After this event we can be sure that neither the event queue
687 neither the system will raise an event associated to this wd.
688 """
689 event_ = self.process_default(raw_event)
690 try:
691 del self._watch_manager._wmd[raw_event.wd]
692 except KeyError, err:
693 log.error(err)
694 return event_
695
697 """
698 Common handling for the following events:
699
700 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
701 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
702 """
703 ret = None
704 watch_ = self._watch_manager._wmd.get(raw_event.wd)
705 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
706
707 dir_ = watch_.dir
708 else:
709 dir_ = bool(raw_event.mask & IN_ISDIR)
710 dict_ = {'wd': raw_event.wd,
711 'mask': raw_event.mask,
712 'path': watch_.path,
713 'name': raw_event.name,
714 'dir': dir_}
715 dict_.update(to_append)
716 return Event(dict_)
717
720 """
721 Process events objects, can be specialized via subclassing, thus its
722 behavior can be overriden:
723
724 Note: you should not override __init__ in your subclass instead define
725 a my_init() method, this method will be called from the constructor of
726 this class with optional parameters.
727
728 1. Provide methods, e.g. process_IN_DELETE for processing a given kind
729 of event (eg. IN_DELETE in this case).
730 2. Or/and provide methods for processing events by 'family', e.g.
731 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
732 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
733 process_IN_CLOSE_NOWRITE aren't defined).
734 3. Or/and override process_default for processing the remaining kind of
735 events.
736 """
737 pevent = None
738
739 - def __init__(self, pevent=None, **kargs):
740 """
741 Enable chaining of ProcessEvent instances.
742
743 @param pevent: optional callable object, will be called on event
744 processing (before self).
745 @type pevent: callable
746 @param kargs: optional arguments delagated to template method my_init
747 @type kargs: dict
748 """
749 self.pevent = pevent
750 self.my_init(**kargs)
751
753 """
754 Override this method when subclassing if you want to achieve
755 custom initialization of your subclass' instance. You MUST pass
756 keyword arguments. This method does nothing by default.
757
758 @param kargs: optional arguments delagated to template method my_init
759 @type kargs: dict
760 """
761 pass
762
764 stop_chaining = False
765 if self.pevent is not None:
766
767
768
769
770
771 stop_chaining = self.pevent(event)
772 if not stop_chaining:
773 return _ProcessEvent.__call__(self, event)
774
777
779 """
780 Default default processing event method. Print event
781 on standart output.
782
783 @param event: Event to be processed.
784 @type event: Event instance
785 """
786 print(repr(event))
787
790 """
791 Makes conditional chaining depending on the result of the nested
792 processing instance.
793 """
796
798 return not self._func(event)
799
800
801 -class Stats(ProcessEvent):
803 self._start_time = time.time()
804 self._stats = {}
805 self._stats_lock = threading.Lock()
806
808 self._stats_lock.acquire()
809 try:
810 events = event.maskname.split('|')
811 for event_name in events:
812 count = self._stats.get(event_name, 0)
813 self._stats[event_name] = count + 1
814 finally:
815 self._stats_lock.release()
816
818 self._stats_lock.acquire()
819 try:
820 return self._stats.copy()
821 finally:
822 self._stats_lock.release()
823
825 stats = self._stats_copy()
826
827 t = int(time.time() - self._start_time)
828 if t < 60:
829 ts = str(t) + 'sec'
830 elif 60 <= t < 3600:
831 ts = '%dmn%dsec' % (t / 60, t % 60)
832 elif 3600 <= t < 86400:
833 ts = '%dh%dmn' % (t / 3600, (t % 3600) / 60)
834 elif t >= 86400:
835 ts = '%dd%dh' % (t / 86400, (t % 86400) / 3600)
836 stats['ElapsedTime'] = ts
837
838 l = []
839 for ev, value in sorted(stats.items(), key=lambda x: x[0]):
840 l.append(' %s=%s' % (Color.FieldName(ev),
841 Color.FieldValue(value)))
842 s = '<%s%s >' % (Color.ClassName(self.__class__.__name__),
843 ''.join(l))
844 return s
845
846 - def dump(self, filename):
847 fo = file(filename, 'wb')
848 try:
849 fo.write(str(self))
850 finally:
851 fo.close()
852
854 stats = self._stats_copy()
855 if not stats:
856 return ''
857
858 m = max(stats.values())
859 unity = int(round(float(m) / scale)) or 1
860 fmt = '%%-26s%%-%ds%%s' % (len(Color.FieldValue('@' * scale))
861 + 1)
862 def func(x):
863 return fmt % (Color.FieldName(x[0]),
864 Color.FieldValue('@' * (x[1] / unity)),
865 Color.Simple('%d' % x[1], 'yellow'))
866 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
867 return s
868
871 """
872 Notifier Exception. Raised on Notifier error.
873
874 """
876 """
877 @param err: Exception string's description.
878 @type err: string
879 """
880 PyinotifyError.__init__(self, err)
881
884 """
885 Read notifications, process events.
886
887 """
888 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
889 read_freq=0, treshold=0, timeout=None):
890 """
891 Initialization. read_freq, treshold and timeout parameters are used
892 when looping.
893
894 @param watch_manager: Watch Manager.
895 @type watch_manager: WatchManager instance
896 @param default_proc_fun: Default processing method.
897 @type default_proc_fun: instance of ProcessEvent
898 @param read_freq: if read_freq == 0, events are read asap,
899 if read_freq is > 0, this thread sleeps
900 max(0, read_freq - timeout) seconds. But if
901 timeout is None it can be different because
902 poll is blocking waiting for something to read.
903 @type read_freq: int
904 @param treshold: File descriptor will be read only if its size to
905 read is >= treshold. If != 0, you likely want to
906 use it in combination with read_freq because
907 without that you keep looping without really reading
908 anything and that until the amount to read
909 is >= treshold. At least with read_freq you may sleep.
910 @type treshold: int
911 @param timeout:
912 http://docs.python.org/lib/poll-objects.html#poll-objects
913 @type timeout: int
914 """
915
916 self._watch_manager = watch_manager
917
918 self._fd = self._watch_manager._fd
919
920 self._pollobj = select.poll()
921 self._pollobj.register(self._fd, select.POLLIN)
922
923 self._pipe = (-1, -1)
924
925 self._eventq = deque()
926
927 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
928
929 self._default_proc_fun = default_proc_fun
930
931 self._read_freq = read_freq
932 self._treshold = treshold
933 self._timeout = timeout
934
936 return self._default_proc_fun
937
939 """
940 Check for new events available to read, blocks up to timeout
941 milliseconds.
942
943 @return: New events to read.
944 @rtype: bool
945 """
946 while True:
947 try:
948
949 ret = self._pollobj.poll(self._timeout)
950 except select.error, err:
951 if err[0] == errno.EINTR:
952 continue
953 else:
954 raise
955 else:
956 break
957
958 if not ret or (self._pipe[0] == ret[0][0]):
959 return False
960
961 return ret[0][1] & select.POLLIN
962
964 """
965 Read events from device, build _RawEvents, and enqueue them.
966 """
967 buf_ = array.array('i', [0])
968
969 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
970 return
971 queue_size = buf_[0]
972 if queue_size < self._treshold:
973 log.debug('(fd: %d) %d bytes available to read but '
974 'treshold is fixed to %d bytes' % (self._fd,
975 queue_size,
976 self._treshold))
977 return
978
979 try:
980
981 r = os.read(self._fd, queue_size)
982 except Exception, msg:
983 raise NotifierError(msg)
984 log.debug('event queue size: %d' % queue_size)
985 rsum = 0
986 while rsum < queue_size:
987 s_size = 16
988
989 s_ = struct.unpack('iIII', r[rsum:rsum+s_size])
990
991 fname_len = s_[3]
992
993 s_ = s_[:-1]
994
995 s_ += struct.unpack('%ds' % fname_len,
996 r[rsum + s_size:rsum + s_size + fname_len])
997 self._eventq.append(_RawEvent(*s_))
998 rsum += s_size + fname_len
999
1001 """
1002 Routine for processing events from queue by calling their
1003 associated proccessing function (instance of ProcessEvent).
1004 It also do internal processings, to keep the system updated.
1005 """
1006 while self._eventq:
1007 raw_event = self._eventq.popleft()
1008 watch_ = self._watch_manager._wmd.get(raw_event.wd)
1009 revent = self._sys_proc_fun(raw_event)
1010 if watch_ and watch_.proc_fun:
1011 watch_.proc_fun(revent)
1012 else:
1013 self._default_proc_fun(revent)
1014 self._sys_proc_fun.cleanup()
1015
1016
1017 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull,
1018 stdout=os.devnull, stderr=os.devnull):
1019 """
1020 pid_file: file to which pid will be written.
1021 force_kill: if True kill the process associated to pid_file.
1022 stdin, stdout, stderr: files associated to common streams.
1023 """
1024 if pid_file is None:
1025 dirname = '/var/run/'
1026 basename = sys.argv[0] or 'pyinotify'
1027 pid_file = os.path.join(dirname, basename + '.pid')
1028
1029 if os.path.exists(pid_file):
1030 fo = file(pid_file, 'rb')
1031 try:
1032 try:
1033 pid = int(fo.read())
1034 except ValueError:
1035 pid = None
1036 if pid is not None:
1037 try:
1038 os.kill(pid, 0)
1039 except OSError, err:
1040 pass
1041 else:
1042 if not force_kill:
1043 s = 'There is already a pid file %s with pid %d'
1044 raise NotifierError(s % (pid_file, pid))
1045 else:
1046 os.kill(pid, 9)
1047 finally:
1048 fo.close()
1049
1050
1051 def fork_daemon():
1052
1053 pid = os.fork()
1054 if (pid == 0):
1055
1056 os.setsid()
1057 pid = os.fork()
1058 if (pid == 0):
1059
1060 os.chdir('/')
1061 os.umask(0)
1062 else:
1063
1064 os._exit(0)
1065 else:
1066
1067 os._exit(0)
1068
1069 fd_inp = os.open(stdin, os.O_RDONLY)
1070 os.dup2(fd_inp, 0)
1071 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT)
1072 os.dup2(fd_out, 1)
1073 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT)
1074 os.dup2(fd_err, 2)
1075
1076
1077 fork_daemon()
1078
1079
1080 fo = file(pid_file, 'wb')
1081 try:
1082 fo.write(str(os.getpid()) + '\n')
1083 finally:
1084 fo.close()
1085
1086 atexit.register(lambda : os.unlink(pid_file))
1087
1088
1090
1091 if self._read_freq > 0:
1092 cur_time = time.time()
1093 sleep_amount = self._read_freq - (cur_time - ref_time)
1094 if sleep_amount > 0:
1095 log.debug('Now sleeping %d seconds' % sleep_amount)
1096 time.sleep(sleep_amount)
1097
1098
1099 - def loop(self, callback=None, daemonize=False, **args):
1100 """
1101 Events are read only once time every min(read_freq, timeout)
1102 seconds at best and only if the size to read is >= treshold.
1103
1104 @param callback: Functor called after each event processing. Expects
1105 to receive notifier object (self) as first parameter.
1106 @type callback: callable
1107 @param daemonize: This thread is daemonized if set to True.
1108 @type daemonize: boolean
1109 """
1110 if daemonize:
1111 self.__daemonize(**args)
1112
1113
1114 while 1:
1115 try:
1116 self.process_events()
1117 if callback is not None:
1118 callback(self)
1119 ref_time = time.time()
1120
1121 if self.check_events():
1122 self._sleep(ref_time)
1123 self.read_events()
1124 except KeyboardInterrupt:
1125
1126 log.debug('Pyinotify stops monitoring.')
1127
1128 self.stop()
1129 break
1130
1132 """
1133 Close the inotify's instance (close its file descriptor).
1134 It destroys all existing watches, pending events,...
1135 """
1136 self._pollobj.unregister(self._fd)
1137 os.close(self._fd)
1138
1141 """
1142 This notifier inherits from threading.Thread for instantiating a separate
1143 thread, and also inherits from Notifier, because it is a threaded notifier.
1144
1145 Note that everything possible with this class is also possible through
1146 Notifier. Moreover Notifier is _better_ under many aspects: not threaded,
1147 can be easily daemonized.
1148 """
1149 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
1150 read_freq=0, treshold=0, timeout=None):
1151 """
1152 Initialization, initialize base classes. read_freq, treshold and
1153 timeout parameters are used when looping.
1154
1155 @param watch_manager: Watch Manager.
1156 @type watch_manager: WatchManager instance
1157 @param default_proc_fun: Default processing method.
1158 @type default_proc_fun: instance of ProcessEvent
1159 @param read_freq: if read_freq == 0, events are read asap,
1160 if read_freq is > 0, this thread sleeps
1161 max(0, read_freq - timeout) seconds.
1162 @type read_freq: int
1163 @param treshold: File descriptor will be read only if its size to
1164 read is >= treshold. If != 0, you likely want to
1165 use it in combination with read_freq because
1166 without that you keep looping without really reading
1167 anything and that until the amount to read
1168 is >= treshold. At least with read_freq you may sleep.
1169 @type treshold: int
1170 @param timeout:
1171 see http://docs.python.org/lib/poll-objects.html#poll-objects
1172 Read the corresponding comment in the source code before changing
1173 it.
1174 @type timeout: int
1175 """
1176
1177 threading.Thread.__init__(self)
1178
1179 self._stop_event = threading.Event()
1180
1181 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1182 treshold, timeout)
1183
1184 self._pipe = os.pipe()
1185 self._pollobj.register(self._pipe[0], select.POLLIN)
1186
1188 """
1189 Stop the notifier's loop. Stop notification. Join the thread.
1190 """
1191 self._stop_event.set()
1192 os.write(self._pipe[1], 'stop')
1193 threading.Thread.join(self)
1194 Notifier.stop(self)
1195 self._pollobj.unregister(self._pipe[0])
1196 os.close(self._pipe[0])
1197 os.close(self._pipe[1])
1198
1200 """
1201 Thread's main loop. Don't meant to be called by user directly.
1202 Call start() instead.
1203
1204 Events are read only once time every min(read_freq, timeout)
1205 seconds at best and only if the size of events to read is >= treshold.
1206 """
1207
1208
1209
1210
1211 while not self._stop_event.isSet():
1212 self.process_events()
1213 ref_time = time.time()
1214 if self.check_events():
1215 self._sleep(ref_time)
1216 self.read_events()
1217
1219 """
1220 Start the thread's loop: read and process events until the method
1221 stop() is called.
1222 Never call this method directly, instead call the start() method
1223 inherited from threading.Thread, which then will call run().
1224 """
1225 self.loop()
1226
1229 """
1230 Represent a watch, i.e. a file or directory being watched.
1231
1232 """
1234 """
1235 Initializations.
1236
1237 @param wd: Watch descriptor.
1238 @type wd: int
1239 @param path: Path of the file or directory being watched.
1240 @type path: str
1241 @param mask: Mask.
1242 @type mask: int
1243 @param proc_fun: Processing callable object.
1244 @type proc_fun:
1245 @param auto_add: Automatically add watches on new directories.
1246 @type auto_add: bool
1247 """
1248 for k, v in keys.iteritems():
1249 setattr(self, k, v)
1250 self.dir = os.path.isdir(self.path)
1251
1267
1270 """
1271 ExcludeFilter is an exclusion filter.
1272 """
1273
1275 """
1276 @param arg_lst: is either a list or dict of patterns:
1277 [pattern1, ..., patternn]
1278 {'filename1': (list1, listn), ...} where list1 is
1279 a list of patterns
1280 @type arg_lst: list or dict
1281 """
1282 if isinstance(arg_lst, dict):
1283 lst = self._load_patterns(arg_lst)
1284 elif isinstance(arg_lst, list):
1285 lst = arg_lst
1286 else:
1287 raise TypeError
1288
1289 self._lregex = []
1290 for regex in lst:
1291 self._lregex.append(re.compile(regex, re.UNICODE))
1292
1294 lst = []
1295 for path, varnames in dct.iteritems():
1296 loc = {}
1297 execfile(path, {}, loc)
1298 for varname in varnames:
1299 lst.extend(loc.get(varname, []))
1300 return lst
1301
1302 - def _match(self, regex, path):
1303 return regex.match(path) is not None
1304
1306 """
1307 @param path: path to match against regexps.
1308 @type path: str
1309 @return: return True is path has been matched and should
1310 be excluded, False otherwise.
1311 @rtype: bool
1312 """
1313 for regex in self._lregex:
1314 if self._match(regex, path):
1315 return True
1316 return False
1317
1320 """
1321 WatchManager Exception. Raised on error encountered on watches
1322 operations.
1323
1324 """
1326 """
1327 @param msg: Exception string's description.
1328 @type msg: string
1329 @param wmd: Results of previous operations made by the same function
1330 on previous wd or paths. It also contains the item which
1331 raised this exception.
1332 @type wmd: dict
1333 """
1334 self.wmd = wmd
1335 Exception.__init__(self, msg)
1336
1339 """
1340 Provide operations for watching files and directories. Integrated
1341 dictionary is used to reference watched items.
1342 """
1343 - def __init__(self, exclude_filter=lambda path: False):
1344 """
1345 Initialization: init inotify, init watch manager dictionary.
1346 Raise OSError if initialization fails.
1347
1348 @param exclude_filter: boolean function, returns True if current
1349 path must be excluded from being watched.
1350 Convenient for providing a common exclusion
1351 filter for every call to add_watch.
1352 @type exclude_filter: bool
1353 """
1354 self._exclude_filter = exclude_filter
1355 self._wmd = {}
1356 self._fd = LIBC.inotify_init()
1357 if self._fd < 0:
1358 raise OSError()
1359
1360 - def __add_watch(self, path, mask, proc_fun, auto_add):
1361 """
1362 Add a watch on path, build a Watch object and insert it in the
1363 watch manager dictionary. Return the wd value.
1364 """
1365 wd_ = LIBC.inotify_add_watch(self._fd,
1366 ctypes.create_string_buffer(path),
1367 mask)
1368 if wd_ < 0:
1369 return wd_
1370 watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask,
1371 proc_fun=proc_fun, auto_add=auto_add)
1372 self._wmd[wd_] = watch_
1373 log.debug('New %s' % watch_)
1374 return wd_
1375
1376 - def __glob(self, path, do_glob):
1377 if do_glob:
1378 return iglob(path)
1379 else:
1380 return [path]
1381
1382 - def add_watch(self, path, mask, proc_fun=None, rec=False,
1383 auto_add=False, do_glob=False, quiet=True,
1384 exclude_filter=None):
1385 """
1386 Add watch(s) on given path(s) with the specified mask and
1387 optionnally with a processing function and recursive flag.
1388
1389 @param path: Path to watch, the path can either be a file or a
1390 directory. Also accepts a sequence (list) of paths.
1391 @type path: string or list of string
1392 @param mask: Bitmask of events.
1393 @type mask: int
1394 @param proc_fun: Processing object.
1395 @type proc_fun: function or ProcessEvent instance or instance of
1396 one of its subclasses or callable object.
1397 @param rec: Recursively add watches from path on all its
1398 subdirectories, set to False by default (doesn't
1399 follows symlinks).
1400 @type rec: bool
1401 @param auto_add: Automatically add watches on newly created
1402 directories in the watch's path.
1403 @type auto_add: bool
1404 @param do_glob: Do globbing on pathname.
1405 @type do_glob: bool
1406 @param quiet: if True raise an WatchManagerError exception on
1407 error. See example not_quiet.py
1408 @type quiet: bool
1409 @param exclude_filter: boolean function, returns True if current
1410 path must be excluded from being watched.
1411 Has precedence on exclude_filter defined
1412 into __init__.
1413 @type exclude_filter: bool
1414 @return: dict of paths associated to watch descriptors. A wd value
1415 is positive if the watch has been sucessfully added,
1416 otherwise the value is negative. If the path is invalid
1417 it will be not included into this dict.
1418 @rtype: dict of {str: int}
1419 """
1420 ret_ = {}
1421
1422 if exclude_filter is None:
1423 exclude_filter = self._exclude_filter
1424
1425
1426 for npath in self.__format_param(path):
1427
1428 for apath in self.__glob(npath, do_glob):
1429
1430 for rpath in self.__walk_rec(apath, rec):
1431 if not exclude_filter(rpath):
1432 wd = ret_[rpath] = self.__add_watch(rpath, mask,
1433 proc_fun,
1434 auto_add)
1435 if wd < 0:
1436 err = 'add_watch: cannot watch %s (WD=%d)'
1437 err = err % (rpath, wd)
1438 if quiet:
1439 log.error(err)
1440 else:
1441 raise WatchManagerError(err, ret_)
1442 else:
1443
1444
1445 ret_[rpath] = -2
1446 return ret_
1447
1449 """
1450 Get every wd from self._wmd if its path is under the path of
1451 one (at least) of those in lpath. Doesn't follow symlinks.
1452
1453 @param lpath: list of watch descriptor
1454 @type lpath: list of int
1455 @return: list of watch descriptor
1456 @rtype: list of int
1457 """
1458 for d in lpath:
1459 root = self.get_path(d)
1460 if root:
1461
1462 yield d
1463 else:
1464
1465 continue
1466
1467
1468 if not os.path.isdir(root):
1469 continue
1470
1471
1472 root = os.path.normpath(root)
1473
1474 lend = len(root)
1475 for iwd in self._wmd.items():
1476 cur = iwd[1].path
1477 pref = os.path.commonprefix([root, cur])
1478 if root == os.sep or (len(pref) == lend and \
1479 len(cur) > lend and \
1480 cur[lend] == os.sep):
1481 yield iwd[1].wd
1482
1483 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
1484 auto_add=False, quiet=True):
1485 """
1486 Update existing watch(s). Both the mask and the processing
1487 object can be modified.
1488
1489 @param wd: Watch Descriptor to update. Also accepts a list of
1490 watch descriptors.
1491 @type wd: int or list of int
1492 @param mask: Optional new bitmask of events.
1493 @type mask: int
1494 @param proc_fun: Optional new processing function.
1495 @type proc_fun: function or ProcessEvent instance or instance of
1496 one of its subclasses or callable object.
1497 @param rec: Recursively update watches on every already watched
1498 subdirectories and subfiles.
1499 @type rec: bool
1500 @param auto_add: Automatically add watches on newly created
1501 directories in the watch's path.
1502 @type auto_add: bool
1503 @param quiet: if True raise an WatchManagerError exception on
1504 error. See example not_quiet.py
1505 @type quiet: bool
1506 @return: dict of watch descriptors associated to booleans values.
1507 True if the corresponding wd has been successfully
1508 updated, False otherwise.
1509 @rtype: dict of int: bool
1510 """
1511 lwd = self.__format_param(wd)
1512 if rec:
1513 lwd = self.__get_sub_rec(lwd)
1514
1515 ret_ = {}
1516 for awd in lwd:
1517 apath = self.get_path(awd)
1518 if not apath or awd < 0:
1519 err = 'update_watch: invalid WD=%d' % awd
1520 if quiet:
1521 log.error(err)
1522 continue
1523 raise WatchManagerError(err, ret_)
1524
1525 if mask:
1526 addw = LIBC.inotify_add_watch
1527 wd_ = addw(self._fd,
1528 ctypes.create_string_buffer(apath),
1529 mask)
1530 if wd_ < 0:
1531 ret_[awd] = False
1532 err = 'update_watch: cannot update WD=%d (%s)' % (wd_,
1533 apath)
1534 if quiet:
1535 log.error(err)
1536 continue
1537 raise WatchManagerError(err, ret_)
1538
1539 assert(awd == wd_)
1540
1541 if proc_fun or auto_add:
1542 watch_ = self._wmd[awd]
1543
1544 if proc_fun:
1545 watch_.proc_fun = proc_fun
1546
1547 if auto_add:
1548 watch_.proc_fun = auto_add
1549
1550 ret_[awd] = True
1551 log.debug('Updated watch - %s' % self._wmd[awd])
1552 return ret_
1553
1566
1568 """
1569 Returns the watch descriptor associated to path. This method
1570 has an prohibitive cost, always prefer to keep the WD.
1571 If path is unknown None is returned.
1572
1573 @param path: path.
1574 @type path: str
1575 @return: WD or None.
1576 @rtype: int or None
1577 """
1578 path = os.path.normpath(path)
1579 for iwd in self._wmd.iteritems():
1580 if iwd[1].path == path:
1581 return iwd[0]
1582 log.debug('get_wd: unknown path %s' % path)
1583
1585 """
1586 Returns the path associated to WD, if WD is unknown
1587 None is returned.
1588
1589 @param wd: watch descriptor.
1590 @type wd: int
1591 @return: path or None.
1592 @rtype: string or None
1593 """
1594 watch_ = self._wmd.get(wd)
1595 if watch_:
1596 return watch_.path
1597 log.debug('get_path: unknown WD %d' % wd)
1598
1600 """
1601 Yields each subdirectories of top, doesn't follow symlinks.
1602 If rec is false, only yield top.
1603
1604 @param top: root directory.
1605 @type top: string
1606 @param rec: recursive flag.
1607 @type rec: bool
1608 @return: path of one subdirectory.
1609 @rtype: string
1610 """
1611 if not rec or os.path.islink(top) or not os.path.isdir(top):
1612 yield top
1613 else:
1614 for root, dirs, files in os.walk(top):
1615 yield root
1616
1617 - def rm_watch(self, wd, rec=False, quiet=True):
1618 """
1619 Removes watch(s).
1620
1621 @param wd: Watch Descriptor of the file or directory to unwatch.
1622 Also accepts a list of WDs.
1623 @type wd: int or list of int.
1624 @param rec: Recursively removes watches on every already watched
1625 subdirectories and subfiles.
1626 @type rec: bool
1627 @param quiet: if True raise an WatchManagerError exception on
1628 error. See example not_quiet.py
1629 @type quiet: bool
1630 @return: dict of watch descriptors associated to booleans values.
1631 True if the corresponding wd has been successfully
1632 removed, False otherwise.
1633 @rtype: dict of int: bool
1634 """
1635 lwd = self.__format_param(wd)
1636 if rec:
1637 lwd = self.__get_sub_rec(lwd)
1638
1639 ret_ = {}
1640 for awd in lwd:
1641
1642 wd_ = LIBC.inotify_rm_watch(self._fd, awd)
1643 if wd_ < 0:
1644 ret_[awd] = False
1645 err = 'rm_watch: cannot remove WD=%d' % awd
1646 if quiet:
1647 log.error(err)
1648 continue
1649 raise WatchManagerError(err, ret_)
1650
1651 ret_[awd] = True
1652 log.debug('watch WD=%d (%s) removed' % (awd, self.get_path(awd)))
1653 return ret_
1654
1655
1657 """
1658 Watch a transient file, which will be created and deleted frequently
1659 over time (e.g. pid file).
1660
1661 @attention: Under the call to this function it will be impossible
1662 to correctly watch the events triggered into the same
1663 base directory than the directory where is located this watched
1664 transient file. For instance it would actually be wrong to make these
1665 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...)
1666 and wm.add_watch('/var/run/', ...)
1667
1668 @param filename: Filename.
1669 @type filename: string
1670 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE.
1671 @type mask: int
1672 @param proc_class: ProcessEvent (or of one of its subclass), beware of
1673 accepting a ProcessEvent's instance as argument into
1674 __init__, see transient_file.py example for more
1675 details.
1676 @type proc_class: ProcessEvent's instance or of one of its subclasses.
1677 @return: See add_watch().
1678 @rtype: See add_watch().
1679 """
1680 dirname = os.path.dirname(filename)
1681 if dirname == '':
1682 return {}
1683 basename = os.path.basename(filename)
1684
1685 mask |= IN_CREATE | IN_DELETE
1686
1687 def cmp_name(event):
1688 return basename == event.name
1689 return self.add_watch(dirname, mask,
1690 proc_fun=proc_class(ChainIfTrue(func=cmp_name)),
1691 rec=False,
1692 auto_add=False, do_glob=False)
1693
1696 normal = "\033[0m"
1697 black = "\033[30m"
1698 red = "\033[31m"
1699 green = "\033[32m"
1700 yellow = "\033[33m"
1701 blue = "\033[34m"
1702 purple = "\033[35m"
1703 cyan = "\033[36m"
1704 bold = "\033[1m"
1705 uline = "\033[4m"
1706 blink = "\033[5m"
1707 invert = "\033[7m"
1708
1709 @staticmethod
1712
1713 @staticmethod
1718
1719 @staticmethod
1722
1723 @staticmethod
1726
1727 @staticmethod
1729 if not isinstance(s, str):
1730 s = str(s)
1731 try:
1732 color_attr = getattr(Color, color)
1733 except AttributeError:
1734 return s
1735 return color_attr + s + Color.normal
1736
1739
1740
1741
1742
1743
1744 from optparse import OptionParser
1745
1746 usage = "usage: %prog [options] [path1] [path2] [pathn]"
1747
1748 parser = OptionParser(usage=usage)
1749 parser.add_option("-v", "--verbose", action="store_true",
1750 dest="verbose", help="Verbose mode")
1751 parser.add_option("-r", "--recursive", action="store_true",
1752 dest="recursive",
1753 help="Add watches recursively on paths")
1754 parser.add_option("-a", "--auto_add", action="store_true",
1755 dest="auto_add",
1756 help="Automatically add watches on new directories")
1757 parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
1758 dest="events_list",
1759 help=("A comma-separated list of events to watch for - "
1760 "see the documentation for valid options (defaults"
1761 " to everything)"))
1762 parser.add_option("-s", "--stats", action="store_true",
1763 dest="stats",
1764 help="Display statistics")
1765
1766 (options, args) = parser.parse_args()
1767
1768 if options.verbose:
1769 log.setLevel(10)
1770
1771 if len(args) < 1:
1772 path = '/tmp'
1773 else:
1774 path = args
1775
1776
1777 wm = WatchManager()
1778
1779 if options.stats:
1780 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5)
1781 else:
1782 notifier = Notifier(wm)
1783
1784
1785 mask = 0
1786 if options.events_list:
1787 events_list = options.events_list.split(',')
1788 for ev in events_list:
1789 evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
1790 if evcode:
1791 mask |= evcode
1792 else:
1793 parser.error("The event '%s' specified with option -e"
1794 " is not valid" % ev)
1795 else:
1796 mask = ALL_EVENTS
1797
1798
1799 cb_fun = None
1800 if options.stats:
1801 def cb(s):
1802 print('%s\n%s\n' % (repr(s.proc_fun()),
1803 s.proc_fun()))
1804 cb_fun = cb
1805
1806 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path)
1807
1808 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add)
1809
1810 notifier.loop(callback=cb_fun)
1811
1812
1813 if __name__ == '__main__':
1814 command_line()
1815