Merge lp:~diegosarmentero/ubuntuone-client/darwin3-fsevents into lp:ubuntuone-client

Proposed by Diego Sarmentero on 2012-06-22
Status: Merged
Approved by: Alejandro J. Cura on 2012-07-03
Approved revision: 1278
Merged at revision: 1271
Proposed branch: lp:~diegosarmentero/ubuntuone-client/darwin3-fsevents
Merge into: lp:ubuntuone-client
Prerequisite: lp:~diegosarmentero/ubuntuone-client/darwin2-fsevents
Diff against target: 1295 lines (+1052/-75)
9 files modified
tests/platform/filesystem_notifications/test_darwin.py (+781/-0)
ubuntuone/platform/filesystem_notifications/__init__.py (+0/-1)
ubuntuone/platform/filesystem_notifications/common.py (+59/-39)
ubuntuone/platform/filesystem_notifications/darwin.py (+133/-0)
ubuntuone/platform/filesystem_notifications/windows.py (+38/-34)
ubuntuone/platform/os_helper/__init__.py (+6/-0)
ubuntuone/platform/os_helper/darwin.py (+27/-0)
ubuntuone/platform/os_helper/linux.py (+3/-0)
ubuntuone/platform/os_helper/windows.py (+5/-1)
To merge this branch: bzr merge lp:~diegosarmentero/ubuntuone-client/darwin3-fsevents
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve on 2012-07-03
Manuel de la Peña (community) 2012-06-22 Approve on 2012-06-27
Review via email: mp+111666@code.launchpad.net

Commit Message

- Adding Watch and WatchManager to darwin (LP: #1013323).

Description of the Change

Now you can execute:
./run-mac-tests tests/platform/filesystem_notifications/test_darwin.py

To post a comment you must log in.
1269. By Diego Sarmentero on 2012-06-22

tests fixed for darwin

Manuel de la Peña (mandel) wrote :

There seems to be a merge issue:

838 +<<<<<<< TREE
839 + For Windows:
840 + Also, they must not be literal paths, that is the \\?\ prefix should not be
841 + in the path.
842 +
843 +=======
844 +>>>>>>> MERGE-SOURCE
845 """

review: Needs Fixing
Diego Sarmentero (diegosarmentero) wrote :

> There seems to be a merge issue:
>
> 838 +<<<<<<< TREE
> 839 + For Windows:
> 840 + Also, they must not be literal paths, that is the \\?\ prefix should
> not be
> 841 + in the path.
> 842 +
> 843 +=======
> 844 +>>>>>>> MERGE-SOURCE
> 845 """

Yes, i realize in the next branch, i miss it here, and i didn't notice because it's inside a string :P
Fixed

Manuel de la Peña (mandel) wrote :
Download full text (4.9 KiB)

I noticed that in the tests we have asserts like:

self.assertEqual(0x100, event.mask)
self.assertEqual(0x40000100, event.mask)
self.assertEqual(0x200, event.mask)
...

Can we use constants that point to those value, there are two reasons for that:

1. if the constant value change we catch the error.
2. Is more readable for the possible maintainer.

I guess there was some inspiration from windows on test_darwin:

478 + # on windows a move to outside a watched dir translates to a remove

and

377 + # while on linux we will have to do some sort of magic like facundo
378 + # did, on windows we will get a deleted event which is much more
379 + # cleaner, first time ever windows is nicer!

Are we doing the exact same tests, can't we merge them?

Does watch take unicode of bytes, the following tests use diff values:

690 + def test_stream_created(self):
691 + """Test that the stream is created."""
692 + def fake_call(*args, **kwargs):
693 + """Fake call."""
694 +
695 + path = '/Users/username/folder'
696 + watch = Watch(1, path, None, True, None)
697 + self.assertEqual(watch._process_events, watch.stream.callback)
698 + self.assertEqual(watch.stream.paths, [path])
699 + self.assertEqual(watch.stream.file_events, True)
700 +
701 + def test_watching_property(self):
702 + """Test that the stopped property returns the stopped deferred."""
703 + path = u'/Users/username/folder'
704 + watch = Watch(1, path, None, True, None)
705 + self.assertFalse(watch._watching)

We should agree on one, either always bytes or always unicode. I'm guessing it should be unicode due to:

1028 + if not isinstance(path, unicode):
1029 + e = NotImplementedError("No implementation on this platform.")

which brings me to point out that we are doing:

873 + def __init__(self, watch_descriptor, path, mask, auto_add, processor,
874 + buf_size=8192):
875 + super(Watch, self).__init__(watch_descriptor, path, mask, auto_add,
876 + processor, buf_size)
877 + # Create stream with folder to watch
878 + try:
879 + path = self._path.encode('utf-8')
880 + except (UnicodeDecodeError, UnicodeEncodeError):
881 + path = self._path
882 + self.stream = fsevents.Stream(self._process_events,
883 + path, file_events=True)

We certainly need to make it more clear what we want to avoid mixing bytes and unicode. I prefer to have sd fail because we passed the wrong type to be honest.

If I understand correctly we get from the fsevents api:

896 + action, cookie, file_name = (event.mask, event.cookie, event.name)

which probably means that cookie is unique, right? If so we do not need to do:

self._cookie = str(uuid4())

when creating the cookie for the fake IN_MOVED_FROM and IN_MOVED_TO events because we should be able to reuse the cookie passed by fsevents, is the assumption correct?

AFAIK we don't need the [] in:

897 + if any([file_name.startswith(path)
898 + for path in self._ignore_paths]):
899 + return

Looks like a copy paste from the windows one (me to blame) we should fix it in both, what is more, the method has a number of steps that are very similar do we really need to copy the entire func?

The following comment looks outdated and from the windows code:

932 + # add the event only if we ...

Read more...

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

One more issue due to the copy&paste:

902 + syncdaemon_path = os.path.join(self._path, file_name)
903 + full_dir_path = os.path.join(self._path, file_name)

Those two are the same on darwin and there is no reason to have both.

review: Needs Fixing
1270. By Diego Sarmentero on 2012-06-26

improving branch

1271. By Diego Sarmentero on 2012-06-26

merge

Diego Sarmentero (diegosarmentero) wrote :
Download full text (5.3 KiB)

> I noticed that in the tests we have asserts like:
>
> self.assertEqual(0x100, event.mask)
> self.assertEqual(0x40000100, event.mask)
> self.assertEqual(0x200, event.mask)
> ...
>
> Can we use constants that point to those value, there are two reasons for
> that:
>
> 1. if the constant value change we catch the error.
> 2. Is more readable for the possible maintainer.
>
> I guess there was some inspiration from windows on test_darwin:
>
> 478 + # on windows a move to outside a watched dir translates to a remove
>
> and
>
> 377 + # while on linux we will have to do some sort of magic like facundo
> 378 + # did, on windows we will get a deleted event which is much more
> 379 + # cleaner, first time ever windows is nicer!
>
> Are we doing the exact same tests, can't we merge them?
>
> Does watch take unicode of bytes, the following tests use diff values:
>
> 690 + def test_stream_created(self):
> 691 + """Test that the stream is created."""
> 692 + def fake_call(*args, **kwargs):
> 693 + """Fake call."""
> 694 +
> 695 + path = '/Users/username/folder'
> 696 + watch = Watch(1, path, None, True, None)
> 697 + self.assertEqual(watch._process_events, watch.stream.callback)
> 698 + self.assertEqual(watch.stream.paths, [path])
> 699 + self.assertEqual(watch.stream.file_events, True)
> 700 +
> 701 + def test_watching_property(self):
> 702 + """Test that the stopped property returns the stopped deferred."""
> 703 + path = u'/Users/username/folder'
> 704 + watch = Watch(1, path, None, True, None)
> 705 + self.assertFalse(watch._watching)
>
> We should agree on one, either always bytes or always unicode. I'm guessing it
> should be unicode due to:
>
> 1028 + if not isinstance(path, unicode):
> 1029 + e = NotImplementedError("No implementation on this platform.")
>
> which brings me to point out that we are doing:
>
> 873 + def __init__(self, watch_descriptor, path, mask, auto_add,
> processor,
> 874 + buf_size=8192):
> 875 + super(Watch, self).__init__(watch_descriptor, path, mask, auto_add,
> 876 + processor, buf_size)
> 877 + # Create stream with folder to watch
> 878 + try:
> 879 + path = self._path.encode('utf-8')
> 880 + except (UnicodeDecodeError, UnicodeEncodeError):
> 881 + path = self._path
> 882 + self.stream = fsevents.Stream(self._process_events,
> 883 + path, file_events=True)
>
> We certainly need to make it more clear what we want to avoid mixing bytes and
> unicode. I prefer to have sd fail because we passed the wrong type to be
> honest.
>
> If I understand correctly we get from the fsevents api:
>
> 896 + action, cookie, file_name = (event.mask, event.cookie, event.name)
>
> which probably means that cookie is unique, right? If so we do not need to do:
>
> self._cookie = str(uuid4())
>
> when creating the cookie for the fake IN_MOVED_FROM and IN_MOVED_TO events
> because we should be able to reuse the cookie passed by fsevents, is the
> assumption correct?
>
> AFAIK we don't need the [] in:
>
> 897 + if any([file_name.startswith(path)
> 898 + for path in self._ignore_paths]):...

Read more...

1272. By Diego Sarmentero on 2012-06-26

merge

Manuel de la Peña (mandel) wrote :

Minor things:

Typo:

997 + """Add a new path tp be watch.
1103 + """Add a new path tp be watch.

Seems to happen a number of times, a :%s/tp/to in vim or a search and replace will do the trick :)

This are in the unix.py should they be in the darwin one:

1169 +# TODO: Implement this decorators to fix some encoding issues in darwin
1170 +
1171 +def is_valid_syncdaemon_path(path_indexes=None):
1172 + def decorator(func):
1173 + def wrapped(*args, **kwargs):
1174 + return func(*args, **kwargs)
1175 + return wrapped
1176 + return decorator
1177 +
1178 +
1179 +def is_valid_os_path(path_indexes=None):
1180 + def decorator(func):
1181 + def wrapped(*args, **kwargs):
1182 + return func(*args, **kwargs)
1183 + return wrapped
1184 + return decorator
1185 +
1186 +
1187 +def os_path(path_indexes=None):
1188 + def decorator(func):
1189 + def wrapped(*args, **kwargs):
1190 + return func(*args, **kwargs)
1191 + return wrapped
1192 + return decorator

There is not yield, so there most me something wrong, or the decorator is not needed or the yield to super is missing:

1025 + @defer.inlineCallbacks
1026 + def del_watch(self, wd):
1027 + """Delete the watch with the given descriptor."""
1028 + watch = self.get_watch(wd)
1029 + self.observer.unschedule(watch.stream)
1030 + super(WatchManager, self).del_watch(wd)

This have to be sorted in alphabetical order:

45 +from ubuntuone.platform.filesystem_notifications.pyinotify_agnostic import (
46 + EventsCodes,
47 + ProcessEvent,
48 + IN_CREATE,
49 + IN_DELETE,
50 +)
51 +from ubuntuone.platform.filesystem_notifications import (
52 + darwin as filesystem_notifications,
53 +)
54 +from ubuntuone.platform.filesystem_notifications.darwin import (
55 + Watch,
56 + WatchManager,
57 +)

review: Needs Fixing
1273. By Diego Sarmentero on 2012-06-27

Fixing merge proposal

1274. By Diego Sarmentero on 2012-06-27

resolving conflict

1275. By Diego Sarmentero on 2012-06-27

removing unnecessary functions from unix

1276. By Diego Sarmentero on 2012-06-27

changing decorator+yield for return

Manuel de la Peña (mandel) wrote :

When running the tests on windows I get the following error:

tests.syncdaemon.test_sync.TestSync.test_deleting_open_files_is_no_cause_for_des
pair
===============================================================================
[FAIL]
Traceback (most recent call last):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\darwin3-fsevents\tes
ts\syncdaemon\test_config.py", line 519, in test_get_config_files_path_encoding
    self.assertIn(branch_config, config_files)
twisted.trial.unittest.FailTest: u'tests.syncdaemon.test_config\\UnicodePathsTes
tCase\\test_get_config_files_path_encod\\tijav6\\temp\\\xd1and\xfa\\syncdaemon.c
onf' not in ['C:\\Users\\Mandel\\Projects\\Canonical\\ubuntuone-client\\darwin3-
fsevents\\ubuntuone\\syncdaemon\\..\\..\\data\\syncdaemon.conf']

tests.syncdaemon.test_config.UnicodePathsTestCase.test_get_config_files_path_enc
oding

Can you reproduce it?

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

The error is in trunk to, so +1

review: Approve
Alejandro J. Cura (alecu) wrote :

I think that EVENT_CODES should be defined in darwin.py and windows.py and imported from __init__.py

----

"Add a new path to be watch" -> "Add a new path to be watched"
"oberserver" -> "observer"
"All paths passed to methods in this class should be __windows__ paths" in darwin.py
"to keep trak" -> "to keep track"

----

"_adding_watch" has no docstring, and the comment it has is wrong.

"_process_events", "append_event" have no docstring.

----

Please rework this comment:

        # we transform the events to be the same as the one in pyinotify
        # and then use the proc_fun

----

"#create a rever mapping to use it in the tests." ->
"# A reverse mapping for the tests"

----

Why is this in test_darwin.py???

        msg = 'Got from ReadDirectoryChangesW %r.'

----

Diego Sarmentero (diegosarmentero) wrote :

> I think that EVENT_CODES should be defined in darwin.py and windows.py and
> imported from __init__.py
>
> ----
>
> "Add a new path to be watch" -> "Add a new path to be watched"
> "oberserver" -> "observer"
> "All paths passed to methods in this class should be __windows__ paths" in
> darwin.py
> "to keep trak" -> "to keep track"
>
> ----
>
> "_adding_watch" has no docstring, and the comment it has is wrong.
>
> "_process_events", "append_event" have no docstring.
>
> ----
>
> Please rework this comment:
>
> # we transform the events to be the same as the one in pyinotify
> # and then use the proc_fun
>
> ----
>
> "#create a rever mapping to use it in the tests." ->
> "# A reverse mapping for the tests"
>
> ----
>
> Why is this in test_darwin.py???
>
> msg = 'Got from ReadDirectoryChangesW %r.'
>
> ----

Everything has been fixed!
Except for the EVENT_CODES thing, the problem is that we are using the event codes at a module level in common.py
So the __init__ import-> darwin/windows which imports-> common, so when common is loaded we can't have access to EVENT_CODES in __init__ because if we define it after importing darwin/windows, it won't exist yet.

1277. By Diego Sarmentero on 2012-06-28

changing event codes

1278. By Diego Sarmentero on 2012-06-28

merge

Alejandro J. Cura (alecu) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'tests/platform/filesystem_notifications/test_darwin.py'
2--- tests/platform/filesystem_notifications/test_darwin.py 1970-01-01 00:00:00 +0000
3+++ tests/platform/filesystem_notifications/test_darwin.py 2012-06-28 17:23:38 +0000
4@@ -0,0 +1,781 @@
5+# -*- coding: utf-8 *-*
6+#
7+# Copyright 2012 Canonical Ltd.
8+#
9+# This program is free software: you can redistribute it and/or modify it
10+# under the terms of the GNU General Public License version 3, as published
11+# by the Free Software Foundation.
12+#
13+# This program is distributed in the hope that it will be useful, but
14+# WITHOUT ANY WARRANTY; without even the implied warranties of
15+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+# PURPOSE. See the GNU General Public License for more details.
17+#
18+# You should have received a copy of the GNU General Public License along
19+# with this program. If not, see <http://www.gnu.org/licenses/>.
20+#
21+# In addition, as a special exception, the copyright holders give
22+# permission to link the code of portions of this program with the
23+# OpenSSL library under certain conditions as described in each
24+# individual source file, and distribute linked combinations
25+# including the two.
26+# You must obey the GNU General Public License in all respects
27+# for all of the code used other than OpenSSL. If you modify
28+# file(s) with this exception, you may extend this exception to your
29+# version of the file(s), but you are not obligated to do so. If you
30+# do not wish to do so, delete this exception statement from your
31+# version. If you delete this exception statement from all source
32+# files in the program, then also delete it here.
33+"""Test the filesystem notifications on MAC OS."""
34+
35+import logging
36+import os
37+import tempfile
38+import thread
39+import time
40+
41+from twisted.internet import defer
42+
43+from contrib.testing.testcase import BaseTwistedTestCase
44+from ubuntuone.devtools.handlers import MementoHandler
45+from ubuntuone.platform.filesystem_notifications import (
46+ darwin as filesystem_notifications,
47+)
48+from ubuntuone.platform.filesystem_notifications.darwin import (
49+ Watch,
50+ WatchManager,
51+)
52+from ubuntuone.platform.filesystem_notifications.pyinotify_agnostic import (
53+ EventsCodes,
54+ ProcessEvent,
55+ IN_CREATE,
56+ IN_DELETE,
57+)
58+
59+# A reverse mapping for the tests
60+REVERSE_MACOS_ACTIONS = {}
61+for key, value in filesystem_notifications.common.COMMON_ACTIONS.iteritems():
62+ REVERSE_MACOS_ACTIONS[value] = key
63+
64+
65+OP_FLAGS = EventsCodes.FLAG_COLLECTIONS['OP_FLAGS']
66+IS_DIR = EventsCodes.FLAG_COLLECTIONS['SPECIAL_FLAGS']['IN_ISDIR']
67+
68+
69+class FakeException(Exception):
70+ """A fake Exception used in tests."""
71+
72+
73+class FakeVolume(object):
74+ """A fake volume."""
75+
76+ def __init__(self, path, ancestors):
77+ """Create a new instance."""
78+ super(FakeVolume, self).__init__()
79+ self.volume_id = path
80+ self.path = path
81+ self.ancestors = ancestors
82+
83+
84+class FakeFileEvent(object):
85+ """A Fake FileEvent from macfsevents"""
86+
87+ def __init__(self, mask, cookie, name):
88+ self.mask = mask
89+ self.cookie = cookie
90+ self.name = name
91+
92+
93+class TestCaseHandler(ProcessEvent):
94+ """ProcessEvent used for test cases."""
95+
96+ thread_id = None
97+
98+ def my_init(self, main_thread_id=None, number_events=None, **kwargs):
99+ """Init the event notifier."""
100+ self.processed_events = []
101+ self.main_thread_id = main_thread_id
102+ self.deferred = defer.Deferred()
103+ assert number_events is not None
104+ self.number_events = number_events
105+
106+ def append_event(self, event):
107+ """Control that we received the number of events that we want."""
108+ self.processed_events.append(event)
109+ if len(self.processed_events) == self.number_events:
110+ self.deferred.callback(self.processed_events)
111+
112+ def process_IN_CREATE(self, event):
113+ """Process the event and add it to the list."""
114+ self.append_event(event)
115+ self._verify_thread_id()
116+
117+ def process_IN_DELETE(self, event):
118+ """Process the event and add it to the list."""
119+ self.append_event(event)
120+ self._verify_thread_id()
121+
122+ def process_default(self, event):
123+ """Process the event and add it to the list."""
124+ self.append_event(event)
125+ self._verify_thread_id()
126+
127+ def _verify_thread_id(self):
128+ """Verify that the event was processed in the correct thread."""
129+ if self.main_thread_id:
130+ assert self.main_thread_id == thread.get_ident()
131+
132+
133+class TestWatch(BaseTwistedTestCase):
134+ """Test the watch so that it returns the same events as pyinotify."""
135+
136+ timeout = 5
137+
138+ @defer.inlineCallbacks
139+ def setUp(self):
140+ yield super(TestWatch, self).setUp()
141+ self.basedir = self.mktemp('test_root')
142+ self.mask = None
143+ self.memento = MementoHandler()
144+ self.memento.setLevel(logging.DEBUG)
145+ self.raw_events = []
146+ self.paths_checked = []
147+ old_is_dir = Watch._path_is_dir
148+
149+ def path_is_dir_wrapper(watch, path):
150+ """Wrapper that gets the checked paths."""
151+ result = old_is_dir(watch, path)
152+ self.paths_checked.append((path, result))
153+ return result
154+
155+ self.patch(filesystem_notifications.Watch, '_path_is_dir',
156+ path_is_dir_wrapper)
157+
158+ @defer.inlineCallbacks
159+ def _perform_operations(self, path, mask, auto_add, actions,
160+ number_events):
161+ """Perform the file operations and returns the recorded events."""
162+ handler = TestCaseHandler(number_events=number_events)
163+ manager = WatchManager(handler)
164+ yield manager.add_watch(path, mask, auto_add=auto_add)
165+ # change the logger so that we can check the logs if we wanted
166+ manager._wdm[0].log.addHandler(self.memento)
167+ # clean logger later
168+ self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
169+ # execution the actions
170+ actions()
171+ # process the recorded events
172+ ret = yield handler.deferred
173+ self.addCleanup(manager.stop)
174+ defer.returnValue(ret)
175+
176+ def _perform_timed_operations(self, path, mask, auto_add, actions,
177+ time_out):
178+ """Perform the file operations and returns the recorded events."""
179+ manager = WatchManager()
180+ manager.add_watch(path, mask, auto_add=auto_add)
181+ # change the logger so that we can check the logs if we wanted
182+ manager._wdm[0].log.addHandler(self.memento)
183+ # clean logger later
184+ self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
185+ # execution the actions
186+ actions()
187+ # process the recorded events
188+ time.sleep(time_out)
189+ events = self.handler.processed_events
190+ return events
191+
192+ def _assert_logs(self, events):
193+ """Assert the debug logs."""
194+ logs = []
195+ msg = 'Is path %r a dir? %s'
196+ logs.extend([msg % data for data in self.paths_checked])
197+ msg = 'Pushing event %r to processor.'
198+ logs.extend([msg % e for e in events])
199+ for msg in logs:
200+ self.assertTrue(self.memento.check_debug(msg))
201+
202+ @defer.inlineCallbacks
203+ def test_file_create(self):
204+ """Test that the correct event is returned on a file create."""
205+ file_name = os.path.join(self.basedir, 'test_file_create')
206+
207+ def create_file():
208+ """Action used for the test."""
209+ # simply create a new file
210+ fd = open(file_name, 'w')
211+ fd.flush()
212+ os.fsync(fd)
213+ fd.close()
214+
215+ events = yield self._perform_operations(self.basedir, self.mask, False,
216+ create_file, 1)
217+ event = events[0]
218+ self.assertFalse(event.dir)
219+ self.assertEqual(OP_FLAGS['IN_CREATE'], event.mask)
220+ self.assertEqual('IN_CREATE', event.maskname)
221+ self.assertEqual(os.path.split(file_name)[1], event.name)
222+ self.assertEqual('.', event.path)
223+ self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
224+ self.assertEqual(0, event.wd)
225+ # assert the logging
226+ self._assert_logs(events)
227+
228+ @defer.inlineCallbacks
229+ def test_dir_create(self):
230+ """Test that the correct event is returned on a dir creation."""
231+ dir_name = os.path.join(self.basedir, 'test_dir_create')
232+
233+ def create_dir():
234+ """Action for the test."""
235+ os.mkdir(dir_name)
236+
237+ events = yield self._perform_operations(self.basedir, self.mask, False,
238+ create_dir, 1)
239+ event = events[0]
240+ self.assertTrue(event.dir)
241+ self.assertEqual(OP_FLAGS['IN_CREATE'] | IS_DIR, event.mask)
242+ self.assertEqual('IN_CREATE|IN_ISDIR', event.maskname)
243+ self.assertEqual(os.path.split(dir_name)[1], event.name)
244+ self.assertEqual('.', event.path)
245+ self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
246+ self.assertEqual(0, event.wd)
247+ # assert the logging
248+ self._assert_logs(events)
249+
250+ @defer.inlineCallbacks
251+ def test_file_remove(self):
252+ """Test that the correct event is raised when a file is removed."""
253+ file_name = os.path.join(self.basedir, 'test_file_remove')
254+ # create the file before recording
255+ open(file_name, 'w').close()
256+
257+ def remove_file():
258+ """Action for the test."""
259+ os.remove(file_name)
260+
261+ events = yield self._perform_operations(self.basedir, self.mask, False,
262+ remove_file, 1)
263+ event = events[0]
264+ self.assertFalse(event.dir)
265+ self.assertEqual(OP_FLAGS['IN_DELETE'], event.mask)
266+ self.assertEqual('IN_DELETE', event.maskname)
267+ self.assertEqual(os.path.split(file_name)[1], event.name)
268+ self.assertEqual('.', event.path)
269+ self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
270+ self.assertEqual(0, event.wd)
271+ # assert the logging
272+ self._assert_logs(events)
273+
274+ @defer.inlineCallbacks
275+ def test_dir_remove(self):
276+ """Test that the correct event is raised when a dir is removed."""
277+ dir_name = os.path.join(self.basedir, 'test_dir_remove')
278+ # create the dir before recording
279+ os.mkdir(dir_name)
280+
281+ def remove_dir():
282+ """Action for the test."""
283+ os.rmdir(dir_name)
284+
285+ events = yield self._perform_operations(self.basedir, self.mask, False,
286+ remove_dir, 1)
287+ event = events[0]
288+ self.assertTrue(event.dir)
289+ self.assertEqual(OP_FLAGS['IN_DELETE'] | IS_DIR, event.mask)
290+ self.assertEqual('IN_DELETE|IN_ISDIR', event.maskname)
291+ self.assertEqual('.', event.path)
292+ self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
293+ self.assertEqual(0, event.wd)
294+ # assert the logging
295+ self._assert_logs(events)
296+
297+ @defer.inlineCallbacks
298+ def test_file_write(self):
299+ """Test that the correct event is raised when a file is written."""
300+ file_name = os.path.join(self.basedir, 'test_file_write')
301+ # clean behind us by removing the file
302+ self.addCleanup(os.remove, file_name)
303+
304+ def write_file():
305+ """Action for the test."""
306+ # create the file before recording
307+ fd = open(file_name, 'w')
308+ fd.write('test')
309+ fd.close()
310+
311+ events = yield self._perform_operations(self.basedir, self.mask, False,
312+ write_file, 1)
313+ event = events[0]
314+ self.assertFalse(event.dir)
315+ self.assertEqual(OP_FLAGS['IN_CREATE'], event.mask)
316+ self.assertEqual('IN_CREATE', event.maskname)
317+ self.assertEqual(os.path.split(file_name)[1], event.name)
318+ self.assertEqual('.', event.path)
319+ self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
320+ self.assertEqual(0, event.wd)
321+ # assert the logging
322+ self._assert_logs(events)
323+
324+ @defer.inlineCallbacks
325+ def test_file_moved_to_watched_dir_same_watcher(self):
326+ """Test that the correct event is raised when a file is moved."""
327+ from_file_name = os.path.join(self.basedir,
328+ 'test_file_moved_to_watched_dir_same_watcher')
329+ to_file_name = os.path.join(self.basedir,
330+ 'test_file_moved_to_watched_dir_same_watcher_2')
331+ open(from_file_name, 'w').close()
332+ #create file before recording
333+
334+ def move_file():
335+ """Action for the test."""
336+ os.rename(from_file_name, to_file_name)
337+
338+ events = yield self._perform_operations(self.basedir, self.mask,
339+ False, move_file, 2)
340+ move_from_event = events[0]
341+ move_to_event = events[1]
342+ # first test the move from
343+ self.assertFalse(move_from_event.dir)
344+ self.assertEqual(OP_FLAGS['IN_MOVED_FROM'], move_from_event.mask)
345+ self.assertEqual('IN_MOVED_FROM', move_from_event.maskname)
346+ self.assertEqual(os.path.split(from_file_name)[1],
347+ move_from_event.name)
348+ self.assertEqual('.', move_from_event.path)
349+ self.assertEqual(os.path.join(self.basedir, from_file_name),
350+ move_from_event.pathname)
351+ self.assertEqual(0, move_from_event.wd)
352+ # test the move to
353+ self.assertFalse(move_to_event.dir)
354+ self.assertEqual(OP_FLAGS['IN_MOVED_TO'], move_to_event.mask)
355+ self.assertEqual('IN_MOVED_TO', move_to_event.maskname)
356+ self.assertEqual(os.path.split(to_file_name)[1], move_to_event.name)
357+ self.assertEqual('.', move_to_event.path)
358+ self.assertEqual(os.path.join(self.basedir, to_file_name),
359+ move_to_event.pathname)
360+ self.assertEqual(os.path.split(from_file_name)[1],
361+ move_to_event.src_pathname)
362+ self.assertEqual(0, move_to_event.wd)
363+ # assert that both cookies are the same
364+ self.assertEqual(move_from_event.cookie, move_to_event.cookie)
365+ # assert the logging
366+ self._assert_logs(events)
367+
368+ @defer.inlineCallbacks
369+ def test_file_moved_to_not_watched_dir(self):
370+ """Test that the correct event is raised when a file is moved."""
371+ from_file_name = os.path.join(self.basedir,
372+ 'test_file_moved_to_not_watched_dir')
373+ open(from_file_name, 'w').close()
374+
375+ def move_file():
376+ """Action for the test."""
377+ os.rename(from_file_name, os.path.join(tempfile.mkdtemp(),
378+ 'test_file_moved_to_not_watched_dir'))
379+
380+ # We need to test that we get a delete operation when moving
381+ # a file to an unwatched folder
382+ events = yield self._perform_operations(self.basedir, self.mask, False,
383+ move_file, 1)
384+ event = events[0]
385+ self.assertFalse(event.dir)
386+ self.assertEqual(OP_FLAGS['IN_DELETE'], event.mask)
387+ self.assertEqual('IN_DELETE', event.maskname)
388+ self.assertEqual(os.path.split(from_file_name)[1], event.name)
389+ self.assertEqual('.', event.path)
390+ self.assertEqual(os.path.join(self.basedir, from_file_name),
391+ event.pathname)
392+ self.assertEqual(0, event.wd)
393+ # assert the logging
394+ self._assert_logs(events)
395+
396+ @defer.inlineCallbacks
397+ def test_file_move_from_not_watched_dir(self):
398+ """Test that the correct event is raised when a file is moved."""
399+ from_file_name = os.path.join(tempfile.mkdtemp(),
400+ 'test_file_move_from_not_watched_dir')
401+ to_file_name = os.path.join(self.basedir,
402+ 'test_file_move_from_not_watched_dir')
403+ # create file before we record
404+ open(from_file_name, 'w').close()
405+
406+ def move_files():
407+ """Action for the test."""
408+ os.rename(from_file_name, to_file_name)
409+
410+ # We need to test that we get a delete operation when moving
411+ # a file from an unwatched folder
412+ events = yield self._perform_operations(self.basedir, self.mask, False,
413+ move_files, 1)
414+ event = events[0]
415+ self.assertFalse(event.dir)
416+ self.assertEqual(OP_FLAGS['IN_CREATE'], event.mask)
417+ self.assertEqual('IN_CREATE', event.maskname)
418+ self.assertEqual(os.path.split(to_file_name)[1], event.name)
419+ self.assertEqual('.', event.path)
420+ self.assertEqual(os.path.join(self.basedir, to_file_name),
421+ event.pathname)
422+ self.assertEqual(0, event.wd)
423+ # assert the logging
424+ self._assert_logs(events)
425+
426+ @defer.inlineCallbacks
427+ def test_dir_moved_to_watched_dir_same_watcher(self):
428+ """Test that the correct event is raised when a dir is moved."""
429+ from_dir_name = os.path.join(self.basedir,
430+ 'test_dir_moved_to_watched_dir_same_watcher')
431+ to_dir_name = os.path.join(self.basedir,
432+ 'test_dir_moved_to_watched_dir_same_watcher_2')
433+ os.mkdir(from_dir_name)
434+
435+ def move_file():
436+ """Action for the test."""
437+ os.rename(from_dir_name, to_dir_name)
438+
439+ events = yield self._perform_operations(self.basedir,
440+ self.mask, False, move_file, 2)
441+ move_from_event = events[0]
442+ move_to_event = events[1]
443+ # first test the move from
444+ self.assertTrue(move_from_event.dir)
445+ self.assertEqual(OP_FLAGS['IN_MOVED_FROM'] | IS_DIR,
446+ move_from_event.mask)
447+ self.assertEqual('IN_MOVED_FROM|IN_ISDIR', move_from_event.maskname)
448+ self.assertEqual(os.path.split(from_dir_name)[1], move_from_event.name)
449+ self.assertEqual('.', move_from_event.path)
450+ self.assertEqual(os.path.join(self.basedir, from_dir_name),
451+ move_from_event.pathname)
452+ self.assertEqual(0, move_from_event.wd)
453+ # test the move to
454+ self.assertTrue(move_to_event.dir)
455+ self.assertEqual(OP_FLAGS['IN_MOVED_TO'] | IS_DIR, move_to_event.mask)
456+ self.assertEqual('IN_MOVED_TO|IN_ISDIR', move_to_event.maskname)
457+ self.assertEqual(os.path.split(to_dir_name)[1], move_to_event.name)
458+ self.assertEqual('.', move_to_event.path)
459+ self.assertEqual(os.path.join(self.basedir, to_dir_name),
460+ move_to_event.pathname)
461+ self.assertEqual(os.path.split(from_dir_name)[1],
462+ move_to_event.src_pathname)
463+ self.assertEqual(0, move_to_event.wd)
464+ # assert that both cookies are the same
465+ self.assertEqual(move_from_event.cookie, move_to_event.cookie)
466+ # assert the logging
467+ self._assert_logs(events)
468+
469+ @defer.inlineCallbacks
470+ def test_dir_moved_to_not_watched_dir(self):
471+ """Test that the correct event is raised when a file is moved."""
472+ dir_name = os.path.join(self.basedir,
473+ 'test_dir_moved_to_not_watched_dir')
474+ os.mkdir(dir_name)
475+
476+ def move_dir():
477+ """Action for the test."""
478+ os.rename(dir_name, os.path.join(tempfile.mkdtemp(),
479+ 'test_dir_moved_to_not_watched_dir'))
480+
481+ # We need to test that we get a delete operation when moving
482+ # a file to an unwatched folder
483+ events = yield self._perform_operations(self.basedir, self.mask, False,
484+ move_dir, 1)
485+ event = events[0]
486+ self.assertTrue(event.dir)
487+ self.assertEqual(OP_FLAGS['IN_DELETE'] | IS_DIR, event.mask)
488+ self.assertEqual('IN_DELETE|IN_ISDIR', event.maskname)
489+ self.assertEqual('.', event.path)
490+ self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
491+ self.assertEqual(0, event.wd)
492+ # assert the logging
493+ self._assert_logs(events)
494+
495+ @defer.inlineCallbacks
496+ def test_dir_move_from_not_watched_dir(self):
497+ """Test that the correct event is raised when a file is moved."""
498+ from_dir_name = os.path.join(tempfile.mkdtemp(),
499+ 'test_dir_move_from_not_watched_dir')
500+ to_dir_name = os.path.join(self.basedir,
501+ 'test_dir_move_from_not_watched_dir')
502+ # create file before we record
503+ os.mkdir(from_dir_name)
504+
505+ def move_dir():
506+ """Action for the test."""
507+ os.rename(from_dir_name, to_dir_name)
508+
509+ events = yield self._perform_operations(self.basedir, self.mask, False,
510+ move_dir, 1)
511+ event = events[0]
512+ self.assertTrue(event.dir)
513+ self.assertEqual(OP_FLAGS['IN_CREATE'] | IS_DIR, event.mask)
514+ self.assertEqual('IN_CREATE|IN_ISDIR', event.maskname)
515+ self.assertEqual(os.path.split(from_dir_name)[1], event.name)
516+ self.assertEqual('.', event.path)
517+ self.assertEqual(os.path.join(self.basedir, to_dir_name),
518+ event.pathname)
519+ self.assertEqual(0, event.wd)
520+
521+ def test_exclude_filter(self):
522+ """Test that the exclude filter works as expectd."""
523+ handler = TestCaseHandler(number_events=0)
524+ manager = WatchManager(handler)
525+ # add a watch that will always exclude all actions
526+ manager.add_watch(self.basedir, self.mask, auto_add=True,
527+ exclude_filter=lambda x: True)
528+ # execution the actions
529+ file_name = os.path.join(self.basedir, 'test_file_create')
530+ open(file_name, 'w').close()
531+ # give some time for the system to get the events
532+ self.assertEqual(0, len(handler.processed_events))
533+ test_exclude_filter.skip = "we must rethink this test."
534+
535+ def test_open_dir_muted(self):
536+ """Test that the opening of dirs is ignored."""
537+ dir_name = os.path.join(tempfile.mkdtemp(), 'test_dir_open')
538+ # create file before we record
539+ os.mkdir(dir_name)
540+
541+ def open_dir():
542+ """Action for the test."""
543+ os.startfile(dir_name)
544+
545+ events = self._perform_timed_operations(self.basedir, self.mask, False,
546+ open_dir, 2)
547+ self.assertEqual(0, len(events))
548+ test_open_dir_muted.skip = "we must rethink this test."
549+
550+ def test_ignore_path(self):
551+ """Test that events from a path are ignored."""
552+ events = []
553+
554+ def fake_processor(event):
555+ """Memorize the processed events."""
556+ events.append(event)
557+
558+ path = '/Users/username/folder'
559+ child = 'child'
560+ watch = Watch(1, path, None, True, fake_processor)
561+ watch.ignore_path(os.path.join(path, child))
562+ # ensure that the watch is watching
563+ watch._watching = True
564+ for file_name in 'abcdef':
565+ event = FakeFileEvent(256, None, os.path.join(child, file_name))
566+ watch._process_events(event)
567+ self.assertEqual(0, len(events),
568+ 'All events should have been ignored.')
569+
570+ def test_not_ignore_path(self):
571+ """Test that we do get the events when they do not match."""
572+ events = []
573+
574+ def fake_processor(event):
575+ """Memorize the processed events."""
576+ events.append(event)
577+
578+ self.patch(filesystem_notifications.reactor, 'callFromThread',
579+ lambda x, e: x(e))
580+
581+ path = '/Users/username/folder'
582+ child = 'child'
583+ watch = Watch(1, path, None, True, fake_processor)
584+ watch.ignore_path(os.path.join(path, child))
585+ paths_not_to_ignore = []
586+ for file_name in 'abcdef':
587+ event = FakeFileEvent(256, None, os.path.join(child + file_name,
588+ file_name))
589+ paths_not_to_ignore.append(event)
590+ # ensure that the watch is watching
591+ watch._watching = True
592+ for event in paths_not_to_ignore:
593+ watch._process_events(event)
594+ self.assertEqual(len(paths_not_to_ignore), len(events),
595+ 'No events should have been ignored.')
596+
597+ def test_mixed_ignore_path(self):
598+ """Test that we do get the correct events."""
599+ events = []
600+
601+ def fake_processor(event):
602+ """Memorize the processed events."""
603+ events.append(event.pathname)
604+
605+ self.patch(filesystem_notifications.reactor, 'callFromThread',
606+ lambda x, e: x(e))
607+
608+ child = 'child'
609+ path = '/Users/username/folder'
610+ watch = Watch(1, path, None, True, fake_processor)
611+ watch.ignore_path(os.path.join(path, child))
612+ paths_not_to_ignore = []
613+ paths_to_ignore = []
614+ expected_events = []
615+ for file_name in 'abcdef':
616+ valid = os.path.join(child + file_name, file_name)
617+ paths_to_ignore.append((1, os.path.join(child, file_name)))
618+ event = FakeFileEvent(256, None, valid)
619+ paths_not_to_ignore.append(event)
620+ expected_events.append(os.path.join(path, valid))
621+ # ensure that the watch is watching
622+ watch._watching = True
623+ for event in paths_not_to_ignore:
624+ watch._process_events(event)
625+ self.assertEqual(len(paths_not_to_ignore), len(events),
626+ 'Wrong number of events ignored.')
627+ self.assertTrue(all([event in expected_events for event in events]),
628+ 'Paths ignored that should have not been ignored.')
629+
630+ def test_undo_ignore_path_ignored(self):
631+ """Test that we do deal with events from and old ignored path."""
632+ events = []
633+
634+ def fake_processor(event):
635+ """Memorize the processed events."""
636+ events.append(event)
637+
638+ self.patch(filesystem_notifications.reactor, 'callFromThread',
639+ lambda x, e: x(e))
640+
641+ path = '/Users/username/folder'
642+ child = 'child'
643+ watch = Watch(1, path, None, True, fake_processor)
644+ watch.ignore_path(os.path.join(path, child))
645+ watch.remove_ignored_path(os.path.join(path, child))
646+ paths_not_to_ignore = []
647+ for file_name in 'abcdef':
648+ event = FakeFileEvent(256, None, os.path.join(child, file_name))
649+ paths_not_to_ignore.append(event)
650+ # ensure that the watch is watching
651+ watch._watching = True
652+ for event in paths_not_to_ignore:
653+ watch._process_events(event)
654+ self.assertEqual(len(paths_not_to_ignore), len(events),
655+ 'All events should have been accepted.')
656+
657+ def test_undo_ignore_path_other_ignored(self):
658+ """Test that we can undo and the other path is ignored."""
659+ events = []
660+
661+ def fake_processor(event):
662+ """Memorize the processed events."""
663+ events.append(event.pathname)
664+
665+ self.patch(filesystem_notifications.reactor, 'callFromThread',
666+ lambda x, e: x(e))
667+
668+ path = '/Users/username/folder'
669+ child_a = 'childa'
670+ child_b = 'childb'
671+ watch = Watch(1, path, None, True, fake_processor)
672+ watch.ignore_path(os.path.join(path, child_a))
673+ watch.ignore_path(os.path.join(path, child_b))
674+ watch.remove_ignored_path(os.path.join(path, child_a))
675+ paths_to_ignore = []
676+ paths_not_to_ignore = []
677+ expected_events = []
678+ for file_name in 'abcdef':
679+ paths_to_ignore.append((1, os.path.join(child_b, file_name)))
680+ valid = os.path.join(child_a, file_name)
681+ event = FakeFileEvent(256, None, valid)
682+ paths_not_to_ignore.append(event)
683+ expected_events.append(os.path.join(path, valid))
684+ # ensure that the watch is watching
685+ watch._watching = True
686+ for event in paths_not_to_ignore:
687+ watch._process_events(event)
688+ self.assertEqual(len(paths_not_to_ignore), len(events),
689+ 'All events should have been accepted.')
690+ self.assertTrue(all([event in expected_events for event in events]),
691+ 'Paths ignored that should have not been ignored.')
692+
693+ def test_stream_created(self):
694+ """Test that the stream is created."""
695+ def fake_call(*args, **kwargs):
696+ """Fake call."""
697+
698+ path = '/Users/username/folder'
699+ watch = Watch(1, path, None, True, None)
700+ self.assertEqual(watch._process_events, watch.stream.callback)
701+ self.assertEqual(watch.stream.paths, [path])
702+ self.assertEqual(watch.stream.file_events, True)
703+
704+ def test_watching_property(self):
705+ """Test that the stopped property returns the stopped deferred."""
706+ path = '/Users/username/folder'
707+ watch = Watch(1, path, None, True, None)
708+ self.assertFalse(watch._watching)
709+
710+ def random_error(self, *args):
711+ """Throw a fake exception."""
712+ raise FakeException()
713+
714+ def test_is_path_dir_missing_no_subdir(self):
715+ """Test when the path does not exist and is no a subdir."""
716+ path = '/Users/username/path/to/not/dir'
717+ test_path = self.mktemp("test_directory")
718+ self.patch(os.path, 'exists', lambda path: False)
719+ watch = Watch(1, test_path, self.mask, True, None)
720+ self.assertFalse(watch._path_is_dir(path))
721+
722+ def test_is_path_dir_missing_in_subdir(self):
723+ """Test when the path does not exist and is a subdir."""
724+ path = '/Users/username/path/to/not/dir'
725+ test_path = self.mktemp("test_directory")
726+ self.patch(os.path, 'exists', lambda path: False)
727+ watch = Watch(1, test_path, self.mask, True, None)
728+ watch._subdirs.add(path)
729+ self.assertTrue(watch._path_is_dir(path))
730+
731+ def test_is_path_dir_present_is_dir(self):
732+ """Test when the path is present and is dir."""
733+ path = '/Users/username/path/to/not/dir'
734+ test_path = self.mktemp("test_directory")
735+ self.patch(os.path, 'exists', lambda path: True)
736+ self.patch(os.path, 'isdir', lambda path: True)
737+ watch = Watch(1, test_path, self.mask, True, None)
738+ watch._subdirs.add(path)
739+ self.assertTrue(watch._path_is_dir(path))
740+
741+ def test_is_path_dir_present_no_dir(self):
742+ """Test when the path is present but not a dir."""
743+ path = '/Users/username/path/to/not/dir'
744+ test_path = self.mktemp("test_directory")
745+ self.patch(os.path, 'exists', lambda path: True)
746+ self.patch(os.path, 'isdir', lambda path: False)
747+ watch = Watch(1, test_path, self.mask, True, None)
748+ watch._subdirs.add(path)
749+ self.assertFalse(watch._path_is_dir(path))
750+
751+ def test_update_subdirs_create_not_present(self):
752+ """Test when we update on a create event and not present."""
753+ path = '/Users/username/path/to/not/dir'
754+ test_path = self.mktemp("test_directory")
755+ watch = Watch(1, test_path, self.mask, True, None)
756+ watch._update_subdirs(path, REVERSE_MACOS_ACTIONS[IN_CREATE])
757+ self.assertTrue(path in watch._subdirs)
758+
759+ def test_update_subdirs_create_present(self):
760+ """Test when we update on a create event and is present."""
761+ path = '/Users/username/path/to/not/dir'
762+ test_path = self.mktemp("test_directory")
763+ watch = Watch(1, test_path, self.mask, True, None)
764+ watch._subdirs.add(path)
765+ old_length = len(watch._subdirs)
766+ watch._update_subdirs(path, REVERSE_MACOS_ACTIONS[IN_CREATE])
767+ self.assertTrue(path in watch._subdirs)
768+ self.assertEqual(old_length, len(watch._subdirs))
769+
770+ def test_update_subdirs_delete_not_present(self):
771+ """Test when we delete and is not present."""
772+ path = '/Users/username/path/to/not/dir'
773+ test_path = self.mktemp("test_directory")
774+ watch = Watch(1, test_path, self.mask, True, None)
775+ watch._update_subdirs(path, REVERSE_MACOS_ACTIONS[IN_DELETE])
776+ self.assertTrue(path not in watch._subdirs)
777+
778+ def test_update_subdirs_delete_present(self):
779+ """Test when we delete and is present."""
780+ path = '/Users/username/path/to/not/dir'
781+ test_path = self.mktemp("test_directory")
782+ watch = Watch(1, test_path, self.mask, True, None)
783+ watch._subdirs.add(path)
784+ watch._update_subdirs(path, REVERSE_MACOS_ACTIONS[IN_DELETE])
785+ self.assertTrue(path not in watch._subdirs)
786
787=== modified file 'ubuntuone/platform/filesystem_notifications/__init__.py'
788--- ubuntuone/platform/filesystem_notifications/__init__.py 2012-06-22 19:07:11 +0000
789+++ ubuntuone/platform/filesystem_notifications/__init__.py 2012-06-28 17:23:38 +0000
790@@ -32,7 +32,6 @@
791
792
793 if sys.platform == "win32":
794- EVENT_CODES = [1, 2, 3, 4, 5]
795 from ubuntuone.platform.filesystem_notifications import windows
796 FilesystemMonitor = windows.FilesystemMonitor
797 _GeneralINotifyProcessor = windows.NotifyProcessor
798
799=== modified file 'ubuntuone/platform/filesystem_notifications/common.py'
800--- ubuntuone/platform/filesystem_notifications/common.py 2012-06-26 19:20:38 +0000
801+++ ubuntuone/platform/filesystem_notifications/common.py 2012-06-28 17:23:38 +0000
802@@ -33,7 +33,6 @@
803
804 from twisted.internet import defer
805
806-from ubuntuone.platform.filesystem_notifications import EVENT_CODES
807 from ubuntuone.platform.filesystem_notifications.pyinotify_agnostic import (
808 Event,
809 WatchManagerError,
810@@ -45,36 +44,28 @@
811 IN_IGNORED,
812 IN_ISDIR,
813 IN_DELETE,
814+ IN_MODIFY as in_modify,
815 IN_MOVED_FROM,
816- IN_MOVED_TO,
817- IN_MODIFY)
818+ IN_MOVED_TO)
819
820 from ubuntuone import logger
821
822-from ubuntuone.platform.os_helper.windows import (
823+from ubuntuone.platform.os_helper import (
824 is_valid_syncdaemon_path,
825- is_valid_windows_path as is_valid_os_path,
826- windowspath as os_path,
827+ is_valid_os_path,
828+ os_path,
829 )
830
831 # a map between the few events that we have on common platforms and those
832 # found in pyinotify
833-COMMON_ACTIONS = {
834- EVENT_CODES[0]: IN_CREATE,
835- EVENT_CODES[1]: IN_DELETE,
836- EVENT_CODES[2]: IN_MODIFY,
837- EVENT_CODES[3]: IN_MOVED_FROM,
838- EVENT_CODES[4]: IN_MOVED_TO,
839-}
840+COMMON_ACTIONS = {}
841
842 # a map of the actions to names so that we have better logs.
843-COMMON_ACTIONS_NAMES = {
844- EVENT_CODES[0]: 'IN_CREATE',
845- EVENT_CODES[1]: 'IN_DELETE',
846- EVENT_CODES[2]: 'IN_MODIFY',
847- EVENT_CODES[3]: 'IN_MOVED_FROM',
848- EVENT_CODES[4]: 'IN_MOVED_TO',
849-}
850+COMMON_ACTIONS_NAMES = {}
851+
852+# We should have this here, because we use if from other modules that
853+# share this, but we need to declare it this way yo avoid flakes issues.
854+IN_MODIFY = in_modify
855
856 # translates quickly the event and it's is_dir state to our standard events
857 NAME_TRANSLATIONS = {
858@@ -123,6 +114,46 @@
859 self._path = os.path.abspath(path)
860 self._mask = mask
861
862+ def _process_events_from_filesystem(self, action, file_name, cookie,
863+ syncdaemon_path):
864+ """Process the events from the queue."""
865+ # map the filesystem events to the pyinotify ones, tis is dirty but
866+ # makes the multiplatform better, linux was first :P
867+ full_dir_path = os.path.join(self._path, file_name)
868+ is_dir = self._path_is_dir(full_dir_path)
869+ if is_dir:
870+ # we need to update the list of subdirs that we have
871+ self._update_subdirs(full_dir_path, action)
872+ mask = COMMON_ACTIONS[action]
873+ head, tail = os.path.split(file_name)
874+ if is_dir:
875+ mask |= IN_ISDIR
876+ event_raw_data = {
877+ 'wd': self._descriptor,
878+ 'dir': is_dir,
879+ 'mask': mask,
880+ 'name': tail,
881+ 'path': '.'}
882+ # by the way in which the win api fires the events we know for
883+ # sure that no move events will be added in the wrong order, this
884+ # is kind of hacky, I dont like it too much
885+ if COMMON_ACTIONS[action] == IN_MOVED_FROM:
886+ self._cookie = cookie
887+ self._source_pathname = tail
888+ event_raw_data['cookie'] = self._cookie
889+ if COMMON_ACTIONS[action] == IN_MOVED_TO:
890+ event_raw_data['src_pathname'] = self._source_pathname
891+ event_raw_data['cookie'] = self._cookie
892+ event = Event(event_raw_data)
893+ # FIXME: event deduces the pathname wrong and we need to manually
894+ # set it
895+ event.pathname = syncdaemon_path
896+ # add the event only if we do not have an exclude filter or
897+ # the exclude filter returns False, that is, the event will not
898+ # be excluded
899+ self.log.debug('Pushing event %r to processor.', event)
900+ self._processor(event)
901+
902 @is_valid_os_path(path_indexes=[1])
903 def _update_subdirs(self, path, event):
904 """Adds the path to the internal subdirs.
905@@ -220,6 +251,13 @@
906 self._wd_count = 0
907 self._ignored_paths = []
908
909+ def add_watch(self, path, mask, auto_add=False, quiet=True):
910+ """Add a new path to be watch.
911+
912+ The method will ensure that the path is not already present.
913+ """
914+ raise NotImplementedError("Not implemented on this platform.")
915+
916 def stop(self):
917 """Close the manager and stop all watches."""
918 # Should be implemented for each platform
919@@ -256,23 +294,6 @@
920 # This should be implemented for each OS
921 raise NotImplementedError("Not implemented on this platform.")
922
923- @is_valid_os_path(path_indexes=[1])
924- def add_watch(self, path, mask, auto_add=False, quiet=True):
925- """Add a new path tp be watch.
926-
927- The method will ensure that the path is not already present.
928- """
929- if not isinstance(path, unicode):
930- e = NotImplementedError("No implementation on this platform.")
931- return defer.fail(e)
932- wd = self.get_wd(path)
933- if wd is None:
934- self.log.debug('Adding single watch on %r', path)
935- return self._add_single_watch(path, mask, auto_add, quiet)
936- else:
937- self.log.debug('Watch already exists on %r', path)
938- return self._wdm[wd].started
939-
940 def update_watch(self, wd, mask=None, rec=False,
941 auto_add=False, quiet=True):
942 raise NotImplementedError("Not implemented on this platform.")
943@@ -321,7 +342,6 @@
944
945 This interface will be exposed to syncdaemon, ergo all passed
946 and returned paths must be a sequence of BYTES encoded with utf8.
947-
948 """
949
950 def __init__(self, monitor, ignore_config=None):
951@@ -563,8 +583,8 @@
952
953 def add_watches_to_udf_ancestors(self, volume):
954 """Add a inotify watch to volume's ancestors if it's an UDF."""
955-
956 # Should be implemented in each OS if necessary
957+ raise NotImplementedError("Not implemented on this platform.")
958
959 def is_frozen(self):
960 """Checks if there's something frozen."""
961
962=== modified file 'ubuntuone/platform/filesystem_notifications/darwin.py'
963--- ubuntuone/platform/filesystem_notifications/darwin.py 2012-05-15 17:10:02 +0000
964+++ ubuntuone/platform/filesystem_notifications/darwin.py 2012-06-28 17:23:38 +0000
965@@ -28,6 +28,139 @@
966 # files in the program, then also delete it here.
967 """Filesystem Notifications module for MAC OS."""
968
969+import os
970+
971+import fsevents
972+from twisted.internet import defer, reactor
973+
974+from ubuntuone.platform.filesystem_notifications import common
975+
976+
977+# a map between the few events that we have on common platforms and those
978+# found in pyinotify
979+common.COMMON_ACTIONS = {
980+ fsevents.IN_CREATE: common.IN_CREATE,
981+ fsevents.IN_DELETE: common.IN_DELETE,
982+ fsevents.IN_MODIFY: common.IN_MODIFY,
983+ fsevents.IN_MOVED_FROM: common.IN_MOVED_FROM,
984+ fsevents.IN_MOVED_TO: common.IN_MOVED_TO,
985+}
986+
987+# a map of the actions to names so that we have better logs.
988+common.COMMON_ACTIONS_NAMES = {
989+ fsevents.IN_CREATE: 'IN_CREATE',
990+ fsevents.IN_DELETE: 'IN_DELETE',
991+ fsevents.IN_MODIFY: 'IN_MODIFY',
992+ fsevents.IN_MOVED_FROM: 'IN_MOVED_FROM',
993+ fsevents.IN_MOVED_TO: 'IN_MOVED_TO',
994+}
995+
996+
997+# The implementation of the code that is provided as the pyinotify substitute
998+class Watch(common.Watch):
999+ """Implement the same functions as pyinotify.Watch."""
1000+
1001+ def __init__(self, watch_descriptor, path, mask, auto_add, processor,
1002+ buf_size=8192):
1003+ super(Watch, self).__init__(watch_descriptor, path, mask, auto_add,
1004+ processor, buf_size)
1005+ # Create stream with folder to watch
1006+ self.stream = fsevents.Stream(self._process_events,
1007+ path, file_events=True)
1008+
1009+ def _process_events(self, event):
1010+ """Receive the filesystem event and move it to the main thread."""
1011+ reactor.callFromThread(self._process_events_in_main_thread, event)
1012+
1013+ def _process_events_in_main_thread(self, event):
1014+ """Process the events from the queue."""
1015+ # do not do it if we stop watching and the events are empty
1016+ if not self._watching:
1017+ return
1018+
1019+ action, cookie, file_name = (event.mask, event.cookie, event.name)
1020+ if any([file_name.startswith(path)
1021+ for path in self._ignore_paths]):
1022+ return
1023+ syncdaemon_path = os.path.join(self._path, file_name)
1024+ self._process_events_from_filesystem(action, file_name, cookie,
1025+ syncdaemon_path)
1026+
1027+ # For API compatibility
1028+ @property
1029+ def started(self):
1030+ """A deferred that will be called when the watch is running."""
1031+ return defer.succeed(self._watching)
1032+
1033+ @property
1034+ def stopped(self):
1035+ """A deferred fired when the watch thread has finished."""
1036+ return defer.succeed(self._watching)
1037+
1038+
1039+class WatchManager(common.WatchManager):
1040+ """Implement the same functions as pyinotify.WatchManager.
1041+
1042+ All paths passed to methods in this class should be darwin paths.
1043+
1044+ """
1045+
1046+ def __init__(self, processor):
1047+ """Init the manager to keep track of the different watches."""
1048+ super(WatchManager, self).__init__(processor)
1049+ self.observer = fsevents.Observer()
1050+ self.observer.start()
1051+
1052+ def add_watch(self, path, mask, auto_add=False, quiet=True):
1053+ """Add a new path to be watched.
1054+
1055+ The method will ensure that the path is not already present.
1056+ """
1057+ if not isinstance(path, str):
1058+ e = NotImplementedError("No implementation on this platform.")
1059+ return defer.fail(e)
1060+ wd = self.get_wd(path)
1061+ if wd is None:
1062+ self.log.debug('Adding single watch on %r', path)
1063+ return self._add_single_watch(path, mask, auto_add, quiet)
1064+ else:
1065+ self.log.debug('Watch already exists on %r', path)
1066+ return self._wdm[wd].started
1067+
1068+ def __del__(self):
1069+ """Stop the observer."""
1070+ self.observer.stop()
1071+
1072+ def stop(self):
1073+ """Close the manager and stop all watches."""
1074+ self.log.debug('Stopping watches.')
1075+ for current_wd in self._wdm:
1076+ self._wdm[current_wd].stop_watching()
1077+ self.observer.unschedule(self._wdm[current_wd].stream)
1078+ self.log.debug('Stopping Watch on %r.', self._wdm[current_wd].path)
1079+ self.observer.stop()
1080+
1081+ def del_watch(self, wd):
1082+ """Delete the watch with the given descriptor."""
1083+ watch = self.get_watch(wd)
1084+ self.observer.unschedule(watch.stream)
1085+ return super(WatchManager, self).del_watch(wd)
1086+
1087+ def _adding_watch(self, path, mask, auto_add):
1088+ """This method perform actually the action of registering the watch."""
1089+ watch = Watch(self._wd_count, path, mask, auto_add, self._processor)
1090+ self._wdm[self._wd_count] = watch
1091+ self._wdm[self._wd_count].start_watching()
1092+ self.observer.schedule(self._wdm[self._wd_count].stream)
1093+ self._wd_count += 1
1094+ return defer.succeed(True)
1095+
1096+ def rm_watch(self, wd, rec=False, quiet=True):
1097+ """Remove the the watch with the given wd."""
1098+ watch = self.get_watch(wd)
1099+ self.observer.unschedule(watch.stream)
1100+ super(WatchManager, self).del_watch(wd, rec, quiet)
1101+
1102
1103 class FilesystemMonitor(object):
1104 """Empty implementation of FilesystemMonitor"""
1105
1106=== modified file 'ubuntuone/platform/filesystem_notifications/windows.py'
1107--- ubuntuone/platform/filesystem_notifications/windows.py 2012-06-22 14:17:44 +0000
1108+++ ubuntuone/platform/filesystem_notifications/windows.py 2012-06-28 17:23:38 +0000
1109@@ -68,6 +68,25 @@
1110
1111 from ubuntuone.platform.filesystem_notifications import common
1112
1113+# a map between the few events that we have on common platforms and those
1114+# found in pyinotify
1115+common.COMMON_ACTIONS = {
1116+ 1: common.IN_CREATE,
1117+ 2: common.IN_DELETE,
1118+ 3: common.IN_MODIFY,
1119+ 4: common.IN_MOVED_FROM,
1120+ 5: common.IN_MOVED_TO,
1121+}
1122+
1123+# a map of the actions to names so that we have better logs.
1124+common.COMMON_ACTIONS_NAMES = {
1125+ 1: 'IN_CREATE',
1126+ 2: 'IN_DELETE',
1127+ 3: 'IN_MODIFY',
1128+ 4: 'IN_MOVED_FROM',
1129+ 5: 'IN_MOVED_TO',
1130+}
1131+
1132
1133 # constant found in the msdn documentation:
1134 # http://msdn.microsoft.com/en-us/library/ff538834(v=vs.85).aspx
1135@@ -113,40 +132,8 @@
1136 # makes the multiplatform better, linux was first :P
1137 syncdaemon_path = get_syncdaemon_valid_path(
1138 os.path.join(self._path, file_name))
1139- full_dir_path = os.path.join(self._path, file_name)
1140- is_dir = self._path_is_dir(full_dir_path)
1141- if is_dir:
1142- # we need to update the list of subdirs that we have
1143- self._update_subdirs(full_dir_path, action)
1144- mask = common.COMMON_ACTIONS[action]
1145- head, tail = os.path.split(file_name)
1146- if is_dir:
1147- mask |= common.IN_ISDIR
1148- event_raw_data = {
1149- 'wd': self._descriptor,
1150- 'dir': is_dir,
1151- 'mask': mask,
1152- 'name': tail,
1153- 'path': '.'}
1154- # by the way in which the win api fires the events we know for
1155- # sure that no move events will be added in the wrong order, this
1156- # is kind of hacky, I dont like it too much
1157- if common.COMMON_ACTIONS[action] == common.IN_MOVED_FROM:
1158- self._cookie = str(uuid4())
1159- self._source_pathname = tail
1160- event_raw_data['cookie'] = self._cookie
1161- if common.COMMON_ACTIONS[action] == common.IN_MOVED_TO:
1162- event_raw_data['src_pathname'] = self._source_pathname
1163- event_raw_data['cookie'] = self._cookie
1164- event = common.Event(event_raw_data)
1165- # FIXME: event deduces the pathname wrong and we need to manually
1166- # set it
1167- event.pathname = syncdaemon_path
1168- # add the event only if we do not have an exclude filter or
1169- # the exclude filter returns False, that is, the event will not
1170- # be excluded
1171- self.log.debug('Pushing event %r to processor.', event)
1172- self._processor(event)
1173+ self._process_events_from_filesystem(action, file_name,
1174+ str(uuid4()), syncdaemon_path)
1175
1176 def _call_deferred(self, f, *args):
1177 """Executes the deferred call avoiding possible race conditions."""
1178@@ -246,6 +233,23 @@
1179
1180 class WatchManager(common.WatchManager):
1181
1182+ @common.is_valid_os_path(path_indexes=[1])
1183+ def add_watch(self, path, mask, auto_add=False, quiet=True):
1184+ """Add a new path to be watch.
1185+
1186+ The method will ensure that the path is not already present.
1187+ """
1188+ if not isinstance(path, unicode):
1189+ e = NotImplementedError("No implementation on this platform.")
1190+ return defer.fail(e)
1191+ wd = self.get_wd(path)
1192+ if wd is None:
1193+ self.log.debug('Adding single watch on %r', path)
1194+ return self._add_single_watch(path, mask, auto_add, quiet)
1195+ else:
1196+ self.log.debug('Watch already exists on %r', path)
1197+ return self._wdm[wd].started
1198+
1199 def stop(self):
1200 """Close the manager and stop all watches."""
1201 self.log.debug('Stopping watches.')
1202
1203=== modified file 'ubuntuone/platform/os_helper/__init__.py'
1204--- ubuntuone/platform/os_helper/__init__.py 2012-05-15 13:25:37 +0000
1205+++ ubuntuone/platform/os_helper/__init__.py 2012-06-28 17:23:38 +0000
1206@@ -70,3 +70,9 @@
1207 is_root = source.is_root
1208 get_path_list = source.get_path_list
1209 normpath = source.normpath
1210+
1211+# Decorators
1212+
1213+is_valid_syncdaemon_path = source.is_valid_syncdaemon_path
1214+is_valid_os_path = source.is_valid_os_path
1215+os_path = source.os_path
1216
1217=== modified file 'ubuntuone/platform/os_helper/darwin.py'
1218--- ubuntuone/platform/os_helper/darwin.py 2012-06-22 13:54:58 +0000
1219+++ ubuntuone/platform/os_helper/darwin.py 2012-06-28 17:23:38 +0000
1220@@ -107,6 +107,33 @@
1221 """Set the name of the application."""
1222 # nothing to be done let the plist take care of it
1223
1224+
1225+# TODO: Implement this decorators to fix some encoding issues in darwin
1226+
1227+def is_valid_syncdaemon_path(path_indexes=None):
1228+ def decorator(func):
1229+ def wrapped(*args, **kwargs):
1230+ return func(*args, **kwargs)
1231+ return wrapped
1232+ return decorator
1233+
1234+
1235+def is_valid_os_path(path_indexes=None):
1236+ def decorator(func):
1237+ def wrapped(*args, **kwargs):
1238+ return func(*args, **kwargs)
1239+ return wrapped
1240+ return decorator
1241+
1242+
1243+def os_path(path_indexes=None):
1244+ def decorator(func):
1245+ def wrapped(*args, **kwargs):
1246+ return func(*args, **kwargs)
1247+ return wrapped
1248+ return decorator
1249+
1250+
1251 def get_syncdaemon_valid_path(path):
1252 """Get a 'darwin' path and modify it so that it can be used in sd."""
1253 return unicodedata.normalize('NFC', path.decode('utf-8')).encode('utf-8')
1254
1255=== modified file 'ubuntuone/platform/os_helper/linux.py'
1256--- ubuntuone/platform/os_helper/linux.py 2012-05-17 11:57:49 +0000
1257+++ ubuntuone/platform/os_helper/linux.py 2012-06-28 17:23:38 +0000
1258@@ -123,3 +123,6 @@
1259 is_root = unix.is_root
1260 get_path_list = unix.get_path_list
1261 normpath = unix.normpath
1262+is_valid_syncdaemon_path = None
1263+is_valid_os_path = None
1264+os_path = None
1265
1266=== modified file 'ubuntuone/platform/os_helper/windows.py'
1267--- ubuntuone/platform/os_helper/windows.py 2012-06-28 10:26:50 +0000
1268+++ ubuntuone/platform/os_helper/windows.py 2012-06-28 17:23:38 +0000
1269@@ -326,6 +326,8 @@
1270 """
1271 return _is_valid_path(assert_windows_path, path_indexes)
1272
1273+is_valid_os_path = is_valid_windows_path
1274+
1275
1276 def is_valid_syncdaemon_path(path_indexes=None):
1277 """Decorator to validate the parameters using assert_syncdaemon_path.
1278@@ -410,6 +412,8 @@
1279 """
1280 return _transform_path(get_windows_valid_path, path_indexes)
1281
1282+os_path = windowspath
1283+
1284
1285 def syncdamonpath(path_indexes=None):
1286 """Decorator to validate and transform path parameters.
1287@@ -885,7 +889,7 @@
1288
1289 def is_root():
1290 """Return if the user is running as root."""
1291- # Always return False. Trying to be smart about OS versions and
1292+ # Always return False. Trying to be smart about OS versions and
1293 # only calling Windows APIs under certain conditions has still
1294 # proven not to work in some cases. Overall it should not matter
1295 # if we know whether we are Administrator or not on Windows.

Subscribers

People subscribed via source and target branches