Merge lp:~dobey/ubuntuone-client/update-4-0 into lp:ubuntuone-client/stable-4-0

Proposed by dobey on 2012-06-25
Status: Merged
Approved by: dobey on 2012-06-25
Approved revision: no longer in the source branch.
Merged at revision: 1250
Proposed branch: lp:~dobey/ubuntuone-client/update-4-0
Merge into: lp:ubuntuone-client/stable-4-0
Diff against target: 1672 lines (+619/-676)
23 files modified
bin/ubuntuone-syncdaemon (+1/-1)
contrib/dump_metadata.py (+1/-1)
contrib/testing/testcase.py (+8/-4)
data/source_ubuntuone-client.py (+1/-1)
data/syncdaemon.conf (+1/-0)
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)
tests/platform/test_logger.py (+3/-5)
tests/syncdaemon/test_config.py (+2/-2)
tests/syncdaemon/test_main.py (+1/-1)
ubuntuone/platform/__init__.py (+2/-2)
ubuntuone/platform/credentials/__init__.py (+1/-4)
ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py (+518/-518)
ubuntuone/platform/logger/__init__.py (+7/-0)
ubuntuone/platform/os_helper/darwin.py (+1/-1)
ubuntuone/platform/xdg_base_directory/__init__.py (+0/-40)
ubuntuone/proxy/logger.py (+1/-1)
ubuntuone/status/logger.py (+1/-4)
ubuntuone/syncdaemon/config.py (+2/-4)
ubuntuone/syncdaemon/logger.py (+1/-3)
ubuntuone/syncdaemon/main.py (+3/-3)
To merge this branch: bzr merge lp:~dobey/ubuntuone-client/update-4-0
Reviewer Review Type Date Requested Status
Brian Curtin (community) Approve on 2012-06-25
Mike McCracken (community) 2012-06-25 Approve on 2012-06-25
Review via email: mp+111939@code.launchpad.net

Commit Message

[Diego Sarmentero]

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

[Rodney Dawes]

    - Avoid using ubuntu_sso.xdg_base_directory.
    - Move ubuntuone_log_dir to ubuntuone.platform.logger.
    - Remove ubuntuone.platform.xdg_base_directory.

