Merge lp:~alecu/ubuntuone-client/watch-finished-deferred into lp:ubuntuone-client
- watch-finished-deferred
- Merge into trunk
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 |
Related bugs: |
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
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: |
Looks great!