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