Merge lp:~mandel/ubuntuone-client/add-virtual-watches into lp:ubuntuone-client

Proposed by Manuel de la Peña
Status: Rejected
Rejected by: dobey
Proposed branch: lp:~mandel/ubuntuone-client/add-virtual-watches
Merge into: lp:ubuntuone-client
Diff against target: 1545 lines (+636/-333)
3 files modified
tests/platform/windows/test_filesystem_notifications.py (+527/-255)
ubuntuone/platform/windows/filesystem_notifications.py (+102/-75)
ubuntuone/platform/windows/os_helper.py (+7/-3)
To merge this branch: bzr merge lp:~mandel/ubuntuone-client/add-virtual-watches
Reviewer Review Type Date Requested Status
Facundo Batista (community) Needs Fixing
Diego Sarmentero (community) Approve
Review via email: mp+88726@code.launchpad.net

Commit message

Fixes lp:907511

This branch allows the Windows Watch implementation to behave more like the one found on pyinotify. The way this is achieved is the following:

* Moved _subdirs var to be a dictionary that store a map between a child path and if it is watched or not. The reason is that child_paths are relative to self._path which means that we store less info. The boolean state if we are watching the path, if not, we ignore all the events from that path.
* When we start watching all child events are added to _subdirs are not being watch until sd explicitly asks it.
* When adding a child we add it children to the _subdirs. The are not thread or run issues because the sd will add the watches always in the main twisted thread which is the one used to process the event.
* Moved _ignored_paths to be a set for performance.
* Remove the auto-add parameters and var since this are not expose to the rest of sd and they do not work on linux.
* Modify the tests to use a MementoWatch. The MementoWatch memorizes all the events that have been triggered by the ReadDirecotryChangesW and later processes them, that way we can have tests and assert if the correct number of events was raised and that we filtered the expected ones.
* Added a new filter that ensures that IN_MODIFY|IS_DIR events are not raised when the child is not watched.

Description of the change

Fixes lp:907511

This branch allows the Windows Watch implementation to behave more like the one found on pyinotify. The way this is achieved is the following:

* Moved _subdirs var to be a dictionary that store a map between a child path and if it is watched or not. The reason is that child_paths are relative to self._path which means that we store less info. The boolean state if we are watching the path, if not, we ignore all the events from that path.
* When we start watching all child events are added to _subdirs are not being watch until sd explicitly asks it.
* When adding a child we add it children to the _subdirs. The are not thread or run issues because the sd will add the watches always in the main twisted thread which is the one used to process the event.
* Moved _ignored_paths to be a set for performance.
* Remove the auto-add parameters and var since this are not expose to the rest of sd and they do not work on linux.
* Modify the tests to use a MementoWatch. The MementoWatch memorizes all the events that have been triggered by the ReadDirecotryChangesW and later processes them, that way we can have tests and assert if the correct number of events was raised and that we filtered the expected ones.
* Added a new filter that ensures that IN_MODIFY|IS_DIR events are not raised when the child is not watched.

To post a comment you must log in.
Revision history for this message
Brian Curtin (brian.curtin) wrote :

Here's a mini-review. I don't have my dev environment setup and don't know a lot of the context, plus I can't run it yet...but here's a few things I noticed.

######
test_filesystem_notifications.py

80 + self.mask = fs.FILE_NOTIFY_CHANGE_FILE_NAME | \
81 + fs.FILE_NOTIFY_CHANGE_DIR_NAME | \

Minor style thing, but I noticed in a big import (that was removed) that long lines were wrapped with parentheses instead of breaking with backslashes. Maybe do the same here? (Sorry, had to get my first nitpick cosmetic comment out of the way :)

===========================
filesystem_notifications.py

_get_partial_child_path_dir

It's about 2-3x faster to take a slice of the last character and compare it rather than using path.endswith. This may not be as clean as using endswith, but figured it may be worth mentioning. Something like "if not path[-1] == os.path.sep:"

Other than that, this method looks good.
******
+ self.log.warn('Adding watch to none existing path "%s"', path)

none existing > nonexistent
******
+ def add_child_watch(self, path):

This new method looks fine to me.
******
+ """Add a new path to be watch.

watch > watched
============
os_helper.py

native_listdir

What is the purpose of having listdir and native_listdir if they are the same?

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

> Here's a mini-review. I don't have my dev environment setup and don't know a
> lot of the context, plus I can't run it yet...but here's a few things I
> noticed.
>
> ######
> test_filesystem_notifications.py
>
> 80 + self.mask = fs.FILE_NOTIFY_CHANGE_FILE_NAME | \
> 81 + fs.FILE_NOTIFY_CHANGE_DIR_NAME | \
>
> Minor style thing, but I noticed in a big import (that was removed) that long
> lines were wrapped with parentheses instead of breaking with backslashes.
> Maybe do the same here? (Sorry, had to get my first nitpick cosmetic comment
> out of the way :)

It makes sense, I actually prefer to use () than \ since for me it seems cleaner. Will get that fix.

>
> ===========================
> filesystem_notifications.py
>
> _get_partial_child_path_dir
>
> It's about 2-3x faster to take a slice of the last character and compare it
> rather than using path.endswith. This may not be as clean as using endswith,
> but figured it may be worth mentioning. Something like "if not path[-1] ==
> os.path.sep:"
>

On it! good catch!

> Other than that, this method looks good.
> ******
> + self.log.warn('Adding watch to none existing path "%s"', path)
>
> none existing > nonexistent

Fixing

> ******
> + def add_child_watch(self, path):
>
> This new method looks fine to me.
> ******
> + """Add a new path to be watch.
>
> watch > watched

Fixing.

> ============
> os_helper.py
>
> native_listdir
>
> What is the purpose of having listdir and native_listdir if they are the same?

Really good question, and it is indeed a bug. os.listdir does return certain values, such as system folders, that we want to ignore. I should be using the wrapped method from os_helper.

Thx for the review!

1199. By Manuel de la Peña

Fixed code according to review.

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

+1
All green!

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

The branch looks interesting.

A few comments:
----
941 + if any([file_name.startswith(path) and not watched
942 + for path, watched in self._subdirs.iteritems()]):

The list comprehension used inside the "any" could be replaced by a generator expression just by dropping the [ and ]. With that change a new list won't have to be constructed, and the any will be able to finish faster by not checking the rest if any item matches the expression.

----

"we get no events, lets therefore ignore them" ->
"we get no events, let's therefore ignore them

----

"lets remove the path" ->
"let's remove the path"

----

There are many references to "manager._wdm[0].", and it looks a bit flaky.
I think that instead this line:

   self.patch(fs, 'Watch', MementoWatch)

should be replaced by something like:

   self.patch(fs, 'Watch', lambda *args, **kwargs: self.mementowatch_factory(all_events_count, *args, **kwargs))

and:

   def mementowatch_factory(self, all_events_count, *args, **kwargs):
       self.memento_watch = MementoWatch(...)
       return self.memento_watch

----
"suddirs" -> "subdirs"

1200. By Manuel de la Peña

Fixed code according to review comments.

1201. By Manuel de la Peña

Fixed some stupidly broken tests.

1202. By Manuel de la Peña

Merged with trunk.

Revision history for this message
Facundo Batista (facundo) wrote :

There's a race condition, as we talked by IRC, when getting subdirs for the _subdirs internal structure, and the real directory/files creation in disk.

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

Line 132 seems to be missing a leading space:

130 + def mementowatch_factory(all_events_count, *args, **kwargs):
131 + """Factory that creates a new Watch for the tests."""
132 + kwargs['all_events_count'] = all_events_count
133 + self.memento_watch = MementoWatch(*args, **kwargs)
134 + return self.memento_watch

1203. By Manuel de la Peña

Added a number of extra tests to ensure that we can work around the race condition.

1204. By Manuel de la Peña

Face palm.

1205. By Manuel de la Peña

Face palm.

1206. By Manuel de la Peña

Fixed the race condition to ensure that if a child is missing we add it to the _subdirs dict. Fixed old tests that relied in some implementation details.

1207. By Manuel de la Peña

Fixed broken listdir.

1208. By Manuel de la Peña

Simplified the number of if statements used to ensure that the child path is present.

1209. By Manuel de la Peña

Face palm.

1210. By Manuel de la Peña

Place the parent_dir definition closer to the use. Fix the deep test because a and A are the same on windows.

Unmerged revisions

1210. By Manuel de la Peña

Place the parent_dir definition closer to the use. Fix the deep test because a and A are the same on windows.

1209. By Manuel de la Peña

Face palm.

1208. By Manuel de la Peña

Simplified the number of if statements used to ensure that the child path is present.

1207. By Manuel de la Peña

Fixed broken listdir.

1206. By Manuel de la Peña

Fixed the race condition to ensure that if a child is missing we add it to the _subdirs dict. Fixed old tests that relied in some implementation details.

1205. By Manuel de la Peña

Face palm.

1204. By Manuel de la Peña

Face palm.

1203. By Manuel de la Peña

Added a number of extra tests to ensure that we can work around the race condition.

1202. By Manuel de la Peña

Merged with trunk.

1201. By Manuel de la Peña

