Merge lp:~alecu/ubuntuone-client/watch-finished-deferred into lp:ubuntuone-client

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Roberto Alsina
Approved revision: 1112
Merged at revision: 1110
Proposed branch: lp:~alecu/ubuntuone-client/watch-finished-deferred
Merge into: lp:ubuntuone-client
Diff against target: 639 lines (+180/-53)
14 files modified
run-tests.bat (+1/-1)
tests/platform/test_filesystem_notifications.py (+3/-8)
tests/platform/test_os_helper.py (+2/-1)
tests/platform/windows/test_filesystem_notifications.py (+66/-13)
tests/syncdaemon/test_eq_inotify.py (+2/-1)
tests/syncdaemon/test_eventqueue.py (+33/-2)
tests/syncdaemon/test_main.py (+2/-1)
tests/syncdaemon/test_tritcask.py (+1/-0)
tests/syncdaemon/test_vm_helper.py (+32/-10)
ubuntuone/platform/linux/os_helper.py (+1/-1)
ubuntuone/platform/windows/filesystem_notifications.py (+29/-11)
ubuntuone/syncdaemon/event_queue.py (+4/-1)
ubuntuone/syncdaemon/main.py (+2/-1)
ubuntuone/syncdaemon/vm_helper.py (+2/-2)
To merge this branch: bzr merge lp:~alecu/ubuntuone-client/watch-finished-deferred
Reviewer Review Type Date Requested Status
Guillermo Gonzalez Approve
Natalia Bidart (community) Approve
Review via email: mp+71955@code.launchpad.net

Commit message

A deferred that is fired when watches are completely removed. (LP: #824819)

Description of the change

A deferred that is fired when watches are completely removed. (LP: #824819)

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks great!

review: Approve
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'run-tests.bat'
2--- run-tests.bat 2011-07-12 14:57:17 +0000
3+++ run-tests.bat 2011-08-17 21:29:24 +0000
4@@ -52,7 +52,7 @@
5 ECHO Python found, executing the tests...
6 COPY windows\clientdefs.py ubuntuone\clientdefs.py
7 :: execute the tests with a number of ignored linux only modules
8-"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=twisted -c tests -p tests\platform\linux %1
9+"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=twisted -c tests -p tests\platform\linux %*
10 "%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1lint"
11 :: test for style if we can, if pep8 is not present, move to the end
12 IF EXIST "%PYTHONEXEPATH%\Scripts\pep8.exe"
13
14=== modified file 'tests/platform/test_filesystem_notifications.py'
15--- tests/platform/test_filesystem_notifications.py 2011-08-09 12:56:06 +0000
16+++ tests/platform/test_filesystem_notifications.py 2011-08-17 21:29:24 +0000
17@@ -86,6 +86,7 @@
18 self.vm = testcase.FakeVolumeManager(self.root_dir)
19 self.tritcask_dir = self.mktemp("tritcask_dir")
20 self.db = Tritcask(self.tritcask_dir)
21+ self.addCleanup(self.db.shutdown)
22 self.fs = filesystem_manager.FileSystemManager(fsmdir, partials_dir,
23 self.vm, self.db)
24 self.fs.create(path=self.root_dir, share_id='', is_dir=True)
25@@ -102,17 +103,11 @@
26
27 eq.subscribe(HitMe())
28 self.monitor = eq.monitor
29+ self.addCleanup(self.monitor.shutdown)
30 self.log_handler = MementoHandler()
31 self.log_handler.setLevel(logging.DEBUG)
32 self.monitor.log.addHandler(self.log_handler)
33-
34- @defer.inlineCallbacks
35- def tearDown(self):
36- """Clean up the tests."""
37- self.monitor.shutdown()
38- self.db.shutdown()
39- self.monitor.log.removeHandler(self.log_handler)
40- yield super(BaseFSMonitorTestCase, self).tearDown()
41+ self.addCleanup(self.monitor.log.removeHandler, self.log_handler)
42
43
44 class DynamicHitMe(object):
45
46=== modified file 'tests/platform/test_os_helper.py'
47--- tests/platform/test_os_helper.py 2011-08-15 20:29:25 +0000
48+++ tests/platform/test_os_helper.py 2011-08-17 21:29:24 +0000
49@@ -313,7 +313,8 @@
50 make_link(self.testfile, destination)
51
52 self.assertTrue(is_link(destination))
53- self.assertEqual(self.testfile, read_link(destination))
54+ self.assertEqual(os.path.normcase(self.testfile),
55+ os.path.normcase(read_link(destination)))
56
57 def test_movetotrash_file_ok(self):
58 """Move a file to trash ok.
59
60=== modified file 'tests/platform/windows/test_filesystem_notifications.py'
61--- tests/platform/windows/test_filesystem_notifications.py 2011-08-11 21:49:15 +0000
62+++ tests/platform/windows/test_filesystem_notifications.py 2011-08-17 21:29:24 +0000
63@@ -618,6 +618,12 @@
64 watch = Watch(1, path, None, True, None)
65 self.assertEqual(watch.started, watch._watch_started_deferred)
66
67+ def test_stopped_property(self):
68+ """Test that the stopped property returns the stopped deferred."""
69+ path = u'\\\\?\\C:\\path' # a valid windows path
70+ watch = Watch(1, path, None, True, None)
71+ self.assertEqual(watch.stopped, watch._watch_stopped_deferred)
72+
73 def random_error(self, *args):
74 """Throw a fake exception."""
75 raise FakeException()
76@@ -646,14 +652,28 @@
77 """CloseHandle is called when there's an error in the watch thread."""
78 test_path = self.mktemp("test_directory")
79 close_called = []
80+ self.patch(filesystem_notifications, "CreateFileW", lambda *_: None)
81 self.patch(filesystem_notifications, "CloseHandle",
82 close_called.append)
83 self.patch(filesystem_notifications, "ReadDirectoryChangesW",
84 self.random_error)
85- watch = Watch(1, test_path, None, True, None)
86+ watch = Watch(1, test_path, self.mask, True, None)
87 d = watch.start_watching()
88 yield self.assertFailure(d, FakeException)
89 self.assertEqual(len(close_called), 1)
90+ yield watch.stop_watching()
91+
92+ @defer.inlineCallbacks
93+ def test_stop_watching_fired_when_watch_thread_finishes(self):
94+ """The deferred returned is fired when the watch thread finishes."""
95+ events = []
96+ test_path = self.mktemp("another_test_directory")
97+ fake_processor = events.append
98+ watch = Watch(1, test_path, self.mask, True, fake_processor)
99+ yield watch.start_watching()
100+ self.assertNotEqual(watch._watch_handle, None)
101+ yield watch.stop_watching()
102+ self.assertEqual(watch._watch_handle, None)
103
104
105 class TestWatchManager(BaseTwistedTestCase):
106@@ -668,17 +688,39 @@
107 self.manager = WatchManager(None)
108 self.manager._wdm = {1: self.watch}
109
110+ @defer.inlineCallbacks
111 def test_stop(self):
112 """Test that the different watches are stopped."""
113 self.was_called = False
114
115- def stop_watching():
116+ def fake_stop_watching(watch):
117 """Fake stop watch."""
118 self.was_called = True
119-
120- self.watch.stop_watching = stop_watching
121- self.manager.stop()
122- self.assertTrue(self.was_called, 'The watch stop was not called.')
123+ return defer.succeed(True)
124+
125+ self.patch(Watch, "stop_watching", fake_stop_watching)
126+ yield self.manager.stop()
127+ self.assertTrue(self.was_called, 'The watch stop should be called.')
128+
129+ @defer.inlineCallbacks
130+ def test_stop_multiple(self):
131+ """Test that stop is fired when *all* watches have stopped."""
132+
133+ def fake_stop_watching(watch):
134+ """Another fake stop watch."""
135+ return watch.stopped
136+
137+ self.patch(Watch, "stop_watching", fake_stop_watching)
138+ second_path = self.parent_path + u"second_path"
139+ second_watch = Watch(2, second_path, None, True, None)
140+ self.manager._wdm[2] = second_watch
141+ d = self.manager.stop()
142+ self.assertFalse(d.called, "Not fired before all watches end")
143+ self.watch.stopped.callback(None)
144+ self.assertFalse(d.called, "Not fired before all watches end")
145+ second_watch.stopped.callback(None)
146+ yield d
147+ self.assertTrue(d.called, "Fired after the watches ended")
148
149 def test_get_present_watch(self):
150 """Test that we can get a Watch using is wd."""
151@@ -688,6 +730,7 @@
152 """Test that we get an error when trying to get a missing wd."""
153 self.assertRaises(KeyError, self.manager.get_watch, (1,))
154
155+ @defer.inlineCallbacks
156 def test_delete_present_watch(self):
157 """Test that we can remove a present watch."""
158 self.was_called = False
159@@ -695,20 +738,21 @@
160 def stop_watching():
161 """Fake stop watch."""
162 self.was_called = True
163+ return defer.succeed(True)
164
165 self.watch.stop_watching = stop_watching
166- self.manager.del_watch(1)
167+ yield self.manager.del_watch(1)
168 self.assertRaises(KeyError, self.manager.get_watch, (1,))
169
170 def test_add_single_watch(self):
171 """Test the addition of a new single watch."""
172 self.was_called = False
173
174- def start_watching(*args):
175- """Fake stop watch."""
176+ def fake_start_watching(*args):
177+ """Fake start watch."""
178 self.was_called = True
179
180- Watch.start_watching = start_watching
181+ self.patch(Watch, "start_watching", fake_start_watching)
182 self.manager._wdm = {}
183
184 mask = 'bit_mask'
185@@ -748,9 +792,16 @@
186 """A watch on an unwatched path returns None."""
187 self.assertEqual(None, self.manager.get_wd(self.parent_path))
188
189+ @defer.inlineCallbacks
190 def test_rm_present_wd(self):
191 """Test the removal of a present watch."""
192- self.manager.rm_watch(1)
193+
194+ def fake_stop_watching():
195+ """Fake stop watch."""
196+ return defer.succeed(True)
197+
198+ self.patch(self.watch, "stop_watching", fake_stop_watching)
199+ yield self.manager.rm_watch(1)
200 self.assertEqual(None, self.manager._wdm.get(1))
201
202 def test_rm_root_path(self):
203@@ -791,12 +842,13 @@
204
205 class TestWatchManagerAddWatches(BaseTwistedTestCase):
206 """Test the watch manager."""
207+ timeout = 5
208
209 def test_add_watch_twice(self):
210 """Adding a watch twice succeeds when the watch is running."""
211 self.patch(Watch, "start_watching", lambda self: self.started)
212 manager = WatchManager(None)
213- self.addCleanup(manager.stop)
214+ # no need to stop watching because start_watching is fake
215
216 path = u'\\\\?\\C:\\test' # a valid windows path
217 mask = 'fake bit mask'
218@@ -1243,12 +1295,13 @@
219
220 class FilesystemMonitorTestCase(BaseTwistedTestCase):
221 """Tests for the FilesystemMonitor."""
222+ timeout = 5
223
224 def test_add_watch_twice(self):
225 """Check the deferred returned by a second add_watch."""
226 self.patch(Watch, "start_watching", lambda self: self.started)
227 monitor = FilesystemMonitor(None, None)
228- self.addCleanup(monitor.shutdown)
229+ # no need to stop watching because start_watching is fake
230
231 parent_path = 'C:\\test' # a valid windows path in utf-8 bytes
232 child_path = parent_path + "\\child"
233
234=== modified file 'tests/syncdaemon/test_eq_inotify.py'
235--- tests/syncdaemon/test_eq_inotify.py 2011-08-10 18:27:24 +0000
236+++ tests/syncdaemon/test_eq_inotify.py 2011-08-17 21:29:24 +0000
237@@ -577,6 +577,7 @@
238 self.partials_dir = self.mktemp('partials_dir')
239 self.main = FakeMain(self.root_dir, self.shares_dir,
240 self.data_dir, self.partials_dir)
241+ self.addCleanup(self.main.shutdown)
242 self.eq = self.main.event_q
243 assert self.eq is self.eq.fs.vm.m.event_q
244
245@@ -621,7 +622,6 @@
246 def tearDown(self):
247 """Cleanup."""
248 self.eq.unsubscribe(self.listener)
249- self.main.shutdown()
250
251 if self.old_value is None:
252 os.environ.pop(self.env_var)
253@@ -649,6 +649,7 @@
254 return first
255 assertEqual = failUnlessEqual
256
257+ @skipIfOS('win32', "Event watches are not set on ancestors on windows.")
258 @defer.inlineCallbacks
259 def test_file_events_are_ignored_on_udf_ancestor(self):
260 """Events on UDF ancestors are ignored."""
261
262=== modified file 'tests/syncdaemon/test_eventqueue.py'
263--- tests/syncdaemon/test_eventqueue.py 2011-07-27 14:26:36 +0000
264+++ tests/syncdaemon/test_eventqueue.py 2011-08-17 21:29:24 +0000
265@@ -21,13 +21,15 @@
266
267 import logging
268
269+from twisted.internet import defer
270+from twisted.trial.unittest import TestCase
271+
272+from contrib.testing.testcase import BaseTwistedTestCase, FakeVolumeManager
273 from ubuntuone.syncdaemon import (
274 event_queue,
275 filesystem_manager,
276 tritcask,
277 )
278-from contrib.testing.testcase import BaseTwistedTestCase, FakeVolumeManager
279-from twisted.internet import defer
280 from ubuntuone.devtools.handlers import MementoHandler
281
282
283@@ -379,3 +381,32 @@
284
285 d.addCallback(callback)
286 return d
287+
288+
289+class FakeMonitor(object):
290+ """A fake FilesystemMonitor."""
291+
292+ def __init__(self, *args):
293+ """Initialize this fake."""
294+ self.shutdown_d = defer.Deferred()
295+
296+ def shutdown(self):
297+ """Get the shutdown deferred."""
298+ return self.shutdown_d
299+
300+
301+class EventQueueShutdownTestCase(TestCase):
302+ """Test the shutdown method in EQ."""
303+
304+ timeout = 2
305+
306+ @defer.inlineCallbacks
307+ def test_shutdown_defers(self):
308+ """The shutdown method in eq defers on the shutdown of the monitor."""
309+ self.patch(event_queue, "FilesystemMonitor", FakeMonitor)
310+ eq = event_queue.EventQueue(None)
311+ d = eq.shutdown()
312+ self.assertFalse(d.called, "shutdown is fired after the monitor.")
313+ eq.monitor.shutdown_d.callback(True)
314+ self.assertTrue(d.called, "shutdown is fired after the monitor.")
315+ yield d
316
317=== modified file 'tests/syncdaemon/test_main.py'
318--- tests/syncdaemon/test_main.py 2011-08-10 18:27:24 +0000
319+++ tests/syncdaemon/test_main.py 2011-08-17 21:29:24 +0000
320@@ -123,6 +123,7 @@
321 main.event_q.push('SYS_UNKNOWN_ERROR')
322 self.assertTrue(self.restarted)
323
324+ @defer.inlineCallbacks
325 def test_shutdown_pushes_sys_quit(self):
326 """When shutting down, the SYS_QUIT event is pushed."""
327 params = get_main_params(self, _get_main_common_params(self))
328@@ -131,7 +132,7 @@
329 self.patch(main.event_q, 'push',
330 lambda *a, **kw: events.append((a, kw)))
331
332- main.shutdown()
333+ yield main.shutdown()
334 expected = [(('SYS_USER_DISCONNECT',), {}), (('SYS_QUIT',), {})]
335 self.assertEqual(expected, events)
336
337
338=== modified file 'tests/syncdaemon/test_tritcask.py'
339--- tests/syncdaemon/test_tritcask.py 2011-08-10 12:14:19 +0000
340+++ tests/syncdaemon/test_tritcask.py 2011-08-17 21:29:24 +0000
341@@ -726,6 +726,7 @@
342 self.db.live_file.make_immutable()
343 self.db.shutdown()
344 self.db = Tritcask(self.base_dir)
345+ self.addCleanup(self.db.shutdown)
346 self.db.put(1, 'foo1', 'bar1')
347 self.assertEqual(1, len(self.db._immutable))
348 # read from the old file
349
350=== modified file 'tests/syncdaemon/test_vm_helper.py'
351--- tests/syncdaemon/test_vm_helper.py 2011-08-03 20:44:10 +0000
352+++ tests/syncdaemon/test_vm_helper.py 2011-08-17 21:29:24 +0000
353@@ -25,6 +25,9 @@
354
355 from tests.syncdaemon.test_vm import BaseVolumeManagerTests
356
357+from contrib.testing.testcase import BaseTwistedTestCase
358+from ubuntuone.platform import os_helper
359+from ubuntuone.syncdaemon import vm_helper
360 from ubuntuone.syncdaemon.vm_helper import (
361 create_shares_link,
362 get_share_dir_name,
363@@ -78,16 +81,35 @@
364 self.assertRaises(ValueError, get_udf_suggested_path, None)
365 self.assertRaises(ValueError, get_udf_suggested_path, relative_home)
366
367- def _test_create_shares_link(self):
368- """Test for create_shares_link."""
369- source = 'source'
370- dest = 'dest'
371- make_link = self.mocker.replace(
372- 'ubuntuone.platform.windows.os_helper.make_link')
373- make_link(source, dest)
374- self.mocker.replay()
375- create_shares_link(source, dest)
376- self.mocker.verify()
377+
378+class VMHelperLinkTestCase(BaseTwistedTestCase):
379+ """Tests for the VM Helper symlinks."""
380+
381+ def test_create_shares_link_exists(self):
382+ """create_shares_link is noop when there's something with that name."""
383+ base = self.mktemp("test_create_shares_link_exists")
384+ source_path = os.path.join(base, "source")
385+ dest_path = os.path.join(base, "dest")
386+ os_helper.make_dir(dest_path)
387+ self.assertFalse(create_shares_link(source_path, dest_path))
388+
389+ def test_create_shares_link_makes_the_link(self):
390+ """create_shares_link makes the link as expected."""
391+ base = self.mktemp("test_create_shares_link_makes_the_link")
392+ source_path = os.path.join(base, "source")
393+ dest_path = os.path.join(base, "dest")
394+ os_helper.make_dir(source_path)
395+ self.assertTrue(create_shares_link(source_path, dest_path))
396+ self.assertTrue(vm_helper.is_link(dest_path))
397+
398+ def test_create_shares_link_existing(self):
399+ """create_shares_link on an existing path does nothing."""
400+ base = self.mktemp("test_create_shares_link_makes_the_link")
401+ source_path = os.path.join(base, "source")
402+ dest_path = os.path.join(base, "dest")
403+ os_helper.make_dir(source_path)
404+ self.assertTrue(create_shares_link(source_path, dest_path))
405+ self.assertFalse(create_shares_link(source_path, dest_path))
406
407
408 class GetShareDirNameTests(BaseVolumeManagerTests):
409
410=== modified file 'ubuntuone/platform/linux/os_helper.py'
411--- ubuntuone/platform/linux/os_helper.py 2011-08-15 18:54:45 +0000
412+++ ubuntuone/platform/linux/os_helper.py 2011-08-17 21:29:24 +0000
413@@ -131,7 +131,7 @@
414
415 def remove_link(path):
416 """Removes a link."""
417- if os.path.exists(path):
418+ if is_link(path):
419 os.unlink(path)
420
421
422
423=== modified file 'ubuntuone/platform/windows/filesystem_notifications.py'
424--- ubuntuone/platform/windows/filesystem_notifications.py 2011-08-11 21:27:07 +0000
425+++ ubuntuone/platform/windows/filesystem_notifications.py 2011-08-17 21:29:24 +0000
426@@ -1,5 +1,7 @@
427+#
428 # Authors: Manuel de la Pena <manuel@canonical.com>
429 # Natalia B. Bidart <natalia.bidart@canonical.com>
430+# Alejandro J. Cura <alecu@canonical.com>
431 #
432 # Copyright 2011 Canonical Ltd.
433 #
434@@ -23,6 +25,7 @@
435 from uuid import uuid4
436
437 from twisted.internet import defer, reactor
438+from twisted.python.failure import Failure
439 from pywintypes import OVERLAPPED
440 from win32api import CloseHandle
441 from win32con import (
442@@ -137,6 +140,7 @@
443 self._cookie = None
444 self._source_pathname = None
445 self._process_thread = None
446+ self._watch_handle = None
447 # remember the subdirs we have so that when we have a delete we can
448 # check if it was a remove
449 self._subdirs = []
450@@ -149,6 +153,8 @@
451 # this deferred is fired when the watch has started monitoring
452 # a directory from a thread
453 self._watch_started_deferred = defer.Deferred()
454+ # and this one is fired when the watch has stopped
455+ self._watch_stopped_deferred = defer.Deferred()
456
457 @is_valid_windows_path(path_indexes=[1])
458 def _path_is_dir(self, path):
459@@ -170,7 +176,7 @@
460 return is_dir
461
462 def _process_events(self, events):
463- """Process the events form the queue."""
464+ """Process the events from the queue."""
465 # do not do it if we stop watching and the events are empty
466 if not self._watching:
467 return
468@@ -227,9 +233,9 @@
469 """Wrap _watch, and errback on any unhandled error."""
470 try:
471 self._watch()
472- except Exception as error:
473+ except Exception:
474 reactor.callFromThread(self._call_deferred,
475- self._watch_started_deferred.errback, error)
476+ self._watch_started_deferred.errback, Failure())
477
478 def _watch(self):
479 """Watch a path that is a directory."""
480@@ -238,7 +244,7 @@
481 os.path.exists(self._path), os.path.isdir(self._path))
482 # we are going to be using the ReadDirectoryChangesW whihc requires
483 # a directory handle and the mask to be used.
484- handle = CreateFileW(
485+ self._watch_handle = CreateFileW(
486 self._path,
487 FILE_LIST_DIRECTORY,
488 FILE_SHARE_READ | FILE_SHARE_WRITE,
489@@ -248,9 +254,11 @@
490 None)
491
492 try:
493- self._watch_loop(handle)
494+ self._watch_loop(self._watch_handle)
495 finally:
496- CloseHandle(handle)
497+ CloseHandle(self._watch_handle)
498+ self._watch_handle = None
499+ reactor.callFromThread(self.stopped.callback, True)
500
501 def _watch_loop(self, handle):
502 """The loop where we watch the directory."""
503@@ -325,6 +333,7 @@
504 SetEvent(self._wait_stop)
505 self._watching = False
506 self._subdirs = []
507+ return self.stopped
508
509 def update(self, mask, auto_add=False):
510 """Update the info used by the watcher."""
511@@ -346,6 +355,11 @@
512 """A deferred that will be called when the watch is running."""
513 return self._watch_started_deferred
514
515+ @property
516+ def stopped(self):
517+ """A deferred fired when the watch thread has finished."""
518+ return self._watch_stopped_deferred
519+
520
521 class WatchManager(object):
522 """Implement the same functions as pyinotify.WatchManager.
523@@ -368,19 +382,22 @@
524 def stop(self):
525 """Close the manager and stop all watches."""
526 self.log.debug('Stopping watches.')
527+ wait_list = []
528 for current_wd in self._wdm:
529- self._wdm[current_wd].stop_watching()
530- self.log.debug('Watch for %s stopped.', self._wdm[current_wd].path)
531+ wait_list.append(self._wdm[current_wd].stop_watching())
532+ self.log.debug('Stopping Watch on %r.', self._wdm[current_wd].path)
533+ return defer.DeferredList(wait_list)
534
535 def get_watch(self, wd):
536 """Return the watch with the given descriptor."""
537 return self._wdm[wd]
538
539+ @defer.inlineCallbacks
540 def del_watch(self, wd):
541 """Delete the watch with the given descriptor."""
542 try:
543 watch = self._wdm[wd]
544- watch.stop_watching()
545+ yield watch.stop_watching()
546 del self._wdm[wd]
547 self.log.debug('Watch %s removed.', wd)
548 except KeyError, e:
549@@ -445,11 +462,12 @@
550 if watch:
551 return watch.path
552
553+ @defer.inlineCallbacks
554 def rm_watch(self, wd, rec=False, quiet=True):
555 """Remove the the watch with the given wd."""
556 try:
557 watch = self._wdm[wd]
558- watch.stop_watching()
559+ yield watch.stop_watching()
560 del self._wdm[wd]
561 except KeyError, err:
562 self.log.error(str(err))
563@@ -700,7 +718,7 @@
564
565 def shutdown(self):
566 """Prepares the EQ to be closed."""
567- self._watch_manager.stop()
568+ return self._watch_manager.stop()
569
570 @windowspath(path_indexes=[1])
571 def rm_watch(self, dirpath):
572
573=== modified file 'ubuntuone/syncdaemon/event_queue.py'
574--- ubuntuone/syncdaemon/event_queue.py 2011-08-03 22:01:22 +0000
575+++ ubuntuone/syncdaemon/event_queue.py 2011-08-17 21:29:24 +0000
576@@ -21,6 +21,8 @@
577 import functools
578 import logging
579
580+from twisted.internet import defer
581+
582 from ubuntuone.platform import FilesystemMonitor
583
584 # these are our internal events, what is inserted into the whole system
585@@ -202,9 +204,10 @@
586 if not self.empty_event_queue_callbacks:
587 self._have_empty_eq_cback = False
588
589+ @defer.inlineCallbacks
590 def shutdown(self):
591 """Make the monitor shutdown."""
592- self.monitor.shutdown()
593+ yield self.monitor.shutdown()
594 # clean up all registered listeners
595 if len(self.listener_map.items()) > 0:
596 for k, v in self.listener_map.items():
597
598=== modified file 'ubuntuone/syncdaemon/main.py'
599--- ubuntuone/syncdaemon/main.py 2011-08-10 14:34:36 +0000
600+++ ubuntuone/syncdaemon/main.py 2011-08-17 21:29:24 +0000
601@@ -199,6 +199,7 @@
602 # hook the event queue to the root dir
603 self.event_q.push('SYS_INIT_DONE')
604
605+ @defer.inlineCallbacks
606 def shutdown(self, with_restart=False):
607 """Shutdown the daemon; optionally restart it."""
608 self.event_q.push('SYS_USER_DISCONNECT')
609@@ -215,7 +216,7 @@
610 self.hash_q.shutdown()
611 self.external.shutdown(with_restart)
612
613- self.event_q.shutdown()
614+ yield self.event_q.shutdown()
615 self.db.shutdown()
616 self.mark.stop()
617
618
619=== modified file 'ubuntuone/syncdaemon/vm_helper.py'
620--- ubuntuone/syncdaemon/vm_helper.py 2011-08-09 12:56:06 +0000
621+++ ubuntuone/syncdaemon/vm_helper.py 2011-08-17 21:29:24 +0000
622@@ -27,7 +27,7 @@
623 make_link,
624 path_exists,
625 read_link,
626- remove_file,
627+ remove_link,
628 )
629
630
631@@ -71,7 +71,7 @@
632 if not path_exists(dest):
633 # remove the symlink if it's broken
634 if is_link(dest) and read_link(dest) != source:
635- remove_file(dest)
636+ remove_link(dest)
637 make_link(source, dest)
638 return True
639 else:

Subscribers

People subscribed via source and target branches