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 (int(LIBC_VERSION.split('.')[0]) < 2 or
104 (int(LIBC_VERSION.split('.')[0]) == 2 and
105 int(LIBC_VERSION.split('.')[1]) < 4)):
106 raise UnsupportedLibcVersionError(LIBC_VERSION)
107
108
109
110 log = logging.getLogger("pyinotify")
111 console_handler = logging.StreamHandler()
112 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
113 log.addHandler(console_handler)
114 log.setLevel(20)
115
116
117
118 try:
119 if False:
120 import psyco
121 psyco.full()
122 except ImportError:
123
124 pass
131 """
132 Access (read, write) inotify's variables through sysctl.
133
134 Examples:
135 - Read variable: myvar = max_queued_events.value
136 - Update variable: max_queued_events.value = 42
137 """
138
139 inotify_attrs = {'max_user_instances': 1,
140 'max_user_watches': 2,
141 'max_queued_events': 3}
142
147
149 """
150 @return: stored value.
151 @rtype: int
152 """
153 oldv = ctypes.c_int(0)
154 size = ctypes.c_int(ctypes.sizeof(oldv))
155 LIBC.sysctl(self._attr, 3,
156 ctypes.c_voidp(ctypes.addressof(oldv)),
157 ctypes.addressof(size),
158 None, 0)
159 return oldv.value
160
162 """
163 @param nval: set to nval.
164 @type nval: int
165 """
166 oldv = ctypes.c_int(0)
167 sizeo = ctypes.c_int(ctypes.sizeof(oldv))
168 newv = ctypes.c_int(nval)
169 sizen = ctypes.c_int(ctypes.sizeof(newv))
170 LIBC.sysctl(self._attr, 3,
171 ctypes.c_voidp(ctypes.addressof(oldv)),
172 ctypes.addressof(sizeo),
173 ctypes.c_voidp(ctypes.addressof(newv)),
174 ctypes.addressof(sizen))
175
176 value = property(get_val, set_val)
177
179 return '<%s=%d>' % (self._attrname, self.get_val())
180
181
182
183
184
185
186
187 for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
188 globals()[attrname] = SysCtlINotify(attrname)
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 -def iglob(pathname):
212 if not has_magic(pathname):
213 if hasattr(os.path, 'lexists'):
214 if os.path.lexists(pathname):
215 yield pathname
216 else:
217 if os.path.islink(pathname) or os.path.exists(pathname):
218 yield pathname
219 return
220 dirname, basename = os.path.split(pathname)
221
222 if not dirname:
223 return
224
225 if has_magic(dirname):
226 dirs = iglob(dirname)
227 else:
228 dirs = [dirname]
229 if has_magic(basename):
230 glob_in_dir = glob1
231 else:
232 glob_in_dir = glob0
233 for dirname in dirs:
234 for name in glob_in_dir(dirname, basename):
235 yield os.path.join(dirname, name)
236
237 -def glob1(dirname, pattern):
238 if not dirname:
239 dirname = os.curdir
240 try:
241 names = os.listdir(dirname)
242 except os.error:
243 return []
244 return fnmatch.filter(names, pattern)
245
246 -def glob0(dirname, basename):
247 if basename == '' and os.path.isdir(dirname):
248
249
250 return [basename]
251 if hasattr(os.path, 'lexists'):
252 if os.path.lexists(os.path.join(dirname, basename)):
253 return [basename]
254 else:
255 if (os.path.islink(os.path.join(dirname, basename)) or
256 os.path.exists(os.path.join(dirname, basename))):
257 return [basename]
258 return []
259
260 magic_check = re.compile('[*?[]')
264
271 """
272 Set of codes corresponding to each kind of events.
273 Some of these flags are used to communicate with inotify, whereas
274 the others are sent to userspace by inotify notifying some events.
275
276 @cvar IN_ACCESS: File was accessed.
277 @type IN_ACCESS: int
278 @cvar IN_MODIFY: File was modified.
279 @type IN_MODIFY: int
280 @cvar IN_ATTRIB: Metadata changed.
281 @type IN_ATTRIB: int
282 @cvar IN_CLOSE_WRITE: Writtable file was closed.
283 @type IN_CLOSE_WRITE: int
284 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
285 @type IN_CLOSE_NOWRITE: int
286 @cvar IN_OPEN: File was opened.
287 @type IN_OPEN: int
288 @cvar IN_MOVED_FROM: File was moved from X.
289 @type IN_MOVED_FROM: int
290 @cvar IN_MOVED_TO: File was moved to Y.
291 @type IN_MOVED_TO: int
292 @cvar IN_CREATE: Subfile was created.
293 @type IN_CREATE: int
294 @cvar IN_DELETE: Subfile was deleted.
295 @type IN_DELETE: int
296 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
297 @type IN_DELETE_SELF: int
298 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
299 @type IN_MOVE_SELF: int
300 @cvar IN_UNMOUNT: Backing fs was unmounted.
301 @type IN_UNMOUNT: int
302 @cvar IN_Q_OVERFLOW: Event queued overflowed.
303 @type IN_Q_OVERFLOW: int
304 @cvar IN_IGNORED: File was ignored.
305 @type IN_IGNORED: int
306 @cvar IN_ONLYDIR: only watch the path if it is a directory (new
307 in kernel 2.6.15).
308 @type IN_ONLYDIR: int
309 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
310 IN_ONLYDIR we can make sure that we don't watch
311 the target of symlinks.
312 @type IN_DONT_FOLLOW: int
313 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
314 in kernel 2.6.14).
315 @type IN_MASK_ADD: int
316 @cvar IN_ISDIR: Event occurred against dir.
317 @type IN_ISDIR: int
318 @cvar IN_ONESHOT: Only send event once.
319 @type IN_ONESHOT: int
320 @cvar ALL_EVENTS: Alias for considering all of the events.
321 @type ALL_EVENTS: int
322 """
323
324
325
326
327 FLAG_COLLECTIONS = {'OP_FLAGS': {
328 'IN_ACCESS' : 0x00000001,
329 'IN_MODIFY' : 0x00000002,
330 'IN_ATTRIB' : 0x00000004,
331 'IN_CLOSE_WRITE' : 0x00000008,
332 'IN_CLOSE_NOWRITE' : 0x00000010,
333 'IN_OPEN' : 0x00000020,
334 'IN_MOVED_FROM' : 0x00000040,
335 'IN_MOVED_TO' : 0x00000080,
336 'IN_CREATE' : 0x00000100,
337 'IN_DELETE' : 0x00000200,
338 'IN_DELETE_SELF' : 0x00000400,
339
340 'IN_MOVE_SELF' : 0x00000800,
341 },
342 'EVENT_FLAGS': {
343 'IN_UNMOUNT' : 0x00002000,
344 'IN_Q_OVERFLOW' : 0x00004000,
345 'IN_IGNORED' : 0x00008000,
346 },
347 'SPECIAL_FLAGS': {
348 'IN_ONLYDIR' : 0x01000000,
349
350 'IN_DONT_FOLLOW' : 0x02000000,
351 'IN_MASK_ADD' : 0x20000000,
352
353 'IN_ISDIR' : 0x40000000,
354 'IN_ONESHOT' : 0x80000000,
355 },
356 }
357
359 """
360 Return the event name associated to mask. IN_ISDIR is appended when
361 appropriate. Note: only one event is returned, because only one is
362 raised once at a time.
363
364 @param mask: mask.
365 @type mask: int
366 @return: event name.
367 @rtype: str
368 """
369 ms = mask
370 name = '%s'
371 if mask & IN_ISDIR:
372 ms = mask - IN_ISDIR
373 name = '%s|IN_ISDIR'
374 return name % EventsCodes.ALL_VALUES[ms]
375
376 maskname = staticmethod(maskname)
377
378
379
380 EventsCodes.ALL_FLAGS = {}
381 EventsCodes.ALL_VALUES = {}
382 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems():
383
384
385 setattr(EventsCodes, flagc, valc)
386
387
388 EventsCodes.ALL_FLAGS.update(valc)
389
390
391
392 for name, val in valc.iteritems():
393 globals()[name] = val
394 EventsCodes.ALL_VALUES[val] = name
395
396
397
398 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues())
399 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
400 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
404 """
405 Event structure, represent events raised by the system. This
406 is the base class and should be subclassed.
407
408 """
410 """
411 Attach attributes (contained in dict_) to self.
412 """
413 for tpl in dict_.iteritems():
414 setattr(self, *tpl)
415
438
441 """
442 Raw event, it contains only the informations provided by the system.
443 It doesn't infer anything.
444 """
445 - def __init__(self, wd, mask, cookie, name):
446 """
447 @param wd: Watch Descriptor.
448 @type wd: int
449 @param mask: Bitmask of events.
450 @type mask: int
451 @param cookie: Cookie.
452 @type cookie: int
453 @param name: Basename of the file or directory against which the
454 event was raised, in case where the watched directory
455 is the parent directory. None if the event was raised
456 on the watched item itself.
457 @type name: string or None
458 """
459
460 super(_RawEvent, self).__init__({'wd': wd,
461 'mask': mask,
462 'cookie': cookie,
463 'name': name.rstrip('\0')})
464 log.debug(repr(self))
465
468 """
469 This class contains all the useful informations about the observed
470 event. However, the incorporation of each field is not guaranteed and
471 depends on the type of event. In effect, some fields are irrelevant
472 for some kind of event (for example 'cookie' is meaningless for
473 IN_CREATE whereas it is useful for IN_MOVE_TO).
474
475 The possible fields are:
476 - wd (int): Watch Descriptor.
477 - mask (int): Mask.
478 - maskname (str): Readable event name.
479 - path (str): path of the file or directory being watched.
480 - name (str): Basename of the file or directory against which the
481 event was raised, in case where the watched directory
482 is the parent directory. None if the event was raised
483 on the watched item itself. This field is always provided
484 even if the string is ''.
485 - pathname (str): absolute path of: path + name
486 - cookie (int): Cookie.
487 - dir (bool): is the event raised against directory.
488
489 """
491 """
492 Concretely, this is the raw event plus inferred infos.
493 """
494 _Event.__init__(self, raw)
495 self.maskname = EventsCodes.maskname(self.mask)
496 try:
497 if self.name:
498 self.pathname = os.path.abspath(os.path.join(self.path,
499 self.name))
500 else:
501 self.pathname = os.path.abspath(self.path)
502 except AttributeError:
503 pass
504
507 """
508 ProcessEventError Exception. Raised on ProcessEvent error.
509 """
511 """
512 @param err: Exception error description.
513 @type err: string
514 """
515 PyinotifyError.__init__(self, err)
516
519 """
520 Abstract processing event class.
521 """
523 """
524 To behave like a functor the object must be callable.
525 This method is a dispatch method. Lookup order:
526 1. process_MASKNAME method
527 2. process_FAMILY_NAME method
528 3. otherwise call process_default
529
530 @param event: Event to be processed.
531 @type event: Event object
532 @return: By convention when used from the ProcessEvent class:
533 - Returning False or None (default value) means keep on
534 executing next chained functors (see chain.py example).
535 - Returning True instead means do not execute next
536 processing functions.
537 @rtype: bool
538 @raise ProcessEventError: Event object undispatchable,
539 unknown event.
540 """
541 stripped_mask = event.mask - (event.mask & IN_ISDIR)
542 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
543 if maskname is None:
544 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
545
546
547 meth = getattr(self, 'process_' + maskname, None)
548 if meth is not None:
549 return meth(event)
550
551 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
552 if meth is not None:
553 return meth(event)
554
555 return self.process_default(event)
556
558 return '<%s>' % self.__class__.__name__
559
562 """
563 There is three kind of processing according to each event:
564
565 1. special handling (deletion from internal container, bug, ...).
566 2. default treatment: which is applied to most of events.
567 4. IN_ISDIR is never sent alone, he is piggybacked with a standart
568 event, he is not processed as the others events, instead, its
569 value is captured and appropriately aggregated to dst event.
570 """
572 """
573
574 @param wm: Watch Manager.
575 @type wm: WatchManager instance
576 @param notifier: notifier.
577 @type notifier: Instance of Notifier.
578 """
579 self._watch_manager = wm
580 self._notifier = notifier
581 self._mv_cookie = {}
582 self._mv = {}
583
585 """
586 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
587 and self._mv.
588 """
589 date_cur_ = datetime.now()
590 for seq in [self._mv_cookie, self._mv]:
591 for k in seq.keys():
592 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
593 log.debug('cleanup: deleting entry %s', seq[k][0])
594 del seq[k]
595
597 """
598 If the event concerns a directory and the auto_add flag of the
599 targetted watch is set to True, a new watch is added on this
600 new directory, with the same attributes's values than those of
601 this watch.
602 """
603 if raw_event.mask & IN_ISDIR:
604 watch_ = self._watch_manager._wmd.get(raw_event.wd)
605 if watch_.auto_add:
606 addw = self._watch_manager.add_watch
607 newwd = addw(os.path.join(watch_.path, raw_event.name),
608 watch_.mask, proc_fun=watch_.proc_fun,
609 rec=False, auto_add=watch_.auto_add)
610
611
612
613
614
615 base = os.path.join(watch_.path, raw_event.name)
616 if newwd[base] > 0:
617 for name in os.listdir(base):
618 inner = os.path.join(base, name)
619 if (os.path.isdir(inner) and
620 self._watch_manager.get_wd(inner) is None):
621
622
623 rawevent = _RawEvent(newwd[base],
624 IN_CREATE | IN_ISDIR,
625 0, name)
626 self._notifier._eventq.append(rawevent)
627 return self.process_default(raw_event)
628
630 """
631 Map the cookie with the source path (+ date for cleaning).
632 """
633 watch_ = self._watch_manager._wmd.get(raw_event.wd)
634 path_ = watch_.path
635 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
636 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
637 return self.process_default(raw_event, {'cookie': raw_event.cookie})
638
640 """
641 Map the source path with the destination path (+ date for
642 cleaning).
643 """
644 watch_ = self._watch_manager._wmd.get(raw_event.wd)
645 path_ = watch_.path
646 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
647 mv_ = self._mv_cookie.get(raw_event.cookie)
648 if mv_:
649 self._mv[mv_[0]] = (dst_path, datetime.now())
650 return self.process_default(raw_event, {'cookie': raw_event.cookie})
651
653 """
654 STATUS: the following bug has been fixed in the recent kernels (fixme:
655 which version ?). Now it raises IN_DELETE_SELF instead.
656
657 Old kernels are bugged, this event is raised when the watched item
658 was moved, so we must update its path, but under some circumstances it
659 can be impossible: if its parent directory and its destination
660 directory aren't watched. The kernel (see include/linux/fsnotify.h)
661 doesn't bring us enough informations like the destination path of
662 moved items.
663 """
664 watch_ = self._watch_manager._wmd.get(raw_event.wd)
665 src_path = watch_.path
666 mv_ = self._mv.get(src_path)
667 if mv_:
668 watch_.path = mv_[0]
669 else:
670 log.error("The path %s of this watch %s is not reliable anymore",
671 watch_.path, watch_)
672 if not watch_.path.endswith('-wrong-path'):
673 watch_.path += '-wrong-path'
674
675 return self.process_default(raw_event)
676
678 """
679 Only signal overflow, most of the common flags are irrelevant
680 for this event (path, wd, name).
681 """
682 return Event({'mask': raw_event.mask})
683
685 """
686 The watch descriptor raised by this event is now ignored (forever),
687 it can be safely deleted from watch manager dictionary.
688 After this event we can be sure that neither the event queue
689 neither the system will raise an event associated to this wd.
690 """
691 event_ = self.process_default(raw_event)
692 try:
693 del self._watch_manager._wmd[raw_event.wd]
694 except KeyError, err:
695 log.error(str(err))
696 return event_
697
699 """
700 Common handling for the following events:
701
702 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
703 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
704 """
705 ret = None
706 watch_ = self._watch_manager._wmd.get(raw_event.wd)
707 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
708
709 dir_ = watch_.dir
710 else:
711 dir_ = bool(raw_event.mask & IN_ISDIR)
712 dict_ = {'wd': raw_event.wd,
713 'mask': raw_event.mask,
714 'path': watch_.path,
715 'name': raw_event.name,
716 'dir': dir_}
717 dict_.update(to_append)
718 return Event(dict_)
719
722 """
723 Process events objects, can be specialized via subclassing, thus its
724 behavior can be overriden:
725
726 Note: you should not override __init__ in your subclass instead define
727 a my_init() method, this method will be called from the constructor of
728 this class with optional parameters.
729
730 1. Provide methods, e.g. process_IN_DELETE for processing a given kind
731 of event (eg. IN_DELETE in this case).
732 2. Or/and provide methods for processing events by 'family', e.g.
733 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
734 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
735 process_IN_CLOSE_NOWRITE aren't defined).
736 3. Or/and override process_default for processing the remaining kind of
737 events.
738 """
739 pevent = None
740
741 - def __init__(self, pevent=None, **kargs):
742 """
743 Enable chaining of ProcessEvent instances.
744
745 @param pevent: optional callable object, will be called on event
746 processing (before self).
747 @type pevent: callable
748 @param kargs: optional arguments delagated to template method my_init
749 @type kargs: dict
750 """
751 self.pevent = pevent
752 self.my_init(**kargs)
753
755 """
756 Override this method when subclassing if you want to achieve
757 custom initialization of your subclass' instance. You MUST pass
758 keyword arguments. This method does nothing by default.
759
760 @param kargs: optional arguments delagated to template method my_init
761 @type kargs: dict
762 """
763 pass
764
766 stop_chaining = False
767 if self.pevent is not None:
768
769
770
771
772
773 stop_chaining = self.pevent(event)
774 if not stop_chaining:
775 return _ProcessEvent.__call__(self, event)
776
779
781 """
782 Default default processing event method. Print event
783 on standart output.
784
785 @param event: Event to be processed.
786 @type event: Event instance
787 """
788 print(repr(event))
789
792 """
793 Makes conditional chaining depending on the result of the nested
794 processing instance.
795 """
798
800 return not self._func(event)
801
802
803 -class Stats(ProcessEvent):
805 self._start_time = time.time()
806 self._stats = {}
807 self._stats_lock = threading.Lock()
808
810 self._stats_lock.acquire()
811 try:
812 events = event.maskname.split('|')
813 for event_name in events:
814 count = self._stats.get(event_name, 0)
815 self._stats[event_name] = count + 1
816 finally:
817 self._stats_lock.release()
818
820 self._stats_lock.acquire()
821 try:
822 return self._stats.copy()
823 finally:
824 self._stats_lock.release()
825
827 stats = self._stats_copy()
828
829 t = int(time.time() - self._start_time)
830 if t < 60:
831 ts = str(t) + 'sec'
832 elif 60 <= t < 3600:
833 ts = '%dmn%dsec' % (t / 60, t % 60)
834 elif 3600 <= t < 86400:
835 ts = '%dh%dmn' % (t / 3600, (t % 3600) / 60)
836 elif t >= 86400:
837 ts = '%dd%dh' % (t / 86400, (t % 86400) / 3600)
838 stats['ElapsedTime'] = ts
839
840 l = []
841 for ev, value in sorted(stats.items(), key=lambda x: x[0]):
842 l.append(' %s=%s' % (Color.FieldName(ev),
843 Color.FieldValue(value)))
844 s = '<%s%s >' % (Color.ClassName(self.__class__.__name__),
845 ''.join(l))
846 return s
847
848 - def dump(self, filename):
849 fo = file(filename, 'wb')
850 try:
851 fo.write(str(self))
852 finally:
853 fo.close()
854
856 stats = self._stats_copy()
857 if not stats:
858 return ''
859
860 m = max(stats.values())
861 unity = int(round(float(m) / scale)) or 1
862 fmt = '%%-26s%%-%ds%%s' % (len(Color.FieldValue('@' * scale))
863 + 1)
864 def func(x):
865 return fmt % (Color.FieldName(x[0]),
866 Color.FieldValue('@' * (x[1] / unity)),
867 Color.Simple('%d' % x[1], 'yellow'))
868 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
869 return s
870
873 """
874 Notifier Exception. Raised on Notifier error.
875
876 """
878 """
879 @param err: Exception string's description.
880 @type err: string
881 """
882 PyinotifyError.__init__(self, err)
883
886 """
887 Read notifications, process events.
888
889 """
890 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
891 read_freq=0, treshold=0, timeout=None):
892 """
893 Initialization. read_freq, treshold and timeout parameters are used
894 when looping.
895
896 @param watch_manager: Watch Manager.
897 @type watch_manager: WatchManager instance
898 @param default_proc_fun: Default processing method.
899 @type default_proc_fun: instance of ProcessEvent
900 @param read_freq: if read_freq == 0, events are read asap,
901 if read_freq is > 0, this thread sleeps
902 max(0, read_freq - timeout) seconds. But if
903 timeout is None it can be different because
904 poll is blocking waiting for something to read.
905 @type read_freq: int
906 @param treshold: File descriptor will be read only if its size to
907 read is >= treshold. If != 0, you likely want to
908 use it in combination with read_freq because
909 without that you keep looping without really reading
910 anything and that until the amount to read
911 is >= treshold. At least with read_freq you may sleep.
912 @type treshold: int
913 @param timeout:
914 http://docs.python.org/lib/poll-objects.html#poll-objects
915 @type timeout: int
916 """
917
918 self._watch_manager = watch_manager
919
920 self._fd = self._watch_manager._fd
921
922 self._pollobj = select.poll()
923 self._pollobj.register(self._fd, select.POLLIN)
924
925 self._pipe = (-1, -1)
926
927 self._eventq = deque()
928
929 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
930
931 self._default_proc_fun = default_proc_fun
932
933 self._read_freq = read_freq
934 self._treshold = treshold
935 self._timeout = timeout
936
938 return self._default_proc_fun
939
941 """
942 Check for new events available to read, blocks up to timeout
943 milliseconds.
944
945 @return: New events to read.
946 @rtype: bool
947 """
948 while True:
949 try:
950
951 ret = self._pollobj.poll(self._timeout)
952 except select.error, err:
953 if err[0] == errno.EINTR:
954 continue
955 else:
956 raise
957 else:
958 break
959
960 if not ret or (self._pipe[0] == ret[0][0]):
961 return False
962
963 return ret[0][1] & select.POLLIN
964
966 """
967 Read events from device, build _RawEvents, and enqueue them.
968 """
969 buf_ = array.array('i', [0])
970
971 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
972 return
973 queue_size = buf_[0]
974 if queue_size < self._treshold:
975 log.debug('(fd: %d) %d bytes available to read but treshold is '
976 'fixed to %d bytes', self._fd, queue_size, 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 False raises a 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 False raises a 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 False raises a 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