Fixed some stupidly broken tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'tests/platform/windows/test_filesystem_notifications.py'
--- tests/platform/windows/test_filesystem_notifications.py 2011-10-28 18:43:23 +0000
+++ tests/platform/windows/test_filesystem_notifications.py 2012-02-03 11:33:18 +0000
@@ -21,8 +21,8 @@
21import os21import os
22import tempfile22import tempfile
23import thread23import thread
24import time
25import itertools24import itertools
25import string
2626
27from twisted.internet import defer27from twisted.internet import defer
28from win32file import FILE_NOTIFY_INFORMATION28from win32file import FILE_NOTIFY_INFORMATION
@@ -37,25 +37,12 @@
37 IN_DELETE,37 IN_DELETE,
38 IN_OPEN,38 IN_OPEN,
39)39)
40from ubuntuone.platform.windows import filesystem_notifications40from ubuntuone.platform.windows import filesystem_notifications as fs
41from ubuntuone.platform.windows.filesystem_notifications import (41from ubuntuone.platform.windows.filesystem_notifications import Watch
42 FilesystemMonitor,
43 NotifyProcessor,
44 Watch,
45 WatchManager,
46 FILE_NOTIFY_CHANGE_FILE_NAME,
47 FILE_NOTIFY_CHANGE_DIR_NAME,
48 FILE_NOTIFY_CHANGE_ATTRIBUTES,
49 FILE_NOTIFY_CHANGE_SIZE,
50 FILE_NOTIFY_CHANGE_LAST_WRITE,
51 FILE_NOTIFY_CHANGE_SECURITY,
52 FILE_NOTIFY_CHANGE_LAST_ACCESS,
53 WINDOWS_ACTIONS_NAMES,
54)
5542
56#create a rever mapping to use it in the tests.43#create a rever mapping to use it in the tests.
57REVERSE_WINDOWS_ACTIONS = {}44REVERSE_WINDOWS_ACTIONS = {}
58for key, value in filesystem_notifications.WINDOWS_ACTIONS.iteritems():45for key, value in fs.WINDOWS_ACTIONS.iteritems():
59 REVERSE_WINDOWS_ACTIONS[value] = key46 REVERSE_WINDOWS_ACTIONS[value] = key
6047
6148
@@ -101,6 +88,25 @@
101 if self.main_thread_id:88 if self.main_thread_id:
102 assert self.main_thread_id == thread.get_ident()89 assert self.main_thread_id == thread.get_ident()
10390
91class MementoWatch(Watch):
92 """A watch that will remember all the events raised."""
93
94 def __init__(self, watch_descriptor, path, mask, processor, buf_size=8192,
95 all_events_count=0):
96 """Create a new instance."""
97 super(MementoWatch, self).__init__(watch_descriptor, path, mask,
98 processor, buf_size)
99 self.all_events_count = all_events_count
100 self._all_events = []
101 self.deferred = defer.Deferred()
102
103 def _process_events(self, events):
104 """Process evens from the queue."""
105 self._all_events.extend(events)
106 super(MementoWatch, self)._process_events(events)
107 if len(self._all_events) == self.all_events_count:
108 self.deferred.callback(self._all_events)
109
104110
105class TestWatch(BaseTwistedTestCase):111class TestWatch(BaseTwistedTestCase):
106 """Test the watch so that it returns the same events as pyinotify."""112 """Test the watch so that it returns the same events as pyinotify."""
@@ -111,18 +117,18 @@
111 def setUp(self):117 def setUp(self):
112 yield super(TestWatch, self).setUp()118 yield super(TestWatch, self).setUp()
113 self.basedir = self.mktemp('test_root')119 self.basedir = self.mktemp('test_root')
114 self.mask = FILE_NOTIFY_CHANGE_FILE_NAME | \120 self.mask = fs.FILE_NOTIFY_CHANGE_FILE_NAME | \
115 FILE_NOTIFY_CHANGE_DIR_NAME | \121 fs.FILE_NOTIFY_CHANGE_DIR_NAME | \
116 FILE_NOTIFY_CHANGE_ATTRIBUTES | \122 fs.FILE_NOTIFY_CHANGE_ATTRIBUTES | \
117 FILE_NOTIFY_CHANGE_SIZE | \123 fs.FILE_NOTIFY_CHANGE_SIZE | \
118 FILE_NOTIFY_CHANGE_LAST_WRITE | \124 fs.FILE_NOTIFY_CHANGE_LAST_WRITE | \
119 FILE_NOTIFY_CHANGE_SECURITY | \125 fs.FILE_NOTIFY_CHANGE_SECURITY | \
120 FILE_NOTIFY_CHANGE_LAST_ACCESS126 fs.FILE_NOTIFY_CHANGE_LAST_ACCESS
121 self.memento = MementoHandler()127 self.memento = MementoHandler()
122 self.memento.setLevel(logging.DEBUG)128 self.memento.setLevel(logging.DEBUG)
123 self.raw_events = []129 self.raw_events = []
124 self.paths_checked = []130 self.paths_checked = []
125 old_is_dir = Watch._path_is_dir131 old_is_dir = fs.Watch._path_is_dir
126132
127 def file_notify_information_wrapper(buf, data):133 def file_notify_information_wrapper(buf, data):
128 """Wrapper that gets the events and adds them to the list."""134 """Wrapper that gets the events and adds them to the list."""
@@ -131,7 +137,7 @@
131 # If we use extend we wont have the same logging because it will137 # If we use extend we wont have the same logging because it will
132 # group all events in a single lists which is not what the COM API138 # group all events in a single lists which is not what the COM API
133 # does.139 # does.
134 str_events = [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in140 str_events = [(fs.WINDOWS_ACTIONS_NAMES[action], path) for action, path in
135 events]141 events]
136 self.raw_events.append(str_events)142 self.raw_events.append(str_events)
137 return events143 return events
@@ -142,46 +148,55 @@
142 self.paths_checked.append((path, result))148 self.paths_checked.append((path, result))
143 return result149 return result
144150
145 self.patch(filesystem_notifications, 'FILE_NOTIFY_INFORMATION',151 self.patch(fs, 'FILE_NOTIFY_INFORMATION',
146 file_notify_information_wrapper)152 file_notify_information_wrapper)
147 self.patch(filesystem_notifications.Watch, '_path_is_dir',153 self.patch(fs.Watch, '_path_is_dir',
148 path_is_dir_wrapper)154 path_is_dir_wrapper)
149155
150 @defer.inlineCallbacks156 @defer.inlineCallbacks
151 def _perform_operations(self, path, mask, auto_add, actions,157 def _perform_operations(self, path, mask, actions, filtered_events_count,
152 number_events):158 children_paths=[], all_events_count=0, ignored_paths=[]):
153 """Perform the file operations and returns the recorded events."""159 """Perform the file operations and returns the recorded events."""
154 handler = TestCaseHandler(number_events=number_events)160 handler = TestCaseHandler(number_events=filtered_events_count)
155 manager = WatchManager(handler)161 manager = fs.WatchManager(handler)
156 yield manager.add_watch(os_helper.get_windows_valid_path(path), mask,162 self.memento_watch = None
157 auto_add=auto_add)163
164 def mementowatch_factory(all_events_count, *args, **kwargs):
165 """Factory that creates a new Watch for the tests."""
166 kwargs['all_events_count'] = all_events_count
167 self.memento_watch = MementoWatch(*args, **kwargs)
168 return self.memento_watch
169
170 # lets always use the memento watch, that way we know the number of events in total
171 self.patch(fs, 'Watch', lambda *args, **kwargs:
172 mementowatch_factory(all_events_count, *args, **kwargs))
173
174 yield manager.add_watch(os_helper.get_windows_valid_path(path), mask)
175 self.memento_watch.all_events_count = all_events_count
176
177 # add a watch to the children paths
178 for child_path in children_paths:
179 yield manager.add_watch(os_helper.get_windows_valid_path(child_path),
180 mask)
181
182 # add that paths that will be ignored in the watch
183 for ignored in ignored_paths:
184 self.memento_watch.ignore_path(ignored)
185
158 # change the logger so that we can check the logs if we wanted186 # change the logger so that we can check the logs if we wanted
159 manager._wdm[0].log.addHandler(self.memento)187 self.memento_watch.log.addHandler(self.memento)
160 # clean logger later188 # clean logger later
161 self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)189 self.addCleanup(self.memento_watch.log.removeHandler, self.memento)
162 # execution the actions190 # execution the actions
163 actions()191 actions()
164 # process the recorded events192 # process the recorded events
165 ret = yield handler.deferred193 events = yield self.memento_watch.deferred
194 if filtered_events_count != 0:
195 ret = yield handler.deferred
196 else:
197 ret = handler.processed_events
166 self.addCleanup(manager.stop)198 self.addCleanup(manager.stop)
167 defer.returnValue(ret)199 defer.returnValue((ret, events))
168
169 def _perform_timed_operations(self, path, mask, auto_add, actions,
170 time_out):
171 """Perform the file operations and returns the recorded events."""
172 manager = WatchManager()
173 manager.add_watch(os_helper.get_windows_valid_path(path), mask,
174 auto_add=auto_add)
175 # change the logger so that we can check the logs if we wanted
176 manager._wdm[0].log.addHandler(self.memento)
177 # clean logger later
178 self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
179 # execution the actions
180 actions()
181 # process the recorded events
182 time.sleep(time_out)
183 events = self.handler.processed_events
184 return events
185200
186 def _assert_logs(self, events):201 def _assert_logs(self, events):
187 """Assert the debug logs."""202 """Assert the debug logs."""
@@ -208,8 +223,10 @@
208 os.fsync(fd)223 os.fsync(fd)
209 fd.close()224 fd.close()
210225
211 events = yield self._perform_operations(self.basedir, self.mask, False,226 events, all_events = yield self._perform_operations(self.basedir, self.mask,
212 create_file, 1)227 create_file,
228 filtered_events_count=1,
229 all_events_count=1)
213 event = events[0]230 event = events[0]
214 self.assertFalse(event.dir)231 self.assertFalse(event.dir)
215 self.assertEqual(0x100, event.mask)232 self.assertEqual(0x100, event.mask)
@@ -230,8 +247,10 @@
230 """Action for the test."""247 """Action for the test."""
231 os.mkdir(dir_name)248 os.mkdir(dir_name)
232249
233 events = yield self._perform_operations(self.basedir, self.mask, False,250 events, all_events = yield self._perform_operations(self.basedir, self.mask,
234 create_dir, 1)251 create_dir,
252 filtered_events_count=1,
253 all_events_count=1)
235 event = events[0]254 event = events[0]
236 self.assertTrue(event.dir)255 self.assertTrue(event.dir)
237 self.assertEqual(0x40000100, event.mask)256 self.assertEqual(0x40000100, event.mask)
@@ -254,8 +273,10 @@
254 """Action for the test."""273 """Action for the test."""
255 os.remove(file_name)274 os.remove(file_name)
256275
257 events = yield self._perform_operations(self.basedir, self.mask, False,276 events, all_events = yield self._perform_operations(self.basedir, self.mask,
258 remove_file, 1)277 remove_file,
278 filtered_events_count=1,
279 all_events_count=1)
259 event = events[0]280 event = events[0]
260 self.assertFalse(event.dir)281 self.assertFalse(event.dir)
261 self.assertEqual(0x200, event.mask)282 self.assertEqual(0x200, event.mask)
@@ -278,8 +299,10 @@
278 """Action for the test."""299 """Action for the test."""
279 os.rmdir(dir_name)300 os.rmdir(dir_name)
280301
281 events = yield self._perform_operations(self.basedir, self.mask, False,302 events, all_events = yield self._perform_operations(self.basedir, self.mask,
282 remove_dir, 1)303 remove_dir,
304 filtered_events_count=1,
305 all_events_count=1)
283 event = events[0]306 event = events[0]
284 self.assertTrue(event.dir)307 self.assertTrue(event.dir)
285 self.assertEqual(0x40000200, event.mask)308 self.assertEqual(0x40000200, event.mask)
@@ -304,8 +327,10 @@
304 fd.write('test')327 fd.write('test')
305 fd.close()328 fd.close()
306329
307 events = yield self._perform_operations(self.basedir, self.mask, False,330 events, all_events = yield self._perform_operations(self.basedir, self.mask,
308 write_file, 1)331 write_file,
332 filtered_events_count=1,
333 all_events_count=1)
309 event = events[0]334 event = events[0]
310 self.assertFalse(event.dir)335 self.assertFalse(event.dir)
311 self.assertEqual(0x2, event.mask)336 self.assertEqual(0x2, event.mask)
@@ -331,8 +356,10 @@
331 """Action for the test."""356 """Action for the test."""
332 os.rename(from_file_name, to_file_name)357 os.rename(from_file_name, to_file_name)
333358
334 events = yield self._perform_operations(self.basedir, self.mask,359 events, all_events = yield self._perform_operations(self.basedir, self.mask,
335 False, move_file, 2)360 move_file,
361 filtered_events_count=2,
362 all_events_count=2)
336 move_from_event = events[0]363 move_from_event = events[0]
337 move_to_event = events[1]364 move_to_event = events[1]
338 # first test the move from365 # first test the move from
@@ -376,8 +403,10 @@
376 # while on linux we will have to do some sort of magic like facundo403 # while on linux we will have to do some sort of magic like facundo
377 # did, on windows we will get a deleted event which is much more404 # did, on windows we will get a deleted event which is much more
378 # cleaner, first time ever windows is nicer!405 # cleaner, first time ever windows is nicer!
379 events = yield self._perform_operations(self.basedir, self.mask, False,406 events, all_events = yield self._perform_operations(self.basedir, self.mask,
380 move_file, 1)407 move_file,
408 filtered_events_count=1,
409 all_events_count=1)
381 event = events[0]410 event = events[0]
382 self.assertFalse(event.dir)411 self.assertFalse(event.dir)
383 self.assertEqual(0x200, event.mask)412 self.assertEqual(0x200, event.mask)
@@ -406,8 +435,10 @@
406435
407 # while on linux we have to do some magic operations like facundo did436 # while on linux we have to do some magic operations like facundo did
408 # on windows we have a created file, hurray!437 # on windows we have a created file, hurray!
409 events = yield self._perform_operations(self.basedir, self.mask, False,438 events, all_events = yield self._perform_operations(self.basedir, self.mask,
410 move_files, 1)439 move_files,
440 filtered_events_count=1,
441 all_events_count=1)
411 event = events[0]442 event = events[0]
412 self.assertFalse(event.dir)443 self.assertFalse(event.dir)
413 self.assertEqual(0x100, event.mask)444 self.assertEqual(0x100, event.mask)
@@ -433,8 +464,10 @@
433 """Action for the test."""464 """Action for the test."""
434 os.rename(from_dir_name, to_dir_name)465 os.rename(from_dir_name, to_dir_name)
435466
436 events = yield self._perform_operations(self.basedir,467 events, all_events = yield self._perform_operations(self.basedir, self.mask,
437 self.mask, False, move_file, 2)468 move_file,
469 filtered_events_count=2,
470 all_events_count=2)
438 move_from_event = events[0]471 move_from_event = events[0]
439 move_to_event = events[1]472 move_to_event = events[1]
440 # first test the move from473 # first test the move from
@@ -475,8 +508,10 @@
475 'test_dir_moved_to_not_watched_dir'))508 'test_dir_moved_to_not_watched_dir'))
476509
477 # on windows a move to outside a watched dir translates to a remove510 # on windows a move to outside a watched dir translates to a remove
478 events = yield self._perform_operations(self.basedir, self.mask, False,511 events, all_events = yield self._perform_operations(self.basedir, self.mask,
479 move_dir, 1)512 move_dir,
513 filtered_events_count=1,
514 all_events_count=1)
480 event = events[0]515 event = events[0]
481 self.assertTrue(event.dir)516 self.assertTrue(event.dir)
482 self.assertEqual(0x40000200, event.mask)517 self.assertEqual(0x40000200, event.mask)
@@ -501,8 +536,10 @@
501 """Action for the test."""536 """Action for the test."""
502 os.rename(from_dir_name, to_dir_name)537 os.rename(from_dir_name, to_dir_name)
503538
504 events = yield self._perform_operations(self.basedir, self.mask, False,539 events, all_events = yield self._perform_operations(self.basedir, self.mask,
505 move_dir, 1)540 move_dir,
541 filtered_events_count=1,
542 all_events_count=1)
506 event = events[0]543 event = events[0]
507 self.assertTrue(event.dir)544 self.assertTrue(event.dir)
508 self.assertEqual(0x40000100, event.mask)545 self.assertEqual(0x40000100, event.mask)
@@ -513,22 +550,6 @@
513 event.pathname)550 event.pathname)
514 self.assertEqual(0, event.wd)551 self.assertEqual(0, event.wd)
515552
516 def test_exclude_filter(self):
517 """Test that the exclude filter works as expectd."""
518 handler = TestCaseHandler(number_events=0)
519 manager = WatchManager(handler)
520 # add a watch that will always exclude all actions
521 manager.add_watch(os_helper.get_windows_valid_path(self.basedir),
522 self.mask, auto_add=True,
523 exclude_filter=lambda x: True)
524 # execution the actions
525 file_name = os.path.join(self.basedir, 'test_file_create')
526 open(file_name, 'w').close()
527 # give some time for the system to get the events
528 time.sleep(1)
529 self.assertEqual(0, len(handler.processed_events))
530 test_exclude_filter.skip = "we must rethink this test."
531
532 def test_open_dir_muted(self):553 def test_open_dir_muted(self):
533 """Test that the opening of dirs is ignored."""554 """Test that the opening of dirs is ignored."""
534 dir_name = os.path.join(tempfile.mkdtemp(), 'test_dir_open')555 dir_name = os.path.join(tempfile.mkdtemp(), 'test_dir_open')
@@ -539,134 +560,192 @@
539 """Action for the test."""560 """Action for the test."""
540 os.startfile(dir_name)561 os.startfile(dir_name)
541562
542 events = self._perform_timed_operations(self.basedir, self.mask, False,563 events = self._perform_operations(self.basedir, self.mask,
543 open_dir, 2)564 open_dir,
565 filtered_events_count=2,
566 all_events_count=1)
544 self.assertEqual(0, len(events))567 self.assertEqual(0, len(events))
545 test_open_dir_muted.skip = "we must rethink this test."568 test_open_dir_muted.skip = "we must rethink this test."
546569
570 @defer.inlineCallbacks
547 def test_ignore_path(self):571 def test_ignore_path(self):
548 """Test that events from a path are ignored."""572 """Test that events from a path are ignored."""
549 events = []573 child_path = os.path.join(self.basedir, 'child')
550574 os.mkdir(child_path)
551 def fake_processor(event):575 file_names = 'abcdef'
552 """Memorize the processed events."""576
553 events.append(event)577 def create_files():
554578 """Create files to get events."""
555 path = u'\\\\?\\C:\\path' # a valid windows path579 for file_name in file_names:
556 child = u'child'580 # create a file similar to the ignore, but not a dir to test if
557 watch = Watch(1, path, None, True, fake_processor)581 # we do not match them wrongly
558 watch.ignore_path(os.path.join(path, child))582 fd = open(os.path.join(child_path, file_name), 'w')
559 paths_to_ignore = []583 fd.flush()
560 for file_name in 'abcdef':584 os.fsync(fd)
561 paths_to_ignore.append((1, os.path.join(child, file_name)))585 fd.close()
562 # ensure that the watch is watching586
563 watch._watching = True587 events, all_events = yield self._perform_operations(self.basedir, self.mask,
564 watch._process_events(paths_to_ignore)588 create_files,
565 self.assertEqual(0, len(events),589 filtered_events_count=0,
566 'All events should have been ignored.')590 all_events_count= len(file_names) * 2,
567591 children_paths=[child_path],
592 ignored_paths=[u'\\\\?\\' + child_path])
593
594 not_processed_children = [file_name for event, file_name in all_events]
595 for file_name in file_names:
596 self.assertIn(os.path.join('child', file_name), not_processed_children)
597
598 @defer.inlineCallbacks
568 def test_not_ignore_path(self):599 def test_not_ignore_path(self):
569 """Test that we do get the events when they do not match."""600 """Test that we do get the events when they do not match."""
570 events = []601 child_path = os.path.join(self.basedir, 'child')
571602 os.mkdir(child_path)
572 def fake_processor(event):603 file_names = 'abcdef'
573 """Memorize the processed events."""604
574 events.append(event)605 def create_files():
575606 """Create files to get events."""
576 path = u'\\\\?\\C:\\path' # a valid windows path607 for file_name in file_names:
577 child = u'child'608 # create a file similar to the ignore, but not a dir to test if
578 watch = Watch(1, path, None, True, fake_processor)609 # we do not match them wrongly
579 watch.ignore_path(os.path.join(path, child))610 fd = open(child_path + file_name, 'w')
580 paths_not_to_ignore = []611 fd.flush()
581 for file_name in 'abcdef':612 os.fsync(fd)
582 paths_not_to_ignore.append((1, os.path.join(613 fd.close()
583 child + file_name, file_name)))614
584 # ensure that the watch is watching615 events, all_events = yield self._perform_operations(self.basedir, self.mask,
585 watch._watching = True616 create_files,
586 watch._process_events(paths_not_to_ignore)617 filtered_events_count=len(file_names),
587 self.assertEqual(len(paths_not_to_ignore), len(events),618 all_events_count= len(file_names),
588 'No events should have been ignored.')619 children_paths=[child_path],
589620 ignored_paths=[u'\\\\?\\' + child_path])
621
622 processed_children = [e.pathname for e in events]
623 for file_name in file_names:
624 self.assertIn(child_path + file_name, processed_children)
625
626
627 @defer.inlineCallbacks
590 def test_mixed_ignore_path(self):628 def test_mixed_ignore_path(self):
591 """Test that we do get the correct events."""629 """Test that we do get the correct events."""
592 events = []630 child_path = os.path.join(self.basedir, 'child')
593631 os.mkdir(child_path)
594 def fake_processor(event):632 ignored_path = os.path.join(self.basedir, 'ignored')
595 """Memorize the processed events."""633 os.mkdir(ignored_path)
596 events.append(event.pathname)634 file_names = 'abcdef'
597635
598 child = u'child'636 def create_files():
599 path = u'\\\\?\\C:\\path\\' # a valid windows path637 """Create files to get events."""
600 watch = Watch(1, path, None, True, fake_processor)638 for file_name in file_names:
601 watch.ignore_path(os.path.join(path, child))639 # create a file per child path
602 paths_not_to_ignore = []640 fd = open(os.path.join(child_path, file_name), 'w')
603 paths_to_ignore = []641 fd.flush()
604 expected_events = []642 os.fsync(fd)
605 for file_name in 'abcdef':643 fd.close()
606 valid = os.path.join(child + file_name, file_name)644 fd = open(os.path.join(ignored_path, file_name), 'w')
607 paths_to_ignore.append((1, os.path.join(child, file_name)))645 fd.flush()
608 paths_not_to_ignore.append((1, valid))646 os.fsync(fd)
609 expected_events.append(os.path.join('C:\\path', valid))647 fd.close()
610 # ensure that the watch is watching648
611 watch._watching = True649 events, all_events = yield self._perform_operations(self.basedir, self.mask,
612 watch._process_events(paths_not_to_ignore)650 create_files,
613 self.assertEqual(len(paths_not_to_ignore), len(events),651 filtered_events_count=len(file_names),
614 'Wrong number of events ignored.')652 all_events_count= len(file_names * 2) * 2,
615 self.assertTrue(all([event in expected_events for event in events]),653 children_paths=[child_path, ignored_path],
616 'Paths ignored that should have not been ignored.')654 ignored_paths=[u'\\\\?\\' + ignored_path])
617655
656 processed_children = [e.pathname for e in events]
657
658 # assert that each expected child path is present
659 for file_name in file_names:
660 self.assertIn(os.path.join(child_path, file_name), processed_children)
661
662 # assert that the ignored are not present
663 for file_name in file_names:
664 self.assertNotIn(os.path.join(ignored_path, file_name), processed_children)
665
666 # assert that the ignored path are indeed in all the events
667 not_processed_children = [file_name for event, file_name in all_events]
668
669 for file_name in file_names:
670 self.assertIn(os.path.join('ignored', file_name), not_processed_children)
671
672 @defer.inlineCallbacks
618 def test_undo_ignore_path_ignored(self):673 def test_undo_ignore_path_ignored(self):
619 """Test that we do deal with events from and old ignored path."""674 """Test that we do deal with events from and old ignored path."""
620 events = []675 child_path = os.path.join(self.basedir, 'child')
621676 os.mkdir(child_path)
622 def fake_processor(event):677 file_names = 'abcdef'
623 """Memorize the processed events."""678
624 events.append(event)679 def create_files():
625680 """Create files to get events."""
626 path = u'\\\\?\\C:\\path' # a valid windows path681 self.memento_watch.remove_ignored_path(u'\\\\?\\' + child_path)
627 child = u'child'682 for file_name in file_names:
628 watch = Watch(1, path, None, True, fake_processor)683 # create a file similar to the ignore, but not a dir to test if
629 watch.ignore_path(os.path.join(path, child))684 # we do not match them wrongly
630 watch.remove_ignored_path(os.path.join(path, child))685 fd = open(os.path.join(child_path, file_name), 'w')
631 paths_not_to_ignore = []686 fd.flush()
632 for file_name in 'abcdef':687 os.fsync(fd)
633 paths_not_to_ignore.append((1, os.path.join(child, file_name)))688 fd.close()
634 # ensure that the watch is watching689
635 watch._watching = True690 events, all_events = yield self._perform_operations(self.basedir, self.mask,
636 watch._process_events(paths_not_to_ignore)691 create_files,
637 self.assertEqual(len(paths_not_to_ignore), len(events),692 filtered_events_count=len(file_names),
638 'All events should have been accepted.')693 all_events_count= len(file_names) * 2,
639694 children_paths=[child_path],
695 ignored_paths=[u'\\\\?\\' + child_path])
696
697 processed_children = [e.pathname for e in events]
698
699 # assert that each expected child path is present
700 for file_name in file_names:
701 self.assertIn(os.path.join(child_path, file_name), processed_children)
702
703 @defer.inlineCallbacks
640 def test_undo_ignore_path_other_ignored(self):704 def test_undo_ignore_path_other_ignored(self):
641 """Test that we can undo and the other path is ignored."""705 """Test that we can undo and the other path is ignored."""
642 events = []706 child_path = os.path.join(self.basedir, 'child')
643707 os.mkdir(child_path)
644 def fake_processor(event):708 ignored_path = os.path.join(self.basedir, 'ignored')
645 """Memorize the processed events."""709 os.mkdir(ignored_path)
646 events.append(event.pathname)710 file_names = 'abcdef'
647711
648 path = u'\\\\?\\C:\\path' # a valid windows path712 def create_files():
649 child_a = u'childa'713 """Create files to get events."""
650 child_b = u'childb'714 self.memento_watch.remove_ignored_path(u'\\\\?\\' + child_path)
651 watch = Watch(1, path, None, True, fake_processor)715 for file_name in file_names:
652 watch.ignore_path(os.path.join(path, child_a))716 # create a file per child path
653 watch.ignore_path(os.path.join(path, child_b))717 fd = open(os.path.join(child_path, file_name), 'w')
654 watch.remove_ignored_path(os.path.join(path, child_a))718 fd.flush()
655 paths_to_ignore = []719 os.fsync(fd)
656 paths_not_to_ignore = []720 fd.close()
657 expected_events = []721 fd = open(os.path.join(ignored_path, file_name), 'w')
658 for file_name in 'abcdef':722 fd.flush()
659 paths_to_ignore.append((1, os.path.join(child_b, file_name)))723 os.fsync(fd)
660 valid = os.path.join(child_a, file_name)724 fd.close()
661 paths_not_to_ignore.append((1, valid))725
662 expected_events.append(os.path.join('C:\\path', valid))726 events, all_events = yield self._perform_operations(self.basedir, self.mask,
663 # ensure that the watch is watching727 create_files,
664 watch._watching = True728 filtered_events_count=len(file_names),
665 watch._process_events(paths_not_to_ignore)729 all_events_count= len(file_names * 2) * 2,
666 self.assertEqual(len(paths_not_to_ignore), len(events),730 children_paths=[child_path, ignored_path],
667 'All events should have been accepted.')731 ignored_paths=[u'\\\\?\\' + ignored_path,
668 self.assertTrue(all([event in expected_events for event in events]),732 u'\\\\?\\' + child_path])
669 'Paths ignored that should have not been ignored.')733
734 processed_children = [e.pathname for e in events]
735
736 # assert that each expected child path is present
737 for file_name in file_names:
738 self.assertIn(os.path.join(child_path, file_name), processed_children)
739
740 # assert that the ignored are not present
741 for file_name in file_names:
742 self.assertNotIn(os.path.join(ignored_path, file_name), processed_children)
743
744 # assert that the ignored path are indeed in all the events
745 not_processed_children = [file_name for event, file_name in all_events]
746
747 for file_name in file_names:
748 self.assertIn(os.path.join('ignored', file_name), not_processed_children)
670749
671 @defer.inlineCallbacks750 @defer.inlineCallbacks
672 def test_call_deferred_already_called(self):751 def test_call_deferred_already_called(self):
@@ -678,7 +757,7 @@
678 method_args.append((args, kwargs),)757 method_args.append((args, kwargs),)
679758
680 path = u'\\\\?\\C:\\path' # a valid windows path759 path = u'\\\\?\\C:\\path' # a valid windows path
681 watch = Watch(1, path, None, True, None)760 watch = fs.Watch(1, path, None, None)
682 yield watch._watch_started_deferred.callback(True)761 yield watch._watch_started_deferred.callback(True)
683 watch._call_deferred(fake_call, None)762 watch._call_deferred(fake_call, None)
684 self.assertEqual(0, len(method_args))763 self.assertEqual(0, len(method_args))
@@ -692,20 +771,20 @@
692 method_args.append((args, kwargs),)771 method_args.append((args, kwargs),)
693772
694 path = u'\\\\?\\C:\\path' # a valid windows path773 path = u'\\\\?\\C:\\path' # a valid windows path
695 watch = Watch(1, path, None, True, None)774 watch = fs.Watch(1, path, None, None)
696 watch._call_deferred(fake_call, None)775 watch._call_deferred(fake_call, None)
697 self.assertEqual(1, len(method_args))776 self.assertEqual(1, len(method_args))
698777
699 def test_started_property(self):778 def test_started_property(self):
700 """Test that the started property returns the started deferred."""779 """Test that the started property returns the started deferred."""
701 path = u'\\\\?\\C:\\path' # a valid windows path780 path = u'\\\\?\\C:\\path' # a valid windows path
702 watch = Watch(1, path, None, True, None)781 watch = fs.Watch(1, path, None, None)
703 self.assertEqual(watch.started, watch._watch_started_deferred)782 self.assertEqual(watch.started, watch._watch_started_deferred)
704783
705 def test_stopped_property(self):784 def test_stopped_property(self):
706 """Test that the stopped property returns the stopped deferred."""785 """Test that the stopped property returns the stopped deferred."""
707 path = u'\\\\?\\C:\\path' # a valid windows path786 path = u'\\\\?\\C:\\path' # a valid windows path
708 watch = Watch(1, path, None, True, None)787 watch = fs.Watch(1, path, None, None)
709 self.assertEqual(watch.stopped, watch._watch_stopped_deferred)788 self.assertEqual(watch.stopped, watch._watch_stopped_deferred)
710789
711 def random_error(self, *args):790 def random_error(self, *args):
@@ -716,8 +795,8 @@
716 def test_start_watching_fails_early_in_thread(self):795 def test_start_watching_fails_early_in_thread(self):
717 """An early failure inside the thread should errback the deferred."""796 """An early failure inside the thread should errback the deferred."""
718 test_path = self.mktemp("test_directory")797 test_path = self.mktemp("test_directory")
719 self.patch(filesystem_notifications, "CreateFileW", self.random_error)798 self.patch(fs, "CreateFileW", self.random_error)
720 watch = Watch(1, test_path, None, True, None)799 watch = fs.Watch(1, test_path, None, None)
721 d = watch.start_watching()800 d = watch.start_watching()
722 yield self.assertFailure(d, FakeException)801 yield self.assertFailure(d, FakeException)
723802
@@ -725,9 +804,9 @@
725 def test_start_watching_fails_late_in_thread(self):804 def test_start_watching_fails_late_in_thread(self):
726 """A late failure inside the thread should errback the deferred."""805 """A late failure inside the thread should errback the deferred."""
727 test_path = self.mktemp("test_directory")806 test_path = self.mktemp("test_directory")
728 self.patch(filesystem_notifications, "ReadDirectoryChangesW",807 self.patch(fs, "ReadDirectoryChangesW",
729 self.random_error)808 self.random_error)
730 watch = Watch(1, test_path, None, True, None)809 watch = fs.Watch(1, test_path, None, None)
731 d = watch.start_watching()810 d = watch.start_watching()
732 yield self.assertFailure(d, FakeException)811 yield self.assertFailure(d, FakeException)
733812
@@ -736,12 +815,12 @@
736 """CloseHandle is called when there's an error in the watch thread."""815 """CloseHandle is called when there's an error in the watch thread."""
737 test_path = self.mktemp("test_directory")816 test_path = self.mktemp("test_directory")
738 close_called = []817 close_called = []
739 self.patch(filesystem_notifications, "CreateFileW", lambda *_: None)818 self.patch(fs, "CreateFileW", lambda *_: None)
740 self.patch(filesystem_notifications, "CloseHandle",819 self.patch(fs, "CloseHandle",
741 close_called.append)820 close_called.append)
742 self.patch(filesystem_notifications, "ReadDirectoryChangesW",821 self.patch(fs, "ReadDirectoryChangesW",
743 self.random_error)822 self.random_error)
744 watch = Watch(1, test_path, self.mask, True, None)823 watch = fs.Watch(1, test_path, self.mask, None)
745 d = watch.start_watching()824 d = watch.start_watching()
746 yield self.assertFailure(d, FakeException)825 yield self.assertFailure(d, FakeException)
747 self.assertEqual(len(close_called), 1)826 self.assertEqual(len(close_called), 1)
@@ -751,7 +830,7 @@
751 def test_stop_watching_fired_when_watch_thread_finishes(self):830 def test_stop_watching_fired_when_watch_thread_finishes(self):
752 """The deferred returned is fired when the watch thread finishes."""831 """The deferred returned is fired when the watch thread finishes."""
753 test_path = self.mktemp("another_test_directory")832 test_path = self.mktemp("another_test_directory")
754 watch = Watch(1, test_path, self.mask, True, None)833 watch = fs.Watch(1, test_path, self.mask, None)
755 yield watch.start_watching()834 yield watch.start_watching()
756 self.assertNotEqual(watch._watch_handle, None)835 self.assertNotEqual(watch._watch_handle, None)
757 yield watch.stop_watching()836 yield watch.stop_watching()
@@ -762,7 +841,7 @@
762 path = u'\\\\?\\C:\\path\\to\\no\\dir'841 path = u'\\\\?\\C:\\path\\to\\no\\dir'
763 test_path = self.mktemp("test_directory")842 test_path = self.mktemp("test_directory")
764 self.patch(os.path, 'exists', lambda path: False)843 self.patch(os.path, 'exists', lambda path: False)
765 watch = Watch(1, test_path, self.mask, True, None)844 watch = fs.Watch(1, test_path, self.mask, None)
766 self.assertFalse(watch._path_is_dir(path))845 self.assertFalse(watch._path_is_dir(path))
767846
768 def test_is_path_dir_missing_in_subdir(self):847 def test_is_path_dir_missing_in_subdir(self):
@@ -770,8 +849,9 @@
770 path = u'\\\\?\\C:\\path\\to\\no\\dir'849 path = u'\\\\?\\C:\\path\\to\\no\\dir'
771 test_path = self.mktemp("test_directory")850 test_path = self.mktemp("test_directory")
772 self.patch(os.path, 'exists', lambda path: False)851 self.patch(os.path, 'exists', lambda path: False)
773 watch = Watch(1, test_path, self.mask, True, None)852 watch = fs.Watch(1, test_path, self.mask, None)
774 watch._subdirs.add(path)853 child_path = watch._get_partial_child_path_dir(path)
854 watch._subdirs[child_path] = False
775 self.assertTrue(watch._path_is_dir(path))855 self.assertTrue(watch._path_is_dir(path))
776856
777 def test_is_path_dir_present_is_dir(self):857 def test_is_path_dir_present_is_dir(self):
@@ -780,8 +860,8 @@
780 test_path = self.mktemp("test_directory")860 test_path = self.mktemp("test_directory")
781 self.patch(os.path, 'exists', lambda path: True)861 self.patch(os.path, 'exists', lambda path: True)
782 self.patch(os.path, 'isdir', lambda path: True)862 self.patch(os.path, 'isdir', lambda path: True)
783 watch = Watch(1, test_path, self.mask, True, None)863 watch = fs.Watch(1, test_path, self.mask, None)
784 watch._subdirs.add(path)864 watch._subdirs[path] = False
785 self.assertTrue(watch._path_is_dir(path))865 self.assertTrue(watch._path_is_dir(path))
786866
787 def test_is_path_dir_present_no_dir(self):867 def test_is_path_dir_present_no_dir(self):
@@ -790,34 +870,36 @@
790 test_path = self.mktemp("test_directory")870 test_path = self.mktemp("test_directory")
791 self.patch(os.path, 'exists', lambda path: True)871 self.patch(os.path, 'exists', lambda path: True)
792 self.patch(os.path, 'isdir', lambda path: False)872 self.patch(os.path, 'isdir', lambda path: False)
793 watch = Watch(1, test_path, self.mask, True, None)873 watch = fs.Watch(1, test_path, self.mask, None)
794 watch._subdirs.add(path)874 watch._subdirs[path] = False
795 self.assertFalse(watch._path_is_dir(path))875 self.assertFalse(watch._path_is_dir(path))
796876
797 def test_update_subdirs_create_not_present(self):877 def test_update_subdirs_create_not_present(self):
798 """Test when we update on a create event and not present."""878 """Test when we update on a create event and not present."""
799 path = u'\\\\?\\C:\\path\\to\\no\\dir'879 path = u'\\\\?\\C:\\path\\to\\no\\dir'
800 test_path = self.mktemp("test_directory")880 test_path = self.mktemp("test_directory")
801 watch = Watch(1, test_path, self.mask, True, None)881 watch = fs.Watch(1, test_path, self.mask, None)
802 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])882 child_path = watch._get_partial_child_path_dir(path)
803 self.assertTrue(path in watch._subdirs)883 watch._update_subdirs(child_path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])
884 self.assertTrue(child_path in watch._subdirs)
804885
805 def test_update_subdirs_create_present(self):886 def test_update_subdirs_create_present(self):
806 """Test when we update on a create event and is present."""887 """Test when we update on a create event and is present."""
807 path = u'\\\\?\\C:\\path\\to\\no\\dir'888 path = u'\\\\?\\C:\\path\\to\\no\\dir'
808 test_path = self.mktemp("test_directory")889 test_path = self.mktemp("test_directory")
809 watch = Watch(1, test_path, self.mask, True, None)890 watch = fs.Watch(1, test_path, self.mask, None)
810 watch._subdirs.add(path)891 child_path = watch._get_partial_child_path_dir(path)
892 watch._subdirs[child_path] = False
811 old_length = len(watch._subdirs)893 old_length = len(watch._subdirs)
812 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])894 watch._update_subdirs(child_path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])
813 self.assertTrue(path in watch._subdirs)895 self.assertTrue(child_path in watch._subdirs)
814 self.assertEqual(old_length, len(watch._subdirs))896 self.assertEqual(old_length, len(watch._subdirs))
815897
816 def test_update_subdirs_delete_not_present(self):898 def test_update_subdirs_delete_not_present(self):
817 """Test when we delete and is not present."""899 """Test when we delete and is not present."""
818 path = u'\\\\?\\C:\\path\\to\\no\\dir'900 path = u'\\\\?\\C:\\path\\to\\no\\dir'
819 test_path = self.mktemp("test_directory")901 test_path = self.mktemp("test_directory")
820 watch = Watch(1, test_path, self.mask, True, None)902 watch = fs.Watch(1, test_path, self.mask, None)
821 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])903 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])
822 self.assertTrue(path not in watch._subdirs)904 self.assertTrue(path not in watch._subdirs)
823905
@@ -825,11 +907,203 @@
825 """Test when we delete and is present."""907 """Test when we delete and is present."""
826 path = u'\\\\?\\C:\\path\\to\\no\\dir'908 path = u'\\\\?\\C:\\path\\to\\no\\dir'
827 test_path = self.mktemp("test_directory")909 test_path = self.mktemp("test_directory")
828 watch = Watch(1, test_path, self.mask, True, None)910 watch = fs.Watch(1, test_path, self.mask, None)
829 watch._subdirs.add(path)911 child_path = watch._get_partial_child_path_dir(path)
912 watch._subdirs[child_path] = False
830 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])913 watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])
831 self.assertTrue(path not in watch._subdirs)914 self.assertTrue(path not in watch._subdirs)
832915
916 @defer.inlineCallbacks
917 def test_events_ignored_no_virtual_watch(self):
918 """Test when the events of a subdir are ignored."""
919 child_folder = os.path.join(self.basedir, 'child')
920 os.mkdir(child_folder)
921 file_name = os.path.join(child_folder, 'test_file_create')
922
923 def create_file():
924 """Action used for the test."""
925 # simply create a new file
926 fd = open(file_name, 'w')
927 fd.flush()
928 os.fsync(fd)
929 fd.close()
930
931 events, all_events = yield self._perform_operations(self.basedir, self.mask,
932 create_file,
933 filtered_events_count=0,
934 all_events_count=2)
935 self.assertEqual(0, len(events), events)
936 # assert the logging
937 self._assert_logs(events)
938
939 @defer.inlineCallbacks
940 def test_file_events_raised_no_virtual_watch(self):
941 """Test when the events of files are fired but not suddirs."""
942 child_folder = os.path.join(self.basedir, 'child')
943 os.mkdir(child_folder)
944 file_name = os.path.join(self.basedir, 'test_file_create')
945 child_file_name = os.path.join(child_folder, 'test_file_create')
946
947 def create_file():
948 """Action used for the test."""
949 # simply create a new file
950 fd = open(file_name, 'w')
951 fd.flush()
952 os.fsync(fd)
953 fd.close()
954 # do the same with the file that should be ignored
955 fd = open(child_file_name, 'w')
956 fd.flush()
957 os.fsync(fd)
958 fd.close()
959
960 events, all_events = yield self._perform_operations(self.basedir, self.mask,
961 create_file,
962 filtered_events_count=1,
963 all_events_count=2)
964 self.assertEqual(1, len(events))
965 event = events[0]
966 self.assertFalse(event.dir)
967 self.assertEqual(0x100, event.mask)
968 self.assertEqual('IN_CREATE', event.maskname)
969 self.assertEqual(os.path.split(file_name)[1], event.name)
970 self.assertEqual('.', event.path)
971 self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
972 self.assertEqual(0, event.wd)
973 # assert the logging
974 self._assert_logs(events)
975
976 @defer.inlineCallbacks
977 def test_events_ignored_deep_tree(self):
978 """Test that all the events if a deep tree are ignored."""
979 child_folder = os.path.join(self.basedir, *[c for c in string.letters])
980 os.makedirs(child_folder)
981
982 def create_files():
983 """Action used for the test."""
984 # create a file per level of the child folder
985 current_path = self.basedir
986 for c in string.letters[:24]:
987 current_path = os.path.join(current_path, c)
988 file_name = os.path.join(current_path, 'test_file_create')
989 fd = open(file_name, 'w')
990 fd.flush()
991 os.fsync(fd)
992 fd.close()
993
994 events, all_events = yield self._perform_operations(self.basedir, self.mask,
995 create_files, filtered_events_count=0,
996 all_events_count=len(string.letters[:24]))
997 self.assertEqual(0, len(events), str(events))
998 # assert the logging
999 self._assert_logs(events)
1000
1001 @defer.inlineCallbacks
1002 def test_events_raised_virtual_watch(self):
1003 """Test that the events from a virtual watch are raised."""
1004 child_folder = os.path.join(self.basedir, 'child')
1005 os.mkdir(child_folder)
1006 file_name = os.path.join(self.basedir, 'test_file_create')
1007 child_file_name = os.path.join(child_folder, 'test_file_create')
1008
1009 def create_file():
1010 """Action used for the test."""
1011 # simply create a new file
1012 fd = open(file_name, 'w')
1013 fd.flush()
1014 os.fsync(fd)
1015 fd.close()
1016 # do the same with the file that should be ignored
1017 fd = open(child_file_name, 'w')
1018 fd.flush()
1019 os.fsync(fd)
1020 fd.close()
1021
1022 events, all_events = yield self._perform_operations(self.basedir, self.mask,
1023 create_file,
1024 filtered_events_count=2,
1025 children_paths=[child_folder],
1026 all_events_count=2)
1027 self.assertEqual(2, len(events))
1028 # assert the logging
1029 self._assert_logs(events)
1030
1031 @defer.inlineCallbacks
1032 def test_start_watching_race_condition(self):
1033 """Test that we can deal with a race condition in the start method."""
1034 # create a dir with several child folders and add a watch. We will
1035 # patch the native_listdir so that we do not see some of the child dirs
1036 # and later will perfrom an event on one of those kids. The event
1037 # should be filtered.
1038 for c in string.letters[:24]:
1039 os.mkdir(os.path.join(self.basedir, c))
1040
1041 forgotten_dirs = set(string.letters[15:22])
1042
1043 def slow_listdir(path):
1044 """Similar to list dir but ignore some dirs."""
1045 all_dirs = set(os.listdir(path))
1046 return all_dirs - forgotten_dirs
1047
1048 self.patch(fs, 'native_listdir', slow_listdir)
1049
1050 # lets perform a number of events in the forgotten dirs
1051 def create_files():
1052 """Create a file on each forgotten dir."""
1053 for c in forgotten_dirs:
1054 file_name = os.path.join(self.basedir, c, 'test.txt')
1055 fd = open(file_name, 'w')
1056 fd.flush()
1057 os.fsync(fd)
1058 fd.close()
1059
1060 events, all_events = yield self._perform_operations(self.basedir, self.mask,
1061 create_files,
1062 filtered_events_count=0,
1063 all_events_count=len(forgotten_dirs) * 2)
1064 # assert that the watch does have the correct subdirs
1065 for c in string.letters[:24]:
1066 self.assertIn(os.path.join(c, u''), self.memento_watch._subdirs)
1067 self.assertEqual(0, len(events))
1068
1069 def test_add_child_watch_race_condition(self):
1070 """Test that we can deal with a race condition in the add child."""
1071 # like with test_Start_watching race condition we are going to add a
1072 # watch and a child watch. In the second addintion the data returned by
1073 # the native_listdir will be wrong.
1074 child_path = os.path.join(self.basedir, 'child_path')
1075 os.mkdir(child_path)
1076 for c in string.letters[:24]:
1077 os.mkdir(os.path.join(child_path), c)
1078
1079 forgotten_dirs = set(string.letters[15:22])
1080
1081 def slow_listdir(path):
1082 """Similar to list dir but ignore some dirs."""
1083 # are we adding a child or stating?
1084 if path == self.basedir:
1085 return os.listdir(path)
1086 else:
1087 all_dirs = set(os.listdir(path))
1088 return all_dirs - forgotten_dirs
1089
1090 def create_files():
1091 """Create a file on each forgotten dir."""
1092 for c in forgotten_dirs:
1093 file_name = os.path.join(self.basedir, c, 'test.txt')
1094 fd = open(file_name, 'w')
1095 fd.flush()
1096 os.fsync(fd)
1097 fd.close()
1098
1099 events, all_events = yield self._perform_operations(self.basedir, self.mask,
1100 create_files,
1101 filtered_events_count=0,
1102 all_events_count=len(forgotten_dirs) * 2)
1103 for c in string.letters[:24]:
1104 self.assertIn(os.path.join(c, u''), self.memento_watch._subdirs)
1105 self.assertEqual(0, len(events))
1106
8331107
834class TestWatchManager(BaseTwistedTestCase):1108class TestWatchManager(BaseTwistedTestCase):
835 """Test the watch manager."""1109 """Test the watch manager."""
@@ -840,8 +1114,8 @@
840 yield super(TestWatchManager, self).setUp()1114 yield super(TestWatchManager, self).setUp()
841 self.parent_path = u'\\\\?\\C:\\' # a valid windows path1115 self.parent_path = u'\\\\?\\C:\\' # a valid windows path
842 self.path = self.parent_path + u'path'1116 self.path = self.parent_path + u'path'
843 self.watch = Watch(1, self.path, None, True, None)1117 self.watch = fs.Watch(1, self.path, None, None)
844 self.manager = WatchManager(None)1118 self.manager = fs.WatchManager(None)
845 self.manager._wdm = {1: self.watch}1119 self.manager._wdm = {1: self.watch}
8461120
847 @defer.inlineCallbacks1121 @defer.inlineCallbacks
@@ -854,7 +1128,7 @@
854 self.was_called = True1128 self.was_called = True
855 return defer.succeed(True)1129 return defer.succeed(True)
8561130
857 self.patch(Watch, "stop_watching", fake_stop_watching)1131 self.patch(fs.Watch, "stop_watching", fake_stop_watching)
858 yield self.manager.stop()1132 yield self.manager.stop()
859 self.assertTrue(self.was_called, 'The watch stop should be called.')1133 self.assertTrue(self.was_called, 'The watch stop should be called.')
8601134
@@ -866,9 +1140,9 @@
866 """Another fake stop watch."""1140 """Another fake stop watch."""
867 return watch.stopped1141 return watch.stopped
8681142
869 self.patch(Watch, "stop_watching", fake_stop_watching)1143 self.patch(fs.Watch, "stop_watching", fake_stop_watching)
870 second_path = self.parent_path + u"second_path"1144 second_path = self.parent_path + u"second_path"
871 second_watch = Watch(2, second_path, None, True, None)1145 second_watch = fs.Watch(2, second_path, None, None)
872 self.manager._wdm[2] = second_watch1146 self.manager._wdm[2] = second_watch
873 d = self.manager.stop()1147 d = self.manager.stop()
874 self.assertFalse(d.called, "Not fired before all watches end")1148 self.assertFalse(d.called, "Not fired before all watches end")
@@ -908,24 +1182,21 @@
908 """Fake start watch."""1182 """Fake start watch."""
909 self.was_called = True1183 self.was_called = True
9101184
911 self.patch(Watch, "start_watching", fake_start_watching)1185 self.patch(fs.Watch, "start_watching", fake_start_watching)
912 self.manager._wdm = {}1186 self.manager._wdm = {}
9131187
914 mask = 'bit_mask'1188 mask = 'bit_mask'
915 auto_add = True1189 self.manager.add_watch(self.path, mask)
916 self.manager.add_watch(self.path, mask, auto_add=auto_add)
917 self.assertEqual(1, len(self.manager._wdm))1190 self.assertEqual(1, len(self.manager._wdm))
918 self.assertTrue(self.was_called, 'The watch start was not called.')1191 self.assertTrue(self.was_called, 'The watch start was not called.')
919 self.assertEqual(self.path + os.path.sep, self.manager._wdm[0]._path)1192 self.assertEqual(self.path + os.path.sep, self.manager._wdm[0]._path)
920 self.assertEqual(mask, self.manager._wdm[0]._mask)1193 self.assertEqual(mask, self.manager._wdm[0]._mask)
921 self.assertEqual(auto_add, self.manager._wdm[0]._auto_add)
9221194
923 def test_update_present_watch(self):1195 def test_update_present_watch(self):
924 """Test the update of a present watch."""1196 """Test the update of a present watch."""
925 mask = 'mask'1197 mask = 'mask'
926 self.assertRaises(NotImplementedError, self.manager.update_watch,1198 self.assertRaises(NotImplementedError, self.manager.update_watch,
927 1, mask)1199 1, mask)
928
929 def test_get_watch_present_wd(self):1200 def test_get_watch_present_wd(self):
930 """Test that the correct path is returned."""1201 """Test that the correct path is returned."""
931 self.assertEqual(self.path + os.path.sep, self.manager.get_path(1))1202 self.assertEqual(self.path + os.path.sep, self.manager.get_path(1))
@@ -1002,11 +1273,12 @@
10021273
1003 def test_add_watch_twice(self):1274 def test_add_watch_twice(self):
1004 """Adding a watch twice succeeds when the watch is running."""1275 """Adding a watch twice succeeds when the watch is running."""
1005 self.patch(Watch, "start_watching", lambda self: self.started)1276 self.patch(fs.Watch, "start_watching", lambda self: self.started)
1006 manager = WatchManager(None)1277 manager = fs.WatchManager(None)
1007 # no need to stop watching because start_watching is fake1278 # no need to stop watching because start_watching is fake
10081279
1009 path = u'\\\\?\\C:\\test' # a valid windows path1280 path = self.mktemp("test_directory") # a valid windows path
1281 path = u'\\\\?\\' + path
1010 mask = 'fake bit mask'1282 mask = 'fake bit mask'
1011 d1 = manager.add_watch(path, mask)1283 d1 = manager.add_watch(path, mask)
1012 d2 = manager.add_watch(path, mask)1284 d2 = manager.add_watch(path, mask)
@@ -1114,7 +1386,7 @@
1114 def setUp(self):1386 def setUp(self):
1115 """set up the diffeent tests."""1387 """set up the diffeent tests."""
1116 yield super(TestNotifyProcessor, self).setUp()1388 yield super(TestNotifyProcessor, self).setUp()
1117 self.processor = NotifyProcessor(None)1389 self.processor = fs.NotifyProcessor(None)
1118 self.general = FakeGeneralProcessor()1390 self.general = FakeGeneralProcessor()
1119 self.processor.general_processor = self.general1391 self.processor.general_processor = self.general
11201392
@@ -1466,8 +1738,8 @@
14661738
1467 def test_add_watch_twice(self):1739 def test_add_watch_twice(self):
1468 """Check the deferred returned by a second add_watch."""1740 """Check the deferred returned by a second add_watch."""
1469 self.patch(Watch, "start_watching", lambda self: self.started)1741 self.patch(fs.Watch, "start_watching", lambda self: self.started)
1470 monitor = FilesystemMonitor(None, None)1742 monitor = fs.FilesystemMonitor(None, None)
1471 # no need to stop watching because start_watching is fake1743 # no need to stop watching because start_watching is fake
14721744
1473 parent_path = 'C:\\test' # a valid windows path in utf-8 bytes1745 parent_path = 'C:\\test' # a valid windows path in utf-8 bytes
@@ -1496,7 +1768,7 @@
14961768
1497 ancestors = ['~', '~\\Pictures', '~\\Pictures\\Home', ]1769 ancestors = ['~', '~\\Pictures', '~\\Pictures\\Home', ]
1498 volume = FakeVolume(ancestors)1770 volume = FakeVolume(ancestors)
1499 monitor = FilesystemMonitor(None, None)1771 monitor = fs.FilesystemMonitor(None, None)
1500 added = yield monitor.add_watches_to_udf_ancestors(volume)1772 added = yield monitor.add_watches_to_udf_ancestors(volume)
1501 self.assertTrue(added, 'We should always return true.')1773 self.assertTrue(added, 'We should always return true.')
1502 # lets ensure that we never added the watches1774 # lets ensure that we never added the watches
15031775
=== modified file 'ubuntuone/platform/windows/filesystem_notifications.py'
--- ubuntuone/platform/windows/filesystem_notifications.py 2011-10-18 13:44:10 +0000
+++ ubuntuone/platform/windows/filesystem_notifications.py 2012-02-03 11:33:18 +0000
@@ -1,9 +1,5 @@
1#1#
2# Authors: Manuel de la Pena <manuel@canonical.com>2# Copyright 2011-2012 Canonical Ltd.
3# Natalia B. Bidart <natalia.bidart@canonical.com>
4# Alejandro J. Cura <alecu@canonical.com>
5#
6# Copyright 2011 Canonical Ltd.
7#3#
8# This program is free software: you can redistribute it and/or modify it4# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published5# under the terms of the GNU General Public License version 3, as published
@@ -70,6 +66,7 @@
70 is_valid_syncdaemon_path,66 is_valid_syncdaemon_path,
71 is_valid_windows_path,67 is_valid_windows_path,
72 get_syncdaemon_valid_path,68 get_syncdaemon_valid_path,
69 native_listdir,
73 windowspath,70 windowspath,
74)71)
75from ubuntuone import logger72from ubuntuone import logger
@@ -118,13 +115,13 @@
118115
119# the default mask to be used in the watches added by the FilesystemMonitor116# the default mask to be used in the watches added by the FilesystemMonitor
120# class117# class
121FILESYSTEM_MONITOR_MASK = FILE_NOTIFY_CHANGE_FILE_NAME | \118FILESYSTEM_MONITOR_MASK = (FILE_NOTIFY_CHANGE_FILE_NAME |
122 FILE_NOTIFY_CHANGE_DIR_NAME | \119 FILE_NOTIFY_CHANGE_DIR_NAME |
123 FILE_NOTIFY_CHANGE_ATTRIBUTES | \120 FILE_NOTIFY_CHANGE_ATTRIBUTES |
124 FILE_NOTIFY_CHANGE_SIZE | \121 FILE_NOTIFY_CHANGE_SIZE |
125 FILE_NOTIFY_CHANGE_LAST_WRITE | \122 FILE_NOTIFY_CHANGE_LAST_WRITE |
126 FILE_NOTIFY_CHANGE_SECURITY | \123 FILE_NOTIFY_CHANGE_SECURITY |
127 FILE_NOTIFY_CHANGE_LAST_ACCESS124 FILE_NOTIFY_CHANGE_LAST_ACCESS)
128125
129THREADPOOL_MAX = 20126THREADPOOL_MAX = 20
130127
@@ -133,9 +130,7 @@
133class Watch(object):130class Watch(object):
134 """Implement the same functions as pyinotify.Watch."""131 """Implement the same functions as pyinotify.Watch."""
135132
136 def __init__(self, watch_descriptor, path, mask, auto_add, processor,133 def __init__(self, watch_descriptor, path, mask, processor, buf_size=8192):
137 buf_size=8192):
138 super(Watch, self).__init__()
139 self.log = logging.getLogger('ubuntuone.SyncDaemon.platform.windows.' +134 self.log = logging.getLogger('ubuntuone.SyncDaemon.platform.windows.' +
140 'filesystem_notifications.Watch')135 'filesystem_notifications.Watch')
141 self.log.setLevel(TRACE)136 self.log.setLevel(TRACE)
@@ -146,18 +141,20 @@
146 self._overlapped.hEvent = CreateEvent(None, 0, 0, None)141 self._overlapped.hEvent = CreateEvent(None, 0, 0, None)
147 self._watching = False142 self._watching = False
148 self._descriptor = watch_descriptor143 self._descriptor = watch_descriptor
149 self._auto_add = auto_add144 # ignored paths are those that we actually do want to ignore while
150 self._ignore_paths = []145 # not watched paths are those whose events are not propagated to
146 # mimic the linux behaviour
147 self._ignore_paths = set()
151 self._cookie = None148 self._cookie = None
152 self._source_pathname = None149 self._source_pathname = None
153 self._process_thread = None150 self._process_thread = None
154 self._watch_handle = None151 self._watch_handle = None
155 # remember the subdirs we have so that when we have a delete we can152 # remember the subdirs we have so that when we have a delete we can
156 # check if it was a remove153 # check if it was a remove
157 self._subdirs = set()154 self._subdirs = {}
158 # ensure that we work with an abspath and that we can deal with155 # ensure that we work with an abspath and that we can deal with
159 # long paths over 260 chars.156 # long paths over 260 chars.
160 if not path.endswith(os.path.sep):157 if path[-1] != os.path.sep:
161 path += os.path.sep158 path += os.path.sep
162 self._path = os.path.abspath(path)159 self._path = os.path.abspath(path)
163 self._mask = mask160 self._mask = mask
@@ -167,7 +164,18 @@
167 # and this one is fired when the watch has stopped164 # and this one is fired when the watch has stopped
168 self._watch_stopped_deferred = defer.Deferred()165 self._watch_stopped_deferred = defer.Deferred()
169166
170 @is_valid_windows_path(path_indexes=[1])167 def _get_partial_child_path_dir(self, path):
168 """Return a partial child path for a dir.
169
170 The path is used internally by the watcher to keep track of
171 child directories.
172 """
173 if path[-1] != os.path.sep:
174 path += os.path.sep
175 if path.startswith(self._path):
176 path = path[len(self._path):]
177 return path
178
171 def _update_subdirs(self, path, event):179 def _update_subdirs(self, path, event):
172 """Adds the path to the internal subdirs.180 """Adds the path to the internal subdirs.
173181
@@ -175,13 +183,13 @@
175 will not be checked.183 will not be checked.
176 """184 """
177 if WINDOWS_ACTIONS[event] == IN_CREATE:185 if WINDOWS_ACTIONS[event] == IN_CREATE:
178 self._subdirs.add(path)186 self._subdirs[path] = False
179 elif WINDOWS_ACTIONS[event] == IN_DELETE and\187 elif WINDOWS_ACTIONS[event] == IN_DELETE and\
180 path in self._subdirs:188 path in self._subdirs:
181 self._subdirs.remove(path)189 del self._subdirs[path]
182190
183 @is_valid_windows_path(path_indexes=[1])191 @is_valid_windows_path(path_indexes=[1])
184 def _path_is_dir(self, path):192 def _path_is_dir(self, full_path):
185 """Check if the path is a dir and update the local subdir list."""193 """Check if the path is a dir and update the local subdir list."""
186194
187 # The logic of this function is the following:195 # The logic of this function is the following:
@@ -191,14 +199,14 @@
191 # 3. We check if a path is a dir by:199 # 3. We check if a path is a dir by:
192 # * Asking the os if the path exists.200 # * Asking the os if the path exists.
193 # * Finding the path in self._subdirs201 # * Finding the path in self._subdirs
194
195 is_dir = False202 is_dir = False
196 if os.path.exists(path):203 if os.path.exists(full_path):
197 is_dir = os.path.isdir(path)204 is_dir = os.path.isdir(full_path)
198 else:205 else:
199 # path does not exists, was it in the internal list?206 # path does not exists, was it in the internal list?
200 is_dir = path in self._subdirs207 child_path = self._get_partial_child_path_dir(full_path)
201 self.log.debug('Is path %r a dir? %s', path, is_dir)208 is_dir = child_path in self._subdirs
209 self.log.debug('Is path %r a dir? %s', full_path, is_dir)
202 return is_dir210 return is_dir
203211
204 def _process_events(self, events):212 def _process_events(self, events):
@@ -210,8 +218,20 @@
210 # we transform the events to be the same as the one in pyinotify218 # we transform the events to be the same as the one in pyinotify
211 # and then use the proc_fun219 # and then use the proc_fun
212 for action, file_name in events:220 for action, file_name in events:
213 if any([file_name.startswith(path)221 # ignore those paths that are present in the _ignore_paths, this
214 for path in self._ignore_paths]):222 # are those paths whose watch has been removed.
223 if any(file_name.startswith(path)
224 for path in self._ignore_paths):
225 continue
226
227 # lets get the parent dir to be faster
228 parent_dir = os.path.dirname(file_name) + os.path.sep
229
230 if parent_dir != unicode(os.path.sep) and\
231 not self._subdirs.get(parent_dir, False):
232 if not parent_dir in self._subdirs:
233 self.log.warn('Child path %r is missing.', parent_dir)
234 self._subdirs[parent_dir] = False
215 continue235 continue
216 # map the windows events to the pyinotify ones, tis is dirty but236 # map the windows events to the pyinotify ones, tis is dirty but
217 # makes the multiplatform better, linux was first :P237 # makes the multiplatform better, linux was first :P
@@ -221,7 +241,7 @@
221 is_dir = self._path_is_dir(full_dir_path)241 is_dir = self._path_is_dir(full_dir_path)
222 if is_dir:242 if is_dir:
223 # we need to update the list of subdirs that we have243 # we need to update the list of subdirs that we have
224 self._update_subdirs(full_dir_path, action)244 self._update_subdirs(file_name + os.path.sep, action)
225 mask = WINDOWS_ACTIONS[action]245 mask = WINDOWS_ACTIONS[action]
226 head, tail = os.path.split(file_name)246 head, tail = os.path.split(file_name)
227 if is_dir:247 if is_dir:
@@ -232,6 +252,11 @@
232 'mask': mask,252 'mask': mask,
233 'name': tail,253 'name': tail,
234 'path': '.'}254 'path': '.'}
255 # if we have a in_modify for a dir that is not watched on linux
256 # we get no events, let's therefore ignore them
257 if (WINDOWS_ACTIONS[action] == IN_MODIFY
258 and is_dir and file_name + os.path.sep in self._subdirs):
259 continue
235 # by the way in which the win api fires the events we know for260 # by the way in which the win api fires the events we know for
236 # sure that no move events will be added in the wrong order, this261 # sure that no move events will be added in the wrong order, this
237 # is kind of hacky, I dont like it too much262 # is kind of hacky, I dont like it too much
@@ -246,9 +271,6 @@
246 # FIXME: event deduces the pathname wrong and we need to manually271 # FIXME: event deduces the pathname wrong and we need to manually
247 # set it272 # set it
248 event.pathname = syncdaemon_path273 event.pathname = syncdaemon_path
249 # add the event only if we do not have an exclude filter or
250 # the exclude filter returns False, that is, the event will not
251 # be excluded
252 self.log.debug('Pushing event %r to processor.', event)274 self.log.debug('Pushing event %r to processor.', event)
253 self._processor(event)275 self._processor(event)
254276
@@ -301,7 +323,7 @@
301 ReadDirectoryChangesW(323 ReadDirectoryChangesW(
302 handle,324 handle,
303 buf,325 buf,
304 self._auto_add,326 True,
305 self._mask,327 self._mask,
306 self._overlapped,328 self._overlapped,
307 )329 )
@@ -316,9 +338,9 @@
316 if rc == WAIT_OBJECT_0:338 if rc == WAIT_OBJECT_0:
317 # Stop event339 # Stop event
318 break340 break
319 # if we continue, it means that we got some data, lets read it341 # if we continue, it means that we got some data, let's read it
320 data = GetOverlappedResult(handle, self._overlapped, True)342 data = GetOverlappedResult(handle, self._overlapped, True)
321 # lets ead the data and store it in the results343 # let's read the data and store it in the results
322 events = FILE_NOTIFY_INFORMATION(buf, data)344 events = FILE_NOTIFY_INFORMATION(buf, data)
323 self.log.debug('Got from ReadDirectoryChangesW %r.',345 self.log.debug('Got from ReadDirectoryChangesW %r.',
324 [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in346 [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in
@@ -328,28 +350,40 @@
328 @is_valid_windows_path(path_indexes=[1])350 @is_valid_windows_path(path_indexes=[1])
329 def ignore_path(self, path):351 def ignore_path(self, path):
330 """Add the path of the events to ignore."""352 """Add the path of the events to ignore."""
331 if not path.endswith(os.path.sep):353 path = self._get_partial_child_path_dir(path)
332 path += os.path.sep354 self._ignore_paths.add(path)
333 if path.startswith(self._path):
334 path = path[len(self._path):]
335 self._ignore_paths.append(path)
336355
337 @is_valid_windows_path(path_indexes=[1])356 @is_valid_windows_path(path_indexes=[1])
338 def remove_ignored_path(self, path):357 def remove_ignored_path(self, path):
339 """Reaccept path."""358 """Reaccept path."""
340 if not path.endswith(os.path.sep):359 path = self._get_partial_child_path_dir(path)
341 path += os.path.sep360 if path in self._ignore_paths:
342 if path.startswith(self._path):361 self._ignore_paths.remove(path)
343 path = path[len(self._path):]362
344 if path in self._ignore_paths:363 @is_valid_windows_path(path_indexes=[1])
345 self._ignore_paths.remove(path)364 def add_child_watch(self, path):
365 """Adds a watch to a child path."""
366 if not os.path.exists(path):
367 self.log.warn('Adding watch to nonexisting path "%s"', path)
368 return
369 # we are adding a watch to a path, but not to the children of that
370 # path, ergo we have to add some extra ignored paths, and then remove
371 # the parent path from the ignored paths
372 for current_child in native_listdir(path):
373 full_child_path = os.path.join(self._path, current_child)
374 if os.path.isdir(full_child_path):
375 self._subdirs[current_child + os.path.sep] = False
376 # let's remove the path from the ignored paths since the children are
377 # the ones ignored.
378 sort_path = self._get_partial_child_path_dir(path)
379 self._subdirs[sort_path] = True
346380
347 def start_watching(self):381 def start_watching(self):
348 """Tell the watch to start processing events."""382 """Tell the watch to start processing events."""
349 for current_child in os.listdir(self._path):383 for current_child in native_listdir(self._path):
350 full_child_path = os.path.join(self._path, current_child)384 full_child_path = os.path.join(self._path, current_child)
351 if os.path.isdir(full_child_path):385 if os.path.isdir(full_child_path):
352 self._subdirs.add(full_child_path)386 self._subdirs[current_child + os.path.sep] = False
353 # start to diff threads, one to watch the path, the other to387 # start to diff threads, one to watch the path, the other to
354 # process the events.388 # process the events.
355 self.log.debug('Start watching path.')389 self.log.debug('Start watching path.')
@@ -362,14 +396,13 @@
362 self.log.info('Stop watching %s', self._path)396 self.log.info('Stop watching %s', self._path)
363 SetEvent(self._wait_stop)397 SetEvent(self._wait_stop)
364 self._watching = False398 self._watching = False
365 self._subdirs = set()399 self._subdirs = {}
366 return self.stopped400 return self.stopped
367401
368 def update(self, mask, auto_add=False):402 def update(self, mask):
369 """Update the info used by the watcher."""403 """Update the info used by the watcher."""
370 self.log.debug('update(%s, %s)', mask, auto_add)404 self.log.debug('update(%s, %s)', mask)
371 self._mask = mask405 self._mask = mask
372 self._auto_add = auto_add
373406
374 @property407 @property
375 def path(self):408 def path(self):
@@ -377,10 +410,6 @@
377 return self._path410 return self._path
378411
379 @property412 @property
380 def auto_add(self):
381 return self._auto_add
382
383 @property
384 def started(self):413 def started(self):
385 """A deferred that will be called when the watch is running."""414 """A deferred that will be called when the watch is running."""
386 return self._watch_started_deferred415 return self._watch_started_deferred
@@ -433,28 +462,26 @@
433 except KeyError, e:462 except KeyError, e:
434 logging.error(str(e))463 logging.error(str(e))
435464
436 def _add_single_watch(self, path, mask, auto_add=False,465 def _add_single_watch(self, path, mask, quiet=True):
437 quiet=True):
438 if path in self._ignored_paths:466 if path in self._ignored_paths:
439 # simply removed it from the filter467 # simply removed it from the filter
440 self._ignored_paths.remove(path)468 self._ignored_paths.remove(path)
441 return469 return
442 # we need to add a new watch470 # we need to add a new watch
443 self.log.debug('add_single_watch(%s, %s, %s, %s)', path, mask,471 self.log.debug('add_single_watch(%s, %s, %s)', path, mask,
444 auto_add, quiet)472 quiet)
445473
446 # adjust the number of threads based on the UDFs watched474 # adjust the number of threads based on the UDFs watched
447 reactor.suggestThreadPoolSize(THREADPOOL_MAX + self._wd_count + 1)475 reactor.suggestThreadPoolSize(THREADPOOL_MAX + self._wd_count + 1)
448 self._wdm[self._wd_count] = Watch(self._wd_count, path,476 self._wdm[self._wd_count] = Watch(self._wd_count, path,
449 mask, auto_add, self._processor)477 mask, self._processor)
450 d = self._wdm[self._wd_count].start_watching()478 d = self._wdm[self._wd_count].start_watching()
451 self._wd_count += 1479 self._wd_count += 1
452 return d480 return d
453481
454 @is_valid_windows_path(path_indexes=[1])482 @is_valid_windows_path(path_indexes=[1])
455 def add_watch(self, path, mask, auto_add=False,483 def add_watch(self, path, mask, quiet=True):
456 quiet=True):484 """Add a new path to be watched.
457 """Add a new path tp be watch.
458485
459 The method will ensure that the path is not already present.486 The method will ensure that the path is not already present.
460 """487 """
@@ -464,24 +491,24 @@
464 wd = self.get_wd(path)491 wd = self.get_wd(path)
465 if wd is None:492 if wd is None:
466 self.log.debug('Adding single watch on %r', path)493 self.log.debug('Adding single watch on %r', path)
467 return self._add_single_watch(path, mask, auto_add, quiet)494 return self._add_single_watch(path, mask, quiet)
468 else:495 else:
469 self.log.debug('Watch already exists on %r', path)496 self.log.debug('Watch already exists on %r', path)
497 if path != self._wdm[wd].path:
498 self._wdm[wd].add_child_watch(path)
470 return self._wdm[wd].started499 return self._wdm[wd].started
471500
472 def update_watch(self, wd, mask=None, rec=False,501 def update_watch(self, wd, mask=None, rec=False, quiet=True):
473 auto_add=False, quiet=True):
474 raise NotImplementedError("Not implemented on windows.")502 raise NotImplementedError("Not implemented on windows.")
475503
476 @is_valid_windows_path(path_indexes=[1])504 @is_valid_windows_path(path_indexes=[1])
477 def get_wd(self, path):505 def get_wd(self, path):
478 """Return the watcher that is used to watch the given path."""506 """Return the watcher that is used to watch the given path."""
479 if not path.endswith(os.path.sep):507 if path[-1] != os.path.sep:
480 path = path + os.path.sep508 path = path + os.path.sep
481 for current_wd in self._wdm:509 for current_wd in self._wdm:
482 watch_path = self._wdm[current_wd].path510 watch_path = self._wdm[current_wd].path
483 if ((watch_path == path or (511 if (watch_path in path
484 watch_path in path and self._wdm[current_wd].auto_add))
485 and path not in self._ignored_paths):512 and path not in self._ignored_paths):
486 return current_wd513 return current_wd
487514
@@ -560,10 +587,10 @@
560587
561 def process_IN_MODIFY(self, event):588 def process_IN_MODIFY(self, event):
562 """Capture a modify event and fake an open ^ close write events."""589 """Capture a modify event and fake an open ^ close write events."""
563 # lets ignore dir changes590 # let's ignore dir changes
564 if event.dir:591 if event.dir:
565 return592 return
566 # on windows we just get IN_MODIFY, lets always fake593 # on windows we just get IN_MODIFY, let's always fake
567 # an OPEN & CLOSE_WRITE couple594 # an OPEN & CLOSE_WRITE couple
568 raw_open = raw_close = {595 raw_open = raw_close = {
569 'wd': event.wd,596 'wd': event.wd,
@@ -760,7 +787,7 @@
760 # the logic to check if the watch is already set787 # the logic to check if the watch is already set
761 # is all in WatchManager.add_watch788 # is all in WatchManager.add_watch
762 return self._watch_manager.add_watch(dirpath,789 return self._watch_manager.add_watch(dirpath,
763 FILESYSTEM_MONITOR_MASK, auto_add=True)790 FILESYSTEM_MONITOR_MASK)
764791
765 def add_watches_to_udf_ancestors(self, volume):792 def add_watches_to_udf_ancestors(self, volume):
766 """Add a inotify watch to volume's ancestors if it's an UDF."""793 """Add a inotify watch to volume's ancestors if it's an UDF."""
767794
=== modified file 'ubuntuone/platform/windows/os_helper.py'
--- ubuntuone/platform/windows/os_helper.py 2012-01-10 18:09:30 +0000
+++ ubuntuone/platform/windows/os_helper.py 2012-02-03 11:33:18 +0000
@@ -707,7 +707,11 @@
707@windowspath()707@windowspath()
708def listdir(directory):708def listdir(directory):
709 """List a directory."""709 """List a directory."""
710710 return map(_unicode_to_bytes, native_listdir(directory))
711
712
713def native_listdir(directory):
714 """List a directory."""
711 # The main reason why we have to append os.path.sep is the following:715 # The main reason why we have to append os.path.sep is the following:
712 #716 #
713 # os.listdir implementation will append a unix path separator to the717 # os.listdir implementation will append a unix path separator to the
@@ -732,8 +736,8 @@
732 # return those paths that are system paths. Those paths are the ones that736 # return those paths that are system paths. Those paths are the ones that
733 # we do not want to work with.737 # we do not want to work with.
734738
735 return map(_unicode_to_bytes, [p for p in os.listdir(directory) if not739 return [p for p in os.listdir(directory) if not
736 native_is_system_path(os.path.join(directory, p))])740 native_is_system_path(os.path.join(directory, p))]
737741
738742
739@windowspath()743@windowspath()

Subscribers

People subscribed via source and target branches