Merge lp:~diegosarmentero/ubuntuone-client/darwin-fsevents-1 into lp:ubuntuone-client

Proposed by Diego Sarmentero
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 1260
Merged at revision: 1264
Proposed branch: lp:~diegosarmentero/ubuntuone-client/darwin-fsevents-1
Merge into: lp:ubuntuone-client
Diff against target: 1298 lines (+589/-602)
6 files modified
contrib/testing/testcase.py (+7/-3)
tests/platform/filesystem_notifications/__init__.py (+55/-0)
tests/platform/filesystem_notifications/test_linux.py (+5/-53)
tests/platform/filesystem_notifications/test_pyinotify_agnostic.py (+4/-1)
tests/platform/filesystem_notifier/__init__.py (+0/-27)
ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py (+518/-518)
To merge this branch: bzr merge lp:~diegosarmentero/ubuntuone-client/darwin-fsevents-1
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Alejandro J. Cura (community) Approve
Review via email: mp+110382@code.launchpad.net

Commit message

- Some refactoring to support mac os filesystem notifications in the future (LP: #1013323).

To post a comment you must log in.
1259. By Diego Sarmentero

Fixing encoding problem on pyinotify_agnostic

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

The only real changes for pyinotify_agnostic are:
- Adding import sys

and:

805def simple(self, s, attribute):
806 + if isinstance(s, unicode):
807 + s = s.encode(sys.getfilesystemencoding(), 'replace')

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Nice branch so far, here's a small comment:
----
The decorators like: "skip_if_darwin_and_uses_metadata_older_than_5"
Instead of having a pair of decorators for both darwin and windows, I think they should be merged so they are called something like "skip_if_not_linux_and_uses..."

review: Needs Fixing
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> Nice branch so far, here's a small comment:
> ----
> The decorators like: "skip_if_darwin_and_uses_metadata_older_than_5"
> Instead of having a pair of decorators for both darwin and windows, I think
> they should be merged so they are called something like
> "skip_if_not_linux_and_uses..."

Fixed

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

Branch reverted, because we really need the two different skips for different situations.

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Here are some more comments that I didn't think of on my first review:

----

This first decorator makes sense; I'm not sure about the other two.
Anyway, the string in the first one is wrong:

+skip_if_darwin_and_uses_metadata_older_than_5 = \
+ skipIfOS('darwin',
+ 'In windows there is no need to migrate metadata older than v5.')

Do we know of any Read/Only issue on darwin to justify this decorator?

+skip_if_darwin_and_uses_readonly = \
+ skipIfOS('darwin', 'Can not test RO shares until bug #820350 is resolved.')

We may have some missing or out of order events in darwin, but we surely need to apply this decorator to the specific tests that fail with darwin, and we have to make sure we don't do a blanket decoration of the same test cases than in windows. In any case, we need a new bug with the darwin specifics.

+skip_if_darwin_missing_fs_event = \
+ skipIfOS('darwin', 'Fails due to missing/out of order FS events, '
+ 'see bug #820598.')

1260. By Diego Sarmentero

branch fixed according comments in the mp

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> Here are some more comments that I didn't think of on my first review:
>
> ----
>
> This first decorator makes sense; I'm not sure about the other two.
> Anyway, the string in the first one is wrong:
>
> +skip_if_darwin_and_uses_metadata_older_than_5 = \
> + skipIfOS('darwin',
> + 'In windows there is no need to migrate metadata older than
> v5.')
>
>
> Do we know of any Read/Only issue on darwin to justify this decorator?
>
> +skip_if_darwin_and_uses_readonly = \
> + skipIfOS('darwin', 'Can not test RO shares until bug #820350 is
> resolved.')
>
>
> We may have some missing or out of order events in darwin, but we surely need
> to apply this decorator to the specific tests that fail with darwin, and we
> have to make sure we don't do a blanket decoration of the same test cases than
> in windows. In any case, we need a new bug with the darwin specifics.
>
> +skip_if_darwin_missing_fs_event = \
> + skipIfOS('darwin', 'Fails due to missing/out of order FS events, '
> + 'see bug #820598.')

I removed the decorators that weren't being used, and leave "skip_if_darwin_missing_fs_event", because we are using it for some tests in futures branches of the darwin implementation.

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks good so far. +1

review: Approve
Revision history for this message
Manuel de la Peña (mandel) wrote :

Looks good, this will be nice to merge with my work so far.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'contrib/testing/testcase.py'
--- contrib/testing/testcase.py 2012-05-22 14:07:55 +0000
+++ contrib/testing/testcase.py 2012-06-21 17:37:46 +0000
@@ -197,7 +197,7 @@
197197
198 cancel_download = cancel_upload = download = upload = make_dir = disconnect198 cancel_download = cancel_upload = download = upload = make_dir = disconnect
199 make_file = move = unlink = list_shares = disconnect199 make_file = move = unlink = list_shares = disconnect
200 list_volumes = create_share = create_udf = inquire_free_space = disconnect200 list_volumes = create_share = create_udf = inquire_free_space = disconnect
201 inquire_account_info = delete_volume = change_public_access = disconnect201 inquire_account_info = delete_volume = change_public_access = disconnect
202 query_volumes = get_delta = rescan_from_scratch = delete_share = disconnect202 query_volumes = get_delta = rescan_from_scratch = delete_share = disconnect
203 node_is_with_queued_move = cleanup = get_public_files = disconnect203 node_is_with_queued_move = cleanup = get_public_files = disconnect
@@ -302,7 +302,7 @@
302 mmtree(path): support read-only shares302 mmtree(path): support read-only shares
303 makedirs(path): support read-only shares303 makedirs(path): support read-only shares
304 """304 """
305 MAX_FILENAME = 32 # some platforms limit lengths of filenames305 MAX_FILENAME = 32 # some platforms limit lengths of filenames
306 tunnel_runner_class = FakeTunnelRunner306 tunnel_runner_class = FakeTunnelRunner
307307
308 def mktemp(self, name='temp'):308 def mktemp(self, name='temp'):
@@ -448,7 +448,7 @@
448 def __init__(self, root_path):448 def __init__(self, root_path):
449 """ Creates the instance"""449 """ Creates the instance"""
450 self.root = volume_manager.Root(node_id="root_node_id", path=root_path)450 self.root = volume_manager.Root(node_id="root_node_id", path=root_path)
451 self.shares = {'':self.root}451 self.shares = {'': self.root}
452 self.udfs = {}452 self.udfs = {}
453 self.log = logging.getLogger('ubuntuone.SyncDaemon.VM-test')453 self.log = logging.getLogger('ubuntuone.SyncDaemon.VM-test')
454454
@@ -686,3 +686,7 @@
686skip_if_win32_missing_fs_event = \686skip_if_win32_missing_fs_event = \
687 skipIfOS('win32', 'Fails due to missing/out of order FS events, '687 skipIfOS('win32', 'Fails due to missing/out of order FS events, '
688 'see bug #820598.')688 'see bug #820598.')
689
690skip_if_darwin_missing_fs_event = \
691 skipIfOS('darwin', 'Fails due to missing/out of order FS events, '
692 'see bug #820598.')
689693
=== modified file 'tests/platform/filesystem_notifications/__init__.py'
--- tests/platform/filesystem_notifications/__init__.py 2012-04-30 15:19:03 +0000
+++ tests/platform/filesystem_notifications/__init__.py 2012-06-21 17:37:46 +0000
@@ -25,3 +25,58 @@
25# version. If you delete this exception statement from all source25# version. If you delete this exception statement from all source
26# files in the program, then also delete it here.26# files in the program, then also delete it here.
27"""Platform/File System Notifications test code."""27"""Platform/File System Notifications test code."""
28
29import logging
30
31from twisted.internet import defer
32
33from ubuntuone.syncdaemon import (
34 event_queue,
35 filesystem_manager,
36)
37from contrib.testing import testcase
38from ubuntuone.devtools.handlers import MementoHandler
39from ubuntuone.syncdaemon.tritcask import Tritcask
40
41
42class BaseFSMonitorTestCase(testcase.BaseTwistedTestCase):
43 """Test the structures where we have the path/watch."""
44
45 timeout = 3
46
47 @defer.inlineCallbacks
48 def setUp(self):
49 """Set up."""
50 yield super(BaseFSMonitorTestCase, self).setUp()
51 fsmdir = self.mktemp('fsmdir')
52 partials_dir = self.mktemp('partials_dir')
53 self.root_dir = self.mktemp('root_dir')
54 self.vm = testcase.FakeVolumeManager(self.root_dir)
55 self.tritcask_dir = self.mktemp("tritcask_dir")
56 self.db = Tritcask(self.tritcask_dir)
57 self.fs = filesystem_manager.FileSystemManager(fsmdir, partials_dir,
58 self.vm, self.db)
59 self.fs.create(path=self.root_dir, share_id='', is_dir=True)
60 self.fs.set_by_path(path=self.root_dir,
61 local_hash=None, server_hash=None)
62 eq = event_queue.EventQueue(self.fs)
63
64 self.deferred = deferred = defer.Deferred()
65
66 class HitMe(object):
67 # class-closure, cannot use self, pylint: disable-msg=E0213
68 def handle_default(innerself, event, **args):
69 deferred.callback(True)
70
71 eq.subscribe(HitMe())
72 self.monitor = eq.monitor
73 self.log_handler = MementoHandler()
74 self.log_handler.setLevel(logging.DEBUG)
75 self.monitor.log.addHandler(self.log_handler)
76
77 @defer.inlineCallbacks
78 def tearDown(self):
79 """Clean up the tests."""
80 self.monitor.shutdown()
81 self.monitor.log.removeHandler(self.log_handler)
82 yield super(BaseFSMonitorTestCase, self).tearDown()
2883
=== modified file 'tests/platform/filesystem_notifications/test_linux.py'
--- tests/platform/filesystem_notifications/test_linux.py 2012-05-23 13:06:42 +0000
+++ tests/platform/filesystem_notifications/test_linux.py 2012-06-21 17:37:46 +0000
@@ -30,23 +30,17 @@
30# files in the program, then also delete it here.30# files in the program, then also delete it here.
31"""Tests for the Event Queue."""31"""Tests for the Event Queue."""
3232
33import logging
34import os33import os
3534
36from twisted.internet import defer, reactor35from twisted.internet import defer, reactor
37from twisted.trial.unittest import TestCase as PlainTestCase36from twisted.trial.unittest import TestCase as PlainTestCase
3837
39from ubuntuone.syncdaemon import (
40 event_queue,
41 filesystem_manager,
42)
43from contrib.testing import testcase38from contrib.testing import testcase
44from ubuntuone.devtools.handlers import MementoHandler
45from ubuntuone.syncdaemon import volume_manager39from ubuntuone.syncdaemon import volume_manager
46from ubuntuone.syncdaemon.tritcask import Tritcask
47from ubuntuone.platform.filesystem_notifications import (40from ubuntuone.platform.filesystem_notifications import (
48 linux as filesystem_notifications,41 linux as filesystem_notifications,
49)42)
43from tests.platform.filesystem_notifications import BaseFSMonitorTestCase
5044
51# We normally access to private attribs in tests45# We normally access to private attribs in tests
52# pylint: disable=W021246# pylint: disable=W0212
@@ -101,50 +95,6 @@
101 self.assertFalse(processor.timer.active())95 self.assertFalse(processor.timer.active())
10296
10397
104
105class BaseFSMonitorTestCase(testcase.BaseTwistedTestCase):
106 """Test the structures where we have the path/watch."""
107
108 timeout = 3
109
110 @defer.inlineCallbacks
111 def setUp(self):
112 """Set up."""
113 yield super(BaseFSMonitorTestCase, self).setUp()
114 fsmdir = self.mktemp('fsmdir')
115 partials_dir = self.mktemp('partials_dir')
116 self.root_dir = self.mktemp('root_dir')
117 self.vm = testcase.FakeVolumeManager(self.root_dir)
118 self.tritcask_dir = self.mktemp("tritcask_dir")
119 self.db = Tritcask(self.tritcask_dir)
120 self.fs = filesystem_manager.FileSystemManager(fsmdir, partials_dir,
121 self.vm, self.db)
122 self.fs.create(path=self.root_dir, share_id='', is_dir=True)
123 self.fs.set_by_path(path=self.root_dir,
124 local_hash=None, server_hash=None)
125 eq = event_queue.EventQueue(self.fs)
126
127 self.deferred = deferred = defer.Deferred()
128
129 class HitMe(object):
130 # class-closure, cannot use self, pylint: disable-msg=E0213
131 def handle_default(innerself, event, **args):
132 deferred.callback(True)
133
134 eq.subscribe(HitMe())
135 self.monitor = eq.monitor
136 self.log_handler = MementoHandler()
137 self.log_handler.setLevel(logging.DEBUG)
138 self.monitor.log.addHandler(self.log_handler)
139
140 @defer.inlineCallbacks
141 def tearDown(self):
142 """Clean up the tests."""
143 self.monitor.shutdown()
144 self.monitor.log.removeHandler(self.log_handler)
145 yield super(BaseFSMonitorTestCase, self).tearDown()
146
147
148class WatchManagerTests(BaseFSMonitorTestCase):98class WatchManagerTests(BaseFSMonitorTestCase):
149 """Test the structures where we have the path/watch."""99 """Test the structures where we have the path/watch."""
150100
@@ -222,7 +172,8 @@
222 self.monitor._general_watchs = {'/path1/foo': 1, '/other': 2}172 self.monitor._general_watchs = {'/path1/foo': 1, '/other': 2}
223 self.monitor._ancestors_watchs = {'/foo': 3}173 self.monitor._ancestors_watchs = {'/foo': 3}
224 self.monitor.inotify_watch_fix('/path1/foo', '/path1/new')174 self.monitor.inotify_watch_fix('/path1/foo', '/path1/new')
225 self.assertEqual(self.monitor._general_watchs, {'/path1/new': 1, '/other': 2})175 self.assertEqual(self.monitor._general_watchs,
176 {'/path1/new': 1, '/other': 2})
226 self.assertEqual(self.monitor._ancestors_watchs, {'/foo': 3})177 self.assertEqual(self.monitor._ancestors_watchs, {'/foo': 3})
227178
228 def test_fix_path_ancestors(self):179 def test_fix_path_ancestors(self):
@@ -231,7 +182,8 @@
231 self.monitor._ancestors_watchs = {'/oth': 1, '/other': 2}182 self.monitor._ancestors_watchs = {'/oth': 1, '/other': 2}
232 self.monitor.inotify_watch_fix('/oth', '/baz')183 self.monitor.inotify_watch_fix('/oth', '/baz')
233 self.assertEqual(self.monitor._general_watchs, {'/bar': 3})184 self.assertEqual(self.monitor._general_watchs, {'/bar': 3})
234 self.assertEqual(self.monitor._ancestors_watchs, {'/baz': 1, '/other': 2})185 self.assertEqual(self.monitor._ancestors_watchs,
186 {'/baz': 1, '/other': 2})
235187
236188
237class DynamicHitMe(object):189class DynamicHitMe(object):
238190
=== renamed file 'tests/platform/filesystem_notifier/test_windows.py' => 'tests/platform/filesystem_notifications/test_pyinotify_agnostic.py'
--- tests/platform/filesystem_notifier/test_windows.py 2012-05-14 21:24:24 +0000
+++ tests/platform/filesystem_notifications/test_pyinotify_agnostic.py 2012-06-21 17:37:46 +0000
@@ -30,6 +30,8 @@
30# files in the program, then also delete it here.30# files in the program, then also delete it here.
31"""Test for the pyinotify implementation on windows."""31"""Test for the pyinotify implementation on windows."""
3232
33import sys
34
33from twisted.internet import defer35from twisted.internet import defer
34from twisted.trial.unittest import TestCase36from twisted.trial.unittest import TestCase
3537
@@ -51,7 +53,8 @@
51 attr = 'attribute'53 attr = 'attribute'
52 self.format[attr] = attr54 self.format[attr] = attr
53 value = u'ñoño'55 value = u'ñoño'
54 expected_result = (attr + value.encode('mbcs', 'replace') +56 expected_result = (attr + value.encode(
57 sys.getfilesystemencoding(), 'replace') +
55 self.format['normal'])58 self.format['normal'])
56 self.assertEqual(expected_result, self.formatter.simple(value, attr))59 self.assertEqual(expected_result, self.formatter.simple(value, attr))
5760
5861
=== removed directory 'tests/platform/filesystem_notifier'
=== removed file 'tests/platform/filesystem_notifier/__init__.py'
--- tests/platform/filesystem_notifier/__init__.py 2012-05-14 19:04:43 +0000
+++ tests/platform/filesystem_notifier/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,27 +0,0 @@
1# Copyright 2012 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# In addition, as a special exception, the copyright holders give
16# permission to link the code of portions of this program with the
17# OpenSSL library under certain conditions as described in each
18# individual source file, and distribute linked combinations
19# including the two.
20# You must obey the GNU General Public License in all respects
21# for all of the code used other than OpenSSL. If you modify
22# file(s) with this exception, you may extend this exception to your
23# version of the file(s), but you are not obligated to do so. If you
24# do not wish to do so, delete this exception statement from your
25# version. If you delete this exception statement from all source
26# files in the program, then also delete it here.
27"""Platform/File System Notifier (Pyinotify agnostic) test code."""
280
=== modified file 'ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py'
--- ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py 2012-05-14 20:38:23 +0000
+++ ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py 2012-06-21 17:37:46 +0000
@@ -1,518 +1,518 @@
1#!/usr/bin/env python1#!/usr/bin/env python
22
3# pyinotify.py - python interface to inotify3# pyinotify.py - python interface to inotify
4# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>4# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>
5#5#
6# Permission is hereby granted, free of charge, to any person obtaining a copy6# Permission is hereby granted, free of charge, to any person obtaining a copy
7# of this software and associated documentation files (the "Software"), to deal7# of this software and associated documentation files (the "Software"), to deal
8# in the Software without restriction, including without limitation the rights8# in the Software without restriction, including without limitation the rights
9# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell9# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10# copies of the Software, and to permit persons to whom the Software is10# copies of the Software, and to permit persons to whom the Software is
11# furnished to do so, subject to the following conditions:11# furnished to do so, subject to the following conditions:
12#12#
13# The above copyright notice and this permission notice shall be included in13# The above copyright notice and this permission notice shall be included in
14# all copies or substantial portions of the Software.14# all copies or substantial portions of the Software.
15#15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19# 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,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 IN21# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22# THE SOFTWARE.22# THE SOFTWARE.
23"""Platform agnostic code grabed from pyinotify."""23"""Platform agnostic code grabed from pyinotify."""
24import logging24import logging
25import os25import os
26import sys26import sys
2727
28COMPATIBILITY_MODE = False28COMPATIBILITY_MODE = False
2929
30class PyinotifyError(Exception):30class PyinotifyError(Exception):
31 """Indicates exceptions raised by a Pyinotify class."""31 """Indicates exceptions raised by a Pyinotify class."""
32 pass32 pass
3333
3434
35class RawOutputFormat:35class RawOutputFormat:
36 """36 """
37 Format string representations.37 Format string representations.
38 """38 """
39 def __init__(self, format=None):39 def __init__(self, format=None):
40 self.format = format or {}40 self.format = format or {}
4141
42 def simple(self, s, attribute):42 def simple(self, s, attribute):
43 if isinstance(s, unicode):43 if isinstance(s, unicode):
44 s = s.encode('mbcs', 'replace')44 s = s.encode(sys.getfilesystemencoding(), 'replace')
45 else:45 else:
46 s = str(s)46 s = str(s)
47 return (self.format.get(attribute, '') + s +47 return (self.format.get(attribute, '') + s +
48 self.format.get('normal', ''))48 self.format.get('normal', ''))
4949
50 def punctuation(self, s):50 def punctuation(self, s):
51 """Punctuation color."""51 """Punctuation color."""
52 return self.simple(s, 'normal')52 return self.simple(s, 'normal')
5353
54 def field_value(self, s):54 def field_value(self, s):
55 """Field value color."""55 """Field value color."""
56 return self.simple(s, 'purple')56 return self.simple(s, 'purple')
5757
58 def field_name(self, s):58 def field_name(self, s):
59 """Field name color."""59 """Field name color."""
60 return self.simple(s, 'blue')60 return self.simple(s, 'blue')
6161
62 def class_name(self, s):62 def class_name(self, s):
63 """Class name color."""63 """Class name color."""
64 return self.format.get('red', '') + self.simple(s, 'bold')64 return self.format.get('red', '') + self.simple(s, 'bold')
6565
66output_format = RawOutputFormat()66output_format = RawOutputFormat()
6767
6868
69class EventsCodes:69class EventsCodes:
70 """70 """
71 Set of codes corresponding to each kind of events.71 Set of codes corresponding to each kind of events.
72 Some of these flags are used to communicate with inotify, whereas72 Some of these flags are used to communicate with inotify, whereas
73 the others are sent to userspace by inotify notifying some events.73 the others are sent to userspace by inotify notifying some events.
7474
75 @cvar IN_ACCESS: File was accessed.75 @cvar IN_ACCESS: File was accessed.
76 @type IN_ACCESS: int76 @type IN_ACCESS: int
77 @cvar IN_MODIFY: File was modified.77 @cvar IN_MODIFY: File was modified.
78 @type IN_MODIFY: int78 @type IN_MODIFY: int
79 @cvar IN_ATTRIB: Metadata changed.79 @cvar IN_ATTRIB: Metadata changed.
80 @type IN_ATTRIB: int80 @type IN_ATTRIB: int
81 @cvar IN_CLOSE_WRITE: Writtable file was closed.81 @cvar IN_CLOSE_WRITE: Writtable file was closed.
82 @type IN_CLOSE_WRITE: int82 @type IN_CLOSE_WRITE: int
83 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.83 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
84 @type IN_CLOSE_NOWRITE: int84 @type IN_CLOSE_NOWRITE: int
85 @cvar IN_OPEN: File was opened.85 @cvar IN_OPEN: File was opened.
86 @type IN_OPEN: int86 @type IN_OPEN: int
87 @cvar IN_MOVED_FROM: File was moved from X.87 @cvar IN_MOVED_FROM: File was moved from X.
88 @type IN_MOVED_FROM: int88 @type IN_MOVED_FROM: int
89 @cvar IN_MOVED_TO: File was moved to Y.89 @cvar IN_MOVED_TO: File was moved to Y.
90 @type IN_MOVED_TO: int90 @type IN_MOVED_TO: int
91 @cvar IN_CREATE: Subfile was created.91 @cvar IN_CREATE: Subfile was created.
92 @type IN_CREATE: int92 @type IN_CREATE: int
93 @cvar IN_DELETE: Subfile was deleted.93 @cvar IN_DELETE: Subfile was deleted.
94 @type IN_DELETE: int94 @type IN_DELETE: int
95 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.95 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
96 @type IN_DELETE_SELF: int96 @type IN_DELETE_SELF: int
97 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.97 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
98 @type IN_MOVE_SELF: int98 @type IN_MOVE_SELF: int
99 @cvar IN_UNMOUNT: Backing fs was unmounted.99 @cvar IN_UNMOUNT: Backing fs was unmounted.
100 @type IN_UNMOUNT: int100 @type IN_UNMOUNT: int
101 @cvar IN_Q_OVERFLOW: Event queued overflowed.101 @cvar IN_Q_OVERFLOW: Event queued overflowed.
102 @type IN_Q_OVERFLOW: int102 @type IN_Q_OVERFLOW: int
103 @cvar IN_IGNORED: File was ignored.103 @cvar IN_IGNORED: File was ignored.
104 @type IN_IGNORED: int104 @type IN_IGNORED: int
105 @cvar IN_ONLYDIR: only watch the path if it is a directory (new105 @cvar IN_ONLYDIR: only watch the path if it is a directory (new
106 in kernel 2.6.15).106 in kernel 2.6.15).
107 @type IN_ONLYDIR: int107 @type IN_ONLYDIR: int
108 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).108 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
109 IN_ONLYDIR we can make sure that we don't watch109 IN_ONLYDIR we can make sure that we don't watch
110 the target of symlinks.110 the target of symlinks.
111 @type IN_DONT_FOLLOW: int111 @type IN_DONT_FOLLOW: int
112 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new112 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
113 in kernel 2.6.14).113 in kernel 2.6.14).
114 @type IN_MASK_ADD: int114 @type IN_MASK_ADD: int
115 @cvar IN_ISDIR: Event occurred against dir.115 @cvar IN_ISDIR: Event occurred against dir.
116 @type IN_ISDIR: int116 @type IN_ISDIR: int
117 @cvar IN_ONESHOT: Only send event once.117 @cvar IN_ONESHOT: Only send event once.
118 @type IN_ONESHOT: int118 @type IN_ONESHOT: int
119 @cvar ALL_EVENTS: Alias for considering all of the events.119 @cvar ALL_EVENTS: Alias for considering all of the events.
120 @type ALL_EVENTS: int120 @type ALL_EVENTS: int
121 """121 """
122122
123 # The idea here is 'configuration-as-code' - this way, we get123 # The idea here is 'configuration-as-code' - this way, we get
124 # our nice class constants, but we also get nice human-friendly text124 # our nice class constants, but we also get nice human-friendly text
125 # mappings to do lookups against as well, for free:125 # mappings to do lookups against as well, for free:
126 FLAG_COLLECTIONS = {'OP_FLAGS': {126 FLAG_COLLECTIONS = {'OP_FLAGS': {
127 'IN_ACCESS' : 0x00000001, # File was accessed127 'IN_ACCESS' : 0x00000001, # File was accessed
128 'IN_MODIFY' : 0x00000002, # File was modified128 'IN_MODIFY' : 0x00000002, # File was modified
129 'IN_ATTRIB' : 0x00000004, # Metadata changed129 'IN_ATTRIB' : 0x00000004, # Metadata changed
130 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed130 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
131 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed131 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
132 'IN_OPEN' : 0x00000020, # File was opened132 'IN_OPEN' : 0x00000020, # File was opened
133 'IN_MOVED_FROM' : 0x00000040, # File was moved from X133 'IN_MOVED_FROM' : 0x00000040, # File was moved from X
134 'IN_MOVED_TO' : 0x00000080, # File was moved to Y134 'IN_MOVED_TO' : 0x00000080, # File was moved to Y
135 'IN_CREATE' : 0x00000100, # Subfile was created135 'IN_CREATE' : 0x00000100, # Subfile was created
136 'IN_DELETE' : 0x00000200, # Subfile was deleted136 'IN_DELETE' : 0x00000200, # Subfile was deleted
137 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)137 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)
138 # was deleted138 # was deleted
139 'IN_MOVE_SELF' : 0x00000800, # Self(watched item itself) was moved139 'IN_MOVE_SELF' : 0x00000800, # Self(watched item itself) was moved
140 },140 },
141 'EVENT_FLAGS': {141 'EVENT_FLAGS': {
142 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted142 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
143 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed143 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
144 'IN_IGNORED' : 0x00008000, # File was ignored144 'IN_IGNORED' : 0x00008000, # File was ignored
145 },145 },
146 'SPECIAL_FLAGS': {146 'SPECIAL_FLAGS': {
147 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a147 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a
148 # directory148 # directory
149 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink149 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
150 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already150 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already
151 # existing watch151 # existing watch
152 'IN_ISDIR' : 0x40000000, # event occurred against dir152 'IN_ISDIR' : 0x40000000, # event occurred against dir
153 'IN_ONESHOT' : 0x80000000, # only send event once153 'IN_ONESHOT' : 0x80000000, # only send event once
154 },154 },
155 }155 }
156156
157 def maskname(mask):157 def maskname(mask):
158 """158 """
159 Returns the event name associated to mask. IN_ISDIR is appended to159 Returns the event name associated to mask. IN_ISDIR is appended to
160 the result when appropriate. Note: only one event is returned, because160 the result when appropriate. Note: only one event is returned, because
161 only one event can be raised at a given time.161 only one event can be raised at a given time.
162162
163 @param mask: mask.163 @param mask: mask.
164 @type mask: int164 @type mask: int
165 @return: event name.165 @return: event name.
166 @rtype: str166 @rtype: str
167 """167 """
168 ms = mask168 ms = mask
169 name = '%s'169 name = '%s'
170 if mask & IN_ISDIR:170 if mask & IN_ISDIR:
171 ms = mask - IN_ISDIR171 ms = mask - IN_ISDIR
172 name = '%s|IN_ISDIR'172 name = '%s|IN_ISDIR'
173 return name % EventsCodes.ALL_VALUES[ms]173 return name % EventsCodes.ALL_VALUES[ms]
174174
175 maskname = staticmethod(maskname)175 maskname = staticmethod(maskname)
176176
177177
178# So let's now turn the configuration into code178# So let's now turn the configuration into code
179EventsCodes.ALL_FLAGS = {}179EventsCodes.ALL_FLAGS = {}
180EventsCodes.ALL_VALUES = {}180EventsCodes.ALL_VALUES = {}
181for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():181for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():
182 # Make the collections' members directly accessible through the182 # Make the collections' members directly accessible through the
183 # class dictionary183 # class dictionary
184 setattr(EventsCodes, flagc, valc)184 setattr(EventsCodes, flagc, valc)
185185
186 # Collect all the flags under a common umbrella186 # Collect all the flags under a common umbrella
187 EventsCodes.ALL_FLAGS.update(valc)187 EventsCodes.ALL_FLAGS.update(valc)
188188
189 # Make the individual masks accessible as 'constants' at globals() scope189 # Make the individual masks accessible as 'constants' at globals() scope
190 # and masknames accessible by values.190 # and masknames accessible by values.
191 for name, val in valc.items():191 for name, val in valc.items():
192 globals()[name] = val192 globals()[name] = val
193 EventsCodes.ALL_VALUES[val] = name193 EventsCodes.ALL_VALUES[val] = name
194194
195195
196# all 'normal' events196# all 'normal' events
197ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())197ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())
198EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS198EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
199EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'199EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
200200
201201
202class _Event:202class _Event:
203 """203 """
204 Event structure, represent events raised by the system. This204 Event structure, represent events raised by the system. This
205 is the base class and should be subclassed.205 is the base class and should be subclassed.
206206
207 """207 """
208 def __init__(self, dict_):208 def __init__(self, dict_):
209 """209 """
210 Attach attributes (contained in dict_) to self.210 Attach attributes (contained in dict_) to self.
211211
212 @param dict_: Set of attributes.212 @param dict_: Set of attributes.
213 @type dict_: dictionary213 @type dict_: dictionary
214 """214 """
215 for tpl in dict_.items():215 for tpl in dict_.items():
216 setattr(self, *tpl)216 setattr(self, *tpl)
217217
218 def __repr__(self):218 def __repr__(self):
219 """219 """
220 @return: Generic event string representation.220 @return: Generic event string representation.
221 @rtype: str221 @rtype: str
222 """222 """
223 s = ''223 s = ''
224 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):224 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
225 if attr.startswith('_'):225 if attr.startswith('_'):
226 continue226 continue
227 if attr == 'mask':227 if attr == 'mask':
228 value = hex(getattr(self, attr))228 value = hex(getattr(self, attr))
229 elif isinstance(value, basestring) and not value:229 elif isinstance(value, basestring) and not value:
230 value = "''"230 value = "''"
231 s += ' %s%s%s' % (output_format.field_name(attr),231 s += ' %s%s%s' % (output_format.field_name(attr),
232 output_format.punctuation('='),232 output_format.punctuation('='),
233 output_format.field_value(value))233 output_format.field_value(value))
234234
235 s = '%s%s%s %s' % (output_format.punctuation('<'),235 s = '%s%s%s %s' % (output_format.punctuation('<'),
236 output_format.class_name(self.__class__.__name__),236 output_format.class_name(self.__class__.__name__),
237 s,237 s,
238 output_format.punctuation('>'))238 output_format.punctuation('>'))
239 return s239 return s
240240
241 def __str__(self):241 def __str__(self):
242 return repr(self)242 return repr(self)
243243
244244
245class _RawEvent(_Event):245class _RawEvent(_Event):
246 """246 """
247 Raw event, it contains only the informations provided by the system.247 Raw event, it contains only the informations provided by the system.
248 It doesn't infer anything.248 It doesn't infer anything.
249 """249 """
250 def __init__(self, wd, mask, cookie, name):250 def __init__(self, wd, mask, cookie, name):
251 """251 """
252 @param wd: Watch Descriptor.252 @param wd: Watch Descriptor.
253 @type wd: int253 @type wd: int
254 @param mask: Bitmask of events.254 @param mask: Bitmask of events.
255 @type mask: int255 @type mask: int
256 @param cookie: Cookie.256 @param cookie: Cookie.
257 @type cookie: int257 @type cookie: int
258 @param name: Basename of the file or directory against which the258 @param name: Basename of the file or directory against which the
259 event was raised in case where the watched directory259 event was raised in case where the watched directory
260 is the parent directory. None if the event was raised260 is the parent directory. None if the event was raised
261 on the watched item itself.261 on the watched item itself.
262 @type name: string or None262 @type name: string or None
263 """263 """
264 # Use this variable to cache the result of str(self), this object264 # Use this variable to cache the result of str(self), this object
265 # is immutable.265 # is immutable.
266 self._str = None266 self._str = None
267 # name: remove trailing '\0'267 # name: remove trailing '\0'
268 d = {'wd': wd,268 d = {'wd': wd,
269 'mask': mask,269 'mask': mask,
270 'cookie': cookie,270 'cookie': cookie,
271 'name': name.rstrip('\0')}271 'name': name.rstrip('\0')}
272 _Event.__init__(self, d)272 _Event.__init__(self, d)
273 logging.debug(str(self))273 logging.debug(str(self))
274274
275 def __str__(self):275 def __str__(self):
276 if self._str is None:276 if self._str is None:
277 self._str = _Event.__str__(self)277 self._str = _Event.__str__(self)
278 return self._str278 return self._str
279279
280280
281class Event(_Event):281class Event(_Event):
282 """282 """
283 This class contains all the useful informations about the observed283 This class contains all the useful informations about the observed
284 event. However, the presence of each field is not guaranteed and284 event. However, the presence of each field is not guaranteed and
285 depends on the type of event. In effect, some fields are irrelevant285 depends on the type of event. In effect, some fields are irrelevant
286 for some kind of event (for example 'cookie' is meaningless for286 for some kind of event (for example 'cookie' is meaningless for
287 IN_CREATE whereas it is mandatory for IN_MOVE_TO).287 IN_CREATE whereas it is mandatory for IN_MOVE_TO).
288288
289 The possible fields are:289 The possible fields are:
290 - wd (int): Watch Descriptor.290 - wd (int): Watch Descriptor.
291 - mask (int): Mask.291 - mask (int): Mask.
292 - maskname (str): Readable event name.292 - maskname (str): Readable event name.
293 - path (str): path of the file or directory being watched.293 - path (str): path of the file or directory being watched.
294 - name (str): Basename of the file or directory against which the294 - name (str): Basename of the file or directory against which the
295 event was raised in case where the watched directory295 event was raised in case where the watched directory
296 is the parent directory. None if the event was raised296 is the parent directory. None if the event was raised
297 on the watched item itself. This field is always provided297 on the watched item itself. This field is always provided
298 even if the string is ''.298 even if the string is ''.
299 - pathname (str): Concatenation of 'path' and 'name'.299 - pathname (str): Concatenation of 'path' and 'name'.
300 - src_pathname (str): Only present for IN_MOVED_TO events and only in300 - src_pathname (str): Only present for IN_MOVED_TO events and only in
301 the case where IN_MOVED_FROM events are watched too. Holds the301 the case where IN_MOVED_FROM events are watched too. Holds the
302 source pathname from where pathname was moved from.302 source pathname from where pathname was moved from.
303 - cookie (int): Cookie.303 - cookie (int): Cookie.
304 - dir (bool): True if the event was raised against a directory.304 - dir (bool): True if the event was raised against a directory.
305305
306 """306 """
307 def __init__(self, raw):307 def __init__(self, raw):
308 """308 """
309 Concretely, this is the raw event plus inferred infos.309 Concretely, this is the raw event plus inferred infos.
310 """310 """
311 _Event.__init__(self, raw)311 _Event.__init__(self, raw)
312 self.maskname = EventsCodes.maskname(self.mask)312 self.maskname = EventsCodes.maskname(self.mask)
313 if COMPATIBILITY_MODE:313 if COMPATIBILITY_MODE:
314 self.event_name = self.maskname314 self.event_name = self.maskname
315 try:315 try:
316 if self.name:316 if self.name:
317 self.pathname = os.path.abspath(os.path.join(self.path,317 self.pathname = os.path.abspath(os.path.join(self.path,
318 self.name))318 self.name))
319 else:319 else:
320 self.pathname = os.path.abspath(self.path)320 self.pathname = os.path.abspath(self.path)
321 except AttributeError, err:321 except AttributeError, err:
322 # Usually it is not an error some events are perfectly valids322 # Usually it is not an error some events are perfectly valids
323 # despite the lack of these attributes.323 # despite the lack of these attributes.
324 logging.debug(err)324 logging.debug(err)
325325
326326
327class ProcessEventError(PyinotifyError):327class ProcessEventError(PyinotifyError):
328 """328 """
329 ProcessEventError Exception. Raised on ProcessEvent error.329 ProcessEventError Exception. Raised on ProcessEvent error.
330 """330 """
331 def __init__(self, err):331 def __init__(self, err):
332 """332 """
333 @param err: Exception error description.333 @param err: Exception error description.
334 @type err: string334 @type err: string
335 """335 """
336 PyinotifyError.__init__(self, err)336 PyinotifyError.__init__(self, err)
337337
338338
339class _ProcessEvent:339class _ProcessEvent:
340 """340 """
341 Abstract processing event class.341 Abstract processing event class.
342 """342 """
343 def __call__(self, event):343 def __call__(self, event):
344 """344 """
345 To behave like a functor the object must be callable.345 To behave like a functor the object must be callable.
346 This method is a dispatch method. Its lookup order is:346 This method is a dispatch method. Its lookup order is:
347 1. process_MASKNAME method347 1. process_MASKNAME method
348 2. process_FAMILY_NAME method348 2. process_FAMILY_NAME method
349 3. otherwise calls process_default349 3. otherwise calls process_default
350350
351 @param event: Event to be processed.351 @param event: Event to be processed.
352 @type event: Event object352 @type event: Event object
353 @return: By convention when used from the ProcessEvent class:353 @return: By convention when used from the ProcessEvent class:
354 - Returning False or None (default value) means keep on354 - Returning False or None (default value) means keep on
355 executing next chained functors (see chain.py example).355 executing next chained functors (see chain.py example).
356 - Returning True instead means do not execute next356 - Returning True instead means do not execute next
357 processing functions.357 processing functions.
358 @rtype: bool358 @rtype: bool
359 @raise ProcessEventError: Event object undispatchable,359 @raise ProcessEventError: Event object undispatchable,
360 unknown event.360 unknown event.
361 """361 """
362 stripped_mask = event.mask - (event.mask & IN_ISDIR)362 stripped_mask = event.mask - (event.mask & IN_ISDIR)
363 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)363 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
364 if maskname is None:364 if maskname is None:
365 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)365 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
366366
367 # 1- look for process_MASKNAME367 # 1- look for process_MASKNAME
368 meth = getattr(self, 'process_' + maskname, None)368 meth = getattr(self, 'process_' + maskname, None)
369 if meth is not None:369 if meth is not None:
370 return meth(event)370 return meth(event)
371 # 2- look for process_FAMILY_NAME371 # 2- look for process_FAMILY_NAME
372 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)372 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
373 if meth is not None:373 if meth is not None:
374 return meth(event)374 return meth(event)
375 # 3- default call method process_default375 # 3- default call method process_default
376 return self.process_default(event)376 return self.process_default(event)
377377
378 def __repr__(self):378 def __repr__(self):
379 return '<%s>' % self.__class__.__name__379 return '<%s>' % self.__class__.__name__
380380
381381
382class ProcessEvent(_ProcessEvent):382class ProcessEvent(_ProcessEvent):
383 """383 """
384 Process events objects, can be specialized via subclassing, thus its384 Process events objects, can be specialized via subclassing, thus its
385 behavior can be overriden:385 behavior can be overriden:
386386
387 Note: you should not override __init__ in your subclass instead define387 Note: you should not override __init__ in your subclass instead define
388 a my_init() method, this method will be called automatically from the388 a my_init() method, this method will be called automatically from the
389 constructor of this class with its optionals parameters.389 constructor of this class with its optionals parameters.
390390
391 1. Provide specialized individual methods, e.g. process_IN_DELETE for391 1. Provide specialized individual methods, e.g. process_IN_DELETE for
392 processing a precise type of event (e.g. IN_DELETE in this case).392 processing a precise type of event (e.g. IN_DELETE in this case).
393 2. Or/and provide methods for processing events by 'family', e.g.393 2. Or/and provide methods for processing events by 'family', e.g.
394 process_IN_CLOSE method will process both IN_CLOSE_WRITE and394 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
395 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and395 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
396 process_IN_CLOSE_NOWRITE aren't defined though).396 process_IN_CLOSE_NOWRITE aren't defined though).
397 3. Or/and override process_default for catching and processing all397 3. Or/and override process_default for catching and processing all
398 the remaining types of events.398 the remaining types of events.
399 """399 """
400 pevent = None400 pevent = None
401401
402 def __init__(self, pevent=None, **kargs):402 def __init__(self, pevent=None, **kargs):
403 """403 """
404 Enable chaining of ProcessEvent instances.404 Enable chaining of ProcessEvent instances.
405405
406 @param pevent: Optional callable object, will be called on event406 @param pevent: Optional callable object, will be called on event
407 processing (before self).407 processing (before self).
408 @type pevent: callable408 @type pevent: callable
409 @param kargs: This constructor is implemented as a template method409 @param kargs: This constructor is implemented as a template method
410 delegating its optionals keyworded arguments to the410 delegating its optionals keyworded arguments to the
411 method my_init().411 method my_init().
412 @type kargs: dict412 @type kargs: dict
413 """413 """
414 self.pevent = pevent414 self.pevent = pevent
415 self.my_init(**kargs)415 self.my_init(**kargs)
416416
417 def my_init(self, **kargs):417 def my_init(self, **kargs):
418 """418 """
419 This method is called from ProcessEvent.__init__(). This method is419 This method is called from ProcessEvent.__init__(). This method is
420 empty here and must be redefined to be useful. In effect, if you420 empty here and must be redefined to be useful. In effect, if you
421 need to specifically initialize your subclass' instance then you421 need to specifically initialize your subclass' instance then you
422 just have to override this method in your subclass. Then all the422 just have to override this method in your subclass. Then all the
423 keyworded arguments passed to ProcessEvent.__init__() will be423 keyworded arguments passed to ProcessEvent.__init__() will be
424 transmitted as parameters to this method. Beware you MUST pass424 transmitted as parameters to this method. Beware you MUST pass
425 keyword arguments though.425 keyword arguments though.
426426
427 @param kargs: optional delegated arguments from __init__().427 @param kargs: optional delegated arguments from __init__().
428 @type kargs: dict428 @type kargs: dict
429 """429 """
430 pass430 pass
431431
432 def __call__(self, event):432 def __call__(self, event):
433 stop_chaining = False433 stop_chaining = False
434 if self.pevent is not None:434 if self.pevent is not None:
435 # By default methods return None so we set as guideline435 # By default methods return None so we set as guideline
436 # that methods asking for stop chaining must explicitely436 # that methods asking for stop chaining must explicitely
437 # return non None or non False values, otherwise the default437 # return non None or non False values, otherwise the default
438 # behavior will be to accept chain call to the corresponding438 # behavior will be to accept chain call to the corresponding
439 # local method.439 # local method.
440 stop_chaining = self.pevent(event)440 stop_chaining = self.pevent(event)
441 if not stop_chaining:441 if not stop_chaining:
442 return _ProcessEvent.__call__(self, event)442 return _ProcessEvent.__call__(self, event)
443443
444 def nested_pevent(self):444 def nested_pevent(self):
445 return self.pevent445 return self.pevent
446446
447 def process_IN_Q_OVERFLOW(self, event):447 def process_IN_Q_OVERFLOW(self, event):
448 """448 """
449 By default this method only reports warning messages, you can449 By default this method only reports warning messages, you can
450 overredide it by subclassing ProcessEvent and implement your own450 overredide it by subclassing ProcessEvent and implement your own
451 process_IN_Q_OVERFLOW method. The actions you can take on receiving451 process_IN_Q_OVERFLOW method. The actions you can take on receiving
452 this event is either to update the variable max_queued_events in order452 this event is either to update the variable max_queued_events in order
453 to handle more simultaneous events or to modify your code in order to453 to handle more simultaneous events or to modify your code in order to
454 accomplish a better filtering diminishing the number of raised events.454 accomplish a better filtering diminishing the number of raised events.
455 Because this method is defined, IN_Q_OVERFLOW will never get455 Because this method is defined, IN_Q_OVERFLOW will never get
456 transmitted as arguments to process_default calls.456 transmitted as arguments to process_default calls.
457457
458 @param event: IN_Q_OVERFLOW event.458 @param event: IN_Q_OVERFLOW event.
459 @type event: dict459 @type event: dict
460 """460 """
461 logging.warning('Event queue overflowed.')461 logging.warning('Event queue overflowed.')
462462
463 def process_default(self, event):463 def process_default(self, event):
464 """464 """
465 Default processing event method. By default does nothing. Subclass465 Default processing event method. By default does nothing. Subclass
466 ProcessEvent and redefine this method in order to modify its behavior.466 ProcessEvent and redefine this method in order to modify its behavior.
467467
468 @param event: Event to be processed. Can be of any type of events but468 @param event: Event to be processed. Can be of any type of events but
469 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).469 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
470 @type event: Event instance470 @type event: Event instance
471 """471 """
472 pass472 pass
473473
474474
475class PrintAllEvents(ProcessEvent):475class PrintAllEvents(ProcessEvent):
476 """476 """
477 Dummy class used to print events strings representations. For instance this477 Dummy class used to print events strings representations. For instance this
478 class is used from command line to print all received events to stdout.478 class is used from command line to print all received events to stdout.
479 """479 """
480 def my_init(self, out=None):480 def my_init(self, out=None):
481 """481 """
482 @param out: Where events will be written.482 @param out: Where events will be written.
483 @type out: Object providing a valid file object interface.483 @type out: Object providing a valid file object interface.
484 """484 """
485 if out is None:485 if out is None:
486 out = sys.stdout486 out = sys.stdout
487 self._out = out487 self._out = out
488488
489 def process_default(self, event):489 def process_default(self, event):
490 """490 """
491 Writes event string representation to file object provided to491 Writes event string representation to file object provided to
492 my_init().492 my_init().
493493
494 @param event: Event to be processed. Can be of any type of events but494 @param event: Event to be processed. Can be of any type of events but
495 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).495 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
496 @type event: Event instance496 @type event: Event instance
497 """497 """
498 self._out.write(str(event))498 self._out.write(str(event))
499 self._out.write('\n')499 self._out.write('\n')
500 self._out.flush()500 self._out.flush()
501501
502502
503class WatchManagerError(Exception):503class WatchManagerError(Exception):
504 """504 """
505 WatchManager Exception. Raised on error encountered on watches505 WatchManager Exception. Raised on error encountered on watches
506 operations.506 operations.
507507
508 """508 """
509 def __init__(self, msg, wmd):509 def __init__(self, msg, wmd):
510 """510 """
511 @param msg: Exception string's description.511 @param msg: Exception string's description.
512 @type msg: string512 @type msg: string
513 @param wmd: This dictionary contains the wd assigned to paths of the513 @param wmd: This dictionary contains the wd assigned to paths of the
514 same call for which watches were successfully added.514 same call for which watches were successfully added.
515 @type wmd: dict515 @type wmd: dict
516 """516 """
517 self.wmd = wmd517 self.wmd = wmd
518 Exception.__init__(self, msg)518 Exception.__init__(self, msg)

Subscribers

People subscribed via source and target branches