[Manuel de la Pena]

    - Added the spotlight pattern to the ignore list (LP: #1015137).
    - Do not raise exception in set_application_name so that the sys_init_done signal is pushed
      to the state machine (LP: #1015003).

To post a comment you must log in.
Mike McCracken (mikemc) wrote :

ok

review: Approve
review: Approve
1250. By Diego Sarmentero on 2012-06-25

[Diego Sarmentero]

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

[Rodney Dawes]

    - Avoid using ubuntu_sso.xdg_base_directory.
    - Move ubuntuone_log_dir to ubuntuone.platform.logger.
    - Remove ubuntuone.platform.xdg_base_directory.

[Manuel de la Pena]

    - Added the spotlight pattern to the ignore list (LP: #1015137).
    - Do not raise exception in set_application_name so that the sys_init_done signal is pushed
      to the state machine (LP: #1015003).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/ubuntuone-syncdaemon'
2--- bin/ubuntuone-syncdaemon 2012-04-09 20:07:05 +0000
3+++ bin/ubuntuone-syncdaemon 2012-06-25 20:40:24 +0000
4@@ -64,7 +64,7 @@
5 from ubuntuone.syncdaemon.main import Main
6
7 from twisted.internet import reactor, defer
8-from ubuntu_sso.xdg_base_directory import (
9+from dirspec.basedir import (
10 xdg_cache_home,
11 xdg_data_home,
12 )
13
14=== modified file 'contrib/dump_metadata.py'
15--- contrib/dump_metadata.py 2012-04-09 20:07:05 +0000
16+++ contrib/dump_metadata.py 2012-06-25 20:40:24 +0000
17@@ -44,7 +44,7 @@
18 tritcask,
19 volume_manager,
20 )
21-from ubuntu_sso.xdg_base_directory import (
22+from dirspec.basedir import (
23 xdg_cache_home,
24 xdg_data_home,
25 )
26
27=== modified file 'contrib/testing/testcase.py'
28--- contrib/testing/testcase.py 2012-05-22 14:07:55 +0000
29+++ contrib/testing/testcase.py 2012-06-25 20:40:24 +0000
30@@ -197,7 +197,7 @@
31
32 cancel_download = cancel_upload = download = upload = make_dir = disconnect
33 make_file = move = unlink = list_shares = disconnect
34- list_volumes = create_share = create_udf = inquire_free_space = disconnect
35+ list_volumes = create_share = create_udf = inquire_free_space = disconnect
36 inquire_account_info = delete_volume = change_public_access = disconnect
37 query_volumes = get_delta = rescan_from_scratch = delete_share = disconnect
38 node_is_with_queued_move = cleanup = get_public_files = disconnect
39@@ -302,7 +302,7 @@
40 mmtree(path): support read-only shares
41 makedirs(path): support read-only shares
42 """
43- MAX_FILENAME = 32 # some platforms limit lengths of filenames
44+ MAX_FILENAME = 32 # some platforms limit lengths of filenames
45 tunnel_runner_class = FakeTunnelRunner
46
47 def mktemp(self, name='temp'):
48@@ -407,7 +407,7 @@
49 self._testMethodName)
50 # Patch the user home
51 self.home_dir = self.mktemp('ubuntuonehacker')
52- self.patch(platform, "xdg_home", self.home_dir)
53+ self.patch(platform, "user_home", self.home_dir)
54 self.patch(action_queue.tunnel_runner, "TunnelRunner",
55 self.tunnel_runner_class)
56
57@@ -448,7 +448,7 @@
58 def __init__(self, root_path):
59 """ Creates the instance"""
60 self.root = volume_manager.Root(node_id="root_node_id", path=root_path)
61- self.shares = {'':self.root}
62+ self.shares = {'': self.root}
63 self.udfs = {}
64 self.log = logging.getLogger('ubuntuone.SyncDaemon.VM-test')
65
66@@ -686,3 +686,7 @@
67 skip_if_win32_missing_fs_event = \
68 skipIfOS('win32', 'Fails due to missing/out of order FS events, '
69 'see bug #820598.')
70+
71+skip_if_darwin_missing_fs_event = \
72+ skipIfOS('darwin', 'Fails due to missing/out of order FS events, '
73+ 'see bug #820598.')
74
75=== modified file 'data/source_ubuntuone-client.py'
76--- data/source_ubuntuone-client.py 2012-04-09 20:07:05 +0000
77+++ data/source_ubuntuone-client.py 2012-06-25 20:40:24 +0000
78@@ -33,7 +33,7 @@
79 import apport
80 from apport.hookutils import attach_file_if_exists
81 import os.path
82-from xdg.BaseDirectory import xdg_cache_home, xdg_config_home
83+from dirspec.basedir import xdg_cache_home, xdg_config_home
84
85 # Paths where things we might want live
86 u1_log_path = os.path.join(xdg_cache_home, "ubuntuone", "log")
87
88=== modified file 'data/syncdaemon.conf'
89--- data/syncdaemon.conf 2012-06-13 17:09:19 +0000
90+++ data/syncdaemon.conf 2012-06-25 20:40:24 +0000
91@@ -85,6 +85,7 @@
92 \A\..*\.tmp\Z
93 \A\.~lock\..*#\Z
94 \A\.goutputstream-.*\Z
95+ \A.*-Spotlight\Z
96
97 use_trash.default = True
98 use_trash.parser = bool
99
100=== modified file 'tests/platform/filesystem_notifications/__init__.py'
101--- tests/platform/filesystem_notifications/__init__.py 2012-04-30 15:19:03 +0000
102+++ tests/platform/filesystem_notifications/__init__.py 2012-06-25 20:40:24 +0000
103@@ -25,3 +25,58 @@
104 # version. If you delete this exception statement from all source
105 # files in the program, then also delete it here.
106 """Platform/File System Notifications test code."""
107+
108+import logging
109+
110+from twisted.internet import defer
111+
112+from ubuntuone.syncdaemon import (
113+ event_queue,
114+ filesystem_manager,
115+)
116+from contrib.testing import testcase
117+from ubuntuone.devtools.handlers import MementoHandler
118+from ubuntuone.syncdaemon.tritcask import Tritcask
119+
120+
121+class BaseFSMonitorTestCase(testcase.BaseTwistedTestCase):
122+ """Test the structures where we have the path/watch."""
123+
124+ timeout = 3
125+
126+ @defer.inlineCallbacks
127+ def setUp(self):
128+ """Set up."""
129+ yield super(BaseFSMonitorTestCase, self).setUp()
130+ fsmdir = self.mktemp('fsmdir')
131+ partials_dir = self.mktemp('partials_dir')
132+ self.root_dir = self.mktemp('root_dir')
133+ self.vm = testcase.FakeVolumeManager(self.root_dir)
134+ self.tritcask_dir = self.mktemp("tritcask_dir")
135+ self.db = Tritcask(self.tritcask_dir)
136+ self.fs = filesystem_manager.FileSystemManager(fsmdir, partials_dir,
137+ self.vm, self.db)
138+ self.fs.create(path=self.root_dir, share_id='', is_dir=True)
139+ self.fs.set_by_path(path=self.root_dir,
140+ local_hash=None, server_hash=None)
141+ eq = event_queue.EventQueue(self.fs)
142+
143+ self.deferred = deferred = defer.Deferred()
144+
145+ class HitMe(object):
146+ # class-closure, cannot use self, pylint: disable-msg=E0213
147+ def handle_default(innerself, event, **args):
148+ deferred.callback(True)
149+
150+ eq.subscribe(HitMe())
151+ self.monitor = eq.monitor
152+ self.log_handler = MementoHandler()
153+ self.log_handler.setLevel(logging.DEBUG)
154+ self.monitor.log.addHandler(self.log_handler)
155+
156+ @defer.inlineCallbacks
157+ def tearDown(self):
158+ """Clean up the tests."""
159+ self.monitor.shutdown()
160+ self.monitor.log.removeHandler(self.log_handler)
161+ yield super(BaseFSMonitorTestCase, self).tearDown()
162
163=== modified file 'tests/platform/filesystem_notifications/test_linux.py'
164--- tests/platform/filesystem_notifications/test_linux.py 2012-05-23 13:06:42 +0000
165+++ tests/platform/filesystem_notifications/test_linux.py 2012-06-25 20:40:24 +0000
166@@ -30,23 +30,17 @@
167 # files in the program, then also delete it here.
168 """Tests for the Event Queue."""
169
170-import logging
171 import os
172
173 from twisted.internet import defer, reactor
174 from twisted.trial.unittest import TestCase as PlainTestCase
175
176-from ubuntuone.syncdaemon import (
177- event_queue,
178- filesystem_manager,
179-)
180 from contrib.testing import testcase
181-from ubuntuone.devtools.handlers import MementoHandler
182 from ubuntuone.syncdaemon import volume_manager
183-from ubuntuone.syncdaemon.tritcask import Tritcask
184 from ubuntuone.platform.filesystem_notifications import (
185 linux as filesystem_notifications,
186 )
187+from tests.platform.filesystem_notifications import BaseFSMonitorTestCase
188
189 # We normally access to private attribs in tests
190 # pylint: disable=W0212
191@@ -101,50 +95,6 @@
192 self.assertFalse(processor.timer.active())
193
194
195-
196-class BaseFSMonitorTestCase(testcase.BaseTwistedTestCase):
197- """Test the structures where we have the path/watch."""
198-
199- timeout = 3
200-
201- @defer.inlineCallbacks
202- def setUp(self):
203- """Set up."""
204- yield super(BaseFSMonitorTestCase, self).setUp()
205- fsmdir = self.mktemp('fsmdir')
206- partials_dir = self.mktemp('partials_dir')
207- self.root_dir = self.mktemp('root_dir')
208- self.vm = testcase.FakeVolumeManager(self.root_dir)
209- self.tritcask_dir = self.mktemp("tritcask_dir")
210- self.db = Tritcask(self.tritcask_dir)
211- self.fs = filesystem_manager.FileSystemManager(fsmdir, partials_dir,
212- self.vm, self.db)
213- self.fs.create(path=self.root_dir, share_id='', is_dir=True)
214- self.fs.set_by_path(path=self.root_dir,
215- local_hash=None, server_hash=None)
216- eq = event_queue.EventQueue(self.fs)
217-
218- self.deferred = deferred = defer.Deferred()
219-
220- class HitMe(object):
221- # class-closure, cannot use self, pylint: disable-msg=E0213
222- def handle_default(innerself, event, **args):
223- deferred.callback(True)
224-
225- eq.subscribe(HitMe())
226- self.monitor = eq.monitor
227- self.log_handler = MementoHandler()
228- self.log_handler.setLevel(logging.DEBUG)
229- self.monitor.log.addHandler(self.log_handler)
230-
231- @defer.inlineCallbacks
232- def tearDown(self):
233- """Clean up the tests."""
234- self.monitor.shutdown()
235- self.monitor.log.removeHandler(self.log_handler)
236- yield super(BaseFSMonitorTestCase, self).tearDown()
237-
238-
239 class WatchManagerTests(BaseFSMonitorTestCase):
240 """Test the structures where we have the path/watch."""
241
242@@ -222,7 +172,8 @@
243 self.monitor._general_watchs = {'/path1/foo': 1, '/other': 2}
244 self.monitor._ancestors_watchs = {'/foo': 3}
245 self.monitor.inotify_watch_fix('/path1/foo', '/path1/new')
246- self.assertEqual(self.monitor._general_watchs, {'/path1/new': 1, '/other': 2})
247+ self.assertEqual(self.monitor._general_watchs,
248+ {'/path1/new': 1, '/other': 2})
249 self.assertEqual(self.monitor._ancestors_watchs, {'/foo': 3})
250
251 def test_fix_path_ancestors(self):
252@@ -231,7 +182,8 @@
253 self.monitor._ancestors_watchs = {'/oth': 1, '/other': 2}
254 self.monitor.inotify_watch_fix('/oth', '/baz')
255 self.assertEqual(self.monitor._general_watchs, {'/bar': 3})
256- self.assertEqual(self.monitor._ancestors_watchs, {'/baz': 1, '/other': 2})
257+ self.assertEqual(self.monitor._ancestors_watchs,
258+ {'/baz': 1, '/other': 2})
259
260
261 class DynamicHitMe(object):
262
263=== renamed file 'tests/platform/filesystem_notifier/test_windows.py' => 'tests/platform/filesystem_notifications/test_pyinotify_agnostic.py'
264--- tests/platform/filesystem_notifier/test_windows.py 2012-05-14 21:24:24 +0000
265+++ tests/platform/filesystem_notifications/test_pyinotify_agnostic.py 2012-06-25 20:40:24 +0000
266@@ -30,6 +30,8 @@
267 # files in the program, then also delete it here.
268 """Test for the pyinotify implementation on windows."""
269
270+import sys
271+
272 from twisted.internet import defer
273 from twisted.trial.unittest import TestCase
274
275@@ -51,7 +53,8 @@
276 attr = 'attribute'
277 self.format[attr] = attr
278 value = u'ñoño'
279- expected_result = (attr + value.encode('mbcs', 'replace') +
280+ expected_result = (attr + value.encode(
281+ sys.getfilesystemencoding(), 'replace') +
282 self.format['normal'])
283 self.assertEqual(expected_result, self.formatter.simple(value, attr))
284
285
286=== removed directory 'tests/platform/filesystem_notifier'
287=== removed file 'tests/platform/filesystem_notifier/__init__.py'
288--- tests/platform/filesystem_notifier/__init__.py 2012-05-14 19:04:43 +0000
289+++ tests/platform/filesystem_notifier/__init__.py 1970-01-01 00:00:00 +0000
290@@ -1,27 +0,0 @@
291-# Copyright 2012 Canonical Ltd.
292-#
293-# This program is free software: you can redistribute it and/or modify it
294-# under the terms of the GNU General Public License version 3, as published
295-# by the Free Software Foundation.
296-#
297-# This program is distributed in the hope that it will be useful, but
298-# WITHOUT ANY WARRANTY; without even the implied warranties of
299-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
300-# PURPOSE. See the GNU General Public License for more details.
301-#
302-# You should have received a copy of the GNU General Public License along
303-# with this program. If not, see <http://www.gnu.org/licenses/>.
304-#
305-# In addition, as a special exception, the copyright holders give
306-# permission to link the code of portions of this program with the
307-# OpenSSL library under certain conditions as described in each
308-# individual source file, and distribute linked combinations
309-# including the two.
310-# You must obey the GNU General Public License in all respects
311-# for all of the code used other than OpenSSL. If you modify
312-# file(s) with this exception, you may extend this exception to your
313-# version of the file(s), but you are not obligated to do so. If you
314-# do not wish to do so, delete this exception statement from your
315-# version. If you delete this exception statement from all source
316-# files in the program, then also delete it here.
317-"""Platform/File System Notifier (Pyinotify agnostic) test code."""
318
319=== renamed file 'tests/platform/test_xdg_base_directory.py' => 'tests/platform/test_logger.py'
320--- tests/platform/test_xdg_base_directory.py 2012-04-09 20:07:05 +0000
321+++ tests/platform/test_logger.py 2012-06-25 20:40:24 +0000
322@@ -1,7 +1,5 @@
323 # -*- coding: utf-8 -*-
324 #
325-# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
326-#
327 # Copyright 2011-2012 Canonical Ltd.
328 #
329 # This program is free software: you can redistribute it and/or modify it
330@@ -32,10 +30,10 @@
331
332 import os
333
334+from dirspec.basedir import xdg_cache_home
335 from twisted.trial.unittest import TestCase
336
337-from ubuntu_sso import xdg_base_directory
338-from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
339+from ubuntuone.platform.logger import ubuntuone_log_dir
340
341
342 class TestBaseDirectory(TestCase):
343@@ -43,7 +41,7 @@
344
345 def test_ubuntuone_log_dir(self):
346 """The ubuntuone_log_dir is correct."""
347- expected = os.path.join(xdg_base_directory.xdg_cache_home,
348+ expected = os.path.join(xdg_cache_home,
349 'ubuntuone', 'log')
350 self.assertEqual(expected, ubuntuone_log_dir)
351 self.assertTrue(os.path.exists(expected))
352
353=== modified file 'tests/syncdaemon/test_config.py'
354--- tests/syncdaemon/test_config.py 2012-04-09 20:07:05 +0000
355+++ tests/syncdaemon/test_config.py 2012-06-25 20:40:24 +0000
356@@ -36,7 +36,7 @@
357 from ConfigParser import ConfigParser
358 from twisted.internet import defer
359 from twisted.trial.unittest import TestCase
360-from ubuntu_sso.xdg_base_directory import (
361+from dirspec.basedir import (
362 xdg_data_home,
363 xdg_cache_home,
364 )
365@@ -562,7 +562,7 @@
366 def test_good_value(self):
367 """Test the parser using a good value."""
368 homedir = os.path.join('', 'home', 'fake')
369- self.patch(platform, 'xdg_home', homedir)
370+ self.patch(platform, 'user_home', homedir)
371 expected = os.path.join(self.xdg_dir, 'hola', 'mundo')
372 actual = self.parser(self.good_value)
373 self.assertEqual(expected, actual)
374
375=== modified file 'tests/syncdaemon/test_main.py'
376--- tests/syncdaemon/test_main.py 2012-05-22 14:07:55 +0000
377+++ tests/syncdaemon/test_main.py 2012-06-25 20:40:24 +0000
378@@ -282,7 +282,7 @@
379
380 def test_get_homedir(self):
381 """The get_homedir returns the root dir."""
382- self.patch(main_mod, "xdg_home", self.home_dir)
383+ self.patch(main_mod, "user_home", self.home_dir)
384 expected = expand_user('~')
385 main = self.build_main()
386 self.assertEqual(main.get_homedir(), expected)
387
388=== modified file 'ubuntuone/platform/__init__.py'
389--- ubuntuone/platform/__init__.py 2012-05-14 19:04:43 +0000
390+++ ubuntuone/platform/__init__.py 2012-06-25 20:40:24 +0000
391@@ -31,7 +31,7 @@
392 import os
393 import sys
394
395-from ubuntu_sso.xdg_base_directory import xdg_home
396+from dirspec.utils import user_home
397
398 # very hackish way to avoid "import *" to satisfy pyflakes
399 # and to avoid import ubuntuone.platform.X as source (it wont work)
400@@ -62,7 +62,7 @@
401 if not path.startswith(tilde) or \
402 (len(path) > 1 and path[1:2] != os.path.sep):
403 return path
404- result = path.replace('~', xdg_home, 1)
405+ result = path.replace('~', user_home, 1)
406
407 assert isinstance(result, str)
408 try:
409
410=== modified file 'ubuntuone/platform/credentials/__init__.py'
411--- ubuntuone/platform/credentials/__init__.py 2012-06-14 16:54:45 +0000
412+++ ubuntuone/platform/credentials/__init__.py 2012-06-25 20:40:24 +0000
413@@ -1,8 +1,5 @@
414 # -*- coding: utf-8 -*-
415 #
416-# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
417-# Author: Manuel de la Pena<manuel@canonical.com>
418-#
419 # Copyright 2011-2012 Canonical Ltd.
420 #
421 # This program is free software: you can redistribute it and/or modify it
422@@ -57,7 +54,7 @@
423 CustomRotatingFileHandler,
424 log_call,
425 )
426-from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
427+from ubuntuone.platform.logger import ubuntuone_log_dir
428
429 LOG_LEVEL = logging.DEBUG
430 path = os.path.join(ubuntuone_log_dir, 'credentials.log')
431
432=== modified file 'ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py'
433--- ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py 2012-05-14 20:38:23 +0000
434+++ ubuntuone/platform/filesystem_notifications/pyinotify_agnostic.py 2012-06-25 20:40:24 +0000
435@@ -1,518 +1,518 @@
436-#!/usr/bin/env python
437-
438-# pyinotify.py - python interface to inotify
439-# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>
440-#
441-# Permission is hereby granted, free of charge, to any person obtaining a copy
442-# of this software and associated documentation files (the "Software"), to deal
443-# in the Software without restriction, including without limitation the rights
444-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
445-# copies of the Software, and to permit persons to whom the Software is
446-# furnished to do so, subject to the following conditions:
447-#
448-# The above copyright notice and this permission notice shall be included in
449-# all copies or substantial portions of the Software.
450-#
451-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
452-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
453-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
454-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
455-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
456-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
457-# THE SOFTWARE.
458-"""Platform agnostic code grabed from pyinotify."""
459-import logging
460-import os
461-import sys
462-
463-COMPATIBILITY_MODE = False
464-
465-class PyinotifyError(Exception):
466- """Indicates exceptions raised by a Pyinotify class."""
467- pass
468-
469-
470-class RawOutputFormat:
471- """
472- Format string representations.
473- """
474- def __init__(self, format=None):
475- self.format = format or {}
476-
477- def simple(self, s, attribute):
478- if isinstance(s, unicode):
479- s = s.encode('mbcs', 'replace')
480- else:
481- s = str(s)
482- return (self.format.get(attribute, '') + s +
483- self.format.get('normal', ''))
484-
485- def punctuation(self, s):
486- """Punctuation color."""
487- return self.simple(s, 'normal')
488-
489- def field_value(self, s):
490- """Field value color."""
491- return self.simple(s, 'purple')
492-
493- def field_name(self, s):
494- """Field name color."""
495- return self.simple(s, 'blue')
496-
497- def class_name(self, s):
498- """Class name color."""
499- return self.format.get('red', '') + self.simple(s, 'bold')
500-
501-output_format = RawOutputFormat()
502-
503-
504-class EventsCodes:
505- """
506- Set of codes corresponding to each kind of events.
507- Some of these flags are used to communicate with inotify, whereas
508- the others are sent to userspace by inotify notifying some events.
509-
510- @cvar IN_ACCESS: File was accessed.
511- @type IN_ACCESS: int
512- @cvar IN_MODIFY: File was modified.
513- @type IN_MODIFY: int
514- @cvar IN_ATTRIB: Metadata changed.
515- @type IN_ATTRIB: int
516- @cvar IN_CLOSE_WRITE: Writtable file was closed.
517- @type IN_CLOSE_WRITE: int
518- @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
519- @type IN_CLOSE_NOWRITE: int
520- @cvar IN_OPEN: File was opened.
521- @type IN_OPEN: int
522- @cvar IN_MOVED_FROM: File was moved from X.
523- @type IN_MOVED_FROM: int
524- @cvar IN_MOVED_TO: File was moved to Y.
525- @type IN_MOVED_TO: int
526- @cvar IN_CREATE: Subfile was created.
527- @type IN_CREATE: int
528- @cvar IN_DELETE: Subfile was deleted.
529- @type IN_DELETE: int
530- @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
531- @type IN_DELETE_SELF: int
532- @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
533- @type IN_MOVE_SELF: int
534- @cvar IN_UNMOUNT: Backing fs was unmounted.
535- @type IN_UNMOUNT: int
536- @cvar IN_Q_OVERFLOW: Event queued overflowed.
537- @type IN_Q_OVERFLOW: int
538- @cvar IN_IGNORED: File was ignored.
539- @type IN_IGNORED: int
540- @cvar IN_ONLYDIR: only watch the path if it is a directory (new
541- in kernel 2.6.15).
542- @type IN_ONLYDIR: int
543- @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
544- IN_ONLYDIR we can make sure that we don't watch
545- the target of symlinks.
546- @type IN_DONT_FOLLOW: int
547- @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
548- in kernel 2.6.14).
549- @type IN_MASK_ADD: int
550- @cvar IN_ISDIR: Event occurred against dir.
551- @type IN_ISDIR: int
552- @cvar IN_ONESHOT: Only send event once.
553- @type IN_ONESHOT: int
554- @cvar ALL_EVENTS: Alias for considering all of the events.
555- @type ALL_EVENTS: int
556- """
557-
558- # The idea here is 'configuration-as-code' - this way, we get
559- # our nice class constants, but we also get nice human-friendly text
560- # mappings to do lookups against as well, for free:
561- FLAG_COLLECTIONS = {'OP_FLAGS': {
562- 'IN_ACCESS' : 0x00000001, # File was accessed
563- 'IN_MODIFY' : 0x00000002, # File was modified
564- 'IN_ATTRIB' : 0x00000004, # Metadata changed
565- 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
566- 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
567- 'IN_OPEN' : 0x00000020, # File was opened
568- 'IN_MOVED_FROM' : 0x00000040, # File was moved from X
569- 'IN_MOVED_TO' : 0x00000080, # File was moved to Y
570- 'IN_CREATE' : 0x00000100, # Subfile was created
571- 'IN_DELETE' : 0x00000200, # Subfile was deleted
572- 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)
573- # was deleted
574- 'IN_MOVE_SELF' : 0x00000800, # Self(watched item itself) was moved
575- },
576- 'EVENT_FLAGS': {
577- 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
578- 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
579- 'IN_IGNORED' : 0x00008000, # File was ignored
580- },
581- 'SPECIAL_FLAGS': {
582- 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a
583- # directory
584- 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
585- 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already
586- # existing watch
587- 'IN_ISDIR' : 0x40000000, # event occurred against dir
588- 'IN_ONESHOT' : 0x80000000, # only send event once
589- },
590- }
591-
592- def maskname(mask):
593- """
594- Returns the event name associated to mask. IN_ISDIR is appended to
595- the result when appropriate. Note: only one event is returned, because
596- only one event can be raised at a given time.
597-
598- @param mask: mask.
599- @type mask: int
600- @return: event name.
601- @rtype: str
602- """
603- ms = mask
604- name = '%s'
605- if mask & IN_ISDIR:
606- ms = mask - IN_ISDIR
607- name = '%s|IN_ISDIR'
608- return name % EventsCodes.ALL_VALUES[ms]
609-
610- maskname = staticmethod(maskname)
611-
612-
613-# So let's now turn the configuration into code
614-EventsCodes.ALL_FLAGS = {}
615-EventsCodes.ALL_VALUES = {}
616-for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():
617- # Make the collections' members directly accessible through the
618- # class dictionary
619- setattr(EventsCodes, flagc, valc)
620-
621- # Collect all the flags under a common umbrella
622- EventsCodes.ALL_FLAGS.update(valc)
623-
624- # Make the individual masks accessible as 'constants' at globals() scope
625- # and masknames accessible by values.
626- for name, val in valc.items():
627- globals()[name] = val
628- EventsCodes.ALL_VALUES[val] = name
629-
630-
631-# all 'normal' events
632-ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())
633-EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
634-EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
635-
636-
637-class _Event:
638- """
639- Event structure, represent events raised by the system. This
640- is the base class and should be subclassed.
641-
642- """
643- def __init__(self, dict_):
644- """
645- Attach attributes (contained in dict_) to self.
646-
647- @param dict_: Set of attributes.
648- @type dict_: dictionary
649- """
650- for tpl in dict_.items():
651- setattr(self, *tpl)
652-
653- def __repr__(self):
654- """
655- @return: Generic event string representation.
656- @rtype: str
657- """
658- s = ''
659- for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
660- if attr.startswith('_'):
661- continue
662- if attr == 'mask':
663- value = hex(getattr(self, attr))
664- elif isinstance(value, basestring) and not value:
665- value = "''"
666- s += ' %s%s%s' % (output_format.field_name(attr),
667- output_format.punctuation('='),
668- output_format.field_value(value))
669-
670- s = '%s%s%s %s' % (output_format.punctuation('<'),
671- output_format.class_name(self.__class__.__name__),
672- s,
673- output_format.punctuation('>'))
674- return s
675-
676- def __str__(self):
677- return repr(self)
678-
679-
680-class _RawEvent(_Event):
681- """
682- Raw event, it contains only the informations provided by the system.
683- It doesn't infer anything.
684- """
685- def __init__(self, wd, mask, cookie, name):
686- """
687- @param wd: Watch Descriptor.
688- @type wd: int
689- @param mask: Bitmask of events.
690- @type mask: int
691- @param cookie: Cookie.
692- @type cookie: int
693- @param name: Basename of the file or directory against which the
694- event was raised in case where the watched directory
695- is the parent directory. None if the event was raised
696- on the watched item itself.
697- @type name: string or None
698- """
699- # Use this variable to cache the result of str(self), this object
700- # is immutable.
701- self._str = None
702- # name: remove trailing '\0'
703- d = {'wd': wd,
704- 'mask': mask,
705- 'cookie': cookie,
706- 'name': name.rstrip('\0')}
707- _Event.__init__(self, d)
708- logging.debug(str(self))
709-
710- def __str__(self):
711- if self._str is None:
712- self._str = _Event.__str__(self)
713- return self._str
714-
715-
716-class Event(_Event):
717- """
718- This class contains all the useful informations about the observed
719- event. However, the presence of each field is not guaranteed and
720- depends on the type of event. In effect, some fields are irrelevant
721- for some kind of event (for example 'cookie' is meaningless for
722- IN_CREATE whereas it is mandatory for IN_MOVE_TO).
723-
724- The possible fields are:
725- - wd (int): Watch Descriptor.
726- - mask (int): Mask.
727- - maskname (str): Readable event name.
728- - path (str): path of the file or directory being watched.
729- - name (str): Basename of the file or directory against which the
730- event was raised in case where the watched directory
731- is the parent directory. None if the event was raised
732- on the watched item itself. This field is always provided
733- even if the string is ''.
734- - pathname (str): Concatenation of 'path' and 'name'.
735- - src_pathname (str): Only present for IN_MOVED_TO events and only in
736- the case where IN_MOVED_FROM events are watched too. Holds the
737- source pathname from where pathname was moved from.
738- - cookie (int): Cookie.
739- - dir (bool): True if the event was raised against a directory.
740-
741- """
742- def __init__(self, raw):
743- """
744- Concretely, this is the raw event plus inferred infos.
745- """
746- _Event.__init__(self, raw)
747- self.maskname = EventsCodes.maskname(self.mask)
748- if COMPATIBILITY_MODE:
749- self.event_name = self.maskname
750- try:
751- if self.name:
752- self.pathname = os.path.abspath(os.path.join(self.path,
753- self.name))
754- else:
755- self.pathname = os.path.abspath(self.path)
756- except AttributeError, err:
757- # Usually it is not an error some events are perfectly valids
758- # despite the lack of these attributes.
759- logging.debug(err)
760-
761-
762-class ProcessEventError(PyinotifyError):
763- """
764- ProcessEventError Exception. Raised on ProcessEvent error.
765- """
766- def __init__(self, err):
767- """
768- @param err: Exception error description.
769- @type err: string
770- """
771- PyinotifyError.__init__(self, err)
772-
773-
774-class _ProcessEvent:
775- """
776- Abstract processing event class.
777- """
778- def __call__(self, event):
779- """
780- To behave like a functor the object must be callable.
781- This method is a dispatch method. Its lookup order is:
782- 1. process_MASKNAME method
783- 2. process_FAMILY_NAME method
784- 3. otherwise calls process_default
785-
786- @param event: Event to be processed.
787- @type event: Event object
788- @return: By convention when used from the ProcessEvent class:
789- - Returning False or None (default value) means keep on
790- executing next chained functors (see chain.py example).
791- - Returning True instead means do not execute next
792- processing functions.
793- @rtype: bool
794- @raise ProcessEventError: Event object undispatchable,
795- unknown event.
796- """
797- stripped_mask = event.mask - (event.mask & IN_ISDIR)
798- maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
799- if maskname is None:
800- raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
801-
802- # 1- look for process_MASKNAME
803- meth = getattr(self, 'process_' + maskname, None)
804- if meth is not None:
805- return meth(event)
806- # 2- look for process_FAMILY_NAME
807- meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
808- if meth is not None:
809- return meth(event)
810- # 3- default call method process_default
811- return self.process_default(event)
812-
813- def __repr__(self):
814- return '<%s>' % self.__class__.__name__
815-
816-
817-class ProcessEvent(_ProcessEvent):
818- """
819- Process events objects, can be specialized via subclassing, thus its
820- behavior can be overriden:
821-
822- Note: you should not override __init__ in your subclass instead define
823- a my_init() method, this method will be called automatically from the
824- constructor of this class with its optionals parameters.
825-
826- 1. Provide specialized individual methods, e.g. process_IN_DELETE for
827- processing a precise type of event (e.g. IN_DELETE in this case).
828- 2. Or/and provide methods for processing events by 'family', e.g.
829- process_IN_CLOSE method will process both IN_CLOSE_WRITE and
830- IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
831- process_IN_CLOSE_NOWRITE aren't defined though).
832- 3. Or/and override process_default for catching and processing all
833- the remaining types of events.
834- """
835- pevent = None
836-
837- def __init__(self, pevent=None, **kargs):
838- """
839- Enable chaining of ProcessEvent instances.
840-
841- @param pevent: Optional callable object, will be called on event
842- processing (before self).
843- @type pevent: callable
844- @param kargs: This constructor is implemented as a template method
845- delegating its optionals keyworded arguments to the
846- method my_init().
847- @type kargs: dict
848- """
849- self.pevent = pevent
850- self.my_init(**kargs)
851-
852- def my_init(self, **kargs):
853- """
854- This method is called from ProcessEvent.__init__(). This method is
855- empty here and must be redefined to be useful. In effect, if you
856- need to specifically initialize your subclass' instance then you
857- just have to override this method in your subclass. Then all the
858- keyworded arguments passed to ProcessEvent.__init__() will be
859- transmitted as parameters to this method. Beware you MUST pass
860- keyword arguments though.
861-
862- @param kargs: optional delegated arguments from __init__().
863- @type kargs: dict
864- """
865- pass
866-
867- def __call__(self, event):
868- stop_chaining = False
869- if self.pevent is not None:
870- # By default methods return None so we set as guideline
871- # that methods asking for stop chaining must explicitely
872- # return non None or non False values, otherwise the default
873- # behavior will be to accept chain call to the corresponding
874- # local method.
875- stop_chaining = self.pevent(event)
876- if not stop_chaining:
877- return _ProcessEvent.__call__(self, event)
878-
879- def nested_pevent(self):
880- return self.pevent
881-
882- def process_IN_Q_OVERFLOW(self, event):
883- """
884- By default this method only reports warning messages, you can
885- overredide it by subclassing ProcessEvent and implement your own
886- process_IN_Q_OVERFLOW method. The actions you can take on receiving
887- this event is either to update the variable max_queued_events in order
888- to handle more simultaneous events or to modify your code in order to
889- accomplish a better filtering diminishing the number of raised events.
890- Because this method is defined, IN_Q_OVERFLOW will never get
891- transmitted as arguments to process_default calls.
892-
893- @param event: IN_Q_OVERFLOW event.
894- @type event: dict
895- """
896- logging.warning('Event queue overflowed.')
897-
898- def process_default(self, event):
899- """
900- Default processing event method. By default does nothing. Subclass
901- ProcessEvent and redefine this method in order to modify its behavior.
902-
903- @param event: Event to be processed. Can be of any type of events but
904- IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
905- @type event: Event instance
906- """
907- pass
908-
909-
910-class PrintAllEvents(ProcessEvent):
911- """
912- Dummy class used to print events strings representations. For instance this
913- class is used from command line to print all received events to stdout.
914- """
915- def my_init(self, out=None):
916- """
917- @param out: Where events will be written.
918- @type out: Object providing a valid file object interface.
919- """
920- if out is None:
921- out = sys.stdout
922- self._out = out
923-
924- def process_default(self, event):
925- """
926- Writes event string representation to file object provided to
927- my_init().
928-
929- @param event: Event to be processed. Can be of any type of events but
930- IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
931- @type event: Event instance
932- """
933- self._out.write(str(event))
934- self._out.write('\n')
935- self._out.flush()
936-
937-
938-class WatchManagerError(Exception):
939- """
940- WatchManager Exception. Raised on error encountered on watches
941- operations.
942-
943- """
944- def __init__(self, msg, wmd):
945- """
946- @param msg: Exception string's description.
947- @type msg: string
948- @param wmd: This dictionary contains the wd assigned to paths of the
949- same call for which watches were successfully added.
950- @type wmd: dict
951- """
952- self.wmd = wmd
953- Exception.__init__(self, msg)
954+#!/usr/bin/env python
955+
956+# pyinotify.py - python interface to inotify
957+# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>
958+#
959+# Permission is hereby granted, free of charge, to any person obtaining a copy
960+# of this software and associated documentation files (the "Software"), to deal
961+# in the Software without restriction, including without limitation the rights
962+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
963+# copies of the Software, and to permit persons to whom the Software is
964+# furnished to do so, subject to the following conditions:
965+#
966+# The above copyright notice and this permission notice shall be included in
967+# all copies or substantial portions of the Software.
968+#
969+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
970+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
971+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
972+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
973+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
974+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
975+# THE SOFTWARE.
976+"""Platform agnostic code grabed from pyinotify."""
977+import logging
978+import os
979+import sys
980+
981+COMPATIBILITY_MODE = False
982+
983+class PyinotifyError(Exception):
984+ """Indicates exceptions raised by a Pyinotify class."""
985+ pass
986+
987+
988+class RawOutputFormat:
989+ """
990+ Format string representations.
991+ """
992+ def __init__(self, format=None):
993+ self.format = format or {}
994+
995+ def simple(self, s, attribute):
996+ if isinstance(s, unicode):
997+ s = s.encode(sys.getfilesystemencoding(), 'replace')
998+ else:
999+ s = str(s)
1000+ return (self.format.get(attribute, '') + s +
1001+ self.format.get('normal', ''))
1002+
1003+ def punctuation(self, s):
1004+ """Punctuation color."""
1005+ return self.simple(s, 'normal')
1006+
1007+ def field_value(self, s):
1008+ """Field value color."""
1009+ return self.simple(s, 'purple')
1010+
1011+ def field_name(self, s):
1012+ """Field name color."""
1013+ return self.simple(s, 'blue')
1014+
1015+ def class_name(self, s):
1016+ """Class name color."""
1017+ return self.format.get('red', '') + self.simple(s, 'bold')
1018+
1019+output_format = RawOutputFormat()
1020+
1021+
1022+class EventsCodes:
1023+ """
1024+ Set of codes corresponding to each kind of events.
1025+ Some of these flags are used to communicate with inotify, whereas
1026+ the others are sent to userspace by inotify notifying some events.
1027+
1028+ @cvar IN_ACCESS: File was accessed.
1029+ @type IN_ACCESS: int
1030+ @cvar IN_MODIFY: File was modified.
1031+ @type IN_MODIFY: int
1032+ @cvar IN_ATTRIB: Metadata changed.
1033+ @type IN_ATTRIB: int
1034+ @cvar IN_CLOSE_WRITE: Writtable file was closed.
1035+ @type IN_CLOSE_WRITE: int
1036+ @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
1037+ @type IN_CLOSE_NOWRITE: int
1038+ @cvar IN_OPEN: File was opened.
1039+ @type IN_OPEN: int
1040+ @cvar IN_MOVED_FROM: File was moved from X.
1041+ @type IN_MOVED_FROM: int
1042+ @cvar IN_MOVED_TO: File was moved to Y.
1043+ @type IN_MOVED_TO: int
1044+ @cvar IN_CREATE: Subfile was created.
1045+ @type IN_CREATE: int
1046+ @cvar IN_DELETE: Subfile was deleted.
1047+ @type IN_DELETE: int
1048+ @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
1049+ @type IN_DELETE_SELF: int
1050+ @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
1051+ @type IN_MOVE_SELF: int
1052+ @cvar IN_UNMOUNT: Backing fs was unmounted.
1053+ @type IN_UNMOUNT: int
1054+ @cvar IN_Q_OVERFLOW: Event queued overflowed.
1055+ @type IN_Q_OVERFLOW: int
1056+ @cvar IN_IGNORED: File was ignored.
1057+ @type IN_IGNORED: int
1058+ @cvar IN_ONLYDIR: only watch the path if it is a directory (new
1059+ in kernel 2.6.15).
1060+ @type IN_ONLYDIR: int
1061+ @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
1062+ IN_ONLYDIR we can make sure that we don't watch
1063+ the target of symlinks.
1064+ @type IN_DONT_FOLLOW: int
1065+ @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
1066+ in kernel 2.6.14).
1067+ @type IN_MASK_ADD: int
1068+ @cvar IN_ISDIR: Event occurred against dir.
1069+ @type IN_ISDIR: int
1070+ @cvar IN_ONESHOT: Only send event once.
1071+ @type IN_ONESHOT: int
1072+ @cvar ALL_EVENTS: Alias for considering all of the events.
1073+ @type ALL_EVENTS: int
1074+ """
1075+
1076+ # The idea here is 'configuration-as-code' - this way, we get
1077+ # our nice class constants, but we also get nice human-friendly text
1078+ # mappings to do lookups against as well, for free:
1079+ FLAG_COLLECTIONS = {'OP_FLAGS': {
1080+ 'IN_ACCESS' : 0x00000001, # File was accessed
1081+ 'IN_MODIFY' : 0x00000002, # File was modified
1082+ 'IN_ATTRIB' : 0x00000004, # Metadata changed
1083+ 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
1084+ 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
1085+ 'IN_OPEN' : 0x00000020, # File was opened
1086+ 'IN_MOVED_FROM' : 0x00000040, # File was moved from X
1087+ 'IN_MOVED_TO' : 0x00000080, # File was moved to Y
1088+ 'IN_CREATE' : 0x00000100, # Subfile was created
1089+ 'IN_DELETE' : 0x00000200, # Subfile was deleted
1090+ 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)
1091+ # was deleted
1092+ 'IN_MOVE_SELF' : 0x00000800, # Self(watched item itself) was moved
1093+ },
1094+ 'EVENT_FLAGS': {
1095+ 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
1096+ 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
1097+ 'IN_IGNORED' : 0x00008000, # File was ignored
1098+ },
1099+ 'SPECIAL_FLAGS': {
1100+ 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a
1101+ # directory
1102+ 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
1103+ 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already
1104+ # existing watch
1105+ 'IN_ISDIR' : 0x40000000, # event occurred against dir
1106+ 'IN_ONESHOT' : 0x80000000, # only send event once
1107+ },
1108+ }
1109+
1110+ def maskname(mask):
1111+ """
1112+ Returns the event name associated to mask. IN_ISDIR is appended to
1113+ the result when appropriate. Note: only one event is returned, because
1114+ only one event can be raised at a given time.
1115+
1116+ @param mask: mask.
1117+ @type mask: int
1118+ @return: event name.
1119+ @rtype: str
1120+ """
1121+ ms = mask
1122+ name = '%s'
1123+ if mask & IN_ISDIR:
1124+ ms = mask - IN_ISDIR
1125+ name = '%s|IN_ISDIR'
1126+ return name % EventsCodes.ALL_VALUES[ms]
1127+
1128+ maskname = staticmethod(maskname)
1129+
1130+
1131+# So let's now turn the configuration into code
1132+EventsCodes.ALL_FLAGS = {}
1133+EventsCodes.ALL_VALUES = {}
1134+for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():
1135+ # Make the collections' members directly accessible through the
1136+ # class dictionary
1137+ setattr(EventsCodes, flagc, valc)
1138+
1139+ # Collect all the flags under a common umbrella
1140+ EventsCodes.ALL_FLAGS.update(valc)
1141+
1142+ # Make the individual masks accessible as 'constants' at globals() scope
1143+ # and masknames accessible by values.
1144+ for name, val in valc.items():
1145+ globals()[name] = val
1146+ EventsCodes.ALL_VALUES[val] = name
1147+
1148+
1149+# all 'normal' events
1150+ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())
1151+EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
1152+EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
1153+
1154+
1155+class _Event:
1156+ """
1157+ Event structure, represent events raised by the system. This
1158+ is the base class and should be subclassed.
1159+
1160+ """
1161+ def __init__(self, dict_):
1162+ """
1163+ Attach attributes (contained in dict_) to self.
1164+
1165+ @param dict_: Set of attributes.
1166+ @type dict_: dictionary
1167+ """
1168+ for tpl in dict_.items():
1169+ setattr(self, *tpl)
1170+
1171+ def __repr__(self):
1172+ """
1173+ @return: Generic event string representation.
1174+ @rtype: str
1175+ """
1176+ s = ''
1177+ for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
1178+ if attr.startswith('_'):
1179+ continue
1180+ if attr == 'mask':
1181+ value = hex(getattr(self, attr))
1182+ elif isinstance(value, basestring) and not value:
1183+ value = "''"
1184+ s += ' %s%s%s' % (output_format.field_name(attr),
1185+ output_format.punctuation('='),
1186+ output_format.field_value(value))
1187+
1188+ s = '%s%s%s %s' % (output_format.punctuation('<'),
1189+ output_format.class_name(self.__class__.__name__),
1190+ s,
1191+ output_format.punctuation('>'))
1192+ return s
1193+
1194+ def __str__(self):
1195+ return repr(self)
1196+
1197+
1198+class _RawEvent(_Event):
1199+ """
1200+ Raw event, it contains only the informations provided by the system.
1201+ It doesn't infer anything.
1202+ """
1203+ def __init__(self, wd, mask, cookie, name):
1204+ """
1205+ @param wd: Watch Descriptor.
1206+ @type wd: int
1207+ @param mask: Bitmask of events.
1208+ @type mask: int
1209+ @param cookie: Cookie.
1210+ @type cookie: int
1211+ @param name: Basename of the file or directory against which the
1212+ event was raised in case where the watched directory
1213+ is the parent directory. None if the event was raised
1214+ on the watched item itself.
1215+ @type name: string or None
1216+ """
1217+ # Use this variable to cache the result of str(self), this object
1218+ # is immutable.
1219+ self._str = None
1220+ # name: remove trailing '\0'
1221+ d = {'wd': wd,
1222+ 'mask': mask,
1223+ 'cookie': cookie,
1224+ 'name': name.rstrip('\0')}
1225+ _Event.__init__(self, d)
1226+ logging.debug(str(self))
1227+
1228+ def __str__(self):
1229+ if self._str is None:
1230+ self._str = _Event.__str__(self)
1231+ return self._str
1232+
1233+
1234+class Event(_Event):
1235+ """
1236+ This class contains all the useful informations about the observed
1237+ event. However, the presence of each field is not guaranteed and
1238+ depends on the type of event. In effect, some fields are irrelevant
1239+ for some kind of event (for example 'cookie' is meaningless for
1240+ IN_CREATE whereas it is mandatory for IN_MOVE_TO).
1241+
1242+ The possible fields are:
1243+ - wd (int): Watch Descriptor.
1244+ - mask (int): Mask.
1245+ - maskname (str): Readable event name.
1246+ - path (str): path of the file or directory being watched.
1247+ - name (str): Basename of the file or directory against which the
1248+ event was raised in case where the watched directory
1249+ is the parent directory. None if the event was raised
1250+ on the watched item itself. This field is always provided
1251+ even if the string is ''.
1252+ - pathname (str): Concatenation of 'path' and 'name'.
1253+ - src_pathname (str): Only present for IN_MOVED_TO events and only in
1254+ the case where IN_MOVED_FROM events are watched too. Holds the
1255+ source pathname from where pathname was moved from.
1256+ - cookie (int): Cookie.
1257+ - dir (bool): True if the event was raised against a directory.
1258+
1259+ """
1260+ def __init__(self, raw):
1261+ """
1262+ Concretely, this is the raw event plus inferred infos.
1263+ """
1264+ _Event.__init__(self, raw)
1265+ self.maskname = EventsCodes.maskname(self.mask)
1266+ if COMPATIBILITY_MODE:
1267+ self.event_name = self.maskname
1268+ try:
1269+ if self.name:
1270+ self.pathname = os.path.abspath(os.path.join(self.path,
1271+ self.name))
1272+ else:
1273+ self.pathname = os.path.abspath(self.path)
1274+ except AttributeError, err:
1275+ # Usually it is not an error some events are perfectly valids
1276+ # despite the lack of these attributes.
1277+ logging.debug(err)
1278+
1279+
1280+class ProcessEventError(PyinotifyError):
1281+ """
1282+ ProcessEventError Exception. Raised on ProcessEvent error.
1283+ """
1284+ def __init__(self, err):
1285+ """
1286+ @param err: Exception error description.
1287+ @type err: string
1288+ """
1289+ PyinotifyError.__init__(self, err)
1290+
1291+
1292+class _ProcessEvent:
1293+ """
1294+ Abstract processing event class.
1295+ """
1296+ def __call__(self, event):
1297+ """
1298+ To behave like a functor the object must be callable.
1299+ This method is a dispatch method. Its lookup order is:
1300+ 1. process_MASKNAME method
1301+ 2. process_FAMILY_NAME method
1302+ 3. otherwise calls process_default
1303+
1304+ @param event: Event to be processed.
1305+ @type event: Event object
1306+ @return: By convention when used from the ProcessEvent class:
1307+ - Returning False or None (default value) means keep on
1308+ executing next chained functors (see chain.py example).
1309+ - Returning True instead means do not execute next
1310+ processing functions.
1311+ @rtype: bool
1312+ @raise ProcessEventError: Event object undispatchable,
1313+ unknown event.
1314+ """
1315+ stripped_mask = event.mask - (event.mask & IN_ISDIR)
1316+ maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
1317+ if maskname is None:
1318+ raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
1319+
1320+ # 1- look for process_MASKNAME
1321+ meth = getattr(self, 'process_' + maskname, None)
1322+ if meth is not None:
1323+ return meth(event)
1324+ # 2- look for process_FAMILY_NAME
1325+ meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
1326+ if meth is not None:
1327+ return meth(event)
1328+ # 3- default call method process_default
1329+ return self.process_default(event)
1330+
1331+ def __repr__(self):
1332+ return '<%s>' % self.__class__.__name__
1333+
1334+
1335+class ProcessEvent(_ProcessEvent):
1336+ """
1337+ Process events objects, can be specialized via subclassing, thus its
1338+ behavior can be overriden:
1339+
1340+ Note: you should not override __init__ in your subclass instead define
1341+ a my_init() method, this method will be called automatically from the
1342+ constructor of this class with its optionals parameters.
1343+
1344+ 1. Provide specialized individual methods, e.g. process_IN_DELETE for
1345+ processing a precise type of event (e.g. IN_DELETE in this case).
1346+ 2. Or/and provide methods for processing events by 'family', e.g.
1347+ process_IN_CLOSE method will process both IN_CLOSE_WRITE and
1348+ IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
1349+ process_IN_CLOSE_NOWRITE aren't defined though).
1350+ 3. Or/and override process_default for catching and processing all
1351+ the remaining types of events.
1352+ """
1353+ pevent = None
1354+
1355+ def __init__(self, pevent=None, **kargs):
1356+ """
1357+ Enable chaining of ProcessEvent instances.
1358+
1359+ @param pevent: Optional callable object, will be called on event
1360+ processing (before self).
1361+ @type pevent: callable
1362+ @param kargs: This constructor is implemented as a template method
1363+ delegating its optionals keyworded arguments to the
1364+ method my_init().
1365+ @type kargs: dict
1366+ """
1367+ self.pevent = pevent
1368+ self.my_init(**kargs)
1369+
1370+ def my_init(self, **kargs):
1371+ """
1372+ This method is called from ProcessEvent.__init__(). This method is
1373+ empty here and must be redefined to be useful. In effect, if you
1374+ need to specifically initialize your subclass' instance then you
1375+ just have to override this method in your subclass. Then all the
1376+ keyworded arguments passed to ProcessEvent.__init__() will be
1377+ transmitted as parameters to this method. Beware you MUST pass
1378+ keyword arguments though.
1379+
1380+ @param kargs: optional delegated arguments from __init__().
1381+ @type kargs: dict
1382+ """
1383+ pass
1384+
1385+ def __call__(self, event):
1386+ stop_chaining = False
1387+ if self.pevent is not None:
1388+ # By default methods return None so we set as guideline
1389+ # that methods asking for stop chaining must explicitely
1390+ # return non None or non False values, otherwise the default
1391+ # behavior will be to accept chain call to the corresponding
1392+ # local method.
1393+ stop_chaining = self.pevent(event)
1394+ if not stop_chaining:
1395+ return _ProcessEvent.__call__(self, event)
1396+
1397+ def nested_pevent(self):
1398+ return self.pevent
1399+
1400+ def process_IN_Q_OVERFLOW(self, event):
1401+ """
1402+ By default this method only reports warning messages, you can
1403+ overredide it by subclassing ProcessEvent and implement your own
1404+ process_IN_Q_OVERFLOW method. The actions you can take on receiving
1405+ this event is either to update the variable max_queued_events in order
1406+ to handle more simultaneous events or to modify your code in order to
1407+ accomplish a better filtering diminishing the number of raised events.
1408+ Because this method is defined, IN_Q_OVERFLOW will never get
1409+ transmitted as arguments to process_default calls.
1410+
1411+ @param event: IN_Q_OVERFLOW event.
1412+ @type event: dict
1413+ """
1414+ logging.warning('Event queue overflowed.')
1415+
1416+ def process_default(self, event):
1417+ """
1418+ Default processing event method. By default does nothing. Subclass
1419+ ProcessEvent and redefine this method in order to modify its behavior.
1420+
1421+ @param event: Event to be processed. Can be of any type of events but
1422+ IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
1423+ @type event: Event instance
1424+ """
1425+ pass
1426+
1427+
1428+class PrintAllEvents(ProcessEvent):
1429+ """
1430+ Dummy class used to print events strings representations. For instance this
1431+ class is used from command line to print all received events to stdout.
1432+ """
1433+ def my_init(self, out=None):
1434+ """
1435+ @param out: Where events will be written.
1436+ @type out: Object providing a valid file object interface.
1437+ """
1438+ if out is None:
1439+ out = sys.stdout
1440+ self._out = out
1441+
1442+ def process_default(self, event):
1443+ """
1444+ Writes event string representation to file object provided to
1445+ my_init().
1446+
1447+ @param event: Event to be processed. Can be of any type of events but
1448+ IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
1449+ @type event: Event instance
1450+ """
1451+ self._out.write(str(event))
1452+ self._out.write('\n')
1453+ self._out.flush()
1454+
1455+
1456+class WatchManagerError(Exception):
1457+ """
1458+ WatchManager Exception. Raised on error encountered on watches
1459+ operations.
1460+
1461+ """
1462+ def __init__(self, msg, wmd):
1463+ """
1464+ @param msg: Exception string's description.
1465+ @type msg: string
1466+ @param wmd: This dictionary contains the wd assigned to paths of the
1467+ same call for which watches were successfully added.
1468+ @type wmd: dict
1469+ """
1470+ self.wmd = wmd
1471+ Exception.__init__(self, msg)
1472
1473=== modified file 'ubuntuone/platform/logger/__init__.py'
1474--- ubuntuone/platform/logger/__init__.py 2012-05-16 16:24:23 +0000
1475+++ ubuntuone/platform/logger/__init__.py 2012-06-25 20:40:24 +0000
1476@@ -28,8 +28,10 @@
1477 # files in the program, then also delete it here.
1478 """Logger module."""
1479
1480+import os
1481 import sys
1482
1483+from dirspec.basedir import xdg_cache_home
1484
1485 if sys.platform == "win32":
1486 from ubuntuone.platform.logger import windows
1487@@ -44,3 +46,8 @@
1488
1489 get_filesystem_logger = source.get_filesystem_logger
1490 setup_filesystem_logging = source.setup_filesystem_logging
1491+
1492+ubuntuone_log_dir = os.path.join(xdg_cache_home, 'ubuntuone', 'log')
1493+ubuntuone_log_dir = ubuntuone_log_dir.decode('utf-8')
1494+if not os.path.exists(ubuntuone_log_dir):
1495+ os.makedirs(ubuntuone_log_dir)
1496
1497=== modified file 'ubuntuone/platform/os_helper/darwin.py'
1498--- ubuntuone/platform/os_helper/darwin.py 2012-05-17 12:23:12 +0000
1499+++ ubuntuone/platform/os_helper/darwin.py 2012-06-25 20:40:24 +0000
1500@@ -107,4 +107,4 @@
1501
1502 def set_application_name(app_name):
1503 """Set the name of the application."""
1504- raise Exception("This is not the way to do it on OS X")
1505+ # nothing to be done let the plist take care of it
1506
1507=== removed directory 'ubuntuone/platform/xdg_base_directory'
1508=== removed file 'ubuntuone/platform/xdg_base_directory/__init__.py'
1509--- ubuntuone/platform/xdg_base_directory/__init__.py 2012-04-09 20:07:05 +0000
1510+++ ubuntuone/platform/xdg_base_directory/__init__.py 1970-01-01 00:00:00 +0000
1511@@ -1,40 +0,0 @@
1512-# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
1513-#
1514-# Copyright 2011-2012 Canonical Ltd.
1515-#
1516-# This program is free software: you can redistribute it and/or modify it
1517-# under the terms of the GNU General Public License version 3, as published
1518-# by the Free Software Foundation.
1519-#
1520-# This program is distributed in the hope that it will be useful, but
1521-# WITHOUT ANY WARRANTY; without even the implied warranties of
1522-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1523-# PURPOSE. See the GNU General Public License for more details.
1524-#
1525-# You should have received a copy of the GNU General Public License along
1526-# with this program. If not, see <http://www.gnu.org/licenses/>.
1527-#
1528-# In addition, as a special exception, the copyright holders give
1529-# permission to link the code of portions of this program with the
1530-# OpenSSL library under certain conditions as described in each
1531-# individual source file, and distribute linked combinations
1532-# including the two.
1533-# You must obey the GNU General Public License in all respects
1534-# for all of the code used other than OpenSSL. If you modify
1535-# file(s) with this exception, you may extend this exception to your
1536-# version of the file(s), but you are not obligated to do so. If you
1537-# do not wish to do so, delete this exception statement from your
1538-# version. If you delete this exception statement from all source
1539-# files in the program, then also delete it here.
1540-"""Defines a constant for ubuntuone's log folder based on XDG."""
1541-
1542-import os
1543-
1544-from ubuntu_sso.xdg_base_directory import xdg_cache_home, native_path
1545-
1546-# All usage of the ubuntuone_log_dir is for native access,
1547-# so we only change it here
1548-ubuntuone_log_dir = os.path.join(xdg_cache_home, 'ubuntuone', 'log')
1549-ubuntuone_log_dir = native_path(ubuntuone_log_dir)
1550-if not os.path.exists(ubuntuone_log_dir):
1551- os.makedirs(ubuntuone_log_dir)
1552
1553=== modified file 'ubuntuone/proxy/logger.py'
1554--- ubuntuone/proxy/logger.py 2012-04-09 20:07:05 +0000
1555+++ ubuntuone/proxy/logger.py 2012-06-25 20:40:24 +0000
1556@@ -36,7 +36,7 @@
1557 CustomRotatingFileHandler,
1558 )
1559
1560-from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
1561+from ubuntuone.platform.logger import ubuntuone_log_dir
1562
1563
1564 LOGFILENAME = os.path.join(ubuntuone_log_dir, 'proxy.log')
1565
1566=== modified file 'ubuntuone/status/logger.py'
1567--- ubuntuone/status/logger.py 2012-04-09 20:07:05 +0000
1568+++ ubuntuone/status/logger.py 2012-06-25 20:40:24 +0000
1569@@ -1,8 +1,5 @@
1570 # ubuntuone.syncdaemon.logger - logging utilities
1571 #
1572-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
1573-# Eric Casteleijn <eric.casteleijn@canonical.com>
1574-#
1575 # Copyright 2009-2012 Canonical Ltd.
1576 #
1577 # This program is free software: you can redistribute it and/or modify it
1578@@ -40,7 +37,7 @@
1579 CustomRotatingFileHandler,
1580 )
1581
1582-from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
1583+from ubuntuone.platform.logger import ubuntuone_log_dir
1584
1585
1586 LOGFILENAME = os.path.join(ubuntuone_log_dir, 'status.log')
1587
1588=== modified file 'ubuntuone/syncdaemon/config.py'
1589--- ubuntuone/syncdaemon/config.py 2012-04-09 20:07:05 +0000
1590+++ ubuntuone/syncdaemon/config.py 2012-06-25 20:40:24 +0000
1591@@ -36,9 +36,8 @@
1592
1593 from ConfigParser import NoOptionError, NoSectionError
1594 from optparse import OptionParser
1595-from ubuntu_sso.xdg_base_directory import (
1596+from dirspec.basedir import (
1597 load_config_paths,
1598- native_path,
1599 save_config_path,
1600 xdg_data_home,
1601 xdg_cache_home,
1602@@ -83,7 +82,7 @@
1603 logger = logging.getLogger('ubuntuone.SyncDaemon.config')
1604
1605 # get (and possibly create if don't exists) the user config file
1606-_user_config_path = os.path.join(native_path(save_config_path('ubuntuone')),
1607+_user_config_path = os.path.join(save_config_path('ubuntuone'),
1608 CONFIG_FILE)
1609
1610 # module private config instance.
1611@@ -166,7 +165,6 @@
1612 """
1613 config_files = []
1614 for xdg_config_dir in load_config_paths('ubuntuone'):
1615- xdg_config_dir = native_path(xdg_config_dir)
1616 config_file = os.path.join(xdg_config_dir, CONFIG_FILE)
1617 if os.path.exists(config_file):
1618 config_files.append(config_file)
1619
1620=== modified file 'ubuntuone/syncdaemon/logger.py'
1621--- ubuntuone/syncdaemon/logger.py 2012-04-09 20:07:05 +0000
1622+++ ubuntuone/syncdaemon/logger.py 2012-06-25 20:40:24 +0000
1623@@ -1,7 +1,5 @@
1624 # ubuntuone.syncdaemon.logger - logging utilities
1625 #
1626-# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
1627-#
1628 # Copyright 2009-2012 Canonical Ltd.
1629 #
1630 # This program is free software: you can redistribute it and/or modify it
1631@@ -45,7 +43,7 @@
1632 Logger,
1633 MultiFilter,
1634 )
1635-from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
1636+from ubuntuone.platform.logger import ubuntuone_log_dir
1637 # api compatibility imports
1638 from ubuntuone import logger
1639 from ubuntuone.platform import get_filesystem_logger, setup_filesystem_logging
1640
1641=== modified file 'ubuntuone/syncdaemon/main.py'
1642--- ubuntuone/syncdaemon/main.py 2012-05-22 14:07:55 +0000
1643+++ ubuntuone/syncdaemon/main.py 2012-06-25 20:40:24 +0000
1644@@ -32,9 +32,9 @@
1645 import os
1646 import sys
1647
1648+from dirspec.utils import user_home
1649 from twisted.internet import defer, reactor, task
1650
1651-from ubuntu_sso.xdg_base_directory import native_path, xdg_home
1652 from ubuntuone.syncdaemon import (
1653 action_queue,
1654 config,
1655@@ -111,7 +111,7 @@
1656 self.logger.info("Using %r as root dir", self.root_dir)
1657 self.logger.info("Using %r as data dir", self.data_dir)
1658 self.logger.info("Using %r as shares root dir", self.shares_dir)
1659- self.db = tritcask.Tritcask(native_path(tritcask_dir))
1660+ self.db = tritcask.Tritcask(tritcask_dir)
1661 self.vm = volume_manager.VolumeManager(self)
1662 self.fs = filesystem_manager.FileSystemManager(
1663 data_dir, partials_dir, self.vm, self.db)
1664@@ -266,7 +266,7 @@
1665
1666 def get_homedir(self):
1667 """Return the home dir point."""
1668- return xdg_home
1669+ return user_home
1670
1671 def get_rootdir(self):
1672 """Return the base dir/mount point."""

Subscribers

People subscribed via source and target branches

to all changes: