Merge lp:~mandel/ubuntuone-client/fix-os-helper into lp:ubuntuone-client
- fix-os-helper
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Natalia Bidart |
Proposed branch: | lp:~mandel/ubuntuone-client/fix-os-helper |
Merge into: | lp:ubuntuone-client |
Diff against target: |
998 lines (+346/-174) 5 files modified
tests/platform/windows/test_filesystem_notifications.py (+3/-4) tests/platform/windows/test_os_helper.py (+40/-15) ubuntuone/platform/windows/filesystem_notifications.py (+56/-45) ubuntuone/platform/windows/os_helper.py (+246/-108) ubuntuone/platform/windows/vm_helper.py (+1/-2) |
To merge this branch: | bzr merge lp:~mandel/ubuntuone-client/fix-os-helper |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alejandro J. Cura (community) | Needs Fixing | ||
Roberto Alsina (community) | Needs Fixing | ||
Natalia Bidart (community) | Needs Fixing | ||
Review via email: mp+68530@code.launchpad.net |
Commit message
Ensure that the os_helper layer handles encodings correctly and that does not leak platform details to the rest of the client (that includes not using literal paths).
Description of the change
Ensure that the os_helper layer handles encodings correctly and that does not leak platform details to the rest of the client (that includes not using literal paths).
- 1067. By Manuel de la Peña
-
Added decorators to ensure that the correct encoding is used.
Natalia Bidart (nataliabidart) wrote : | # |
* If you plan to use _get_windows_
* If I understand this test correctly, it makes no sense:
def test_restore_
"""Test that the path does not have utf chars."""
path = self.testfile + ''.join(
path = restore_
it should be something like (please note the docstring was corrected):
def test_restore_
"""The illegal unicode chars are replaced by characters, valid in linux."""
path = remove_
assert not any(c in path for c in LINUX_ILLEGAL_
path = restore_
and you need to add an analogous test for remove_
def test_remove_
"""The illegal chars are replaced by unicode objects, valid on windows."""
path = remove_
assert not any(c in path for c in WINDOWS_
path = restore_
* as per out chat this morning, get_syncdaemon_
275 + 'path': head.replace(
297 + full_filename = get_syncdaemon_
* you added this comment:
284 + # assert that all the paths are byte and are not literals
but nothing is actually asserting anything.
* are you sure we need the decorator "@windowspath(
* typos in:
# literal path, that is not \\?\ prefix in the pat. This is ubber important,
# in case of a doubt ask mandel or nessita
"pat", "ubber"
* please replace:
568 + assert isinstance(path, str), 'All paths returned to sd should be bytes'
by a call to _assert_
578 +def _valiate_
579 +"""Ensure that the collection returned from os helper is valid."""
580 + for path in col:
581 + _assert_
582 + return col
will mutate to:
578 +def _valiate_
579 +"""Ensure that the collection returned from os helper is valid."""
580 + for path in col:
581 + _assert_
Manuel de la Peña (mandel) wrote : | # |
> * If you plan to use _get_windows_
> it with an initial underscore. Things named _something are not meant to be
> used outside the module that defines them.
>
I was hoping to be allowed to use _ so that it was explicit that you should not be using it unless you really really know what you are doing, and I seriously hope I know what I'm doing in this code ;)
I nevertheless can remove it.
> * If I understand this test correctly, it makes no sense:
>
> def test_restore_
> """Test that the path does not have utf chars."""
> path = self.testfile + ''.join(
> path = restore_
> self.assertFals
What is wrong with the test? it is making sure that the method gets a path with all the utf8 chars in it and returns a path with only illegal chars. The assert ensures that once there are no utf8 chars in the returned path.
>
> it should be something like (please note the docstring was corrected):
>
> def test_restore_
> """The illegal unicode chars are replaced by characters, valid in
> linux."""
> original_path = self.testfile + ''.join(
> path = remove_
> assert not any(c in path for c in LINUX_ILLEGAL_
>
> path = restore_
> self.assertEqua
>
I do see the confusion is possible because the name of the test is wrong, it should be test_remove_
From my point of view tests should have a single concern, in this case would be to remove the illegal chars and we would later add a test to restore them. I do prefer your docstring.
> and you need to add an analogous test for remove_
>
> def test_remove_
> """The illegal chars are replaced by unicode objects, valid on
> windows."""
> original_path = self.testfile + ''.join(
> path = remove_
> assert not any(c in path for c in WINDOWS_
>
> path = restore_
> self.assertEqua
>
Same as the previous comment, I would add a test for restore_
> * as per out chat this morning, get_syncdaemon_
> disappear. Why is still being used in filesystem_
>
> 275 + 'path': head.replace(
> 297 + full_filename = get_syncdaemon_
Manuel de la Peña (mandel) wrote : | # |
> > * If you plan to use _get_windows_
> > it with an initial underscore. Things named _something are not meant to be
> > used outside the module that defines them.
> >
>
> I was hoping to be allowed to use _ so that it was explicit that you should
> not be using it unless you really really know what you are doing, and I
> seriously hope I know what I'm doing in this code ;)
>
> I nevertheless can remove it.
>
> > * If I understand this test correctly, it makes no sense:
> >
> > def test_restore_
> > """Test that the path does not have utf chars."""
> > path = self.testfile + ''.join(
> > path = restore_
> > self.assertFals
>
> What is wrong with the test? it is making sure that the method gets a path
> with all the utf8 chars in it and returns a path with only illegal chars. The
> assert ensures that once there are no utf8 chars in the returned path.
>
> >
> > it should be something like (please note the docstring was corrected):
> >
> > def test_restore_
> > """The illegal unicode chars are replaced by characters, valid in
> > linux."""
> > original_path = self.testfile + ''.join(
> > path = remove_
> > assert not any(c in path for c in LINUX_ILLEGAL_
> >
> > path = restore_
> > self.assertEqua
> >
>
> I do see the confusion is possible because the name of the test is wrong, it
> should be test_remove_
> search/replace when renaming methods. On the other hand I do not like your
I correct myself, the names is perfectly OK.
> test because it is testing more than one thing, here you are taking a path
> with illegal chars, asserting that they are not present and getting back the
> original. If this test fails we would need to look at two different methods,
> either remove_
> restore_
>
> From my point of view tests should have a single concern, in this case would
> be to remove the illegal chars and we would later add a test to restore them.
> I do prefer your docstring.
>
> > and you need to add an analogous test for remove_
> >
> > def test_remove_
> > """The illegal chars are replaced by unicode objects, valid on
> > windows."""
> > original_path = self.testfile + ''.join(
> > path = remove_
> > assert not any(c in path for c in WINDOWS_
> >
> > path = restore_
> > self.assertEqua
> >
>
> Same as the previous comment, I would add a test for restore_
> that simply asserts that no illegal chars are present.
>
> > * as per out chat this morning, get_syncdaemon_
> > disappear. Why is...
- 1068. By Manuel de la Peña
-
Renamed _get_windows_
valid_path to get_windows_ valid_path. - 1069. By Manuel de la Peña
-
Added test for remove_
illegal_ chars. Updated docstrings. - 1070. By Manuel de la Peña
-
Improved the implementation of the decorators and assertions.
Natalia Bidart (nataliabidart) wrote : | # |
> > * If you plan to use _get_windows_
> > it with an initial underscore. Things named _something are not meant to be
> > used outside the module that defines them.
>
> I was hoping to be allowed to use _ so that it was explicit that you should
> not be using it unless you really really know what you are doing, and I
> seriously hope I know what I'm doing in this code ;)
We are not allowed to use methods starting with _ outside a module, only exception is when writing tests about that module.
If you need people to be careful using a method, add a huge docstring to it stating why is dangerous and what precautions we need to have when using it.
> > * If I understand this test correctly, it makes no sense:
> >
> > def test_restore_
> > """Test that the path does not have utf chars."""
> > path = self.testfile + ''.join(
> > path = restore_
> > self.assertFals
>
> What is wrong with the test? it is making sure that the method gets a path
> with all the utf8 chars in it and returns a path with only illegal chars. The
> assert ensures that once there are no utf8 chars in the returned path.
What is wrong is that you're not testing the key part of restore_
> > it should be something like (please note the docstring was corrected):
> >
> > def test_restore_
> > """The illegal unicode chars are replaced by characters, valid in
> > linux."""
> > original_path = self.testfile + ''.join(
> > path = remove_
> > assert not any(c in path for c in LINUX_ILLEGAL_
> >
> > path = restore_
> > self.assertEqua
> >
>
> I do see the confusion is possible because the name of the test is wrong, it
> should be test_remove_
> search/replace when renaming methods. On the other hand I do not like your
> test because it is testing more than one thing, here you are taking a path
Is not testing more that one thing. Please note that using the builtin "assert" is not testing something, but being explicit about an assumption that is being made while preparing the variables to perform the actual test, which is the call to assertEqual.
> original. If this test fails we would need to look at two different methods,
We will have either an Error or Failure, depending if the assert failed or the assertEqual, respectively.
If we get an Error, we know some of our assumptions were wrong, and we need to either reconsider our test, or see what thing we trusted that is not behaving as expected.
This is a common strategy when writing tests, you need to as...
Manuel de la Peña (mandel) wrote : | # |
> > > * If you plan to use _get_windows_
> name
> > > it with an initial underscore. Things named _something are not meant to be
> > > used outside the module that defines them.
> >
> > I was hoping to be allowed to use _ so that it was explicit that you should
> > not be using it unless you really really know what you are doing, and I
> > seriously hope I know what I'm doing in this code ;)
>
> We are not allowed to use methods starting with _ outside a module, only
> exception is when writing tests about that module.
> If you need people to be careful using a method, add a huge docstring to it
> stating why is dangerous and what precautions we need to have when using it.
No worries, in this new version the name has changed.
>
> > > * If I understand this test correctly, it makes no sense:
> > >
> > > def test_restore_
> > > """Test that the path does not have utf chars."""
> > > path = self.testfile + ''.join(
> > > path = restore_
> > > self.assertFals
> >
> > What is wrong with the test? it is making sure that the method gets a path
> > with all the utf8 chars in it and returns a path with only illegal chars.
> The
> > assert ensures that once there are no utf8 chars in the returned path.
>
> What is wrong is that you're not testing the key part of restore_
> which is the following: not only some chars are removed, but also the proper
> replacements is being made, which is needed to be able to ensure that we can
> obtain back the original path. See below for a trivial implementation of
> restore_
> horribly.
>
> > > it should be something like (please note the docstring was corrected):
> > >
> > > def test_restore_
> > > """The illegal unicode chars are replaced by characters, valid in
> > > linux."""
> > > original_path = self.testfile + ''.join(
> > > path = remove_
> > > assert not any(c in path for c in LINUX_ILLEGAL_
> > >
> > > path = restore_
> > > self.assertEqua
> > >
> >
> > I do see the confusion is possible because the name of the test is wrong, it
> > should be test_remove_
> > search/replace when renaming methods. On the other hand I do not like your
> > test because it is testing more than one thing, here you are taking a path
>
> Is not testing more that one thing. Please note that using the builtin
> "assert" is not testing something, but being explicit about an assumption that
> is being made while preparing the variables to perform the actual test, which
> is the call to assertEqual.
>
> > original. If this test fails we would need to look at two different methods,
>
> We will have either an Error or Failure, depending if the assert failed or the
> assertEqual, respectively.
> If we get an Error, we know some of our assumptions wer...
- 1071. By Manuel de la Peña
-
Merged with trunk.
- 1072. By Manuel de la Peña
-
Fixed issues created during the merge with trunk.
- 1073. By Manuel de la Peña
-
Added tests implementation as per requested in the reviews.
- 1075. By Manuel de la Peña
-
Fixed screwed up test.
Roberto Alsina (ralsina) wrote : | # |
In line 84, I would use u'test' to be explicit original_path is unicode
In line 86, we are comparing a bytes string (path) against unicode strings (the keys in LINUX_ILLEGAL_
In line 99, we are comparing a unicode string (path) against strings (the keys in WINDOWS_
Those are not well-defined operations, AFAIK, and will tend to give no matches, which means the tests will tend to pass.
I would also add asserts to check that in both those tests path is the right type. Automatic promotion to unicode is a real risk.
Alejandro J. Cura (alecu) wrote : | # |
There's no member self.path in class Watch, so the following line probably means self._path:
191 + 'path': head.replace(
--
Why did we separate assert_
--
The following bit makes no sense in get_windows_
if path.startswith
path = path.replace(
perhaps it should be an assertion like "assert not path.startswith
--
We should never check for: "LONG_PATH_PREFIX in path", we should use "path.startswit
(for instance, it's used in _contains_chars, _modify_chars)
--
typos: litdir, unfortunatly
--
All the times we are using windowspath, we use assert_
we should only be using windowspath, and have it call assert_
The same applies to the proliferation of decorator pairs that are present throughout the code.
--
I understand that vm_helper.
Unmerged revisions
Preview Diff
1 | === modified file 'tests/platform/windows/test_filesystem_notifications.py' |
2 | --- tests/platform/windows/test_filesystem_notifications.py 2011-06-23 22:17:13 +0000 |
3 | +++ tests/platform/windows/test_filesystem_notifications.py 2011-07-21 16:16:14 +0000 |
4 | @@ -23,8 +23,8 @@ |
5 | from mocker import MockerTestCase, ANY |
6 | |
7 | from contrib.testing.testcase import BaseTwistedTestCase |
8 | +from ubuntuone.platform.windows.os_helper import get_windows_valid_path |
9 | from ubuntuone.platform.windows.pyinotify import ProcessEvent |
10 | -from ubuntuone.platform.windows.os_helper import LONG_PATH_PREFIX |
11 | from ubuntuone.platform.windows.filesystem_notifications import ( |
12 | WatchManager, |
13 | Notifier, |
14 | @@ -60,8 +60,7 @@ |
15 | def setUp(self): |
16 | """Set infor for the tests.""" |
17 | super(TestWatch, self).setUp() |
18 | - self.basedir = LONG_PATH_PREFIX + os.path.abspath( |
19 | - self.mktemp('test_root')) |
20 | + self.basedir = os.path.abspath(self.mktemp('test_root')) |
21 | self.mask = FILE_NOTIFY_CHANGE_FILE_NAME | \ |
22 | FILE_NOTIFY_CHANGE_DIR_NAME | \ |
23 | FILE_NOTIFY_CHANGE_ATTRIBUTES | \ |
24 | @@ -443,7 +442,7 @@ |
25 | exclude_filter = lambda x: False |
26 | proc_fun = lambda x: None |
27 | # expect the factory to be called with the passed values |
28 | - self.watch_factory(wd, path, mask, auto_add, |
29 | + self.watch_factory(wd, get_windows_valid_path(os.path.abspath(path)), mask, auto_add, |
30 | events_queue=events_queue, exclude_filter=exclude_filter, |
31 | proc_fun=proc_fun) |
32 | self.mocker.result(self.watch) |
33 | |
34 | === modified file 'tests/platform/windows/test_os_helper.py' |
35 | --- tests/platform/windows/test_os_helper.py 2011-07-21 13:40:32 +0000 |
36 | +++ tests/platform/windows/test_os_helper.py 2011-07-21 16:16:14 +0000 |
37 | @@ -30,7 +30,6 @@ |
38 | _set_file_attributes, |
39 | access, |
40 | can_write, |
41 | - is_illegal_path, |
42 | listdir, |
43 | make_dir, |
44 | normpath, |
45 | @@ -47,6 +46,7 @@ |
46 | EVERYONE_GROUP, |
47 | LONG_PATH_PREFIX, |
48 | WINDOWS_ILLEGAL_CHARS_MAP, |
49 | + LINUX_ILLEGAL_CHARS_MAP, |
50 | ) |
51 | from contrib.testing.testcase import BaseTwistedTestCase |
52 | |
53 | @@ -179,11 +179,15 @@ |
54 | """Return a list of the files in a dir.""" |
55 | open(os.path.join(self.basedir, 'foo'), 'w').close() |
56 | open(os.path.join(self.basedir, 'bar'), 'w').close() |
57 | + # create a path with illegal chars that is written in the fs with |
58 | + # the legal ones |
59 | + test_file = 'test_file' + ''.join(WINDOWS_ILLEGAL_CHARS_MAP) |
60 | + full_test_file = os.path.join(self.basedir, test_file) |
61 | + open(replace_illegal_chars_with_unicode(full_test_file), 'w').close() |
62 | l = listdir(self.basedir) |
63 | - test_file = u'test_file\u200b\u2033\u200b\u200b\u203d\u200b\u200b' +\ |
64 | - u'\u26b9\u200b\u200b\u2223\u200b\u200b\u2236\u200b\u200b' +\ |
65 | - u'\u2039\u200b\u200b\u2044\u200b\u200b\u203a\u200b' |
66 | self.assertEqual(sorted(l), ['bar', 'foo', test_file]) |
67 | + for path in l: |
68 | + self.assertIsInstance(path, type(self.basedir)) |
69 | |
70 | def test_access_rw(self): |
71 | """Test access on a file with full permission.""" |
72 | @@ -234,9 +238,30 @@ |
73 | self.assertFalse(path_exists(os.path.join(self.basedir, 'subdir'))) |
74 | |
75 | def test_replace_unicode_with_illegal_chars(self): |
76 | - """Test that the path does not have utf chars.""" |
77 | - path = replace_unicode_with_illegal_chars(replace_illegal_chars_with_unicode(self.testfile)) |
78 | - self.assertFalse(is_illegal_path(path)) |
79 | + """Test the mangling of the paths. |
80 | + |
81 | + We ensure that the illegal unicode chars are replaced by |
82 | + characters valid in linux. |
83 | + """ |
84 | + original_path = 'test'.join(LINUX_ILLEGAL_CHARS_MAP) |
85 | + path = replace_unicode_with_illegal_chars(original_path) |
86 | + assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP) |
87 | + |
88 | + path = replace_illegal_chars_with_unicode(path) |
89 | + self.assertEqual(original_path, path) |
90 | + |
91 | + def test_replace_illegal_chars_with_unicode(self): |
92 | + """Test the mangling of the paths. |
93 | + |
94 | + We ensure that the illegal chars are replaced by unicode |
95 | + characters valid in windows. |
96 | + """ |
97 | + original_path = 'test'.join(WINDOWS_ILLEGAL_CHARS_MAP) |
98 | + path = replace_illegal_chars_with_unicode(original_path) |
99 | + assert not any(c in path for c in WINDOWS_ILLEGAL_CHARS_MAP) |
100 | + |
101 | + path = replace_unicode_with_illegal_chars(path) |
102 | + self.assertEqual(original_path, path) |
103 | |
104 | |
105 | class TestSpecialOSCalls(BaseTwistedTestCase): |
106 | @@ -244,14 +269,14 @@ |
107 | |
108 | def test_normpath_with_longprefix(self): |
109 | """Ensure that the normpath is correct when it uses the long prefix""" |
110 | - paths = [os.path.join('A','B'), |
111 | - os.path.join('A', 'B') + os.path.sep, |
112 | - os.path.join('A', '.', 'B'), |
113 | - os.path.join('A', 'foo', '..', 'B')] |
114 | + paths = [os.path.join('A','B?'), |
115 | + os.path.join('A', 'B?') + os.path.sep, |
116 | + os.path.join('A:C', '.', 'B?'), |
117 | + os.path.join('A', 'foo', '..', 'B?')] |
118 | for current_path in paths: |
119 | - long_path = LONG_PATH_PREFIX + current_path |
120 | - self.assertEqual(LONG_PATH_PREFIX + os.path.normpath(current_path), |
121 | - normpath(long_path)) |
122 | + self.assertEqual(os.path.normpath(current_path), |
123 | + normpath(current_path)) |
124 | + self.assertFalse(LONG_PATH_PREFIX in current_path) |
125 | |
126 | |
127 | class TestAccess(BaseTwistedTestCase): |
128 | @@ -400,4 +425,4 @@ |
129 | groups = {} |
130 | groups[EVERYONE_GROUP] = FILE_ALL_ACCESS |
131 | _set_file_attributes(self.testfile, groups) |
132 | - self.assertTrue(can_write(self.testfile)) |
133 | \ No newline at end of file |
134 | + self.assertTrue(can_write(self.testfile)) |
135 | |
136 | === modified file 'ubuntuone/platform/windows/filesystem_notifications.py' |
137 | --- ubuntuone/platform/windows/filesystem_notifications.py 2011-07-21 13:40:32 +0000 |
138 | +++ ubuntuone/platform/windows/filesystem_notifications.py 2011-07-21 16:16:14 +0000 |
139 | @@ -66,12 +66,15 @@ |
140 | from ubuntuone.syncdaemon.filesystem_notifications import ( |
141 | GeneralINotifyProcessor) |
142 | from ubuntuone.platform.windows.os_helper import ( |
143 | - LONG_PATH_PREFIX, |
144 | - listdir, |
145 | + assert_path_is_bytes, |
146 | + assert_path_is_not_literal, |
147 | + get_windows_valid_path, |
148 | + get_syncdaemon_valid_path, |
149 | replace_unicode_with_illegal_chars, |
150 | + windowspath, |
151 | ) |
152 | from ubuntuone import logger |
153 | -from ubuntuone.platform.windows.os_helper import longpath |
154 | + |
155 | # our logging level |
156 | TRACE = logger.TRACE |
157 | |
158 | @@ -138,14 +141,14 @@ |
159 | self._proc_fun = proc_fun |
160 | self._cookie = None |
161 | self._source_pathname = None |
162 | + self._watch_thread = None |
163 | + self._process_thread = None |
164 | # remember the subdirs we have so that when we have a delete we can |
165 | # check if it was a remove |
166 | self._subdirs = [] |
167 | # ensure that we work with an abspath and that we can deal with |
168 | # long paths over 260 chars. |
169 | self._path = os.path.abspath(path) |
170 | - if not self._path.startswith(LONG_PATH_PREFIX): |
171 | - self._path = LONG_PATH_PREFIX + self._path |
172 | self._mask = mask |
173 | # lets make the q as big as possible |
174 | self._raw_events_queue = Queue() |
175 | @@ -153,9 +156,9 @@ |
176 | events_queue = Queue() |
177 | self.events_queue = events_queue |
178 | |
179 | - @longpath(paths_indexes=[1]) |
180 | def _path_is_dir(self, path): |
181 | """Check if the path is a dir and update the local subdir list.""" |
182 | + path = replace_unicode_with_illegal_chars(path) |
183 | self.log.debug('Testing if path "%s" is a dir', path) |
184 | is_dir = False |
185 | if os.path.exists(path): |
186 | @@ -209,7 +212,8 @@ |
187 | 'dir': is_dir, |
188 | 'mask': mask, |
189 | 'name': tail, |
190 | - 'path': head.replace(self.path, '.')} |
191 | + 'path': head.replace(get_syncdaemon_valid_path(self.path), |
192 | + '.')} |
193 | # by the way in which the win api fires the events we know for |
194 | # sure that no move events will be added in the wrong order, this |
195 | # is kind of hacky, I dont like it too much |
196 | @@ -281,17 +285,18 @@ |
197 | self.log.debug('Events from ReadDirectoryChangesW are %s', results) |
198 | # add the diff events to the q so that the can be processed no |
199 | # matter the speed. |
200 | - for action, file in results: |
201 | - full_filename = replace_unicode_with_illegal_chars(os.path.join(self._path, |
202 | - file)) |
203 | + for action, file_name in results: |
204 | + file_name = os.path.join(self._path, file_name) |
205 | + full_filename = get_syncdaemon_valid_path(file_name) |
206 | + # the COM api returns unicode files which although later in |
207 | + # the future is something we want atm we are not interested. |
208 | self._raw_events_queue.put((full_filename, action)) |
209 | self.log.debug('Added %s to raw events queue.', |
210 | (full_filename, action)) |
211 | |
212 | def start_watching(self): |
213 | """Tell the watch to start processing events.""" |
214 | - # get the diff dirs in the path |
215 | - for current_child in listdir(self._path): |
216 | + for current_child in os.listdir(self._path): |
217 | full_child_path = os.path.join(self._path, current_child) |
218 | if os.path.isdir(full_child_path): |
219 | self._subdirs.append(full_child_path) |
220 | @@ -313,14 +318,16 @@ |
221 | CloseHandle(self._handle) |
222 | self._watching = False |
223 | # ensure that we have no dangling threads |
224 | - self._watch_thread.join(5) |
225 | - if self._watch_thread.isAlive(): |
226 | - self.log.critical('Failed to stop the watch thread for %s!', |
227 | - self._path) |
228 | - self._process_thread.join(5) |
229 | - if self._process_thread.isAlive(): |
230 | - self.log.critical('Failed to stop the process thread for %s!', |
231 | - self._path) |
232 | + if self._watch_thread: |
233 | + self._watch_thread.join(5) |
234 | + if self._watch_thread.isAlive(): |
235 | + self.log.critical('Failed to stop the watch thread for %s!', |
236 | + self._path) |
237 | + if self._process_thread: |
238 | + self._process_thread.join(5) |
239 | + if self._process_thread.isAlive(): |
240 | + self.log.critical('Failed to stop the process thread for %s!', |
241 | + self._path) |
242 | self._subdirs = [] |
243 | |
244 | def update(self, mask, proc_fun=None, auto_add=False): |
245 | @@ -381,9 +388,11 @@ |
246 | except KeyError, e: |
247 | logging.error(str(e)) |
248 | |
249 | - @longpath(paths_indexes=[1]) |
250 | + @assert_path_is_bytes(paths_indexes=[1]) |
251 | + @assert_path_is_not_literal(paths_indexes=[1]) |
252 | def _add_single_watch(self, path, mask, proc_fun=None, auto_add=False, |
253 | quiet=True, exclude_filter=None): |
254 | + path = get_windows_valid_path(os.path.abspath(path)) |
255 | if path in self._ignored_paths: |
256 | # simply removed it from the filter |
257 | self._ignored_paths.remove(path) |
258 | @@ -432,7 +441,9 @@ |
259 | if not quiet: |
260 | raise WatchManagerError('Watch %s was not found' % wd, {}) |
261 | |
262 | - @longpath(paths_indexes=[1]) |
263 | + @assert_path_is_bytes(paths_indexes=[1]) |
264 | + @assert_path_is_not_literal(paths_indexes=[1]) |
265 | + @windowspath(paths_indexes=[1]) |
266 | def get_wd(self, path): |
267 | """Return the watcher that is used to watch the given path.""" |
268 | for current_wd in self._wdm: |
269 | @@ -459,7 +470,8 @@ |
270 | if not quiet: |
271 | raise WatchManagerError('Watch %s was not found' % wd, {}) |
272 | |
273 | - @longpath(paths_indexes=[1]) |
274 | + @assert_path_is_bytes(paths_indexes=[1]) |
275 | + @assert_path_is_not_literal(paths_indexes=[1]) |
276 | def rm_path(self, path): |
277 | """Remove a watch to the given path.""" |
278 | wd = self.get_wd(path) |
279 | @@ -563,6 +575,10 @@ |
280 | """Stop processing events and the watch manager.""" |
281 | self._watch_manager.stop() |
282 | |
283 | +# Interfaces that will be exposed to syncdaemon, this means that all passed |
284 | +# and returned paths from this point on will have to be in BYTES and not a |
285 | +# literal path, that is not \\?\ prefix in the path. This is uber important, |
286 | +# in case of a doubt ask mandel or nessita |
287 | |
288 | class NotifyProcessor(ProcessEvent): |
289 | """Processor that takes care of dealing with the events.""" |
290 | @@ -581,7 +597,8 @@ |
291 | """Add an event and path(s) to the mute filter.""" |
292 | self.general_processor.add_to_mute_filter(event, paths) |
293 | |
294 | - @longpath(paths_indexes=[1]) |
295 | + @assert_path_is_bytes(paths_indexes=[1]) |
296 | + @assert_path_is_not_literal(paths_indexes=[1]) |
297 | def platform_is_ignored(self, path): |
298 | """Should we ignore this path in the current platform.?""" |
299 | # don't support links yet |
300 | @@ -589,7 +606,8 @@ |
301 | return True |
302 | return False |
303 | |
304 | - @longpath(paths_indexes=[1]) |
305 | + @assert_path_is_bytes(paths_indexes=[1]) |
306 | + @assert_path_is_not_literal(paths_indexes=[1]) |
307 | def is_ignored(self, path): |
308 | """Should we ignore this path?""" |
309 | return self.general_processor.is_ignored(path) |
310 | @@ -699,7 +717,8 @@ |
311 | self.release_held_event() |
312 | self.general_processor.push_event(event) |
313 | |
314 | - @longpath(paths_indexes=[1]) |
315 | + @assert_path_is_bytes(paths_indexes=[1]) |
316 | + @assert_path_is_not_literal(paths_indexes=[1]) |
317 | def handle_dir_delete(self, fullpath): |
318 | """Some special work when a directory is deleted.""" |
319 | # remove the watch on that dir from our structures |
320 | @@ -719,7 +738,8 @@ |
321 | else: |
322 | self.general_processor.eq_push('FS_FILE_DELETE', path=path) |
323 | |
324 | - @longpath(paths_indexes=[1]) |
325 | + @assert_path_is_bytes(paths_indexes=[1]) |
326 | + @assert_path_is_not_literal(paths_indexes=[1]) |
327 | def freeze_begin(self, path): |
328 | """Puts in hold all the events for this path.""" |
329 | self.general_processor.freeze_begin(path) |
330 | @@ -771,26 +791,12 @@ |
331 | self._notifier = Notifier(self._watch_manager, self._processor) |
332 | self._process_task = self._hook_inotify_to_twisted() |
333 | |
334 | - def _fix_mute_filter_info(self, info): |
335 | - # we always make sure that the long path prefix is present so |
336 | - # that we can deal with the paths |
337 | - if 'path' in info and not info['path'].startswith(LONG_PATH_PREFIX): |
338 | - info['path'] = LONG_PATH_PREFIX + info['path'] |
339 | - if 'path_to' in info and \ |
340 | - not info['path_to'].startswith(LONG_PATH_PREFIX): |
341 | - info['path_to'] = LONG_PATH_PREFIX + info['path_to'] |
342 | - if 'path_from' in info and \ |
343 | - not info['path_from'].startswith(LONG_PATH_PREFIX): |
344 | - info['path_from'] = LONG_PATH_PREFIX + info['path_from'] |
345 | - |
346 | def add_to_mute_filter(self, event, **info): |
347 | """Add info to mute filter in the processor.""" |
348 | - self._fix_mute_filter_info(info) |
349 | self._processor.add_to_mute_filter(event, info) |
350 | |
351 | def rm_from_mute_filter(self, event, **info): |
352 | """Remove info to mute filter in the processor.""" |
353 | - self._fix_mute_filter_info(info) |
354 | self._processor.rm_from_mute_filter(event, info) |
355 | |
356 | def _hook_inotify_to_twisted(self): |
357 | @@ -813,13 +819,15 @@ |
358 | self._notifier.stop() |
359 | self._process_task.stop() |
360 | |
361 | - @longpath(paths_indexes=[1]) |
362 | + @assert_path_is_bytes(paths_indexes=[1]) |
363 | + @assert_path_is_not_literal(paths_indexes=[1]) |
364 | def rm_watch(self, dirpath): |
365 | """Remove watch from a dir.""" |
366 | # trust the implementation of the manager |
367 | self._watch_manager.rm_path(dirpath) |
368 | |
369 | - @longpath(paths_indexes=[1]) |
370 | + @assert_path_is_bytes(paths_indexes=[1]) |
371 | + @assert_path_is_not_literal(paths_indexes=[1]) |
372 | def add_watch(self, dirpath): |
373 | """Add watch to a dir.""" |
374 | # we try to get a watch descriptor, it it exists it means that |
375 | @@ -830,7 +838,9 @@ |
376 | self._watch_manager.add_watch(dirpath, FILESYSTEM_MONITOR_MASK, |
377 | auto_add=True) |
378 | |
379 | - @longpath(paths_indexes=[1]) |
380 | + @assert_path_is_bytes(paths_indexes=[1]) |
381 | + @assert_path_is_not_literal(paths_indexes=[1]) |
382 | + @windowspath(paths_indexes=[1]) |
383 | def has_watch(self, dirpath): |
384 | """Check if a dirpath is watched.""" |
385 | wd = self._watch_manager.get_wd(dirpath) |
386 | @@ -840,7 +850,8 @@ |
387 | """Checks if there's something frozen.""" |
388 | return self._processor.frozen_path is not None |
389 | |
390 | - @longpath(paths_indexes=[1]) |
391 | + @assert_path_is_bytes(paths_indexes=[1]) |
392 | + @assert_path_is_not_literal(paths_indexes=[1]) |
393 | def freeze_begin(self, path): |
394 | """Puts in hold all the events for this path.""" |
395 | if self._processor.frozen_path is not None: |
396 | |
397 | === modified file 'ubuntuone/platform/windows/os_helper.py' |
398 | --- ubuntuone/platform/windows/os_helper.py 2011-07-21 13:40:32 +0000 |
399 | +++ ubuntuone/platform/windows/os_helper.py 2011-07-21 16:16:14 +0000 |
400 | @@ -19,6 +19,7 @@ |
401 | """Windows tools to interact with the os.""" |
402 | |
403 | import logging |
404 | +import errno |
405 | import os |
406 | import sys |
407 | import stat |
408 | @@ -76,17 +77,10 @@ |
409 | '?':u'\N{ZERO WIDTH SPACE}\N{INTERROBANG}\N{ZERO WIDTH SPACE}', |
410 | '*':u'\N{ZERO WIDTH SPACE}\N{SEXTILE}\N{ZERO WIDTH SPACE}' |
411 | } |
412 | -LINUX_ILLEGAL_CHARS_MAP = { |
413 | - u'\N{ZERO WIDTH SPACE}\N{SINGLE LEFT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}':'<', |
414 | - u'\N{ZERO WIDTH SPACE}\N{SINGLE RIGHT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}':'>', |
415 | - u'\N{ZERO WIDTH SPACE}\N{RATIO}\N{ZERO WIDTH SPACE}':':', |
416 | - u'\N{ZERO WIDTH SPACE}\N{DOUBLE PRIME}\N{ZERO WIDTH SPACE}':'"', |
417 | - u'\N{ZERO WIDTH SPACE}\N{FRACTION SLASH}\N{ZERO WIDTH SPACE}':'/', |
418 | - u'\N{ZERO WIDTH SPACE}\N{SET MINUS}\N{ZERO WIDTH SPACE}':'\\', |
419 | - u'\N{ZERO WIDTH SPACE}\N{DIVIDES}\N{ZERO WIDTH SPACE}':'|', |
420 | - u'\N{ZERO WIDTH SPACE}\N{INTERROBANG}\N{ZERO WIDTH SPACE}':'?', |
421 | - u'\N{ZERO WIDTH SPACE}\N{SEXTILE}\N{ZERO WIDTH SPACE}':'*' |
422 | -} |
423 | +# inverse map |
424 | +LINUX_ILLEGAL_CHARS_MAP = {} |
425 | +for key, value in WINDOWS_ILLEGAL_CHARS_MAP.iteritems(): |
426 | + LINUX_ILLEGAL_CHARS_MAP[value] = key |
427 | |
428 | |
429 | def _int_to_bin(n): |
430 | @@ -94,12 +88,69 @@ |
431 | return "".join([str((n >> y) & 1) for y in range(32 - 1, -1, -1)]) |
432 | |
433 | |
434 | -def _set_as_long_path(path): |
435 | - """Allows to access files longer than 260 chars.""" |
436 | - if path and not path.startswith(LONG_PATH_PREFIX): |
437 | - # return the path with the required prefix added |
438 | - return LONG_PATH_PREFIX + path |
439 | - return path |
440 | +def get_windows_valid_path(path): |
441 | + """Get a path and modify it so that it can be used. |
442 | + |
443 | + On Windows there are a number of things we have to deal with to |
444 | + ensure that we can sync files with Linux. This method takes care of the |
445 | + following changes: |
446 | + |
447 | + 1. Remove illegal chars: There are a number of illegal chars on windows |
448 | + that are allowed on Linux. The method removes those illegal chars and |
449 | + replaces them with unicode chars that look alike. |
450 | + 2. Long paths: There is a limit of 255 chars per path on windows. This is |
451 | + solved by using literal paths (prepending \\?\ to the absolute path). |
452 | + """ |
453 | + # we get the path in bytes encoded in utf-8 lets build the unicode |
454 | + path = path.decode('utf8', 'error') |
455 | + # lets remove the long path prefix if present since it has an illegal char in it |
456 | + if path.startswith(LONG_PATH_PREFIX): |
457 | + path = path.replace(LONG_PATH_PREFIX, '') |
458 | + # remove the illegal windows chars |
459 | + path = replace_illegal_chars_with_unicode(path) |
460 | + return LONG_PATH_PREFIX + path |
461 | + |
462 | + |
463 | +def _assert_path_is_bytes(path): |
464 | + """Ensure that the path is a byte sequence encoded with utf8.""" |
465 | + assert isinstance(path, str), 'All paths passed to os_helper should be bytes' |
466 | + try: |
467 | + path = path.decode('utf8', 'error') |
468 | + except UnicodeDecodeError: |
469 | + raise AssertionError( |
470 | + 'All paths passed to os_helper should be encoded using utf8') |
471 | + |
472 | + |
473 | +def _assert_path_is_not_literal(path): |
474 | + """Ensure that the path is not a literal one.""" |
475 | + assert not path.startswith(LONG_PATH_PREFIX) |
476 | + |
477 | + |
478 | +def _validate_output_collection(col): |
479 | + """Ensure that the collection returned from os helper is valid.""" |
480 | + for path in col: |
481 | + _assert_path_is_bytes(path) |
482 | + _assert_path_is_not_literal(path) |
483 | + |
484 | + |
485 | +def get_syncdaemon_valid_path(path): |
486 | + """Get a path used on windows and modify it for syncdaemon. |
487 | + |
488 | + On Windows there are a number of changes we make to allow the use of |
489 | + illegal chars, those generate a unicode path which sd does not handle. This |
490 | + method does the following: |
491 | + |
492 | + 1. Remove utf-8 chars: There are some utf-8 chars used instead of the illegal |
493 | + paths, those are moved back to be windows illegal chars. |
494 | + 2. Remove long path prefix: that's an implementation detail isolated in this |
495 | + module. |
496 | + """ |
497 | + assert isinstance(path, |
498 | + unicode), 'All paths from os_helper should be unicode' |
499 | + if path.startswith(LONG_PATH_PREFIX): |
500 | + path = path.replace(LONG_PATH_PREFIX, '') |
501 | + path = replace_unicode_with_illegal_chars(path) |
502 | + return path.encode('utf8', 'error') |
503 | |
504 | |
505 | def _get_group_sid(group_name): |
506 | @@ -135,11 +186,16 @@ |
507 | """Returns if one of the chars is in the path.""" |
508 | if LONG_PATH_PREFIX in path: |
509 | path = path.replace(LONG_PATH_PREFIX, '') |
510 | - return any(c in chars for c in os.path.splitdrive(path)[1]) |
511 | + path = os.path.splitdrive(path)[1] |
512 | + return any(c in path for c in chars) |
513 | |
514 | |
515 | def _modify_chars(path, should_apply, chars): |
516 | - """Remove illegal chars and return a valid windows path.""" |
517 | + """Remove illegal chars defined by 'chars'. |
518 | + |
519 | + Only apply of 'should_apply(path)' holds. |
520 | + |
521 | + """ |
522 | if not should_apply(path): |
523 | return path |
524 | prefix = False |
525 | @@ -162,7 +218,7 @@ |
526 | result = volume + '\\' + result |
527 | if prefix: |
528 | result = LONG_PATH_PREFIX + result |
529 | - return unicode(result) |
530 | + return result |
531 | |
532 | |
533 | def is_illegal_path(path): |
534 | @@ -187,6 +243,8 @@ |
535 | |
536 | def _set_file_attributes(path, groups): |
537 | """Set file attributes using the wind32api.""" |
538 | + if not os.path.exists(path): |
539 | + raise WindowsError('Path %s could not be found.' % path) |
540 | security_descriptor = None |
541 | try: |
542 | security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
543 | @@ -213,31 +271,71 @@ |
544 | SetFileSecurity(path, DACL_SECURITY_INFORMATION, security_descriptor) |
545 | |
546 | |
547 | -def absparam(paths_indexes=[], paths_names=[]): |
548 | - """Ensure that the paths are absolute.""" |
549 | - def decorator(function): |
550 | - """Decorate the function to make sure the paths are absolute.""" |
551 | - @wraps(function) |
552 | - def abspath_wrapper(*args, **kwargs): |
553 | - """Set the paths to be absolute.""" |
554 | - fixed_args = list(args) |
555 | - for current_path in paths_indexes: |
556 | - fixed_args[current_path] = \ |
557 | - os.path.abspath(args[current_path]) |
558 | - fixed_args = tuple(fixed_args) |
559 | - for current_key in paths_names: |
560 | - try: |
561 | - kwargs[current_key] = \ |
562 | - os.path.abspath(kwargs[current_key]) |
563 | - except KeyError: |
564 | - logger.warn('Parameter %s could not be made a long path', |
565 | - current_key) |
566 | - return function(*fixed_args, **kwargs) |
567 | - return abspath_wrapper |
568 | - return decorator |
569 | - |
570 | - |
571 | -def longpath(paths_indexes=[], paths_names=[]): |
572 | +def assert_path_is_bytes(paths_indexes=[], paths_names=[]): |
573 | + """Ensure that passed paths are correct.""" |
574 | + def decorator(function): |
575 | + """Decorate function so that the given params are long paths.""" |
576 | + @wraps(function) |
577 | + def correct_path_wrapper(*args, **kwargs): |
578 | + """Assert paths are correct.""" |
579 | + for current_path in paths_indexes: |
580 | + _assert_path_is_bytes(args[current_path]) |
581 | + for current_key in paths_names: |
582 | + _assert_path_is_bytes(kwargs[current_key]) |
583 | + return function(*args, **kwargs) |
584 | + return correct_path_wrapper |
585 | + return decorator |
586 | + |
587 | + |
588 | +def assert_path_is_not_literal(paths_indexes=[], paths_names=[]): |
589 | + """Ensure that passed paths are correct.""" |
590 | + def decorator(function): |
591 | + """Decorate function so that the given params are long paths.""" |
592 | + @wraps(function) |
593 | + def correct_path_wrapper(*args, **kwargs): |
594 | + """Assert paths are correct.""" |
595 | + for current_path in paths_indexes: |
596 | + _assert_path_is_not_literal(args[current_path]) |
597 | + for current_key in paths_names: |
598 | + _assert_path_is_not_literal(kwargs[current_key]) |
599 | + return function(*args, **kwargs) |
600 | + return correct_path_wrapper |
601 | + return decorator |
602 | + |
603 | + |
604 | +def assert_output_path_is_bytes(function): |
605 | + """Ensure that the returned path is correct.""" |
606 | + @wraps(function) |
607 | + def correct_path_wrapper(*args, **kwargs): |
608 | + """Assert resulting path.""" |
609 | + result = function(*args, **kwargs) |
610 | + _assert_path_is_bytes(result) |
611 | + return result |
612 | + return correct_path_wrapper |
613 | + |
614 | + |
615 | +def assert_output_path_is_not_literal(function): |
616 | + """Ensure that the returned path is not a literal one.""" |
617 | + @wraps(function) |
618 | + def correct_path_wrapper(*args, **kwargs): |
619 | + """Assert resulting path.""" |
620 | + result = function(*args, **kwargs) |
621 | + _assert_path_is_not_literal(result) |
622 | + return result |
623 | + return correct_path_wrapper |
624 | + |
625 | +def validate_output_collection(function): |
626 | + """Ensure that the returned collection of paths is correct.""" |
627 | + @wraps(function) |
628 | + def correct_path_wrapper(*args, **kwargs): |
629 | + """Assert resulting path.""" |
630 | + result = function(*args, **kwargs) |
631 | + _validate_output_collection(result) |
632 | + return result |
633 | + return correct_path_wrapper |
634 | + |
635 | + |
636 | +def windowspath(paths_indexes=[], paths_names=[]): |
637 | """Ensure that the parameters are long paths.""" |
638 | def decorator(function): |
639 | """Decorate function so that the given params are long paths..""" |
640 | @@ -247,15 +345,10 @@ |
641 | fixed_args = list(args) |
642 | for current_path in paths_indexes: |
643 | fixed_args[current_path] = \ |
644 | - _set_as_long_path(args[current_path]) |
645 | + get_windows_valid_path(args[current_path]) |
646 | fixed_args = tuple(fixed_args) |
647 | for current_key in paths_names: |
648 | - try: |
649 | - kwargs[current_key] = \ |
650 | - _set_as_long_path(kwargs[current_key]) |
651 | - except KeyError: |
652 | - logger.warn('Parameter %s could not be made a long path', |
653 | - current_key) |
654 | + kwargs[current_key] = get_windows_valid_path(kwargs[current_key]) |
655 | return function(*fixed_args, **kwargs) |
656 | return long_path_wrapper |
657 | return decorator |
658 | @@ -268,7 +361,8 @@ |
659 | _set_file_attributes(path, groups) |
660 | |
661 | |
662 | -@longpath(paths_indexes=[0]) |
663 | +@assert_path_is_bytes(paths_indexes=[0]) |
664 | +@windowspath(paths_indexes=[0]) |
665 | def set_no_rights(path): |
666 | # set the groups to be empty which will remove all the rights of the file |
667 | try: |
668 | @@ -279,7 +373,8 @@ |
669 | return _check_winerror_123(e, _set_no_rights_helper, path) |
670 | |
671 | |
672 | -@longpath(paths_indexes=[0]) |
673 | +@assert_path_is_bytes(paths_indexes=[0]) |
674 | +@windowspath(paths_indexes=[0]) |
675 | def set_file_readonly(path): |
676 | """Change path permissions to readonly in a file.""" |
677 | # we use the win32 api because chmod just sets the readonly flag and |
678 | @@ -291,7 +386,8 @@ |
679 | _set_file_attributes(path, groups) |
680 | |
681 | |
682 | -@longpath(paths_indexes=[0]) |
683 | +@assert_path_is_bytes(paths_indexes=[0]) |
684 | +@windowspath(paths_indexes=[0]) |
685 | def set_file_readwrite(path): |
686 | """Change path permissions to readwrite in a file.""" |
687 | groups = {} |
688 | @@ -308,7 +404,8 @@ |
689 | return _check_winerror_123(e, os.chmod, path, stat.S_IWRITE) |
690 | |
691 | |
692 | -@longpath(paths_indexes=[0]) |
693 | +@assert_path_is_bytes(paths_indexes=[0]) |
694 | +@windowspath(paths_indexes=[0]) |
695 | def set_dir_readonly(path): |
696 | """Change path permissions to readonly in a dir.""" |
697 | groups = {} |
698 | @@ -318,7 +415,8 @@ |
699 | _set_file_attributes(path, groups) |
700 | |
701 | |
702 | -@longpath(paths_indexes=[0]) |
703 | +@assert_path_is_bytes(paths_indexes=[0]) |
704 | +@windowspath(paths_indexes=[0]) |
705 | def set_dir_readwrite(path): |
706 | """Change path permissions to readwrite in a dir.""" |
707 | groups = {} |
708 | @@ -329,32 +427,25 @@ |
709 | _set_file_attributes(path, groups) |
710 | # remove the read only flag |
711 | os.chmod(path, stat.S_IWRITE) |
712 | - for dirpath, dirs, files in os.walk(path): |
713 | - for dir in dirs: |
714 | - nested_path = os.path.join(dirpath, dir) |
715 | - if not can_write(nested_path): |
716 | - set_dir_readwrite(nested_path) |
717 | - for file in files: |
718 | - nested_path = os.path.join(dirpath, file) |
719 | - if not can_write(nested_path): |
720 | - set_file_readwrite(nested_path) |
721 | - |
722 | - |
723 | + |
724 | + |
725 | +@assert_path_is_bytes(paths_indexes=[0]) |
726 | @contextmanager |
727 | -@longpath(paths_indexes=[0]) |
728 | def allow_writes(path): |
729 | """A very simple context manager to allow witting in RO dirs.""" |
730 | + valid_path = get_windows_valid_path(path) |
731 | # get the old dacl of the file so that we can reset it back when done |
732 | - security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
733 | + security_descriptor = GetFileSecurity(valid_path, DACL_SECURITY_INFORMATION) |
734 | old_dacl = security_descriptor.GetSecurityDescriptorDacl() |
735 | set_dir_readwrite(path) |
736 | yield |
737 | # set the old dacl back |
738 | security_descriptor.SetSecurityDescriptorDacl(1, old_dacl, 0) |
739 | - SetFileSecurity(path, DACL_SECURITY_INFORMATION, security_descriptor) |
740 | - |
741 | - |
742 | -@longpath(paths_indexes=[0]) |
743 | + SetFileSecurity(valid_path, DACL_SECURITY_INFORMATION, security_descriptor) |
744 | + |
745 | + |
746 | +@assert_path_is_bytes(paths_indexes=[0]) |
747 | +@windowspath(paths_indexes=[0]) |
748 | def remove_file(path): |
749 | """Remove a file.""" |
750 | try: |
751 | @@ -364,7 +455,8 @@ |
752 | return _check_winerror_123(e, os.remove, path) |
753 | |
754 | |
755 | -@longpath(paths_indexes=[0]) |
756 | +@assert_path_is_bytes(paths_indexes=[0]) |
757 | +@windowspath(paths_indexes=[0]) |
758 | def remove_dir(path): |
759 | """Remove a dir.""" |
760 | try: |
761 | @@ -374,7 +466,8 @@ |
762 | return _check_winerror_123(e, os.rmdir, path) |
763 | |
764 | |
765 | -@longpath(paths_indexes=[0]) |
766 | +@assert_path_is_bytes(paths_indexes=[0]) |
767 | +@windowspath(paths_indexes=[0]) |
768 | def path_exists(path): |
769 | """Return if the path exists.""" |
770 | if is_illegal_path(path): |
771 | @@ -382,7 +475,8 @@ |
772 | return os.path.exists(path) |
773 | |
774 | |
775 | -@longpath(paths_indexes=[0]) |
776 | +@assert_path_is_bytes(paths_indexes=[0]) |
777 | +@windowspath(paths_indexes=[0]) |
778 | def make_dir(path, recursive=False): |
779 | """Make a dir, optionally creating all the middle ones.""" |
780 | if recursive: |
781 | @@ -399,7 +493,8 @@ |
782 | return _check_winerror_123(e, os.mkdir, path) |
783 | |
784 | |
785 | -@longpath(paths_indexes=[0]) |
786 | +@assert_path_is_bytes(paths_indexes=[0]) |
787 | +@windowspath(paths_indexes=[0]) |
788 | def open_file(path, mode='r'): |
789 | """Open a file.""" |
790 | try: |
791 | @@ -409,8 +504,8 @@ |
792 | return _check_winerror_123(e, open, path, mode) |
793 | |
794 | |
795 | -@absparam(paths_indexes=[0, 1]) |
796 | -@longpath(paths_indexes=[0, 1]) |
797 | +@assert_path_is_bytes(paths_indexes=[0, 1]) |
798 | +@windowspath(paths_indexes=[0, 1]) |
799 | def rename(path_from, path_to): |
800 | """Rename a file or directory.""" |
801 | # to ensure the same behaviors as on linux, use the MoveEx function from |
802 | @@ -444,7 +539,8 @@ |
803 | raise OSError(str(e)) |
804 | |
805 | |
806 | -@absparam(paths_indexes=[0, 1]) |
807 | +@assert_path_is_bytes(paths_indexes=[0, 1]) |
808 | +@windowspath(paths_indexes=[0, 1]) |
809 | def make_link(target, destination): |
810 | """Create a link from the target in the given destination.""" |
811 | # append the correct file type |
812 | @@ -455,12 +551,15 @@ |
813 | if dirname != '' and not os.path.exists(dirname): |
814 | make_dir(dirname, recursive=True) |
815 | shell = Dispatch('WScript.Shell') |
816 | - shortcut = shell.CreateShortCut(destination) |
817 | - shortcut.Targetpath = target |
818 | + shortcut = shell.CreateShortCut(os.path.abspath(destination)) |
819 | + shortcut.Targetpath = os.path.abspath(target) |
820 | shortcut.save() |
821 | |
822 | |
823 | -@absparam(paths_indexes=[0]) |
824 | +@assert_output_path_is_not_literal |
825 | +@assert_output_path_is_bytes |
826 | +@assert_path_is_bytes(paths_indexes=[0]) |
827 | +@windowspath(paths_indexes=[0]) |
828 | def read_link(path): |
829 | """Read the destination of a link.""" |
830 | if not path.endswith('.lnk'): |
831 | @@ -470,7 +569,8 @@ |
832 | return os.path.abspath(shortcut.Targetpath) |
833 | |
834 | |
835 | -@absparam(paths_indexes=[0]) |
836 | +@assert_path_is_bytes(paths_indexes=[0]) |
837 | +@windowspath(paths_indexes=[0]) |
838 | def is_link(path): |
839 | """Returns if a path is a link or not.""" |
840 | if not path.endswith('.lnk'): |
841 | @@ -478,7 +578,8 @@ |
842 | return os.path.exists(path) |
843 | |
844 | |
845 | -@absparam(paths_indexes=[0]) |
846 | +@assert_path_is_bytes(paths_indexes=[0]) |
847 | +@windowspath(paths_indexes=[0]) |
848 | def remove_link(path): |
849 | """Removes a link.""" |
850 | if not path.endswith('.lnk'): |
851 | @@ -486,17 +587,43 @@ |
852 | if os.path.exists(path): |
853 | os.unlink(path) |
854 | |
855 | - |
856 | -@longpath(paths_indexes=[0]) |
857 | +@validate_output_collection |
858 | +@assert_path_is_bytes(paths_indexes=[0]) |
859 | def listdir(directory): |
860 | """List a directory.""" |
861 | - # os.listdir combines / and \ in its search which breaks things on |
862 | - # windows, we use the win32 api instead. |
863 | - if not directory.endswith(os.path.sep): |
864 | - directory += os.path.sep |
865 | - return os.listdir(directory) |
866 | - |
867 | -@longpath(paths_indexes=[0]) |
868 | + |
869 | + def set_correct_output(path): |
870 | + """Return the output in the correct obj type.""" |
871 | + path = replace_unicode_with_illegal_chars(path) |
872 | + if isinstance(directory, str): |
873 | + return path.encode('utf8', 'error') |
874 | + return path |
875 | + |
876 | + valid_path = get_windows_valid_path(directory) |
877 | + # The main reason why we have to append os.path.sep is the following: |
878 | + # |
879 | + # os.listdir implementation will append a unix path separator to the |
880 | + # path used for listdir, for example if we do: |
881 | + # os.litdir('C:\\Python27') |
882 | + # the path would be: |
883 | + # 'C:\\Python27/*.*' |
884 | + # the above does not generate any problems, unfortunatly the same cannot |
885 | + # be said when using literal paths, that is, paths starting with '\\?\'. |
886 | + # So, while the above works (and returns a consistent error if the path |
887 | + # does not exist), the following fails: |
888 | + # os.listdir('\\\\?\\C:\\Python27') |
889 | + # with the following exception: |
890 | + # WindowsError: [Error 123] The filename, directory name, or volume |
891 | + # label syntax is incorrect: '\\\\?\\C:\\Python27/*.*' |
892 | + # which gets fixed if os.path.sep is added, that is, if we use: |
893 | + # os.listdir('\\\\?\\C:\\Python27\\') |
894 | + if not valid_path.endswith(os.path.sep): |
895 | + valid_path += os.path.sep |
896 | + return [set_correct_output(path) for path in os.listdir(valid_path)] |
897 | + |
898 | + |
899 | +@assert_path_is_bytes(paths_indexes=[0]) |
900 | +@windowspath(paths_indexes=[0]) |
901 | def access(path): |
902 | """Return if the path is at least readable.""" |
903 | # lets consider the access on an illegal path to be a special case |
904 | @@ -530,7 +657,8 @@ |
905 | os.access(path, os.R_OK) |
906 | |
907 | |
908 | -@longpath(paths_indexes=[0]) |
909 | +@assert_path_is_bytes(paths_indexes=[0]) |
910 | +@windowspath(paths_indexes=[0]) |
911 | def can_write(path): |
912 | """Return if the path is at least readable.""" |
913 | # lets consider the access on an illegal path to be a special case |
914 | @@ -563,7 +691,9 @@ |
915 | return (GetUserName() in accounts or EVERYONE_GROUP in accounts) and\ |
916 | os.access(path, os.R_OK) |
917 | |
918 | -@longpath(paths_indexes=[0]) |
919 | + |
920 | +@assert_path_is_bytes(paths_indexes=[0]) |
921 | +@windowspath(paths_indexes=[0]) |
922 | def stat_path(path, look_for_link=True): |
923 | """Return stat info about a path.""" |
924 | # if the path end with .lnk, that means we are dealing with a link |
925 | @@ -577,10 +707,13 @@ |
926 | try: |
927 | return os.lstat(path) |
928 | except OSError, e: |
929 | + if e.errno == errno.ENOENT: |
930 | + raise e |
931 | logger.exception('Got exception when trying to stat path %s', path) |
932 | return _check_winerror_123(e, stat_path, path, look_for_link) |
933 | |
934 | |
935 | +@assert_path_is_bytes(paths_indexes=[0]) |
936 | def move_to_trash(path): |
937 | """Move the file or dir to trash. |
938 | |
939 | @@ -602,24 +735,29 @@ |
940 | |
941 | def validate_path_from_unix(path): |
942 | """Get a unix path and return it so that it can be used.""" |
943 | - path = path.replace('/', '\\') |
944 | - return path |
945 | - |
946 | - |
947 | -@longpath(paths_indexes=[0]) |
948 | + return path.replace('/', '\\') |
949 | + |
950 | + |
951 | +@validate_output_collection |
952 | +@assert_path_is_bytes(paths_indexes=[0]) |
953 | +@windowspath(paths_indexes=[0]) |
954 | def get_path_list(path): |
955 | """Return a list with the diff components of the path.""" |
956 | path = path.replace(LONG_PATH_PREFIX, '') |
957 | path = path.replace(os.path.expandvars('%HOMEDRIVE%') + os.path.sep, '') |
958 | - list = [LONG_PATH_PREFIX + os.path.expandvars('%HOMEDRIVE%'),] |
959 | - list.extend(path.split(os.path.sep)) |
960 | - return list |
961 | - |
962 | - |
963 | + result = [LONG_PATH_PREFIX + os.path.expandvars('%HOMEDRIVE%'),] |
964 | + result.extend(path.split(os.path.sep)) |
965 | + # ensure that we do return the original chars |
966 | + return [replace_unicode_with_illegal_chars(path) for path in result] |
967 | + |
968 | + |
969 | +@assert_output_path_is_not_literal |
970 | +@assert_output_path_is_bytes |
971 | +@assert_path_is_bytes(paths_indexes=[0]) |
972 | def normpath(path): |
973 | """Normalize path, eliminating double slashes, etc.""" |
974 | # the basic normpath does not work correctly with \\?\ which we use |
975 | # as a trick for long paths, lets check it is is there |
976 | if path.startswith(LONG_PATH_PREFIX): |
977 | path = path.replace(LONG_PATH_PREFIX, '') |
978 | - return LONG_PATH_PREFIX + os.path.normpath(path) |
979 | \ No newline at end of file |
980 | + return os.path.normpath(path) |
981 | |
982 | === modified file 'ubuntuone/platform/windows/vm_helper.py' |
983 | --- ubuntuone/platform/windows/vm_helper.py 2011-07-08 12:21:59 +0000 |
984 | +++ ubuntuone/platform/windows/vm_helper.py 2011-07-21 16:16:14 +0000 |
985 | @@ -19,7 +19,6 @@ |
986 | |
987 | import os |
988 | from ubuntuone.platform.windows.os_helper import ( |
989 | - LONG_PATH_PREFIX, |
990 | make_link, |
991 | ) |
992 | |
993 | @@ -68,4 +67,4 @@ |
994 | def get_udf_path(suggested_path): |
995 | """Build the udf path using the suggested_path.""" |
996 | path = suggested_path.replace('/', '\\') |
997 | - return LONG_PATH_PREFIX + os.path.expanduser(path).encode("utf8") |
998 | + return os.path.expanduser(path) |
* remove_utf8_chars method name is misleading. As per what we talked in IRC, this method needs to be renamed to restore_ illegal_ chars and remove_ windows_ illegal_ chars will be renamed to remove_ illegal_ chars.
* in windows/ .../filesystem_ manager. py there are several inconsistencies, some methods use the windowspath decorator, and some others don't: for example, rm_watch and add_watch; freeze_begin vs freeze_commit and freeze_rollback, etc.
In IRC we agreed:
(09:26:12 AM) nessita: mandel: ok, so I think this class needs a careful review of decorators, I'll add that to the MP, yes?
(09:26:26 AM) mandel: nessita: completely agree
* Remove _get_syncdaemon _valid_ path as per: _valid_ path, I would expect this class, that assert over the param to be a unicode, to return bytes utf8_chars only replaces unicode chars, but does not transform to bytes n_valid_ path) should do the .encode('utf8') illegla_ chars instead illegal_ chars? :-)
nessita: mandel: so, I'm starting again with the next question to avoid confusions: in os_helper.py: _get_syncdaemon
nessita: mandel: ex-remove_
nessita: mandel: that method (_get_syncdaemo
nessita: mandel: the thing is the name of the method. A method with that name, *should* return bytes since is meant to be used by syncdaemon
mandel: nessita: I'd remove that method and use reinstantiate_
nessita: mandel: restore_
* Rename _validate_ windows_ path to _assert_ path_is_ bytes, and then you can use that in the implementation of _valiate_ output_ path, reaplacing the following code:
assert isinstance(path, str), 'All paths returned to sd should be bytes'