Merge lp:~mandel/ubuntuone-client/unify-processors into lp:ubuntuone-client

Proposed by Manuel de la Peña on 2012-07-13
Status: Merged
Approved by: Manuel de la Peña on 2012-07-19
Approved revision: 1297
Merged at revision: 1278
Proposed branch: lp:~mandel/ubuntuone-client/unify-processors
Merge into: lp:ubuntuone-client
Prerequisite: lp:~mandel/ubuntuone-client/fsevents-daemon
Diff against target: 1430 lines (+686/-516)
11 files modified
tests/platform/filesystem_notifications/test_darwin.py (+8/-5)
tests/platform/filesystem_notifications/test_linux.py (+6/-5)
tests/platform/filesystem_notifications/test_windows.py (+5/-4)
ubuntuone/platform/filesystem_notifications/__init__.py (+4/-2)
ubuntuone/platform/filesystem_notifications/common.py (+2/-224)
ubuntuone/platform/filesystem_notifications/darwin/fsevents_client.py (+0/-8)
ubuntuone/platform/filesystem_notifications/linux.py (+4/-258)
ubuntuone/platform/filesystem_notifications/notify_processor/__init__.py (+46/-0)
ubuntuone/platform/filesystem_notifications/notify_processor/common.py (+289/-0)
ubuntuone/platform/filesystem_notifications/notify_processor/linux.py (+322/-0)
ubuntuone/platform/filesystem_notifications/windows.py (+0/-10)
To merge this branch: bzr merge lp:~mandel/ubuntuone-client/unify-processors
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve on 2012-07-17
Diego Sarmentero (community) 2012-07-13 Approve on 2012-07-16
Review via email: mp+114906@code.launchpad.net

Commit Message

Move the NotifyProcessor to a multipletform package to simplify the code and remove circular imports.

Description of the Change

Move the NotifyProcessor to a multipletform package to simplify the code and remove circular imports.

To post a comment you must log in.
Manuel de la Peña (mandel) wrote :

I really wish there was a way to do a bzr mv of partial files.. sorry for the size.

Diego Sarmentero (diegosarmentero) wrote :

Is there any way to execute the tests that we have on darwin without the need of the deamon?
If i want to check the non-root implementation for example, right now it fails with this branch.

review: Needs Information
Diego Sarmentero (diegosarmentero) wrote :

Also this seems to break the tests on windows:
Traceback (most recent call last):
  File "X:\ubuntuone-client-mandel2\tests\platform\filesystem_notifications\test_windows.py", line 1093, in test_platform_is_ignored
    self.assertTrue(filesystem_notifications.path_is_ignored(ignored),
exceptions.AttributeError: 'module' object has no attribute 'path_is_ignored'

tests.platform.filesystem_notifications.test_windows.TestNotifyProcessor.test_platform_is_ignored

review: Needs Fixing
Manuel de la Peña (mandel) wrote :

> Also this seems to break the tests on windows:
> Traceback (most recent call last):
> File "X:\ubuntuone-client-
> mandel2\tests\platform\filesystem_notifications\test_windows.py", line 1093,
> in test_platform_is_ignored
> self.assertTrue(filesystem_notifications.path_is_ignored(ignored),
> exceptions.AttributeError: 'module' object has no attribute 'path_is_ignored'
>
> tests.platform.filesystem_notifications.test_windows.TestNotifyProcessor.test_
> platform_is_ignored

Fixing!

Diego Sarmentero (diegosarmentero) wrote :

+1

review: Approve
Roberto Alsina (ralsina) wrote :

In line 26 of the diff, it uses iteritems, should use items for python 3 compatibility.

review: Needs Fixing
Roberto Alsina (ralsina) :
review: Approve
1296. By Manuel de la Peña on 2012-07-19

Merged fsevents-daemon into unify-processors.

1297. By Manuel de la Peña on 2012-07-19

Merged fsevents-daemon into unify-processors.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/platform/filesystem_notifications/test_darwin.py'
2--- tests/platform/filesystem_notifications/test_darwin.py 2012-07-12 13:51:33 +0000
3+++ tests/platform/filesystem_notifications/test_darwin.py 2012-07-19 14:25:25 +0000
4@@ -40,10 +40,13 @@
5 from contrib.testing.testcase import BaseTwistedTestCase
6 from ubuntuone.devtools.handlers import MementoHandler
7 from ubuntuone.platform.filesystem_notifications import (
8- darwin as filesystem_notifications,
9-)
10+ common,
11+)
12+from ubuntuone.platform.filesystem_notifications.darwin import (
13+ fsevents_client as filesystem_notifications,
14+)
15+from ubuntuone.platform.filesystem_notifications import notify_processor
16 from ubuntuone.platform.filesystem_notifications.common import (
17- NotifyProcessor,
18 Watch,
19 WatchManager,
20 )
21@@ -60,7 +63,7 @@
22
23 # A reverse mapping for the tests
24 REVERSE_MACOS_ACTIONS = {}
25-for key, value in filesystem_notifications.ACTIONS.iteritems():
26+for key, value in common.ACTIONS.items():
27 REVERSE_MACOS_ACTIONS[value] = key
28
29
30@@ -1030,7 +1033,7 @@
31 def setUp(self):
32 """set up the diffeent tests."""
33 yield super(TestNotifyProcessor, self).setUp()
34- self.processor = NotifyProcessor(None)
35+ self.processor = notify_processor.NotifyProcessor(None)
36 self.general = FakeGeneralProcessor()
37 self.processor.general_processor = self.general
38
39
40=== modified file 'tests/platform/filesystem_notifications/test_linux.py'
41--- tests/platform/filesystem_notifications/test_linux.py 2012-06-14 18:22:16 +0000
42+++ tests/platform/filesystem_notifications/test_linux.py 2012-07-19 14:25:25 +0000
43@@ -37,6 +37,7 @@
44
45 from contrib.testing import testcase
46 from ubuntuone.syncdaemon import volume_manager
47+from ubuntuone.platform.filesystem_notifications import notify_processor
48 from ubuntuone.platform.filesystem_notifications import (
49 linux as filesystem_notifications,
50 )
51@@ -71,12 +72,12 @@
52
53 def test_processor_shutdown_no_timer(self):
54 """Shutdown the processor, no timer."""
55- processor = filesystem_notifications._GeneralINotifyProcessor('mntr')
56+ processor = notify_processor.NotifyProcessor('mntr')
57 processor.shutdown()
58
59 def test_processor_shutdown_timer_inactive(self):
60 """Shutdown the processor, timer inactive."""
61- processor = filesystem_notifications._GeneralINotifyProcessor('mntr')
62+ processor = notify_processor.NotifyProcessor('mntr')
63 d = defer.Deferred()
64
65 def shutdown():
66@@ -89,7 +90,7 @@
67
68 def test_processor_shutdown_timer_active(self):
69 """Shutdown the processor, timer going on."""
70- processor = filesystem_notifications._GeneralINotifyProcessor('mntr')
71+ processor = notify_processor.NotifyProcessor('mntr')
72 processor.timer = reactor.callLater(10, lambda: None)
73 processor.shutdown()
74 self.assertFalse(processor.timer.active())
75@@ -405,7 +406,7 @@
76 """When eCryptFS sends CLOSE_WRITE on folders, ignore it"""
77 result = []
78 monitor = None
79- processor = filesystem_notifications._GeneralINotifyProcessor(monitor)
80+ processor = notify_processor.NotifyProcessor(monitor)
81 self.patch(processor.general_processor, "push_event", result.append)
82
83 fake_event = FakeEvent()
84@@ -420,7 +421,7 @@
85 """When anything sends CLOSE_WRITE on files, handle it."""
86 result = []
87 monitor = None
88- processor = filesystem_notifications._GeneralINotifyProcessor(monitor)
89+ processor = notify_processor.NotifyProcessor(monitor)
90 self.patch(processor.general_processor, "push_event", result.append)
91
92 fake_event = FakeEvent()
93
94=== modified file 'tests/platform/filesystem_notifications/test_windows.py'
95--- tests/platform/filesystem_notifications/test_windows.py 2012-07-10 20:11:18 +0000
96+++ tests/platform/filesystem_notifications/test_windows.py 2012-07-19 14:25:25 +0000
97@@ -53,9 +53,9 @@
98 from ubuntuone.platform.filesystem_notifications import (
99 windows as filesystem_notifications,
100 )
101+from ubuntuone.platform.filesystem_notifications import notify_processor
102 from ubuntuone.platform.filesystem_notifications.common import (
103 FilesystemMonitor,
104- NotifyProcessor,
105 Watch,
106 WatchManager,
107 )
108@@ -1057,7 +1057,7 @@
109 def setUp(self):
110 """set up the diffeent tests."""
111 yield super(TestNotifyProcessor, self).setUp()
112- self.processor = NotifyProcessor(None)
113+ self.processor = notify_processor.NotifyProcessor(None)
114 self.general = FakeGeneralProcessor()
115 self.processor.general_processor = self.general
116
117@@ -1087,9 +1087,10 @@
118 """Test that we do indeed ignore the correct paths."""
119 not_ignored = 'test'
120 ignored = not_ignored + '.lnk'
121- self.assertFalse(filesystem_notifications.path_is_ignored(not_ignored),
122+ path_is_ignored = notify_processor.common.path_is_ignored
123+ self.assertFalse(path_is_ignored(not_ignored),
124 'Only links should be ignored.')
125- self.assertTrue(filesystem_notifications.path_is_ignored(ignored),
126+ self.assertTrue(path_is_ignored(ignored),
127 'Links should be ignored.')
128
129 def test_is_ignored(self):
130
131=== modified file 'ubuntuone/platform/filesystem_notifications/__init__.py'
132--- ubuntuone/platform/filesystem_notifications/__init__.py 2012-07-10 20:11:18 +0000
133+++ ubuntuone/platform/filesystem_notifications/__init__.py 2012-07-19 14:25:25 +0000
134@@ -30,12 +30,14 @@
135
136 import sys
137
138+from ubuntuone.platform.filesystem_notifications import notify_processor
139+
140+_GeneralINotifyProcessor = notify_processor.NotifyProcessor
141+
142
143 if sys.platform in ('darwin', 'win32'):
144 from ubuntuone.platform.filesystem_notifications import common
145 FilesystemMonitor = common.FilesystemMonitor
146- _GeneralINotifyProcessor = common.NotifyProcessor
147 else:
148 from ubuntuone.platform.filesystem_notifications import linux
149 FilesystemMonitor = linux.FilesystemMonitor
150- _GeneralINotifyProcessor = linux._GeneralINotifyProcessor
151
152=== modified file 'ubuntuone/platform/filesystem_notifications/common.py'
153--- ubuntuone/platform/filesystem_notifications/common.py 2012-07-19 14:25:25 +0000
154+++ ubuntuone/platform/filesystem_notifications/common.py 2012-07-19 14:25:25 +0000
155@@ -34,15 +34,11 @@
156
157 from twisted.internet import defer
158
159+from ubuntuone.platform.filesystem_notifications import notify_processor
160 from ubuntuone.platform.filesystem_notifications.pyinotify_agnostic import (
161 Event,
162 WatchManagerError,
163- ProcessEvent,
164- IN_OPEN,
165- IN_CLOSE_NOWRITE,
166- IN_CLOSE_WRITE,
167 IN_CREATE,
168- IN_IGNORED,
169 IN_ISDIR,
170 IN_DELETE,
171 IN_MOVED_FROM,
172@@ -75,27 +71,10 @@
173 # a map of the actions to names so that we have better logs.
174 ACTIONS_NAMES = source.ACTIONS_NAMES
175
176-# ignore paths in the platform, mainly links atm
177-path_is_ignored = source.path_is_ignored
178-
179 # the base class to be use for a platform
180 PlatformWatch = source.Watch
181 PlatformWatchManager = source.WatchManager
182
183-# translates quickly the event and it's is_dir state to our standard events
184-NAME_TRANSLATIONS = {
185- IN_OPEN: 'FS_FILE_OPEN',
186- IN_CLOSE_NOWRITE: 'FS_FILE_CLOSE_NOWRITE',
187- IN_CLOSE_WRITE: 'FS_FILE_CLOSE_WRITE',
188- IN_CREATE: 'FS_FILE_CREATE',
189- IN_CREATE | IN_ISDIR: 'FS_DIR_CREATE',
190- IN_DELETE: 'FS_FILE_DELETE',
191- IN_DELETE | IN_ISDIR: 'FS_DIR_DELETE',
192- IN_MOVED_FROM: 'FS_FILE_DELETE',
193- IN_MOVED_FROM | IN_ISDIR: 'FS_DIR_DELETE',
194- IN_MOVED_TO: 'FS_FILE_CREATE',
195- IN_MOVED_TO | IN_ISDIR: 'FS_DIR_CREATE'}
196-
197 # our logging level
198 TRACE = logger.TRACE
199
200@@ -380,207 +359,6 @@
201 yield self.manager.stop()
202
203
204-class NotifyProcessor(ProcessEvent):
205- """Processor that takes care of dealing with the events.
206-
207- This interface will be exposed to syncdaemon, ergo all passed
208- and returned paths must be a sequence of BYTES encoded with utf8.
209- """
210-
211- def __init__(self, monitor, ignore_config=None):
212- # XXX: avoid circular imports.
213- from ubuntuone.syncdaemon.filesystem_notifications import (
214- GeneralINotifyProcessor)
215- self.general_processor = GeneralINotifyProcessor(monitor,
216- self.handle_dir_delete, NAME_TRANSLATIONS,
217- path_is_ignored, IN_IGNORED, ignore_config=ignore_config)
218- self.held_event = None
219-
220- def rm_from_mute_filter(self, event, paths):
221- """Remove event from the mute filter."""
222- self.general_processor.rm_from_mute_filter(event, paths)
223-
224- def add_to_mute_filter(self, event, paths):
225- """Add an event and path(s) to the mute filter."""
226- self.general_processor.add_to_mute_filter(event, paths)
227-
228- @is_valid_syncdaemon_path(path_indexes=[1])
229- def is_ignored(self, path):
230- """Should we ignore this path?"""
231- return self.general_processor.is_ignored(path)
232-
233- def release_held_event(self, timed_out=False):
234- """Release the event on hold to fulfill its destiny."""
235- self.general_processor.push_event(self.held_event)
236- self.held_event = None
237-
238- def process_IN_MODIFY(self, event):
239- """Capture a modify event and fake an open ^ close write events."""
240- # lets ignore dir changes
241- if event.dir:
242- return
243- # on someplatforms we just get IN_MODIFY, lets always fake
244- # an OPEN & CLOSE_WRITE couple
245- raw_open = raw_close = {
246- 'wd': event.wd,
247- 'dir': event.dir,
248- 'name': event.name,
249- 'path': event.path}
250- # caculate the open mask
251- raw_open['mask'] = IN_OPEN
252- # create the event using the raw data, then fix the pathname param
253- open_event = Event(raw_open)
254- open_event.pathname = event.pathname
255- # push the open
256- self.general_processor.push_event(open_event)
257- raw_close['mask'] = IN_CLOSE_WRITE
258- close_event = Event(raw_close)
259- close_event.pathname = event.pathname
260- # push the close event
261- self.general_processor.push_event(close_event)
262-
263- def process_IN_MOVED_FROM(self, event):
264- """Capture the MOVED_FROM to maybe syntethize FILE_MOVED."""
265- if self.held_event is not None:
266- self.general_processor.log.warn('Lost pair event of %s',
267- self.held_event)
268- self.held_event = event
269-
270- def _fake_create_event(self, event):
271- """Fake the creation of an event."""
272- # this is the case of a MOVE from an ignored path (links for example)
273- # to a valid path
274- if event.dir:
275- evtname = "FS_DIR_"
276- else:
277- evtname = "FS_FILE_"
278- self.general_processor.eq_push(evtname + "CREATE", path=event.pathname)
279- if not event.dir:
280- self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
281- path=event.pathname)
282-
283- def _fake_delete_create_event(self, event):
284- """Fake the deletion and the creation."""
285- # this is the case of a MOVE from a watch UDF to a diff UDF which
286- # means that we have to copy the way linux works.
287- if event.dir:
288- evtname = "FS_DIR_"
289- else:
290- evtname = "FS_FILE_"
291- m = "Delete because of different shares: %r"
292- self.log.info(m, self.held_event.pathname)
293- self.general_processor.eq_push(evtname + "DELETE",
294- path=self.held_event.pathname)
295- self.general_processor.eq_push(evtname + "CREATE", path=event.pathname)
296- if not event.dir:
297- self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
298- path=event.pathname)
299-
300- def process_IN_MOVED_TO(self, event):
301- """Capture the MOVED_TO to maybe syntethize FILE_MOVED."""
302- if self.held_event is not None:
303- if event.cookie == self.held_event.cookie:
304- f_path_dir = os.path.split(self.held_event.pathname)[0]
305- t_path_dir = os.path.split(event.pathname)[0]
306-
307- is_from_forreal = not self.is_ignored(self.held_event.pathname)
308- is_to_forreal = not self.is_ignored(event.pathname)
309- if is_from_forreal and is_to_forreal:
310- f_share_id = self.general_processor.get_path_share_id(
311- f_path_dir)
312- t_share_id = self.general_processor.get_path_share_id(
313- t_path_dir)
314- if f_share_id != t_share_id:
315- # if the share_id are != push a delete/create
316- self._fake_delete_create_event(event)
317- else:
318- if event.dir:
319- evtname = "FS_DIR_"
320- else:
321- evtname = "FS_FILE_"
322- self.general_processor.eq_push(evtname + "MOVE",
323- path_from=self.held_event.pathname,
324- path_to=event.pathname)
325- elif is_to_forreal:
326- # this is the case of a MOVE from something ignored
327- # to a valid filename
328- self._fake_create_event(event)
329-
330- self.held_event = None
331- return
332- else:
333- self.release_held_event()
334- self.general_processor.push_event(event)
335- else:
336- # We should never get here, I really do not know how we
337- # got here
338- self.general_processor.log.warn(
339- 'Cookie does not match the previoues held event!')
340- self.general_processor.log.warn('Ignoring %s', event)
341-
342- def process_default(self, event):
343- """Push the event into the EventQueue."""
344- if self.held_event is not None:
345- self.release_held_event()
346- self.general_processor.push_event(event)
347-
348- @is_valid_syncdaemon_path(path_indexes=[1])
349- def handle_dir_delete(self, fullpath):
350- """Some special work when a directory is deleted."""
351- # remove the watch on that dir from our structures, this mainly tells
352- # the monitor to remove the watch which is fowaded to a watch manager.
353- self.general_processor.rm_watch(fullpath)
354-
355- # handle the case of move a dir to a non-watched directory
356- paths = self.general_processor.get_paths_starting_with(fullpath,
357- include_base=False)
358-
359- paths.sort(reverse=True)
360- for path, is_dir in paths:
361- m = "Pushing deletion because of parent dir move: (is_dir=%s) %r"
362- self.general_processor.log.info(m, is_dir, path)
363- if is_dir:
364- # same as the above remove
365- self.general_processor.rm_watch(path)
366- self.general_processor.eq_push('FS_DIR_DELETE', path=path)
367- else:
368- self.general_processor.eq_push('FS_FILE_DELETE', path=path)
369-
370- @is_valid_syncdaemon_path(path_indexes=[1])
371- def freeze_begin(self, path):
372- """Puts in hold all the events for this path."""
373- self.general_processor.freeze_begin(path)
374-
375- def freeze_rollback(self):
376- """Unfreezes the frozen path, reseting to idle state."""
377- self.general_processor.freeze_rollback()
378-
379- def freeze_commit(self, events):
380- """Unfreezes the frozen path, sending received events if not dirty.
381-
382- If events for that path happened:
383- - return True
384- else:
385- - push the here received events, return False
386- """
387- return self.general_processor.freeze_commit(events)
388-
389- @property
390- def mute_filter(self):
391- """Return the mute filter used by the processor."""
392- return self.general_processor.filter
393-
394- @property
395- def frozen_path(self):
396- """Return the frozen path."""
397- return self.general_processor.frozen_path
398-
399- @property
400- def log(self):
401- """Return the logger of the instance."""
402- return self.general_processor.log
403-
404-
405 class FilesystemMonitor(object):
406 """Manages the signals from filesystem."""
407
408@@ -590,7 +368,7 @@
409 self.log.setLevel(TRACE)
410 self.fs = fs
411 self.eq = eq
412- self._processor = NotifyProcessor(self, ignore_config)
413+ self._processor = notify_processor.NotifyProcessor(self, ignore_config)
414 self._watch_manager = WatchManager(self._processor)
415
416 def add_to_mute_filter(self, event, **info):
417
418=== modified file 'ubuntuone/platform/filesystem_notifications/darwin/fsevents_client.py'
419--- ubuntuone/platform/filesystem_notifications/darwin/fsevents_client.py 2012-07-19 14:25:25 +0000
420+++ ubuntuone/platform/filesystem_notifications/darwin/fsevents_client.py 2012-07-19 14:25:25 +0000
421@@ -61,14 +61,6 @@
422 }
423
424
425-def path_is_ignored(path):
426- """Should we ignore this path in the current platform.?"""
427- # don't support links yet
428- if os.path.islink(path):
429- return True
430- return False
431-
432-
433 # The implementation of the code that is provided as the pyinotify substitute
434 class Watch(object):
435 """Implement the same functions as pyinotify.Watch."""
436
437=== modified file 'ubuntuone/platform/filesystem_notifications/linux.py'
438--- ubuntuone/platform/filesystem_notifications/linux.py 2012-05-23 13:06:42 +0000
439+++ ubuntuone/platform/filesystem_notifications/linux.py 2012-07-19 14:25:25 +0000
440@@ -33,8 +33,10 @@
441 import os
442
443 import pyinotify
444-from twisted.internet import abstract, reactor, error, defer
445+from twisted.internet import abstract, reactor, defer
446+
447 from ubuntuone.platform.os_helper import access
448+from ubuntuone.platform.filesystem_notifications import notify_processor
449
450
451 # translates quickly the event and it's is_dir state to our standard events
452@@ -69,24 +71,6 @@
453 pyinotify.IN_MOVE_SELF)
454
455
456-def validate_filename(real_func):
457- """Decorator that validates the filename."""
458- def func(self, event):
459- """If valid, executes original function."""
460- try:
461- # validate UTF-8
462- event.name.decode("utf8")
463- except UnicodeDecodeError:
464- dirname = event.path.decode("utf8")
465- self.general_processor.invnames_log.info("%s in %r: path %r",
466- event.maskname, dirname, event.name)
467- self.general_processor.monitor.eq.push('FS_INVALID_NAME',
468- dirname=dirname, filename=event.name)
469- else:
470- real_func(self, event)
471- return func
472-
473-
474 class _AncestorsINotifyProcessor(pyinotify.ProcessEvent):
475 """inotify's processor when an event happens on an UDFs ancestor."""
476 def __init__(self, monitor):
477@@ -153,244 +137,6 @@
478 self.monitor.rm_watch(ancestor)
479
480
481-class _GeneralINotifyProcessor(pyinotify.ProcessEvent):
482- """inotify's processor when a general event happens.
483-
484- This class also catchs the MOVEs events, and synthetises a new
485- FS_(DIR|FILE)_MOVE event when possible.
486- """
487- def __init__(self, monitor, ignore_config=None):
488- # XXX: avoid circular imports
489- from ubuntuone.syncdaemon.filesystem_notifications import (
490- GeneralINotifyProcessor,
491- )
492- self.general_processor = GeneralINotifyProcessor(monitor,
493- self.handle_dir_delete, NAME_TRANSLATIONS,
494- self.platform_is_ignored, pyinotify.IN_IGNORED,
495- ignore_config=ignore_config)
496- self.held_event = None
497- self.timer = None
498-
499- def shutdown(self):
500- """Shut down the processor."""
501- if self.timer is not None and self.timer.active():
502- self.timer.cancel()
503-
504- def rm_from_mute_filter(self, event, paths):
505- """Remove an event and path(s) from the mute filter."""
506- self.general_processor.rm_from_mute_filter(event, paths)
507-
508- def add_to_mute_filter(self, event, paths):
509- """Add an event and path(s) to the mute filter."""
510- self.general_processor.add_to_mute_filter(event, paths)
511-
512- def on_timeout(self):
513- """Called on timeout."""
514- if self.held_event is not None:
515- self.release_held_event(True)
516-
517- def release_held_event(self, timed_out=False):
518- """Release the event on hold to fulfill its destiny."""
519- if not timed_out:
520- try:
521- self.timer.cancel()
522- except error.AlreadyCalled:
523- # self.timeout() was *just* called, do nothing here
524- return
525- self.general_processor.push_event(self.held_event)
526- self.held_event = None
527-
528- @validate_filename
529- def process_IN_OPEN(self, event):
530- """Filter IN_OPEN to make it happen only in files."""
531- if not (event.mask & pyinotify.IN_ISDIR):
532- self.general_processor.push_event(event)
533-
534- @validate_filename
535- def process_IN_CLOSE_NOWRITE(self, event):
536- """Filter IN_CLOSE_NOWRITE to make it happen only in files."""
537- if not (event.mask & pyinotify.IN_ISDIR):
538- self.general_processor.push_event(event)
539-
540- @validate_filename
541- def process_IN_CLOSE_WRITE(self, event):
542- """Filter IN_CLOSE_WRITE to make it happen only in files.
543-
544- eCryptFS sends IN_CLOSE_WRITE event for lower directories.
545-
546- """
547- if not (event.mask & pyinotify.IN_ISDIR):
548- self.general_processor.push_event(event)
549-
550- def process_IN_MOVE_SELF(self, event):
551- """Don't do anything here.
552-
553- We just turned this event on because pyinotify does some
554- path-fixing in its internal processing when this happens.
555-
556- """
557-
558- @validate_filename
559- def process_IN_MOVED_FROM(self, event):
560- """Capture the MOVED_FROM to maybe syntethize FILE_MOVED."""
561- if self.held_event is not None:
562- self.release_held_event()
563-
564- self.held_event = event
565- self.timer = reactor.callLater(1, self.on_timeout)
566-
567- def platform_is_ignored(self, path):
568- """Should we ignore this path in the current platform.?"""
569- # don't support links yet
570- if os.path.islink(path):
571- return True
572- return False
573-
574- def is_ignored(self, path):
575- """Should we ignore this path?"""
576- return self.general_processor.is_ignored(path)
577-
578- @validate_filename
579- def process_IN_MOVED_TO(self, event):
580- """Capture the MOVED_TO to maybe syntethize FILE_MOVED."""
581- if self.held_event is not None:
582- if event.cookie == self.held_event.cookie:
583- try:
584- self.timer.cancel()
585- except error.AlreadyCalled:
586- # self.timeout() was *just* called, do nothing here
587- pass
588- else:
589- f_path_dir = self.held_event.path
590- f_path = os.path.join(f_path_dir, self.held_event.name)
591- t_path_dir = event.path
592- t_path = os.path.join(t_path_dir, event.name)
593-
594- is_from_forreal = not self.is_ignored(f_path)
595- is_to_forreal = not self.is_ignored(t_path)
596- if is_from_forreal and is_to_forreal:
597- f_share_id = self.general_processor.get_path_share_id(
598- f_path_dir)
599- t_share_id = self.general_processor.get_path_share_id(
600- t_path_dir)
601- if event.dir:
602- evtname = "FS_DIR_"
603- else:
604- evtname = "FS_FILE_"
605- if f_share_id != t_share_id:
606- # if the share_id are != push a delete/create
607- m = "Delete because of different shares: %r"
608- self.general_processor.log.info(m, f_path)
609- self.general_processor.eq_push(evtname + "DELETE",
610- path=f_path)
611- self.general_processor.eq_push(evtname + "CREATE",
612- path=t_path)
613- if not event.dir:
614- self.general_processor.eq_push(
615- 'FS_FILE_CLOSE_WRITE', path=t_path)
616- else:
617- self.general_processor.monitor.inotify_watch_fix(
618- f_path, t_path)
619- self.general_processor.eq_push(evtname + "MOVE",
620- path_from=f_path, path_to=t_path)
621- elif is_to_forreal:
622- # this is the case of a MOVE from something ignored
623- # to a valid filename
624- if event.dir:
625- evtname = "FS_DIR_"
626- else:
627- evtname = "FS_FILE_"
628- self.general_processor.eq_push(evtname + "CREATE",
629- path=t_path)
630- if not event.dir:
631- self.general_processor.eq_push(
632- 'FS_FILE_CLOSE_WRITE', path=t_path)
633-
634- else:
635- # this is the case of a MOVE from something valid
636- # to an ignored filename
637- if event.dir:
638- evtname = "FS_DIR_"
639- else:
640- evtname = "FS_FILE_"
641- self.general_processor.eq_push(evtname + "DELETE",
642- path=f_path)
643-
644- self.held_event = None
645- return
646- else:
647- self.release_held_event()
648- self.general_processor.push_event(event)
649- else:
650- # we don't have a held_event so this is a move from outside.
651- # if it's a file move it's atomic on POSIX, so we aren't going to
652- # receive a IN_CLOSE_WRITE, so let's fake it for files
653- self.general_processor.push_event(event)
654- if not event.dir:
655- t_path = os.path.join(event.path, event.name)
656- self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
657- path=t_path)
658-
659- @validate_filename
660- def process_default(self, event):
661- """Push the event into the EventQueue."""
662- if self.held_event is not None:
663- self.release_held_event()
664- self.general_processor.push_event(event)
665-
666- def freeze_begin(self, path):
667- """Puts in hold all the events for this path."""
668- self.general_processor.freeze_begin(path)
669-
670- def freeze_rollback(self):
671- """Unfreezes the frozen path, reseting to idle state."""
672- self.general_processor.freeze_rollback()
673-
674- def freeze_commit(self, events):
675- """Unfreezes the frozen path, sending received events if not dirty.
676-
677- If events for that path happened:
678- - return True
679- else:
680- - push the here received events, return False
681- """
682- return self.general_processor.freeze_commit(events)
683-
684- def handle_dir_delete(self, fullpath):
685- """Some special work when a directory is deleted."""
686- # remove the watch on that dir from our structures
687- self.general_processor.rm_watch(fullpath)
688-
689- # handle the case of move a dir to a non-watched directory
690- paths = self.general_processor.get_paths_starting_with(fullpath,
691- include_base=False)
692-
693- paths.sort(reverse=True)
694- for path, is_dir in paths:
695- m = "Pushing deletion because of parent dir move: (is_dir=%s) %r"
696- self.general_processor.log.info(m, is_dir, path)
697- if is_dir:
698- self.general_processor.rm_watch(path)
699- self.general_processor.eq_push('FS_DIR_DELETE', path=path)
700- else:
701- self.general_processor.eq_push('FS_FILE_DELETE', path=path)
702-
703- @property
704- def mute_filter(self):
705- """Return the mute filter used by the processor."""
706- return self.general_processor.filter
707-
708- @property
709- def frozen_path(self):
710- """Return the frozen path."""
711- return self.general_processor.frozen_path
712-
713- @property
714- def log(self):
715- """Return the logger of the instance."""
716- return self.general_processor.log
717-
718-
719 class FilesystemMonitor(object):
720 """Manages the signals from filesystem."""
721
722@@ -401,7 +147,7 @@
723
724 # general inotify
725 self._inotify_general_wm = wm = pyinotify.WatchManager()
726- self._processor = _GeneralINotifyProcessor(self, ignore_config)
727+ self._processor = notify_processor.NotifyProcessor(self, ignore_config)
728 self._inotify_notifier_gral = pyinotify.Notifier(wm, self._processor)
729 self._inotify_reader_gral = self._hook_inotify_to_twisted(
730 wm, self._inotify_notifier_gral)
731
732=== added directory 'ubuntuone/platform/filesystem_notifications/notify_processor'
733=== added file 'ubuntuone/platform/filesystem_notifications/notify_processor/__init__.py'
734--- ubuntuone/platform/filesystem_notifications/notify_processor/__init__.py 1970-01-01 00:00:00 +0000
735+++ ubuntuone/platform/filesystem_notifications/notify_processor/__init__.py 2012-07-19 14:25:25 +0000
736@@ -0,0 +1,46 @@
737+# -*- coding: utf-8 *-*
738+#
739+# Copyright 2012 Canonical Ltd.
740+#
741+# This program is free software: you can redistribute it and/or modify it
742+# under the terms of the GNU General Public License version 3, as published
743+# by the Free Software Foundation.
744+#
745+# This program is distributed in the hope that it will be useful, but
746+# WITHOUT ANY WARRANTY; without even the implied warranties of
747+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
748+# PURPOSE. See the GNU General Public License for more details.
749+#
750+# You should have received a copy of the GNU General Public License along
751+# with this program. If not, see <http://www.gnu.org/licenses/>.
752+#
753+# In addition, as a special exception, the copyright holders give
754+# permission to link the code of portions of this program with the
755+# OpenSSL library under certain conditions as described in each
756+# individual source file, and distribute linked combinations
757+# including the two.
758+# You must obey the GNU General Public License in all respects
759+# for all of the code used other than OpenSSL. If you modify
760+# file(s) with this exception, you may extend this exception to your
761+# version of the file(s), but you are not obligated to do so. If you
762+# do not wish to do so, delete this exception statement from your
763+# version. If you delete this exception statement from all source
764+# files in the program, then also delete it here
765+"""Notify processor diff implementations per platform."""
766+
767+import sys
768+
769+if sys.platform in ('win32', 'darwin'):
770+ from ubuntuone.platform.filesystem_notifications.notify_processor import (
771+ common,
772+ )
773+ # workaround due to pyflakes :(
774+ source = common
775+else:
776+ from ubuntuone.platform.filesystem_notifications.notify_processor import (
777+ linux,
778+ )
779+ # workaround due to pyflakes :(
780+ source = linux
781+
782+NotifyProcessor = source.NotifyProcessor
783
784=== added file 'ubuntuone/platform/filesystem_notifications/notify_processor/common.py'
785--- ubuntuone/platform/filesystem_notifications/notify_processor/common.py 1970-01-01 00:00:00 +0000
786+++ ubuntuone/platform/filesystem_notifications/notify_processor/common.py 2012-07-19 14:25:25 +0000
787@@ -0,0 +1,289 @@
788+# -*- coding: utf-8 *-*
789+#
790+# Copyright 2011-2012 Canonical Ltd.
791+#
792+# This program is free software: you can redistribute it and/or modify it
793+# under the terms of the GNU General Public License version 3, as published
794+# by the Free Software Foundation.
795+#
796+# This program is distributed in the hope that it will be useful, but
797+# WITHOUT ANY WARRANTY; without even the implied warranties of
798+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
799+# PURPOSE. See the GNU General Public License for more details.
800+#
801+# You should have received a copy of the GNU General Public License along
802+# with this program. If not, see <http://www.gnu.org/licenses/>.
803+#
804+# In addition, as a special exception, the copyright holders give
805+# permission to link the code of portions of this program with the
806+# OpenSSL library under certain conditions as described in each
807+# individual source file, and distribute linked combinations
808+# including the two.
809+# You must obey the GNU General Public License in all respects
810+# for all of the code used other than OpenSSL. If you modify
811+# file(s) with this exception, you may extend this exception to your
812+# version of the file(s), but you are not obligated to do so. If you
813+# do not wish to do so, delete this exception statement from your
814+# version. If you delete this exception statement from all source
815+# files in the program, then also delete it here.
816+"""Win and darwin implementation."""
817+
818+import os
819+import sys
820+
821+from ubuntuone.syncdaemon.filesystem_notifications import (
822+ GeneralINotifyProcessor,
823+)
824+from ubuntuone.platform.filesystem_notifications.pyinotify_agnostic import (
825+ Event,
826+ ProcessEvent,
827+ IN_OPEN,
828+ IN_CLOSE_NOWRITE,
829+ IN_CLOSE_WRITE,
830+ IN_CREATE,
831+ IN_IGNORED,
832+ IN_ISDIR,
833+ IN_DELETE,
834+ IN_MOVED_FROM,
835+ IN_MOVED_TO,
836+)
837+
838+from ubuntuone.platform.os_helper import (
839+ is_valid_syncdaemon_path,
840+)
841+
842+if sys.platform == 'win32':
843+
844+ @is_valid_syncdaemon_path()
845+ def win_is_ignored(path):
846+ """Should we ignore this path in the current platform.?"""
847+ # don't support links yet
848+ if path.endswith('.lnk'):
849+ return True
850+ return False
851+
852+ # work around for pyflakes :(
853+ path_is_ignored = win_is_ignored
854+else:
855+
856+ def unix_is_ignored(path):
857+ """Should we ignore this path in the current platform.?"""
858+ # don't support links yet
859+ if os.path.islink(path):
860+ return True
861+ return False
862+
863+ # work around for pyflakes :(
864+ path_is_ignored = unix_is_ignored
865+
866+# translates quickly the event and it's is_dir state to our standard events
867+NAME_TRANSLATIONS = {
868+ IN_OPEN: 'FS_FILE_OPEN',
869+ IN_CLOSE_NOWRITE: 'FS_FILE_CLOSE_NOWRITE',
870+ IN_CLOSE_WRITE: 'FS_FILE_CLOSE_WRITE',
871+ IN_CREATE: 'FS_FILE_CREATE',
872+ IN_CREATE | IN_ISDIR: 'FS_DIR_CREATE',
873+ IN_DELETE: 'FS_FILE_DELETE',
874+ IN_DELETE | IN_ISDIR: 'FS_DIR_DELETE',
875+ IN_MOVED_FROM: 'FS_FILE_DELETE',
876+ IN_MOVED_FROM | IN_ISDIR: 'FS_DIR_DELETE',
877+ IN_MOVED_TO: 'FS_FILE_CREATE',
878+ IN_MOVED_TO | IN_ISDIR: 'FS_DIR_CREATE'}
879+
880+
881+class NotifyProcessor(ProcessEvent):
882+ """Processor that takes care of dealing with the events.
883+
884+ This interface will be exposed to syncdaemon, ergo all passed
885+ and returned paths must be a sequence of BYTES encoded with utf8.
886+ """
887+
888+ def __init__(self, monitor, ignore_config=None):
889+ self.general_processor = GeneralINotifyProcessor(monitor,
890+ self.handle_dir_delete, NAME_TRANSLATIONS,
891+ path_is_ignored, IN_IGNORED, ignore_config=ignore_config)
892+ self.held_event = None
893+
894+ def rm_from_mute_filter(self, event, paths):
895+ """Remove event from the mute filter."""
896+ self.general_processor.rm_from_mute_filter(event, paths)
897+
898+ def add_to_mute_filter(self, event, paths):
899+ """Add an event and path(s) to the mute filter."""
900+ self.general_processor.add_to_mute_filter(event, paths)
901+
902+ @is_valid_syncdaemon_path(path_indexes=[1])
903+ def is_ignored(self, path):
904+ """Should we ignore this path?"""
905+ return self.general_processor.is_ignored(path)
906+
907+ def release_held_event(self, timed_out=False):
908+ """Release the event on hold to fulfill its destiny."""
909+ self.general_processor.push_event(self.held_event)
910+ self.held_event = None
911+
912+ def process_IN_MODIFY(self, event):
913+ """Capture a modify event and fake an open ^ close write events."""
914+ # lets ignore dir changes
915+ if event.dir:
916+ return
917+ # on someplatforms we just get IN_MODIFY, lets always fake
918+ # an OPEN & CLOSE_WRITE couple
919+ raw_open = raw_close = {
920+ 'wd': event.wd,
921+ 'dir': event.dir,
922+ 'name': event.name,
923+ 'path': event.path}
924+ # caculate the open mask
925+ raw_open['mask'] = IN_OPEN
926+ # create the event using the raw data, then fix the pathname param
927+ open_event = Event(raw_open)
928+ open_event.pathname = event.pathname
929+ # push the open
930+ self.general_processor.push_event(open_event)
931+ raw_close['mask'] = IN_CLOSE_WRITE
932+ close_event = Event(raw_close)
933+ close_event.pathname = event.pathname
934+ # push the close event
935+ self.general_processor.push_event(close_event)
936+
937+ def process_IN_MOVED_FROM(self, event):
938+ """Capture the MOVED_FROM to maybe syntethize FILE_MOVED."""
939+ if self.held_event is not None:
940+ self.general_processor.log.warn('Lost pair event of %s',
941+ self.held_event)
942+ self.held_event = event
943+
944+ def _fake_create_event(self, event):
945+ """Fake the creation of an event."""
946+ # this is the case of a MOVE from an ignored path (links for example)
947+ # to a valid path
948+ if event.dir:
949+ evtname = "FS_DIR_"
950+ else:
951+ evtname = "FS_FILE_"
952+ self.general_processor.eq_push(evtname + "CREATE", path=event.pathname)
953+ if not event.dir:
954+ self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
955+ path=event.pathname)
956+
957+ def _fake_delete_create_event(self, event):
958+ """Fake the deletion and the creation."""
959+ # this is the case of a MOVE from a watch UDF to a diff UDF which
960+ # means that we have to copy the way linux works.
961+ if event.dir:
962+ evtname = "FS_DIR_"
963+ else:
964+ evtname = "FS_FILE_"
965+ m = "Delete because of different shares: %r"
966+ self.log.info(m, self.held_event.pathname)
967+ self.general_processor.eq_push(evtname + "DELETE",
968+ path=self.held_event.pathname)
969+ self.general_processor.eq_push(evtname + "CREATE", path=event.pathname)
970+ if not event.dir:
971+ self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
972+ path=event.pathname)
973+
974+ def process_IN_MOVED_TO(self, event):
975+ """Capture the MOVED_TO to maybe syntethize FILE_MOVED."""
976+ if self.held_event is not None:
977+ if event.cookie == self.held_event.cookie:
978+ f_path_dir = os.path.split(self.held_event.pathname)[0]
979+ t_path_dir = os.path.split(event.pathname)[0]
980+
981+ is_from_forreal = not self.is_ignored(self.held_event.pathname)
982+ is_to_forreal = not self.is_ignored(event.pathname)
983+ if is_from_forreal and is_to_forreal:
984+ f_share_id = self.general_processor.get_path_share_id(
985+ f_path_dir)
986+ t_share_id = self.general_processor.get_path_share_id(
987+ t_path_dir)
988+ if f_share_id != t_share_id:
989+ # if the share_id are != push a delete/create
990+ self._fake_delete_create_event(event)
991+ else:
992+ if event.dir:
993+ evtname = "FS_DIR_"
994+ else:
995+ evtname = "FS_FILE_"
996+ self.general_processor.eq_push(evtname + "MOVE",
997+ path_from=self.held_event.pathname,
998+ path_to=event.pathname)
999+ elif is_to_forreal:
1000+ # this is the case of a MOVE from something ignored
1001+ # to a valid filename
1002+ self._fake_create_event(event)
1003+
1004+ self.held_event = None
1005+ return
1006+ else:
1007+ self.release_held_event()
1008+ self.general_processor.push_event(event)
1009+ else:
1010+ # We should never get here, I really do not know how we
1011+ # got here
1012+ self.general_processor.log.warn(
1013+ 'Cookie does not match the previoues held event!')
1014+ self.general_processor.log.warn('Ignoring %s', event)
1015+
1016+ def process_default(self, event):
1017+ """Push the event into the EventQueue."""
1018+ if self.held_event is not None:
1019+ self.release_held_event()
1020+ self.general_processor.push_event(event)
1021+
1022+ @is_valid_syncdaemon_path(path_indexes=[1])
1023+ def handle_dir_delete(self, fullpath):
1024+ """Some special work when a directory is deleted."""
1025+ # remove the watch on that dir from our structures, this mainly tells
1026+ # the monitor to remove the watch which is fowaded to a watch manager.
1027+ self.general_processor.rm_watch(fullpath)
1028+
1029+ # handle the case of move a dir to a non-watched directory
1030+ paths = self.general_processor.get_paths_starting_with(fullpath,
1031+ include_base=False)
1032+
1033+ paths.sort(reverse=True)
1034+ for path, is_dir in paths:
1035+ m = "Pushing deletion because of parent dir move: (is_dir=%s) %r"
1036+ self.general_processor.log.info(m, is_dir, path)
1037+ if is_dir:
1038+ # same as the above remove
1039+ self.general_processor.rm_watch(path)
1040+ self.general_processor.eq_push('FS_DIR_DELETE', path=path)
1041+ else:
1042+ self.general_processor.eq_push('FS_FILE_DELETE', path=path)
1043+
1044+ @is_valid_syncdaemon_path(path_indexes=[1])
1045+ def freeze_begin(self, path):
1046+ """Puts in hold all the events for this path."""
1047+ self.general_processor.freeze_begin(path)
1048+
1049+ def freeze_rollback(self):
1050+ """Unfreezes the frozen path, reseting to idle state."""
1051+ self.general_processor.freeze_rollback()
1052+
1053+ def freeze_commit(self, events):
1054+ """Unfreezes the frozen path, sending received events if not dirty.
1055+
1056+ If events for that path happened:
1057+ - return True
1058+ else:
1059+ - push the here received events, return False
1060+ """
1061+ return self.general_processor.freeze_commit(events)
1062+
1063+ @property
1064+ def mute_filter(self):
1065+ """Return the mute filter used by the processor."""
1066+ return self.general_processor.filter
1067+
1068+ @property
1069+ def frozen_path(self):
1070+ """Return the frozen path."""
1071+ return self.general_processor.frozen_path
1072+
1073+ @property
1074+ def log(self):
1075+ """Return the logger of the instance."""
1076+ return self.general_processor.log
1077
1078=== added file 'ubuntuone/platform/filesystem_notifications/notify_processor/linux.py'
1079--- ubuntuone/platform/filesystem_notifications/notify_processor/linux.py 1970-01-01 00:00:00 +0000
1080+++ ubuntuone/platform/filesystem_notifications/notify_processor/linux.py 2012-07-19 14:25:25 +0000
1081@@ -0,0 +1,322 @@
1082+# -*- coding: utf-8 *-*
1083+#
1084+# Copyright 2012 Canonical Ltd.
1085+#
1086+# This program is free software: you can redistribute it and/or modify it
1087+# under the terms of the GNU General Public License version 3, as published
1088+# by the Free Software Foundation.
1089+#
1090+# This program is distributed in the hope that it will be useful, but
1091+# WITHOUT ANY WARRANTY; without even the implied warranties of
1092+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1093+# PURPOSE. See the GNU General Public License for more details.
1094+#
1095+# You should have received a copy of the GNU General Public License along
1096+# with this program. If not, see <http://www.gnu.org/licenses/>.
1097+#
1098+# In addition, as a special exception, the copyright holders give
1099+# permission to link the code of portions of this program with the
1100+# OpenSSL library under certain conditions as described in each
1101+# individual source file, and distribute linked combinations
1102+# including the two.
1103+# You must obey the GNU General Public License in all respects
1104+# for all of the code used other than OpenSSL. If you modify
1105+# file(s) with this exception, you may extend this exception to your
1106+# version of the file(s), but you are not obligated to do so. If you
1107+# do not wish to do so, delete this exception statement from your
1108+# version. If you delete this exception statement from all source
1109+# files in the program, then also delete it here.
1110+"""Linux implementation."""
1111+
1112+import os
1113+
1114+import pyinotify
1115+from twisted.internet import reactor, error
1116+
1117+
1118+# translates quickly the event and it's is_dir state to our standard events
1119+NAME_TRANSLATIONS = {
1120+ pyinotify.IN_OPEN: 'FS_FILE_OPEN',
1121+ pyinotify.IN_CLOSE_NOWRITE: 'FS_FILE_CLOSE_NOWRITE',
1122+ pyinotify.IN_CLOSE_WRITE: 'FS_FILE_CLOSE_WRITE',
1123+ pyinotify.IN_CREATE: 'FS_FILE_CREATE',
1124+ pyinotify.IN_CREATE | pyinotify.IN_ISDIR: 'FS_DIR_CREATE',
1125+ pyinotify.IN_DELETE: 'FS_FILE_DELETE',
1126+ pyinotify.IN_DELETE | pyinotify.IN_ISDIR: 'FS_DIR_DELETE',
1127+ pyinotify.IN_MOVED_FROM: 'FS_FILE_DELETE',
1128+ pyinotify.IN_MOVED_FROM | pyinotify.IN_ISDIR: 'FS_DIR_DELETE',
1129+ pyinotify.IN_MOVED_TO: 'FS_FILE_CREATE',
1130+ pyinotify.IN_MOVED_TO | pyinotify.IN_ISDIR: 'FS_DIR_CREATE',
1131+}
1132+
1133+# these are the events that will listen from inotify
1134+INOTIFY_EVENTS_GENERAL = (
1135+ pyinotify.IN_OPEN |
1136+ pyinotify.IN_CLOSE_NOWRITE |
1137+ pyinotify.IN_CLOSE_WRITE |
1138+ pyinotify.IN_CREATE |
1139+ pyinotify.IN_DELETE |
1140+ pyinotify.IN_MOVED_FROM |
1141+ pyinotify.IN_MOVED_TO |
1142+ pyinotify.IN_MOVE_SELF)
1143+INOTIFY_EVENTS_ANCESTORS = (
1144+ pyinotify.IN_DELETE |
1145+ pyinotify.IN_MOVED_FROM |
1146+ pyinotify.IN_MOVED_TO |
1147+ pyinotify.IN_MOVE_SELF)
1148+
1149+from ubuntuone.syncdaemon.filesystem_notifications import (
1150+ GeneralINotifyProcessor,
1151+)
1152+
1153+
1154+def validate_filename(real_func):
1155+ """Decorator that validates the filename."""
1156+ def func(self, event):
1157+ """If valid, executes original function."""
1158+ try:
1159+ # validate UTF-8
1160+ event.name.decode("utf8")
1161+ except UnicodeDecodeError:
1162+ dirname = event.path.decode("utf8")
1163+ self.general_processor.invnames_log.info("%s in %r: path %r",
1164+ event.maskname, dirname, event.name)
1165+ self.general_processor.monitor.eq.push('FS_INVALID_NAME',
1166+ dirname=dirname, filename=event.name)
1167+ else:
1168+ real_func(self, event)
1169+ return func
1170+
1171+
1172+class NotifyProcessor(pyinotify.ProcessEvent):
1173+ """inotify's processor when a general event happens.
1174+
1175+ This class also catchs the MOVEs events, and synthetises a new
1176+ FS_(DIR|FILE)_MOVE event when possible.
1177+ """
1178+ def __init__(self, monitor, ignore_config=None):
1179+ self.general_processor = GeneralINotifyProcessor(monitor,
1180+ self.handle_dir_delete, NAME_TRANSLATIONS,
1181+ self.platform_is_ignored, pyinotify.IN_IGNORED,
1182+ ignore_config=ignore_config)
1183+ self.held_event = None
1184+ self.timer = None
1185+
1186+ def shutdown(self):
1187+ """Shut down the processor."""
1188+ if self.timer is not None and self.timer.active():
1189+ self.timer.cancel()
1190+
1191+ def rm_from_mute_filter(self, event, paths):
1192+ """Remove an event and path(s) from the mute filter."""
1193+ self.general_processor.rm_from_mute_filter(event, paths)
1194+
1195+ def add_to_mute_filter(self, event, paths):
1196+ """Add an event and path(s) to the mute filter."""
1197+ self.general_processor.add_to_mute_filter(event, paths)
1198+
1199+ def on_timeout(self):
1200+ """Called on timeout."""
1201+ if self.held_event is not None:
1202+ self.release_held_event(True)
1203+
1204+ def release_held_event(self, timed_out=False):
1205+ """Release the event on hold to fulfill its destiny."""
1206+ if not timed_out:
1207+ try:
1208+ self.timer.cancel()
1209+ except error.AlreadyCalled:
1210+ # self.timeout() was *just* called, do nothing here
1211+ return
1212+ self.general_processor.push_event(self.held_event)
1213+ self.held_event = None
1214+
1215+ @validate_filename
1216+ def process_IN_OPEN(self, event):
1217+ """Filter IN_OPEN to make it happen only in files."""
1218+ if not (event.mask & pyinotify.IN_ISDIR):
1219+ self.general_processor.push_event(event)
1220+
1221+ @validate_filename
1222+ def process_IN_CLOSE_NOWRITE(self, event):
1223+ """Filter IN_CLOSE_NOWRITE to make it happen only in files."""
1224+ if not (event.mask & pyinotify.IN_ISDIR):
1225+ self.general_processor.push_event(event)
1226+
1227+ @validate_filename
1228+ def process_IN_CLOSE_WRITE(self, event):
1229+ """Filter IN_CLOSE_WRITE to make it happen only in files.
1230+
1231+ eCryptFS sends IN_CLOSE_WRITE event for lower directories.
1232+
1233+ """
1234+ if not (event.mask & pyinotify.IN_ISDIR):
1235+ self.general_processor.push_event(event)
1236+
1237+ def process_IN_MOVE_SELF(self, event):
1238+ """Don't do anything here.
1239+
1240+ We just turned this event on because pyinotify does some
1241+ path-fixing in its internal processing when this happens.
1242+
1243+ """
1244+
1245+ @validate_filename
1246+ def process_IN_MOVED_FROM(self, event):
1247+ """Capture the MOVED_FROM to maybe syntethize FILE_MOVED."""
1248+ if self.held_event is not None:
1249+ self.release_held_event()
1250+
1251+ self.held_event = event
1252+ self.timer = reactor.callLater(1, self.on_timeout)
1253+
1254+ def platform_is_ignored(self, path):
1255+ """Should we ignore this path in the current platform.?"""
1256+ # don't support links yet
1257+ if os.path.islink(path):
1258+ return True
1259+ return False
1260+
1261+ def is_ignored(self, path):
1262+ """Should we ignore this path?"""
1263+ return self.general_processor.is_ignored(path)
1264+
1265+ @validate_filename
1266+ def process_IN_MOVED_TO(self, event):
1267+ """Capture the MOVED_TO to maybe syntethize FILE_MOVED."""
1268+ if self.held_event is not None:
1269+ if event.cookie == self.held_event.cookie:
1270+ try:
1271+ self.timer.cancel()
1272+ except error.AlreadyCalled:
1273+ # self.timeout() was *just* called, do nothing here
1274+ pass
1275+ else:
1276+ f_path_dir = self.held_event.path
1277+ f_path = os.path.join(f_path_dir, self.held_event.name)
1278+ t_path_dir = event.path
1279+ t_path = os.path.join(t_path_dir, event.name)
1280+
1281+ is_from_forreal = not self.is_ignored(f_path)
1282+ is_to_forreal = not self.is_ignored(t_path)
1283+ if is_from_forreal and is_to_forreal:
1284+ f_share_id = self.general_processor.get_path_share_id(
1285+ f_path_dir)
1286+ t_share_id = self.general_processor.get_path_share_id(
1287+ t_path_dir)
1288+ if event.dir:
1289+ evtname = "FS_DIR_"
1290+ else:
1291+ evtname = "FS_FILE_"
1292+ if f_share_id != t_share_id:
1293+ # if the share_id are != push a delete/create
1294+ m = "Delete because of different shares: %r"
1295+ self.general_processor.log.info(m, f_path)
1296+ self.general_processor.eq_push(evtname + "DELETE",
1297+ path=f_path)
1298+ self.general_processor.eq_push(evtname + "CREATE",
1299+ path=t_path)
1300+ if not event.dir:
1301+ self.general_processor.eq_push(
1302+ 'FS_FILE_CLOSE_WRITE', path=t_path)
1303+ else:
1304+ self.general_processor.monitor.inotify_watch_fix(
1305+ f_path, t_path)
1306+ self.general_processor.eq_push(evtname + "MOVE",
1307+ path_from=f_path, path_to=t_path)
1308+ elif is_to_forreal:
1309+ # this is the case of a MOVE from something ignored
1310+ # to a valid filename
1311+ if event.dir:
1312+ evtname = "FS_DIR_"
1313+ else:
1314+ evtname = "FS_FILE_"
1315+ self.general_processor.eq_push(evtname + "CREATE",
1316+ path=t_path)
1317+ if not event.dir:
1318+ self.general_processor.eq_push(
1319+ 'FS_FILE_CLOSE_WRITE', path=t_path)
1320+
1321+ else:
1322+ # this is the case of a MOVE from something valid
1323+ # to an ignored filename
1324+ if event.dir:
1325+ evtname = "FS_DIR_"
1326+ else:
1327+ evtname = "FS_FILE_"
1328+ self.general_processor.eq_push(evtname + "DELETE",
1329+ path=f_path)
1330+
1331+ self.held_event = None
1332+ return
1333+ else:
1334+ self.release_held_event()
1335+ self.general_processor.push_event(event)
1336+ else:
1337+ # we don't have a held_event so this is a move from outside.
1338+ # if it's a file move it's atomic on POSIX, so we aren't going to
1339+ # receive a IN_CLOSE_WRITE, so let's fake it for files
1340+ self.general_processor.push_event(event)
1341+ if not event.dir:
1342+ t_path = os.path.join(event.path, event.name)
1343+ self.general_processor.eq_push('FS_FILE_CLOSE_WRITE',
1344+ path=t_path)
1345+
1346+ @validate_filename
1347+ def process_default(self, event):
1348+ """Push the event into the EventQueue."""
1349+ if self.held_event is not None:
1350+ self.release_held_event()
1351+ self.general_processor.push_event(event)
1352+
1353+ def freeze_begin(self, path):
1354+ """Puts in hold all the events for this path."""
1355+ self.general_processor.freeze_begin(path)
1356+
1357+ def freeze_rollback(self):
1358+ """Unfreezes the frozen path, reseting to idle state."""
1359+ self.general_processor.freeze_rollback()
1360+
1361+ def freeze_commit(self, events):
1362+ """Unfreezes the frozen path, sending received events if not dirty.
1363+
1364+ If events for that path happened:
1365+ - return True
1366+ else:
1367+ - push the here received events, return False
1368+ """
1369+ return self.general_processor.freeze_commit(events)
1370+
1371+ def handle_dir_delete(self, fullpath):
1372+ """Some special work when a directory is deleted."""
1373+ # remove the watch on that dir from our structures
1374+ self.general_processor.rm_watch(fullpath)
1375+
1376+ # handle the case of move a dir to a non-watched directory
1377+ paths = self.general_processor.get_paths_starting_with(fullpath,
1378+ include_base=False)
1379+
1380+ paths.sort(reverse=True)
1381+ for path, is_dir in paths:
1382+ m = "Pushing deletion because of parent dir move: (is_dir=%s) %r"
1383+ self.general_processor.log.info(m, is_dir, path)
1384+ if is_dir:
1385+ self.general_processor.rm_watch(path)
1386+ self.general_processor.eq_push('FS_DIR_DELETE', path=path)
1387+ else:
1388+ self.general_processor.eq_push('FS_FILE_DELETE', path=path)
1389+
1390+ @property
1391+ def mute_filter(self):
1392+ """Return the mute filter used by the processor."""
1393+ return self.general_processor.filter
1394+
1395+ @property
1396+ def frozen_path(self):
1397+ """Return the frozen path."""
1398+ return self.general_processor.frozen_path
1399+
1400+ @property
1401+ def log(self):
1402+ """Return the logger of the instance."""
1403+ return self.general_processor.log
1404
1405=== modified file 'ubuntuone/platform/filesystem_notifications/windows.py'
1406--- ubuntuone/platform/filesystem_notifications/windows.py 2012-07-13 12:39:33 +0000
1407+++ ubuntuone/platform/filesystem_notifications/windows.py 2012-07-19 14:25:25 +0000
1408@@ -64,7 +64,6 @@
1409 WAIT_OBJECT_0)
1410
1411 from ubuntuone.platform.os_helper.windows import (
1412- is_valid_syncdaemon_path,
1413 get_syncdaemon_valid_path,
1414 )
1415
1416@@ -113,15 +112,6 @@
1417 FILE_NOTIFY_CHANGE_LAST_ACCESS
1418
1419
1420-@is_valid_syncdaemon_path()
1421-def path_is_ignored(path):
1422- """Should we ignore this path in the current platform.?"""
1423- # don't support links yet
1424- if path.endswith('.lnk'):
1425- return True
1426- return False
1427-
1428-
1429 class Watch(object):
1430 """Implement the same functions as pyinotify.Watch."""
1431

Subscribers

People subscribed via source and target branches