Merge lp:~mandel/ubuntuone-client/fix-os-helper into lp:ubuntuone-client

Proposed by Manuel de la Peña
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
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).

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

* 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:
nessita: mandel: so, I'm starting again with the next question to avoid confusions: in os_helper.py: _get_syncdaemon_valid_path, I would expect this class, that assert over the param to be a unicode, to return bytes
nessita: mandel: ex-remove_utf8_chars only replaces unicode chars, but does not transform to bytes
nessita: mandel: that method (_get_syncdaemon_valid_path) should do the .encode('utf8')
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_illegla_chars instead
nessita: mandel: restore_illegal_chars? :-)

* 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'

review: Needs Fixing
1067. By Manuel de la Peña

Added decorators to ensure that the correct encoding is used.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

* If you plan to use _get_windows_valid_path from other modules, do not name it with an initial underscore. Things named _something are not meant to be used outside the module that defines them.

* If I understand this test correctly, it makes no sense:

    def test_restore_illegal_chars(self):
        """Test that the path does not have utf chars."""
        path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
        path = restore_illegal_chars(path)
        self.assertFalse(any(c in path for c in LINUX_ILLEGAL_CHARS_MAP))

it should be something like (please note the docstring was corrected):

    def test_restore_illegal_chars(self):
        """The illegal unicode chars are replaced by characters, valid in linux."""
        original_path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
        path = remove_illegal_chars(original_path)
        assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP)

        path = restore_illegal_chars(path)
        self.assertEqual(original_path, path)

and you need to add an analogous test for remove_illegal_chars:

    def test_remove_illegal_chars(self):
        """The illegal chars are replaced by unicode objects, valid on windows."""
        original_path = self.testfile + ''.join(WINDOWS_ILLEGAL_CHARS_MAP)
        path = remove_illegal_chars(original_path)
        assert not any(c in path for c in WINDOWS_ILLEGAL_CHARS_MAP)

        path = restore_illegal_chars(path)
        self.assertEqual(original_path, path)

* as per out chat this morning, get_syncdaemon_valid_path was supposed to disappear. Why is still being used in filesystem_notifications.py?

275 + 'path': head.replace(get_syncdaemon_valid_path(self.path),
297 + full_filename = get_syncdaemon_valid_path(file_name)

* 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(paths_indexes=[1])" for filesystem_notifications.py:get_wd?

* 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_path_is_bytes, since you already have that function defined. Also, I think all the _assert_something methods should not return anything, since they do not perfom any modification. So, this code:

578 +def _valiate_output_collection(col):
579 +"""Ensure that the collection returned from os helper is valid."""
580 + for path in col:
581 + _assert_path_is_not_literal(_assert_output_path_is_bytes(path))
582 + return col

will mutate to:

578 +def _valiate_output_collection(col):
579 +"""Ensure that the collection returned from os helper is valid."""
580 + for path in col:
                 _assert_output_path_is_bytes(path)
581 + _assert_path_is_not_literal(path)

review: Needs Fixing
Revision history for this message
Manuel de la Peña (mandel) wrote :
Download full text (4.9 KiB)

> * If you plan to use _get_windows_valid_path from other modules, do not 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 ;)

I nevertheless can remove it.

> * If I understand this test correctly, it makes no sense:
>
> def test_restore_illegal_chars(self):
> """Test that the path does not have utf chars."""
> path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> path = restore_illegal_chars(path)
> self.assertFalse(any(c in path for c in LINUX_ILLEGAL_CHARS_MAP))

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_illegal_chars(self):
> """The illegal unicode chars are replaced by characters, valid in
> linux."""
> original_path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> path = remove_illegal_chars(original_path)
> assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP)
>
> path = restore_illegal_chars(path)
> self.assertEqual(original_path, path)
>

I do see the confusion is possible because the name of the test is wrong, it should be test_remove_illegla_chars, looks like an error when doing the 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 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_illegal_chars failed, and we got a AssertError, or the restore_illegal_chars failed and we got a fail in the assertEqual.

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_illegal_chars:
>
> def test_remove_illegal_chars(self):
> """The illegal chars are replaced by unicode objects, valid on
> windows."""
> original_path = self.testfile + ''.join(WINDOWS_ILLEGAL_CHARS_MAP)
> path = remove_illegal_chars(original_path)
> assert not any(c in path for c in WINDOWS_ILLEGAL_CHARS_MAP)
>
> path = restore_illegal_chars(path)
> self.assertEqual(original_path, path)
>

Same as the previous comment, I would add a test for restore_illegal_chars that simply asserts that no illegal chars are present.

> * as per out chat this morning, get_syncdaemon_valid_path was supposed to
> disappear. Why is still being used in filesystem_notifications.py?
>
> 275 + 'path': head.replace(get_syncdaemon_valid_path(self.path),
> 297 + full_filename = get_syncdaemon_valid...

Read more...

Revision history for this message
Manuel de la Peña (mandel) wrote :
Download full text (5.2 KiB)

> > * If you plan to use _get_windows_valid_path from other modules, do not 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 ;)
>
> I nevertheless can remove it.
>
> > * If I understand this test correctly, it makes no sense:
> >
> > def test_restore_illegal_chars(self):
> > """Test that the path does not have utf chars."""
> > path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > path = restore_illegal_chars(path)
> > self.assertFalse(any(c in path for c in LINUX_ILLEGAL_CHARS_MAP))
>
> 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_illegal_chars(self):
> > """The illegal unicode chars are replaced by characters, valid in
> > linux."""
> > original_path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > path = remove_illegal_chars(original_path)
> > assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP)
> >
> > path = restore_illegal_chars(path)
> > self.assertEqual(original_path, path)
> >
>
> I do see the confusion is possible because the name of the test is wrong, it
> should be test_remove_illegla_chars, looks like an error when doing the
> 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_illegal_chars failed, and we got a AssertError, or the
> restore_illegal_chars failed and we got a fail in the assertEqual.
>
> 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_illegal_chars:
> >
> > def test_remove_illegal_chars(self):
> > """The illegal chars are replaced by unicode objects, valid on
> > windows."""
> > original_path = self.testfile + ''.join(WINDOWS_ILLEGAL_CHARS_MAP)
> > path = remove_illegal_chars(original_path)
> > assert not any(c in path for c in WINDOWS_ILLEGAL_CHARS_MAP)
> >
> > path = restore_illegal_chars(path)
> > self.assertEqual(original_path, path)
> >
>
> Same as the previous comment, I would add a test for restore_illegal_chars
> that simply asserts that no illegal chars are present.
>
> > * as per out chat this morning, get_syncdaemon_valid_path was supposed to
> > disappear. Why is...

Read more...

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.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :
Download full text (5.3 KiB)

> > * If you plan to use _get_windows_valid_path from other modules, do not 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.

> > * If I understand this test correctly, it makes no sense:
> >
> > def test_restore_illegal_chars(self):
> > """Test that the path does not have utf chars."""
> > path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > path = restore_illegal_chars(path)
> > self.assertFalse(any(c in path for c in LINUX_ILLEGAL_CHARS_MAP))
>
> 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_illegal_chars 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_illegal_chars that will make this test pass and break all the code horribly.

> > it should be something like (please note the docstring was corrected):
> >
> > def test_restore_illegal_chars(self):
> > """The illegal unicode chars are replaced by characters, valid in
> > linux."""
> > original_path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > path = remove_illegal_chars(original_path)
> > assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP)
> >
> > path = restore_illegal_chars(path)
> > self.assertEqual(original_path, path)
> >
>
> I do see the confusion is possible because the name of the test is wrong, it
> should be test_remove_illegla_chars, looks like an error when doing the
> 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...

Read more...

Revision history for this message
Manuel de la Peña (mandel) wrote :
Download full text (6.7 KiB)

> > > * If you plan to use _get_windows_valid_path from other modules, do not
> 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_illegal_chars(self):
> > > """Test that the path does not have utf chars."""
> > > path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > > path = restore_illegal_chars(path)
> > > self.assertFalse(any(c in path for c in LINUX_ILLEGAL_CHARS_MAP))
> >
> > 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_illegal_chars
> 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_illegal_chars that will make this test pass and break all the code
> horribly.
>
> > > it should be something like (please note the docstring was corrected):
> > >
> > > def test_restore_illegal_chars(self):
> > > """The illegal unicode chars are replaced by characters, valid in
> > > linux."""
> > > original_path = self.testfile + ''.join(LINUX_ILLEGAL_CHARS_MAP)
> > > path = remove_illegal_chars(original_path)
> > > assert not any(c in path for c in LINUX_ILLEGAL_CHARS_MAP)
> > >
> > > path = restore_illegal_chars(path)
> > > self.assertEqual(original_path, path)
> > >
> >
> > I do see the confusion is possible because the name of the test is wrong, it
> > should be test_remove_illegla_chars, looks like an error when doing the
> > 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...

Read more...

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.

Revision history for this message
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_CHARS_MAP).

In line 99, we are comparing a unicode string (path) against strings (the keys in WINDOWS_ILLEGAL_CHARS_MAP)

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.

review: Needs Fixing
Revision history for this message
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(get_syncdaemon_valid_path(self.path),

--

Why did we separate assert_path_is_bytes and assert_path_is_not_literal? They seem to be called all the time together, and with the same arguments. Perhaps we should make "one" decorator that joins both.

--

The following bit makes no sense in get_windows_valid_path() and it may mask some problem.

    if path.startswith(LONG_PATH_PREFIX):
        path = path.replace(LONG_PATH_PREFIX, '')

perhaps it should be an assertion like "assert not path.startswith(LONG_PATH_PREFIX)

--

We should never check for: "LONG_PATH_PREFIX in path", we should use "path.startswith(LONG_PATH_PREFIX)" instead.
(for instance, it's used in _contains_chars, _modify_chars)

--

typos: litdir, unfortunatly

--

All the times we are using windowspath, we use assert_path_is_bytes above it.
we should only be using windowspath, and have it call assert_path_is_bytes
The same applies to the proliferation of decorator pairs that are present throughout the code.

--

I understand that vm_helper.get_udf_path should not be making any / to \ transformations, and we should be doing those in the os_helper layer.

review: Needs Fixing

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches