3
# pyinotify.py - python interface to inotify
4
# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>
6
# Permission is hereby granted, free of charge, to any person obtaining a copy
7
# of this software and associated documentation files (the "Software"), to deal
8
# in the Software without restriction, including without limitation the rights
9
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
# copies of the Software, and to permit persons to whom the Software is
11
# furnished to do so, subject to the following conditions:
13
# The above copyright notice and this permission notice shall be included in
14
# all copies or substantial portions of the Software.
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
"""Platform agnostic code grabed from pyinotify."""
28
COMPATIBILITY_MODE = False
30
class PyinotifyError(Exception):
31
"""Indicates exceptions raised by a Pyinotify class."""
35
class RawOutputFormat:
37
Format string representations.
39
def __init__(self, format=None):
40
self.format = format or {}
42
def simple(self, s, attribute):
43
if not isinstance(s, str):
45
return (self.format.get(attribute, '') + s +
46
self.format.get('normal', ''))
48
def punctuation(self, s):
49
"""Punctuation color."""
50
return self.simple(s, 'normal')
52
def field_value(self, s):
53
"""Field value color."""
54
return self.simple(s, 'purple')
56
def field_name(self, s):
57
"""Field name color."""
58
return self.simple(s, 'blue')
60
def class_name(self, s):
61
"""Class name color."""
62
return self.format.get('red', '') + self.simple(s, 'bold')
64
output_format = RawOutputFormat()
69
Set of codes corresponding to each kind of events.
70
Some of these flags are used to communicate with inotify, whereas
71
the others are sent to userspace by inotify notifying some events.
73
@cvar IN_ACCESS: File was accessed.
75
@cvar IN_MODIFY: File was modified.
77
@cvar IN_ATTRIB: Metadata changed.
79
@cvar IN_CLOSE_WRITE: Writtable file was closed.
80
@type IN_CLOSE_WRITE: int
81
@cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
82
@type IN_CLOSE_NOWRITE: int
83
@cvar IN_OPEN: File was opened.
85
@cvar IN_MOVED_FROM: File was moved from X.
86
@type IN_MOVED_FROM: int
87
@cvar IN_MOVED_TO: File was moved to Y.
88
@type IN_MOVED_TO: int
89
@cvar IN_CREATE: Subfile was created.
91
@cvar IN_DELETE: Subfile was deleted.
93
@cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
94
@type IN_DELETE_SELF: int
95
@cvar IN_MOVE_SELF: Self (watched item itself) was moved.
96
@type IN_MOVE_SELF: int
97
@cvar IN_UNMOUNT: Backing fs was unmounted.
99
@cvar IN_Q_OVERFLOW: Event queued overflowed.
100
@type IN_Q_OVERFLOW: int
101
@cvar IN_IGNORED: File was ignored.
102
@type IN_IGNORED: int
103
@cvar IN_ONLYDIR: only watch the path if it is a directory (new
105
@type IN_ONLYDIR: int
106
@cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
107
IN_ONLYDIR we can make sure that we don't watch
108
the target of symlinks.
109
@type IN_DONT_FOLLOW: int
110
@cvar IN_MASK_ADD: add to the mask of an already existing watch (new
112
@type IN_MASK_ADD: int
113
@cvar IN_ISDIR: Event occurred against dir.
115
@cvar IN_ONESHOT: Only send event once.
116
@type IN_ONESHOT: int
117
@cvar ALL_EVENTS: Alias for considering all of the events.
118
@type ALL_EVENTS: int
121
# The idea here is 'configuration-as-code' - this way, we get
122
# our nice class constants, but we also get nice human-friendly text
123
# mappings to do lookups against as well, for free:
124
FLAG_COLLECTIONS = {'OP_FLAGS': {
125
'IN_ACCESS' : 0x00000001, # File was accessed
126
'IN_MODIFY' : 0x00000002, # File was modified
127
'IN_ATTRIB' : 0x00000004, # Metadata changed
128
'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
129
'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
130
'IN_OPEN' : 0x00000020, # File was opened
131
'IN_MOVED_FROM' : 0x00000040, # File was moved from X
132
'IN_MOVED_TO' : 0x00000080, # File was moved to Y
133
'IN_CREATE' : 0x00000100, # Subfile was created
134
'IN_DELETE' : 0x00000200, # Subfile was deleted
135
'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)
137
'IN_MOVE_SELF' : 0x00000800, # Self(watched item itself) was moved
140
'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
141
'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
142
'IN_IGNORED' : 0x00008000, # File was ignored
145
'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a
147
'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
148
'IN_MASK_ADD' : 0x20000000, # add to the mask of an already
150
'IN_ISDIR' : 0x40000000, # event occurred against dir
151
'IN_ONESHOT' : 0x80000000, # only send event once
157
Returns the event name associated to mask. IN_ISDIR is appended to
158
the result when appropriate. Note: only one event is returned, because
159
only one event can be raised at a given time.
171
return name % EventsCodes.ALL_VALUES[ms]
173
maskname = staticmethod(maskname)
176
# So let's now turn the configuration into code
177
EventsCodes.ALL_FLAGS = {}
178
EventsCodes.ALL_VALUES = {}
179
for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():
180
# Make the collections' members directly accessible through the
182
setattr(EventsCodes, flagc, valc)
184
# Collect all the flags under a common umbrella
185
EventsCodes.ALL_FLAGS.update(valc)
187
# Make the individual masks accessible as 'constants' at globals() scope
188
# and masknames accessible by values.
189
for name, val in valc.items():
190
globals()[name] = val
191
EventsCodes.ALL_VALUES[val] = name
194
# all 'normal' events
195
ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())
196
EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
197
EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
202
Event structure, represent events raised by the system. This
203
is the base class and should be subclassed.
206
def __init__(self, dict_):
208
Attach attributes (contained in dict_) to self.
210
@param dict_: Set of attributes.
211
@type dict_: dictionary
213
for tpl in dict_.items():
218
@return: Generic event string representation.
222
for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
223
if attr.startswith('_'):
226
value = hex(getattr(self, attr))
227
elif isinstance(value, basestring) and not value:
229
s += ' %s%s%s' % (output_format.field_name(attr),
230
output_format.punctuation('='),
231
output_format.field_value(value))
233
s = '%s%s%s %s' % (output_format.punctuation('<'),
234
output_format.class_name(self.__class__.__name__),
236
output_format.punctuation('>'))
243
class _RawEvent(_Event):
245
Raw event, it contains only the informations provided by the system.
246
It doesn't infer anything.
248
def __init__(self, wd, mask, cookie, name):
250
@param wd: Watch Descriptor.
252
@param mask: Bitmask of events.
254
@param cookie: Cookie.
256
@param name: Basename of the file or directory against which the
257
event was raised in case where the watched directory
258
is the parent directory. None if the event was raised
259
on the watched item itself.
260
@type name: string or None
262
# Use this variable to cache the result of str(self), this object
265
# name: remove trailing '\0'
269
'name': name.rstrip('\0')}
270
_Event.__init__(self, d)
271
logging.debug(str(self))
274
if self._str is None:
275
self._str = _Event.__str__(self)
281
This class contains all the useful informations about the observed
282
event. However, the presence of each field is not guaranteed and
283
depends on the type of event. In effect, some fields are irrelevant
284
for some kind of event (for example 'cookie' is meaningless for
285
IN_CREATE whereas it is mandatory for IN_MOVE_TO).
287
The possible fields are:
288
- wd (int): Watch Descriptor.
290
- maskname (str): Readable event name.
291
- path (str): path of the file or directory being watched.
292
- name (str): Basename of the file or directory against which the
293
event was raised in case where the watched directory
294
is the parent directory. None if the event was raised
295
on the watched item itself. This field is always provided
296
even if the string is ''.
297
- pathname (str): Concatenation of 'path' and 'name'.
298
- src_pathname (str): Only present for IN_MOVED_TO events and only in
299
the case where IN_MOVED_FROM events are watched too. Holds the
300
source pathname from where pathname was moved from.
301
- cookie (int): Cookie.
302
- dir (bool): True if the event was raised against a directory.
305
def __init__(self, raw):
307
Concretely, this is the raw event plus inferred infos.
309
_Event.__init__(self, raw)
310
self.maskname = EventsCodes.maskname(self.mask)
311
if COMPATIBILITY_MODE:
312
self.event_name = self.maskname
315
self.pathname = os.path.abspath(os.path.join(self.path,
318
self.pathname = os.path.abspath(self.path)
319
except AttributeError, err:
320
# Usually it is not an error some events are perfectly valids
321
# despite the lack of these attributes.
325
class ProcessEventError(PyinotifyError):
327
ProcessEventError Exception. Raised on ProcessEvent error.
329
def __init__(self, err):
331
@param err: Exception error description.
334
PyinotifyError.__init__(self, err)
339
Abstract processing event class.
341
def __call__(self, event):
343
To behave like a functor the object must be callable.
344
This method is a dispatch method. Its lookup order is:
345
1. process_MASKNAME method
346
2. process_FAMILY_NAME method
347
3. otherwise calls process_default
349
@param event: Event to be processed.
350
@type event: Event object
351
@return: By convention when used from the ProcessEvent class:
352
- Returning False or None (default value) means keep on
353
executing next chained functors (see chain.py example).
354
- Returning True instead means do not execute next
355
processing functions.
357
@raise ProcessEventError: Event object undispatchable,
360
stripped_mask = event.mask - (event.mask & IN_ISDIR)
361
maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
363
raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
365
# 1- look for process_MASKNAME
366
meth = getattr(self, 'process_' + maskname, None)
369
# 2- look for process_FAMILY_NAME
370
meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
373
# 3- default call method process_default
374
return self.process_default(event)
377
return '<%s>' % self.__class__.__name__
380
class ProcessEvent(_ProcessEvent):
382
Process events objects, can be specialized via subclassing, thus its
383
behavior can be overriden:
385
Note: you should not override __init__ in your subclass instead define
386
a my_init() method, this method will be called automatically from the
387
constructor of this class with its optionals parameters.
389
1. Provide specialized individual methods, e.g. process_IN_DELETE for
390
processing a precise type of event (e.g. IN_DELETE in this case).
391
2. Or/and provide methods for processing events by 'family', e.g.
392
process_IN_CLOSE method will process both IN_CLOSE_WRITE and
393
IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
394
process_IN_CLOSE_NOWRITE aren't defined though).
395
3. Or/and override process_default for catching and processing all
396
the remaining types of events.
400
def __init__(self, pevent=None, **kargs):
402
Enable chaining of ProcessEvent instances.
404
@param pevent: Optional callable object, will be called on event
405
processing (before self).
406
@type pevent: callable
407
@param kargs: This constructor is implemented as a template method
408
delegating its optionals keyworded arguments to the
413
self.my_init(**kargs)
415
def my_init(self, **kargs):
417
This method is called from ProcessEvent.__init__(). This method is
418
empty here and must be redefined to be useful. In effect, if you
419
need to specifically initialize your subclass' instance then you
420
just have to override this method in your subclass. Then all the
421
keyworded arguments passed to ProcessEvent.__init__() will be
422
transmitted as parameters to this method. Beware you MUST pass
423
keyword arguments though.
425
@param kargs: optional delegated arguments from __init__().
430
def __call__(self, event):
431
stop_chaining = False
432
if self.pevent is not None:
433
# By default methods return None so we set as guideline
434
# that methods asking for stop chaining must explicitely
435
# return non None or non False values, otherwise the default
436
# behavior will be to accept chain call to the corresponding
438
stop_chaining = self.pevent(event)
439
if not stop_chaining:
440
return _ProcessEvent.__call__(self, event)
442
def nested_pevent(self):
445
def process_IN_Q_OVERFLOW(self, event):
447
By default this method only reports warning messages, you can
448
overredide it by subclassing ProcessEvent and implement your own
449
process_IN_Q_OVERFLOW method. The actions you can take on receiving
450
this event is either to update the variable max_queued_events in order
451
to handle more simultaneous events or to modify your code in order to
452
accomplish a better filtering diminishing the number of raised events.
453
Because this method is defined, IN_Q_OVERFLOW will never get
454
transmitted as arguments to process_default calls.
456
@param event: IN_Q_OVERFLOW event.
459
logging.warning('Event queue overflowed.')
461
def process_default(self, event):
463
Default processing event method. By default does nothing. Subclass
464
ProcessEvent and redefine this method in order to modify its behavior.
466
@param event: Event to be processed. Can be of any type of events but
467
IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
468
@type event: Event instance
473
class PrintAllEvents(ProcessEvent):
475
Dummy class used to print events strings representations. For instance this
476
class is used from command line to print all received events to stdout.
478
def my_init(self, out=None):
480
@param out: Where events will be written.
481
@type out: Object providing a valid file object interface.
487
def process_default(self, event):
489
Writes event string representation to file object provided to
492
@param event: Event to be processed. Can be of any type of events but
493
IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
494
@type event: Event instance
496
self._out.write(str(event))
497
self._out.write('\n')
501
class WatchManagerError(Exception):
503
WatchManager Exception. Raised on error encountered on watches
507
def __init__(self, msg, wmd):
509
@param msg: Exception string's description.
511
@param wmd: This dictionary contains the wd assigned to paths of the
512
same call for which watches were successfully added.
516
Exception.__init__(self, msg)