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: GPL 2
26 @contact: seb@dbzteam.org
27 """
28
30 """Indicates exceptions raised by a Pyinotify class."""
31
32
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
46
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.2"
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 import psyco
117 psyco.full()
118 except ImportError:
119
120 pass
121
122
123
124
125
127 """
128 Access (read, write) inotify's variables through sysctl.
129
130 Examples:
131 - Read variable: myvar = max_queued_events.value
132 - Update variable: max_queued_events.value = 42
133 """
134
135 inotify_attrs = {'max_user_instances': 1,
136 'max_user_watches': 2,
137 'max_queued_events': 3}
138
140 attrname = p[0]
141 if not attrname in globals():
142 globals()[attrname] = super(SysCtlINotify, cls).__new__(cls, *p,
143 **k)
144 return globals()[attrname]
145
150
152 """
153 @return: stored value.
154 @rtype: int
155 """
156 oldv = ctypes.c_int(0)
157 size = ctypes.c_int(ctypes.sizeof(oldv))
158 LIBC.sysctl(self._attr, 3,
159 ctypes.c_voidp(ctypes.addressof(oldv)),
160 ctypes.addressof(size),
161 None, 0)
162 return oldv.value
163
165 """
166 @param nval: set to nval.
167 @type nval: int
168 """
169 oldv = ctypes.c_int(0)
170 sizeo = ctypes.c_int(ctypes.sizeof(oldv))
171 newv = ctypes.c_int(nval)
172 sizen = ctypes.c_int(ctypes.sizeof(newv))
173 LIBC.sysctl(self._attr, 3,
174 ctypes.c_voidp(ctypes.addressof(oldv)),
175 ctypes.addressof(sizeo),
176 ctypes.c_voidp(ctypes.addressof(newv)),
177 ctypes.addressof(sizen))
178
179 value = property(get_val, set_val)
180
182 return '<%s=%d>' % (self._attrname, self.get_val())
183
184
185
186
187
188
189
190 for i in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
191 SysCtlINotify(i)
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
215 if not has_magic(pathname):
216 if hasattr(os.path, 'lexists'):
217 if os.path.lexists(pathname):
218 yield pathname
219 else:
220 if os.path.islink(pathname) or os.path.exists(pathname):
221 yield pathname
222 return
223 dirname, basename = os.path.split(pathname)
224
225 if not dirname:
226 return
227
228 if has_magic(dirname):
229 dirs = iglob(dirname)
230 else:
231 dirs = [dirname]
232 if has_magic(basename):
233 glob_in_dir = glob1
234 else:
235 glob_in_dir = glob0
236 for dirname in dirs:
237 for name in glob_in_dir(dirname, basename):
238 yield os.path.join(dirname, name)
239
240 -def glob1(dirname, pattern):
241 if not dirname:
242 dirname = os.curdir
243 try:
244 names = os.listdir(dirname)
245 except os.error:
246 return []
247 return fnmatch.filter(names, pattern)
248
249 -def glob0(dirname, basename):
250 if basename == '' and os.path.isdir(dirname):
251
252
253 return [basename]
254 if hasattr(os.path, 'lexists'):
255 if os.path.lexists(os.path.join(dirname, basename)):
256 return [basename]
257 else:
258 if (os.path.islink(os.path.join(dirname, basename)) or
259 os.path.exists(os.path.join(dirname, basename))):
260 return [basename]
261 return []
262
263 magic_check = re.compile('[*?[]')
264
267
268
269
270
271
272
274 """
275 Set of codes corresponding to each kind of events.
276 Some of these flags are used to communicate with inotify, whereas
277 the others are sent to userspace by inotify notifying some events.
278
279 @cvar IN_ACCESS: File was accessed.
280 @type IN_ACCESS: int
281 @cvar IN_MODIFY: File was modified.
282 @type IN_MODIFY: int
283 @cvar IN_ATTRIB: Metadata changed.
284 @type IN_ATTRIB: int
285 @cvar IN_CLOSE_WRITE: Writtable file was closed.
286 @type IN_CLOSE_WRITE: int
287 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
288 @type IN_CLOSE_NOWRITE: int
289 @cvar IN_OPEN: File was opened.
290 @type IN_OPEN: int
291 @cvar IN_MOVED_FROM: File was moved from X.
292 @type IN_MOVED_FROM: int
293 @cvar IN_MOVED_TO: File was moved to Y.
294 @type IN_MOVED_TO: int
295 @cvar IN_CREATE: Subfile was created.
296 @type IN_CREATE: int
297 @cvar IN_DELETE: Subfile was deleted.
298 @type IN_DELETE: int
299 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
300 @type IN_DELETE_SELF: int
301 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
302 @type IN_MOVE_SELF: int
303 @cvar IN_UNMOUNT: Backing fs was unmounted.
304 @type IN_UNMOUNT: int
305 @cvar IN_Q_OVERFLOW: Event queued overflowed.
306 @type IN_Q_OVERFLOW: int
307 @cvar IN_IGNORED: File was ignored.
308 @type IN_IGNORED: int
309 @cvar IN_ONLYDIR: only watch the path if it is a directory (new
310 in kernel 2.6.15).
311 @type IN_ONLYDIR: int
312 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
313 IN_ONLYDIR we can make sure that we don't watch
314 the target of symlinks.
315 @type IN_DONT_FOLLOW: int
316 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
317 in kernel 2.6.14).
318 @type IN_MASK_ADD: int
319 @cvar IN_ISDIR: Event occurred against dir.
320 @type IN_ISDIR: int
321 @cvar IN_ONESHOT: Only send event once.
322 @type IN_ONESHOT: int
323 @cvar ALL_EVENTS: Alias for considering all of the events.
324 @type ALL_EVENTS: int
325 """
326
327
328
329
330 FLAG_COLLECTIONS = {'OP_FLAGS': {
331 'IN_ACCESS' : 0x00000001,
332 'IN_MODIFY' : 0x00000002,
333 'IN_ATTRIB' : 0x00000004,
334 'IN_CLOSE_WRITE' : 0x00000008,
335 'IN_CLOSE_NOWRITE' : 0x00000010,
336 'IN_OPEN' : 0x00000020,
337 'IN_MOVED_FROM' : 0x00000040,
338 'IN_MOVED_TO' : 0x00000080,
339 'IN_CREATE' : 0x00000100,
340 'IN_DELETE' : 0x00000200,
341 'IN_DELETE_SELF' : 0x00000400,
342
343 'IN_MOVE_SELF' : 0x00000800,
344 },
345 'EVENT_FLAGS': {
346 'IN_UNMOUNT' : 0x00002000,
347 'IN_Q_OVERFLOW' : 0x00004000,
348 'IN_IGNORED' : 0x00008000,
349 },
350 'SPECIAL_FLAGS': {
351 'IN_ONLYDIR' : 0x01000000,
352
353 'IN_DONT_FOLLOW' : 0x02000000,
354 'IN_MASK_ADD' : 0x20000000,
355
356 'IN_ISDIR' : 0x40000000,
357 'IN_ONESHOT' : 0x80000000,
358 },
359 }
360
362 """
363 Return the event name associated to mask. IN_ISDIR is appended when
364 appropriate. Note: only one event is returned, because only one is
365 raised once at a time.
366
367 @param mask: mask.
368 @type mask: int
369 @return: event name.
370 @rtype: str
371 """
372 ms = mask
373 name = '%s'
374 if mask & IN_ISDIR:
375 ms = mask - IN_ISDIR
376 name = '%s|IN_ISDIR'
377 return name % EventsCodes.ALL_VALUES[ms]
378
379 maskname = staticmethod(maskname)
380
381
382
383 EventsCodes.ALL_FLAGS = {}
384 EventsCodes.ALL_VALUES = {}
385 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems():
386
387
388 setattr(EventsCodes, flagc, valc)
389
390
391 EventsCodes.ALL_FLAGS.update(valc)
392
393
394
395 for name, val in valc.iteritems():
396 globals()[name] = val
397 EventsCodes.ALL_VALUES[val] = name
398
399
400
401 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues())
402 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
403 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
404
405
407 """
408 Event structure, represent events raised by the system. This
409 is the base class and should be subclassed.
410
411 """
413 """
414 Attach attributes (contained in dict_) to self.
415 """
416 for tpl in dict_.iteritems():
417 setattr(self, *tpl)
418
420 """
421 @return: String representation.
422 @rtype: str
423 """
424 s = ''
425 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
426 if attr.startswith('_'):
427 continue
428 if attr == 'mask':
429 value = hex(getattr(self, attr))
430 elif isinstance(value, str) and not value:
431 value ="''"
432 s += ' %s%s%s' % (color_theme.field_name(attr),
433 color_theme.punct('='),
434 color_theme.field_value(value))
435
436 s = '%s%s%s %s' % (color_theme.punct('<'),
437 color_theme.class_name(self.__class__.__name__),
438 s,
439 color_theme.punct('>'))
440 return s
441
442
444 """
445 Raw event, it contains only the informations provided by the system.
446 It doesn't infer anything.
447 """
448 - def __init__(self, wd, mask, cookie, name):
449 """
450 @param wd: Watch Descriptor.
451 @type wd: int
452 @param mask: Bitmask of events.
453 @type mask: int
454 @param cookie: Cookie.
455 @type cookie: int
456 @param name: Basename of the file or directory against which the
457 event was raised, in case where the watched directory
458 is the parent directory. None if the event was raised
459 on the watched item itself.
460 @type name: string or None
461 """
462
463 super(_RawEvent, self).__init__({'wd': wd,
464 'mask': mask,
465 'cookie': cookie,
466 'name': name.rstrip('\0')})
467 log.debug(repr(self))
468
469
471 """
472 This class contains all the useful informations about the observed
473 event. However, the incorporation of each field is not guaranteed and
474 depends on the type of event. In effect, some fields are irrelevant
475 for some kind of event (for example 'cookie' is meaningless for
476 IN_CREATE whereas it is useful for IN_MOVE_TO).
477
478 The possible fields are:
479 - wd (int): Watch Descriptor.
480 - mask (int): Mask.
481 - maskname (str): Readable event name.
482 - path (str): path of the file or directory being watched.
483 - name (str): Basename of the file or directory against which the
484 event was raised, in case where the watched directory
485 is the parent directory. None if the event was raised
486 on the watched item itself. This field is always provided
487 even if the string is ''.
488 - pathname (str): absolute path of: path + name
489 - cookie (int): Cookie.
490 - dir (bool): is the event raised against directory.
491
492 """
494 """
495 Concretely, this is the raw event plus inferred infos.
496 """
497 _Event.__init__(self, raw)
498 self.maskname = EventsCodes.maskname(self.mask)
499 try:
500 if self.name:
501 self.pathname = os.path.abspath(os.path.join(self.path,
502 self.name))
503 else:
504 self.pathname = os.path.abspath(self.path)
505 except AttributeError:
506 pass
507
508
510 """
511 ProcessEventError Exception. Raised on ProcessEvent error.
512 """
514 """
515 @param err: Exception error description.
516 @type err: string
517 """
518 PyinotifyError.__init__(self, err)
519
520
522 """
523 Abstract processing event class.
524 """
526 """
527 To behave like a functor the object must be callable.
528 This method is a dispatch method. Lookup order:
529 1. process_MASKNAME method
530 2. process_FAMILY_NAME method
531 3. otherwise call process_default
532
533 @param event: Event to be processed.
534 @type event: Event object
535 @return: By convention when used from the ProcessEvent class:
536 - Returning False or None (default value) means keep on
537 executing next chained functors (see chain.py example).
538 - Returning True instead means do not execute next
539 processing functions.
540 @rtype: bool
541 @raise ProcessEventError: Event object undispatchable,
542 unknown event.
543 """
544 stripped_mask = event.mask - (event.mask & IN_ISDIR)
545 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
546 if maskname is None:
547 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
548
549
550 meth = getattr(self, 'process_' + maskname, None)
551 if meth is not None:
552 return meth(event)
553
554 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
555 if meth is not None:
556 return meth(event)
557
558 return self.process_default(event)
559
561 return '<%s>' % self.__class__.__name__
562
563
565 """
566 There is three kind of processing according to each event:
567
568 1. special handling (deletion from internal container, bug, ...).
569 2. default treatment: which is applied to most of events.
570 4. IN_ISDIR is never sent alone, he is piggybacked with a standart
571 event, he is not processed as the others events, instead, its
572 value is captured and appropriately aggregated to dst event.
573 """
575 """
576
577 @param wm: Watch Manager.
578 @type wm: WatchManager instance
579 @param notifier: notifier.
580 @type notifier: Instance of Notifier.
581 """
582 self._watch_manager = wm
583 self._notifier = notifier
584 self._mv_cookie = {}
585 self._mv = {}
586
588 """
589 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
590 and self._mv.
591 """
592 date_cur_ = datetime.now()
593 for seq in [self._mv_cookie, self._mv]:
594 for k in seq.keys():
595 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
596 log.debug('cleanup: deleting entry %s' % seq[k][0])
597 del seq[k]
598
600 """
601 If the event concerns a directory and the auto_add flag of the
602 targetted watch is set to True, a new watch is added on this
603 new directory, with the same attributes's values than those of
604 this watch.
605 """
606 if raw_event.mask & IN_ISDIR:
607 watch_ = self._watch_manager._wmd.get(raw_event.wd)
608 if watch_.auto_add:
609 addw = self._watch_manager.add_watch
610 newwd = addw(os.path.join(watch_.path, raw_event.name),
611 watch_.mask, proc_fun=watch_.proc_fun,
612 rec=False, auto_add=watch_.auto_add)
613
614
615
616
617
618 base = os.path.join(watch_.path, raw_event.name)
619 if newwd[base] > 0:
620 for name in os.listdir(base):
621 inner = os.path.join(base, name)
622 if (os.path.isdir(inner) and
623 self._watch_manager.get_wd(inner) is None):
624
625
626 rawevent = _RawEvent(newwd[base],
627 IN_CREATE | IN_ISDIR,
628 0, name)
629 self._notifier._eventq.append(rawevent)
630 return self.process_default(raw_event)
631
633 """
634 Map the cookie with the source path (+ date for cleaning).
635 """
636 watch_ = self._watch_manager._wmd.get(raw_event.wd)
637 path_ = watch_.path
638 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
639 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
640 return self.process_default(raw_event, {'cookie': raw_event.cookie})
641
643 """
644 Map the source path with the destination path (+ date for
645 cleaning).
646 """
647 watch_ = self._watch_manager._wmd.get(raw_event.wd)
648 path_ = watch_.path
649 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
650 mv_ = self._mv_cookie.get(raw_event.cookie)
651 if mv_:
652 self._mv[mv_[0]] = (dst_path, datetime.now())
653 return self.process_default(raw_event, {'cookie': raw_event.cookie})
654
656 """
657 STATUS: the following bug has been fixed in the recent kernels (fixme:
658 which version ?). Now it raises IN_DELETE_SELF instead.
659
660 Old kernels are bugged, this event is raised when the watched item
661 was moved, so we must update its path, but under some circumstances it
662 can be impossible: if its parent directory and its destination
663 directory aren't watched. The kernel (see include/linux/fsnotify.h)
664 doesn't bring us enough informations like the destination path of
665 moved items.
666 """
667 watch_ = self._watch_manager._wmd.get(raw_event.wd)
668 src_path = watch_.path
669 mv_ = self._mv.get(src_path)
670 if mv_:
671 watch_.path = mv_[0]
672 else:
673 log.error("The path %s of this watch %s must not "
674 "be trusted anymore" % (watch_.path, watch_))
675 if not watch_.path.endswith('-wrong-path'):
676 watch_.path += '-wrong-path'
677
678 return self.process_default(raw_event)
679
681 """
682 Only signal overflow, most of the common flags are irrelevant
683 for this event (path, wd, name).
684 """
685 return Event({'mask': raw_event.mask})
686
688 """
689 The watch descriptor raised by this event is now ignored (forever),
690 it can be safely deleted from watch manager dictionary.
691 After this event we can be sure that neither the event queue
692 neither the system will raise an event associated to this wd.
693 """
694 event_ = self.process_default(raw_event)
695 try:
696 del self._watch_manager._wmd[raw_event.wd]
697 except KeyError, err:
698 log.error(err)
699 return event_
700
702 """
703 Common handling for the following events:
704
705 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
706 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
707 """
708 ret = None
709 watch_ = self._watch_manager._wmd.get(raw_event.wd)
710 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
711
712 dir_ = watch_.dir
713 else:
714 dir_ = bool(raw_event.mask & IN_ISDIR)
715 dict_ = {'wd': raw_event.wd,
716 'mask': raw_event.mask,
717 'path': watch_.path,
718 'name': raw_event.name,
719 'dir': dir_}
720 dict_.update(to_append)
721 return Event(dict_)
722
723
725 """
726 Process events objects, can be specialized via subclassing, thus its
727 behavior can be overriden:
728
729 Note: you should not override __init__ in your subclass instead define
730 a my_init() method, this method will be called from the constructor of
731 this class with optional parameters.
732
733 1. Provide methods, e.g. process_IN_DELETE for processing a given kind
734 of event (eg. IN_DELETE in this case).
735 2. Or/and provide methods for processing events by 'family', e.g.
736 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
737 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
738 process_IN_CLOSE_NOWRITE aren't defined).
739 3. Or/and override process_default for processing the remaining kind of
740 events.
741 """
742 pevent = None
743
744 - def __init__(self, pevent=None, **kargs):
745 """
746 Enable chaining of ProcessEvent instances.
747
748 @param pevent: optional callable object, will be called on event
749 processing (before self).
750 @type pevent: callable
751 @param kargs: optional arguments delagated to template method my_init
752 @type kargs: dict
753 """
754 self.pevent = pevent
755 self.my_init(**kargs)
756
758 """
759 Override this method when subclassing if you want to achieve
760 custom initialization of your subclass' instance. You MUST pass
761 keyword arguments. This method does nothing by default.
762
763 @param kargs: optional arguments delagated to template method my_init
764 @type kargs: dict
765 """
766 pass
767
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
788
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_theme.field_name(ev),
841 color_theme.field_value(value)))
842 s = '<%s%s >' % (color_theme.class_name(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_theme.field_value('@' * scale))
861 + 1)
862 def func(x):
863 return fmt % (color_theme.field_name(x[0]),
864 color_theme.field_value('@' * (x[1] / unity)),
865 color_theme.yellow('%d' % x[1]))
866 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
867 return s
868
869
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
882
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 see 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._eventq = deque()
924
925 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
926
927 self._default_proc_fun = default_proc_fun
928
929 self._read_freq = read_freq
930 self._treshold = treshold
931 self._timeout = timeout
932
934 return self._default_proc_fun
935
937 """
938 Check for new events available to read, blocks up to timeout
939 milliseconds.
940
941 @return: New events to read.
942 @rtype: bool
943 """
944 while True:
945 try:
946
947 ret = self._pollobj.poll(self._timeout)
948 except select.error, err:
949 if err[0] == errno.EINTR:
950 continue
951 else:
952 raise
953 else:
954 break
955
956 if not ret:
957 return False
958
959 return ret[0][1] & select.POLLIN
960
962 """
963 Read events from device, build _RawEvents, and enqueue them.
964 """
965 buf_ = array.array('i', [0])
966
967 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
968 return
969 queue_size = buf_[0]
970 if queue_size < self._treshold:
971 log.debug('(fd: %d) %d bytes available to read but '
972 'treshold is fixed to %d bytes' % (self._fd,
973 queue_size,
974 self._treshold))
975 return
976
977 try:
978
979 r = os.read(self._fd, queue_size)
980 except Exception, msg:
981 raise NotifierError(msg)
982 log.debug('event queue size: %d' % queue_size)
983 rsum = 0
984 while rsum < queue_size:
985 s_size = 16
986
987 s_ = struct.unpack('iIII', r[rsum:rsum+s_size])
988
989 fname_len = s_[3]
990
991 s_ = s_[:-1]
992
993 s_ += struct.unpack('%ds' % fname_len,
994 r[rsum + s_size:rsum + s_size + fname_len])
995 self._eventq.append(_RawEvent(*s_))
996 rsum += s_size + fname_len
997
999 """
1000 Routine for processing events from queue by calling their
1001 associated proccessing function (instance of ProcessEvent).
1002 It also do internal processings, to keep the system updated.
1003 """
1004 while self._eventq:
1005 raw_event = self._eventq.popleft()
1006 watch_ = self._watch_manager._wmd.get(raw_event.wd)
1007 revent = self._sys_proc_fun(raw_event)
1008 if watch_ and watch_.proc_fun:
1009 watch_.proc_fun(revent)
1010 else:
1011 self._default_proc_fun(revent)
1012 self._sys_proc_fun.cleanup()
1013
1014
1015 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull,
1016 stdout=os.devnull, stderr=os.devnull):
1017 """
1018 pid_file: file to which pid will be written.
1019 force_kill: if True kill the process associated to pid_file.
1020 stdin, stdout, stderr: files associated to common streams.
1021 """
1022 if pid_file is None:
1023 dirname = '/var/run/'
1024 basename = sys.argv[0] or 'pyinotify'
1025 pid_file = os.path.join(dirname, basename + '.pid')
1026
1027 if os.path.exists(pid_file):
1028 fo = file(pid_file, 'rb')
1029 try:
1030 try:
1031 pid = int(fo.read())
1032 except ValueError:
1033 pid = None
1034 if pid is not None:
1035 try:
1036 os.kill(pid, 0)
1037 except OSError, err:
1038 pass
1039 else:
1040 if not force_kill:
1041 s = 'There is already a pid file %s with pid %d'
1042 raise NotifierError(s % (pid_file, pid))
1043 else:
1044 os.kill(pid, 9)
1045 finally:
1046 fo.close()
1047
1048
1049 def fork_daemon():
1050
1051 pid = os.fork()
1052 if (pid == 0):
1053
1054 os.setsid()
1055 pid = os.fork()
1056 if (pid == 0):
1057
1058 os.chdir('/')
1059 os.umask(0)
1060 else:
1061
1062 os._exit(0)
1063 else:
1064
1065 os._exit(0)
1066
1067 fd_inp = os.open(stdin, os.O_RDONLY)
1068 os.dup2(fd_inp, 0)
1069 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT)
1070 os.dup2(fd_out, 1)
1071 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT)
1072 os.dup2(fd_err, 2)
1073
1074
1075 fork_daemon()
1076
1077
1078 fo = file(pid_file, 'wb')
1079 try:
1080 fo.write(str(os.getpid()) + '\n')
1081 finally:
1082 fo.close()
1083
1084 atexit.register(lambda : os.unlink(pid_file))
1085
1086
1088
1089 if self._read_freq > 0:
1090 cur_time = time.time()
1091 sleep_amount = self._read_freq - (cur_time - ref_time)
1092 if sleep_amount > 0:
1093 log.debug('Now sleeping %d seconds' % sleep_amount)
1094 time.sleep(sleep_amount)
1095
1096
1097 - def loop(self, callback=None, daemonize=False, **args):
1098 """
1099 Events are read only once time every min(read_freq, timeout)
1100 seconds at best and only if the size to read is >= treshold.
1101
1102 @param callback: Functor called after each event processing. Expects
1103 to receive notifier object (self) as first parameter.
1104 @type callback: callable
1105 @param daemonize: This thread is daemonized if set to True.
1106 @type daemonize: boolean
1107 """
1108 if daemonize:
1109 self.__daemonize(**args)
1110
1111
1112 while 1:
1113 try:
1114 self.process_events()
1115 if callback is not None:
1116 callback(self)
1117 ref_time = time.time()
1118
1119 if self.check_events():
1120 self._sleep(ref_time)
1121 self.read_events()
1122 except KeyboardInterrupt:
1123
1124 log.debug('stop monitoring...')
1125
1126 self.stop()
1127 break
1128 except Exception, err:
1129 log.error(err)
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
1139
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 This class is only maintained for legacy reasons, everything possible with
1146 this class is also possible with Notifier, but Notifier is _better_ under
1147 many aspects (not threaded, can be daemonized, won't unnecessarily read
1148 for events).
1149 """
1150 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
1151 read_freq=0, treshold=0, timeout=10000):
1152 """
1153 Initialization, initialize base classes. read_freq, treshold and
1154 timeout parameters are used when looping.
1155
1156 @param watch_manager: Watch Manager.
1157 @type watch_manager: WatchManager instance
1158 @param default_proc_fun: Default processing method.
1159 @type default_proc_fun: instance of ProcessEvent
1160 @param read_freq: if read_freq == 0, events are read asap,
1161 if read_freq is > 0, this thread sleeps
1162 max(0, read_freq - timeout) seconds.
1163 @type read_freq: int
1164 @param treshold: File descriptor will be read only if its size to
1165 read is >= treshold. If != 0, you likely want to
1166 use it in combination with read_freq because
1167 without that you keep looping without really reading
1168 anything and that until the amount to read
1169 is >= treshold. At least with read_freq you may sleep.
1170 @type treshold: int
1171 @param timeout:
1172 see http://docs.python.org/lib/poll-objects.html#poll-objects
1173 Read the corresponding comment in the source code before changing
1174 it.
1175 @type timeout: int
1176 """
1177
1178 threading.Thread.__init__(self)
1179
1180 self._stop_event = threading.Event()
1181
1182 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1183 treshold, timeout)
1184
1186 """
1187 Stop the notifier's loop. Stop notification. Join the thread.
1188 """
1189 self._stop_event.set()
1190 threading.Thread.join(self)
1191 Notifier.stop(self)
1192
1194 """
1195 Thread's main loop. don't meant to be called by user directly.
1196 Call start() instead.
1197
1198 Events are read only once time every min(read_freq, timeout)
1199 seconds at best and only if the size to read is >= treshold.
1200 """
1201
1202
1203 while not self._stop_event.isSet():
1204 self.process_events()
1205 ref_time = time.time()
1206
1207
1208
1209
1210 if self.check_events():
1211 self._sleep(ref_time)
1212 self.read_events()
1213
1215 """
1216 Start the thread's loop: read and process events until the method
1217 stop() is called.
1218 Never call this method directly, instead call the start() method
1219 inherited from threading.Thread, which then will call run().
1220 """
1221 self.loop()
1222
1223
1225 """
1226 Represent a watch, i.e. a file or directory being watched.
1227
1228 """
1230 """
1231 Initializations.
1232
1233 @param wd: Watch descriptor.
1234 @type wd: int
1235 @param path: Path of the file or directory being watched.
1236 @type path: str
1237 @param mask: Mask.
1238 @type mask: int
1239 @param proc_fun: Processing callable object.
1240 @type proc_fun:
1241 @param auto_add: Automatically add watches on new directories.
1242 @type auto_add: bool
1243 """
1244 for k, v in keys.iteritems():
1245 setattr(self, k, v)
1246 self.dir = os.path.isdir(self.path)
1247
1249 """
1250 @return: String representation.
1251 @rtype: str
1252 """
1253 s = ' '.join(['%s%s%s' % (color_theme.field_name(attr),
1254 color_theme.punct('='),
1255 color_theme.field_value(getattr(self,
1256 attr))) \
1257 for attr in self.__dict__ if not attr.startswith('_')])
1258
1259 s = '%s%s %s %s' % (color_theme.punct('<'),
1260 color_theme.class_name(self.__class__.__name__),
1261 s,
1262 color_theme.punct('>'))
1263 return s
1264
1265
1267 """
1268 ExcludeFilter is an exclusion filter.
1269 """
1270
1272 """
1273 @param arg_lst: is either a list or dict of patterns:
1274 [pattern1, ..., patternn]
1275 {'filename1': (list1, listn), ...} where list1 is
1276 a list of patterns
1277 @type arg_lst: list or dict
1278 """
1279 if isinstance(arg_lst, dict):
1280 lst = self._load_patterns(arg_lst)
1281 elif isinstance(arg_lst, list):
1282 lst = arg_lst
1283 else:
1284 raise TypeError
1285
1286 self._lregex = []
1287 for regex in lst:
1288 self._lregex.append(re.compile(regex, re.UNICODE))
1289
1291 lst = []
1292 for path, varnames in dct.iteritems():
1293 loc = {}
1294 execfile(path, {}, loc)
1295 for varname in varnames:
1296 lst.extend(loc.get(varname, []))
1297 return lst
1298
1299 - def _match(self, regex, path):
1300 return regex.match(path) is not None
1301
1303 """
1304 @param path: path to match against regexps.
1305 @type path: str
1306 @return: return True is path has been matched and should
1307 be excluded, False otherwise.
1308 @rtype: bool
1309 """
1310 for regex in self._lregex:
1311 if self._match(regex, path):
1312 return True
1313 return False
1314
1315
1317 """
1318 WatchManager Exception. Raised on error encountered on watches
1319 operations.
1320
1321 """
1323 """
1324 @param msg: Exception string's description.
1325 @type msg: string
1326 @param wmd: Results of previous operations made by the same function
1327 on previous wd or paths. It also contains the item which
1328 raised this exception.
1329 @type wmd: dict
1330 """
1331 self.wmd = wmd
1332 Exception.__init__(self, msg)
1333
1334
1336 """
1337 Provide operations for watching files and directories. Integrated
1338 dictionary is used to reference watched items.
1339 """
1340 - def __init__(self, exclude_filter=lambda path: False):
1341 """
1342 Initialization: init inotify, init watch manager dictionary.
1343 Raise OSError if initialization fails.
1344
1345 @param exclude_filter: boolean function, returns True if current
1346 path must be excluded from being watched.
1347 Convenient for providing a common exclusion
1348 filter for every call to add_watch.
1349 @type exclude_filter: bool
1350 """
1351 self._exclude_filter = exclude_filter
1352 self._wmd = {}
1353 self._fd = LIBC.inotify_init()
1354 if self._fd < 0:
1355 raise OSError()
1356
1357 - def __add_watch(self, path, mask, proc_fun, auto_add):
1358 """
1359 Add a watch on path, build a Watch object and insert it in the
1360 watch manager dictionary. Return the wd value.
1361 """
1362 wd_ = LIBC.inotify_add_watch(self._fd,
1363 ctypes.create_string_buffer(path),
1364 mask)
1365 if wd_ < 0:
1366 return wd_
1367 watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask,
1368 proc_fun=proc_fun, auto_add=auto_add)
1369 self._wmd[wd_] = watch_
1370 log.debug('New %s' % watch_)
1371 return wd_
1372
1373 - def __glob(self, path, do_glob):
1374 if do_glob:
1375 return iglob(path)
1376 else:
1377 return [path]
1378
1379 - def add_watch(self, path, mask, proc_fun=None, rec=False,
1380 auto_add=False, do_glob=False, quiet=True,
1381 exclude_filter=None):
1382 """
1383 Add watch(s) on given path(s) with the specified mask and
1384 optionnally with a processing function and recursive flag.
1385
1386 @param path: Path to watch, the path can either be a file or a
1387 directory. Also accepts a sequence (list) of paths.
1388 @type path: string or list of string
1389 @param mask: Bitmask of events.
1390 @type mask: int
1391 @param proc_fun: Processing object.
1392 @type proc_fun: function or ProcessEvent instance or instance of
1393 one of its subclasses or callable object.
1394 @param rec: Recursively add watches from path on all its
1395 subdirectories, set to False by default (doesn't
1396 follows symlinks).
1397 @type rec: bool
1398 @param auto_add: Automatically add watches on newly created
1399 directories in the watch's path.
1400 @type auto_add: bool
1401 @param do_glob: Do globbing on pathname.
1402 @type do_glob: bool
1403 @param quiet: if True raise an WatchManagerError exception on
1404 error. See example not_quiet.py
1405 @type quiet: bool
1406 @param exclude_filter: boolean function, returns True if current
1407 path must be excluded from being watched.
1408 Has precedence on exclude_filter defined
1409 into __init__.
1410 @type exclude_filter: bool
1411 @return: dict of paths associated to watch descriptors. A wd value
1412 is positive if the watch has been sucessfully added,
1413 otherwise the value is negative. If the path is invalid
1414 it will be not included into this dict.
1415 @rtype: dict of {str: int}
1416 """
1417 ret_ = {}
1418
1419 if exclude_filter is None:
1420 exclude_filter = self._exclude_filter
1421
1422
1423 for npath in self.__format_param(path):
1424
1425 for apath in self.__glob(npath, do_glob):
1426
1427 for rpath in self.__walk_rec(apath, rec):
1428 if not exclude_filter(rpath):
1429 wd = ret_[rpath] = self.__add_watch(rpath, mask,
1430 proc_fun,
1431 auto_add)
1432 if wd < 0:
1433 err = 'add_watch: cannot watch %s (WD=%d)'
1434 err = err % (rpath, wd)
1435 if quiet:
1436 log.error(err)
1437 else:
1438 raise WatchManagerError(err, ret_)
1439 else:
1440
1441
1442 ret_[rpath] = -2
1443 return ret_
1444
1446 """
1447 Get every wd from self._wmd if its path is under the path of
1448 one (at least) of those in lpath. Doesn't follow symlinks.
1449
1450 @param lpath: list of watch descriptor
1451 @type lpath: list of int
1452 @return: list of watch descriptor
1453 @rtype: list of int
1454 """
1455 for d in lpath:
1456 root = self.get_path(d)
1457 if root:
1458
1459 yield d
1460 else:
1461
1462 continue
1463
1464
1465 if not os.path.isdir(root):
1466 continue
1467
1468
1469 root = os.path.normpath(root)
1470
1471 lend = len(root)
1472 for iwd in self._wmd.items():
1473 cur = iwd[1].path
1474 pref = os.path.commonprefix([root, cur])
1475 if root == os.sep or (len(pref) == lend and \
1476 len(cur) > lend and \
1477 cur[lend] == os.sep):
1478 yield iwd[1].wd
1479
1480 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
1481 auto_add=False, quiet=True):
1482 """
1483 Update existing watch(s). Both the mask and the processing
1484 object can be modified.
1485
1486 @param wd: Watch Descriptor to update. Also accepts a list of
1487 watch descriptors.
1488 @type wd: int or list of int
1489 @param mask: Optional new bitmask of events.
1490 @type mask: int
1491 @param proc_fun: Optional new processing function.
1492 @type proc_fun: function or ProcessEvent instance or instance of
1493 one of its subclasses or callable object.
1494 @param rec: Recursively update watches on every already watched
1495 subdirectories and subfiles.
1496 @type rec: bool
1497 @param auto_add: Automatically add watches on newly created
1498 directories in the watch's path.
1499 @type auto_add: bool
1500 @param quiet: if True raise an WatchManagerError exception on
1501 error. See example not_quiet.py
1502 @type quiet: bool
1503 @return: dict of watch descriptors associated to booleans values.
1504 True if the corresponding wd has been successfully
1505 updated, False otherwise.
1506 @rtype: dict of int: bool
1507 """
1508 lwd = self.__format_param(wd)
1509 if rec:
1510 lwd = self.__get_sub_rec(lwd)
1511
1512 ret_ = {}
1513 for awd in lwd:
1514 apath = self.get_path(awd)
1515 if not apath or awd < 0:
1516 err = 'update_watch: invalid WD=%d' % awd
1517 if quiet:
1518 log.error(err)
1519 continue
1520 raise WatchManagerError(err, ret_)
1521
1522 if mask:
1523 addw = LIBC.inotify_add_watch
1524 wd_ = addw(self._fd,
1525 ctypes.create_string_buffer(apath),
1526 mask)
1527 if wd_ < 0:
1528 ret_[awd] = False
1529 err = 'update_watch: cannot update WD=%d (%s)' % (wd_,
1530 apath)
1531 if quiet:
1532 log.error(err)
1533 continue
1534 raise WatchManagerError(err, ret_)
1535
1536 assert(awd == wd_)
1537
1538 if proc_fun or auto_add:
1539 watch_ = self._wmd[awd]
1540
1541 if proc_fun:
1542 watch_.proc_fun = proc_fun
1543
1544 if auto_add:
1545 watch_.proc_fun = auto_add
1546
1547 ret_[awd] = True
1548 log.debug('Updated watch - %s' % self._wmd[awd])
1549 return ret_
1550
1563
1565 """
1566 Returns the watch descriptor associated to path. This method
1567 has an prohibitive cost, always prefer to keep the WD.
1568 If path is unknown None is returned.
1569
1570 @param path: path.
1571 @type path: str
1572 @return: WD or None.
1573 @rtype: int or None
1574 """
1575 path = os.path.normpath(path)
1576 for iwd in self._wmd.iteritems():
1577 if iwd[1].path == path:
1578 return iwd[0]
1579 log.debug('get_wd: unknown path %s' % path)
1580
1582 """
1583 Returns the path associated to WD, if WD is unknown
1584 None is returned.
1585
1586 @param wd: watch descriptor.
1587 @type wd: int
1588 @return: path or None.
1589 @rtype: string or None
1590 """
1591 watch_ = self._wmd.get(wd)
1592 if watch_:
1593 return watch_.path
1594 log.debug('get_path: unknown WD %d' % wd)
1595
1597 """
1598 Yields each subdirectories of top, doesn't follow symlinks.
1599 If rec is false, only yield top.
1600
1601 @param top: root directory.
1602 @type top: string
1603 @param rec: recursive flag.
1604 @type rec: bool
1605 @return: path of one subdirectory.
1606 @rtype: string
1607 """
1608 if not rec or os.path.islink(top) or not os.path.isdir(top):
1609 yield top
1610 else:
1611 for root, dirs, files in os.walk(top):
1612 yield root
1613
1614 - def rm_watch(self, wd, rec=False, quiet=True):
1615 """
1616 Removes watch(s).
1617
1618 @param wd: Watch Descriptor of the file or directory to unwatch.
1619 Also accepts a list of WDs.
1620 @type wd: int or list of int.
1621 @param rec: Recursively removes watches on every already watched
1622 subdirectories and subfiles.
1623 @type rec: bool
1624 @param quiet: if True raise an WatchManagerError exception on
1625 error. See example not_quiet.py
1626 @type quiet: bool
1627 @return: dict of watch descriptors associated to booleans values.
1628 True if the corresponding wd has been successfully
1629 removed, False otherwise.
1630 @rtype: dict of int: bool
1631 """
1632 lwd = self.__format_param(wd)
1633 if rec:
1634 lwd = self.__get_sub_rec(lwd)
1635
1636 ret_ = {}
1637 for awd in lwd:
1638
1639 wd_ = LIBC.inotify_rm_watch(self._fd, awd)
1640 if wd_ < 0:
1641 ret_[awd] = False
1642 err = 'rm_watch: cannot remove WD=%d' % awd
1643 if quiet:
1644 log.error(err)
1645 continue
1646 raise WatchManagerError(err, ret_)
1647
1648 ret_[awd] = True
1649 log.debug('watch WD=%d (%s) removed' % (awd, self.get_path(awd)))
1650 return ret_
1651
1652
1654 """
1655 Watch a transient file, which will be created and deleted frequently
1656 over time (e.g. pid file).
1657
1658 @param filename: Filename.
1659 @type filename: string
1660 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE.
1661 @type mask: int
1662 @param proc_class: ProcessEvent (or of one of its subclass), beware of
1663 accepting a ProcessEvent's instance as argument into
1664 __init__, see transient_file.py example for more
1665 details.
1666 @type proc_class: ProcessEvent's instance or of one of its subclasses.
1667 @return: See add_watch().
1668 @rtype: See add_watch().
1669 """
1670 dirname = os.path.dirname(filename)
1671 if dirname == '':
1672 return {}
1673 basename = os.path.basename(filename)
1674
1675 mask |= IN_CREATE | IN_DELETE
1676
1677 def cmp_name(event):
1678 return basename == event.name
1679 return self.add_watch(dirname, mask,
1680 proc_fun=proc_class(ChainIf(func=cmp_name)),
1681 rec=False,
1682 auto_add=False, do_glob=False)
1683
1684
1685
1686
1687
1688
1689
1690
1706
1709 return "<%s>" % self.__class__.__name__
1712
1715
1718 if attr.startswith("__"):
1719 raise AttributeError(attr)
1720 s = "style_%s" % attr
1721 if s in self.__class__.__dict__:
1722 before = getattr(self, s)
1723 after = self.style_normal
1724 else:
1725 before = after = ""
1726
1727 def do_style(val, fmt=None, before=before, after=after):
1728 if fmt is None:
1729 if type(val) is not str:
1730 val = str(val)
1731 else:
1732 val = fmt % val
1733 return before+val+after
1734 return do_style
1735
1736
1737 style_normal = ""
1738 style_prompt = ""
1739 style_punct = ""
1740 style_id = ""
1741 style_not_printable = ""
1742 style_class_name = ""
1743 style_field_name = ""
1744 style_field_value = ""
1745 style_emph_field_name = ""
1746 style_emph_field_value = ""
1747 style_watchlist_name = ""
1748 style_watchlist_type = ""
1749 style_watchlist_value = ""
1750 style_fail = ""
1751 style_success = ""
1752 style_odd = ""
1753 style_even = ""
1754 style_yellow = ""
1755 style_active = ""
1756 style_closed = ""
1757 style_left = ""
1758 style_right = ""
1759
1762
1785
1786 color_theme = DefaultTheme()
1787
1788
1789
1791
1792
1793
1794
1795
1796 from optparse import OptionParser
1797
1798 usage = "usage: %prog [options] [path1] [path2] [pathn]"
1799
1800 parser = OptionParser(usage=usage)
1801 parser.add_option("-v", "--verbose", action="store_true",
1802 dest="verbose", help="Verbose mode")
1803 parser.add_option("-r", "--recursive", action="store_true",
1804 dest="recursive",
1805 help="Add watches recursively on paths")
1806 parser.add_option("-a", "--auto_add", action="store_true",
1807 dest="auto_add",
1808 help="Automatically add watches on new directories")
1809 parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
1810 dest="events_list",
1811 help=("A comma-separated list of events to watch for - "
1812 "see the documentation for valid options (defaults"
1813 " to everything)"))
1814 parser.add_option("-s", "--stats", action="store_true",
1815 dest="stats",
1816 help="Display statistics")
1817
1818 (options, args) = parser.parse_args()
1819
1820 if options.verbose:
1821 log.setLevel(10)
1822
1823 if len(args) < 1:
1824 path = '/tmp'
1825 else:
1826 path = args
1827
1828
1829 wm = WatchManager()
1830
1831 if options.stats:
1832 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5)
1833 else:
1834 notifier = Notifier(wm)
1835
1836
1837 mask = 0
1838 if options.events_list:
1839 events_list = options.events_list.split(',')
1840 for ev in events_list:
1841 evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
1842 if evcode:
1843 mask |= evcode
1844 else:
1845 parser.error("The event '%s' specified with option -e"
1846 " is not valid" % ev)
1847 else:
1848 mask = ALL_EVENTS
1849
1850
1851 cb_fun = None
1852 if options.stats:
1853 def cb(s):
1854 print('%s\n%s\n' % (repr(s.proc_fun()),
1855 s.proc_fun()))
1856 cb_fun = cb
1857
1858 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path)
1859
1860 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add)
1861
1862 notifier.loop(callback=cb_fun)
1863
1864
1865 if __name__ == '__main__':
1866 command_line()
1867