Merge lp:~nataliabidart/ubuntuone-control-panel/default-folders into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 290
Merged at revision: 287
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/default-folders
Merge into: lp:ubuntuone-control-panel
Diff against target: 339 lines (+235/-8)
6 files modified
ubuntuone/controlpanel/logger.py (+2/-4)
ubuntuone/controlpanel/utils/__init__.py (+5/-2)
ubuntuone/controlpanel/utils/linux.py (+59/-0)
ubuntuone/controlpanel/utils/tests/test_linux.py (+101/-1)
ubuntuone/controlpanel/utils/tests/test_windows.py (+41/-0)
ubuntuone/controlpanel/utils/windows.py (+27/-1)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/default-folders
Reviewer Review Type Date Requested Status
dobey (community) Approve
Diego Sarmentero (community) Approve
Brian Curtin (community) Approve
Review via email: mp+97949@code.launchpad.net

Commit message

- Implemented a method to list the user's default folders in every platform (part of LP: #933697).

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

94 + with open(dirs_path) as f:
95 + while True:
96 + line = f.readline()

The file object you get back (f) is already iterable, so you could replace the "while True" with "for line in f"

Other than this it looks alright.

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

> 94 + with open(dirs_path) as f:
> 95 + while True:
> 96 + line = f.readline()
>
> The file object you get back (f) is already iterable, so you could replace the
> "while True" with "for line in f"
>
> Other than this it looks alright.

Fixed and pushed, great catch! thanks

289. By Natalia Bidart

- Inprove default_folders file content iteration (thanks brian.curtin).

Revision history for this message
dobey (dobey) wrote :

86 + if dirs_path is None:
87 + dirs_path = os.path.join(user_home, '.config', 'user-dirs.dirs')

We probably want to use xdg_config_home from dirspec.basedir here, instead of assuming ~/.config/.

With the suggestion from Brian, you can also drop the if line is None: break check.

Do we need to check that translated names for directories get handled correctly as well here, and ensure that unicode handling doesn't break again? I don't see a test for that case in your tests.

Also, on Windows, for the special folders you check for, which aren't supported on XP for example, how is that error handled? I suppose there should be a test which causes and checks for the error when that happens, to ensure we handle it properly? The linked MSDN page seems to suggest that _MYVIDEO doesn't exist on 5.0 (XP) for example. It might also be worth noting in the comment that if/when we drop XP support, we probably want to switch to _MYDOCUMENTS instead of _PERSONAL for that folder; as the documentation seems to suggest we should do.

review: Needs Fixing
Revision history for this message
Brian Curtin (brian.curtin) wrote :

+1

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

+1

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

> 86 + if dirs_path is None:
> 87 + dirs_path = os.path.join(user_home, '.config', 'user-dirs.dirs')
>
> We probably want to use xdg_config_home from dirspec.basedir here, instead of
> assuming ~/.config/.

Added.

> With the suggestion from Brian, you can also drop the if line is None: break
> check.

Done already.

> Do we need to check that translated names for directories get handled
> correctly as well here, and ensure that unicode handling doesn't break again?
> I don't see a test for that case in your tests.

Added tests for this.

> Also, on Windows, for the special folders you check for, which aren't
> supported on XP for example, how is that error handled? I suppose there should
> be a test which causes and checks for the error when that happens, to ensure
> we handle it properly? The linked MSDN page seems to suggest that _MYVIDEO
> doesn't exist on 5.0 (XP) for example. It might also be worth noting in the
> comment that if/when we drop XP support, we probably want to switch to
> _MYDOCUMENTS instead of _PERSONAL for that folder; as the documentation seems
> to suggest we should do.

Good catch, made the code more robust and added tests for it.

290. By Natalia Bidart

Better unicode management, and non existent folders.

Revision history for this message
dobey (dobey) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/logger.py'
2--- ubuntuone/controlpanel/logger.py 2012-02-06 18:09:58 +0000
3+++ ubuntuone/controlpanel/logger.py 2012-03-16 20:00:25 +0000
4@@ -1,8 +1,6 @@
5 # -*- coding: utf-8 -*-
6-
7-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
8 #
9-# Copyright 2010 Canonical Ltd.
10+# Copyright 2010-2012 Canonical Ltd.
11 #
12 # This program is free software: you can redistribute it and/or modify it
13 # under the terms of the GNU General Public License version 3, as published
14@@ -30,7 +28,7 @@
15 from ubuntuone.platform.xdg_base_directory import ubuntuone_log_dir
16
17
18-if os.environ.get('DEBUG'):
19+if os.environ.get('U1_DEBUG'):
20 LOG_LEVEL = logging.DEBUG
21 else:
22 # Only log this level and above
23
24=== modified file 'ubuntuone/controlpanel/utils/__init__.py'
25--- ubuntuone/controlpanel/utils/__init__.py 2012-03-09 13:44:53 +0000
26+++ ubuntuone/controlpanel/utils/__init__.py 2012-03-16 20:00:25 +0000
27@@ -1,7 +1,6 @@
28 # -*- coding: utf-8 -*-
29-
30 #
31-# Copyright 2010 Canonical Ltd.
32+# Copyright 2010-2012 Canonical Ltd.
33 #
34 # This program is free software: you can redistribute it and/or modify it
35 # under the terms of the GNU General Public License version 3, as published
36@@ -37,10 +36,14 @@
37 if sys.platform == 'win32':
38 from ubuntuone.controlpanel.utils import windows
39 are_updates_present = windows.are_updates_present
40+ default_folders = windows.default_folders
41 perform_update = windows.perform_update
42 else:
43+ from ubuntuone.controlpanel.utils import linux
44 are_updates_present = lambda *args, **kwargs: False
45+ default_folders = linux.default_folders
46 perform_update = lambda *args, **kwargs: None
47+
48 # pylint: enable=C0103
49
50
51
52=== added file 'ubuntuone/controlpanel/utils/linux.py'
53--- ubuntuone/controlpanel/utils/linux.py 1970-01-01 00:00:00 +0000
54+++ ubuntuone/controlpanel/utils/linux.py 2012-03-16 20:00:25 +0000
55@@ -0,0 +1,59 @@
56+# -*- coding: utf-8 -*-
57+#
58+# Copyright 2012 Canonical Ltd.
59+#
60+# This program is free software: you can redistribute it and/or modify it
61+# under the terms of the GNU General Public License version 3, as published
62+# by the Free Software Foundation.
63+#
64+# This program is distributed in the hope that it will be useful, but
65+# WITHOUT ANY WARRANTY; without even the implied warranties of
66+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
67+# PURPOSE. See the GNU General Public License for more details.
68+#
69+# You should have received a copy of the GNU General Public License along
70+# with this program. If not, see <http://www.gnu.org/licenses/>.
71+
72+"""Miscelaneous functions and constants for linux."""
73+
74+import codecs
75+import os
76+
77+from dirspec.basedir import xdg_config_home
78+from ubuntuone.controlpanel.logger import setup_logging
79+
80+
81+logger = setup_logging('utils.linux')
82+
83+
84+def default_folders(user_home='', dirs_path=None):
85+ """Return a list of the folders to add by default."""
86+ result = []
87+
88+ if dirs_path is None:
89+ dirs_path = os.path.join(xdg_config_home, u'user-dirs.dirs')
90+
91+ if not os.path.exists(dirs_path):
92+ logger.warning('default_folders: dirs_path %r does not exist.',
93+ dirs_path)
94+ return result
95+
96+ # pylint: disable=W0702
97+
98+ try:
99+ with codecs.open(dirs_path, encoding='utf-8') as f:
100+ for line in f:
101+ if line.startswith(u'#'):
102+ continue
103+
104+ try:
105+ _, value = line.strip().split(u'=')
106+ value = value.strip(u'"').replace(u'$HOME', user_home)
107+ except:
108+ logger.exception('default_folders: can not row %r:', line)
109+ else:
110+ result.append(value)
111+ except:
112+ logger.exception('default_folders: can not load file %r:', dirs_path)
113+
114+ return result
115
116=== modified file 'ubuntuone/controlpanel/utils/tests/test_linux.py'
117--- ubuntuone/controlpanel/utils/tests/test_linux.py 2011-10-26 10:45:54 +0000
118+++ ubuntuone/controlpanel/utils/tests/test_linux.py 2012-03-16 20:00:25 +0000
119@@ -17,11 +17,35 @@
120
121 """Test the linux utils functions."""
122
123+import codecs
124+import os
125
126 from twisted.internet import defer
127
128 from ubuntuone.controlpanel import utils
129-from ubuntuone.controlpanel.tests import TestCase
130+from ubuntuone.controlpanel.tests import TestCase, USER_HOME
131+
132+REAL_CONTENT = u"""
133+XDG_DESKTOP_DIR="$HOME/Desktop"
134+XDG_DOWNLOAD_DIR="$HOME/Downloads"
135+XDG_PUBLICSHARE_DIR="$HOME/Public"
136+XDG_DOCUMENTS_DIR="$HOME/Documents"
137+XDG_MUSIC_DIR="$HOME/Music"
138+XDG_PICTURES_DIR="$HOME/Pictures"
139+XDG_VIDEOS_DIR="$HOME/Videos"
140+"""
141+
142+NON_ASCII = u"""
143+XDG_DESKTOP_DIR="$HOME/桌面"
144+XDG_DOWNLOAD_DIR="$HOME/下载"
145+XDG_DOCUMENTS_DIR="$HOME/文档"
146+"""
147+
148+LATIN_1 = u"""
149+XDG_DESKTOP_DIR="$HOME/Súbeme"
150+XDG_DOWNLOAD_DIR="$HOME/Bájame"
151+XDG_DOCUMENTS_DIR="$HOME/Mis ñoños Documentos"
152+"""
153
154
155 class AutoupdaterTestCase(TestCase):
156@@ -36,3 +60,79 @@
157 def test_perform_update(self):
158 """Test the method that performs the update."""
159 self.assertEqual(None, utils.perform_update())
160+
161+
162+class DefaultFoldersTestCase(TestCase):
163+ """Test suite for the default_folders retriever."""
164+
165+ def _create_faked_file(self, content='', encoding=None):
166+ """Create a faked file to be parsed and clean it up afterwards."""
167+ filename = './dirs-parser-test.dirs'
168+ if os.path.exists(filename):
169+ os.remove(filename)
170+
171+ if encoding is None:
172+ encoding = 'utf-8'
173+
174+ with codecs.open(filename, 'w', encoding=encoding) as f:
175+ f.write(content)
176+
177+ self.addCleanup(os.remove, filename)
178+
179+ return filename
180+
181+ def test_default_folders_not_file(self):
182+ """Default folders are empty if the file does not exist."""
183+ path = 'foo/bar/baz/yadda/yo'
184+ assert not os.path.exists(path)
185+ actual = utils.default_folders(dirs_path=path)
186+
187+ self.assertEqual(actual, [])
188+
189+ def test_default_folders_empty_file(self):
190+ """Default folders are empty if the file empty."""
191+ filename = self._create_faked_file(content='')
192+ actual = utils.default_folders(dirs_path=filename)
193+
194+ self.assertEqual(actual, [])
195+
196+ def test_default_folders_only_comments(self):
197+ """Default folders are empty if only comments in the file."""
198+ content = u"# yadda yadda\n# foo bar baz\n#puipuipui"
199+ filename = self._create_faked_file(content=content)
200+ actual = utils.default_folders(dirs_path=filename)
201+
202+ self.assertEqual(actual, [])
203+
204+ def test_default_folders_syntax_error(self):
205+ """Default folders do not explode on syntax error."""
206+ content = u"foo ~ bar\nYADDA=DOO"
207+ filename = self._create_faked_file(content=content)
208+ actual = utils.default_folders(dirs_path=filename)
209+
210+ self.assertEqual(actual, [u'DOO'])
211+
212+ def test_default_folders_parsed(self):
213+ """Default folders parses the content of the file."""
214+ filename = self._create_faked_file(content=REAL_CONTENT)
215+ actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename)
216+
217+ expected = (i.split(u'=')[1].strip(u'"') for i in REAL_CONTENT.split())
218+ expected = [i.replace(u'$HOME', USER_HOME) for i in expected]
219+ self.assertEqual(actual, expected)
220+
221+ def test_default_folders_non_ascii(self):
222+ """Default folders parses the content if it's non-ascii."""
223+ filename = self._create_faked_file(content=NON_ASCII)
224+ actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename)
225+
226+ expected = (i.split(u'=')[1].strip(u'"') for i in NON_ASCII.split())
227+ expected = [i.replace(u'$HOME', USER_HOME) for i in expected]
228+ self.assertEqual(actual, expected)
229+
230+ def test_default_folders_bad_encoding(self):
231+ """Default folders parses the content if it's non-ascii."""
232+ filename = self._create_faked_file(content=LATIN_1, encoding='latin-1')
233+ actual = utils.default_folders(user_home=USER_HOME, dirs_path=filename)
234+
235+ self.assertEqual(actual, [])
236
237=== modified file 'ubuntuone/controlpanel/utils/tests/test_windows.py'
238--- ubuntuone/controlpanel/utils/tests/test_windows.py 2011-11-15 12:36:15 +0000
239+++ ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-16 20:00:25 +0000
240@@ -87,6 +87,47 @@
241 self.assertEqual(0, self._called[0][5])
242
243
244+class DefaultFoldersTestCase(TestCase):
245+ """Test the default_folders method."""
246+
247+ def test_special_folders(self, names=None):
248+ """Test that default_folders does the right thing."""
249+ folders = utils.default_folders()
250+
251+ if names is None:
252+ names = ('PERSONAL', 'MYMUSIC', 'MYPICTURES', 'MYVIDEO')
253+
254+ self.assertEqual(len(folders), len(names))
255+ self.assertEqual(folders, [os.path.abspath(f) for f in folders])
256+
257+ expected = []
258+ for name in names:
259+ name = getattr(utils.windows.shellcon, 'CSIDL_%s' % name)
260+ folder = utils.windows.shell.SHGetFolderPath(0, name, None, 0)
261+ expected.append(folder.encode('utf8'))
262+
263+ self.assertEqual(sorted(folders), sorted(expected))
264+
265+ def test_special_folders_with_error(self):
266+ """Test that default_folders handles errors."""
267+ failing_name = 'MYVIDEO'
268+ original_func = utils.windows.shell.SHGetFolderPath
269+
270+ class SomeError(Exception):
271+ """For test only."""
272+
273+ def fail_for_some(code, name, parent, flag):
274+ """Custom failer."""
275+ fail = getattr(utils.windows.shellcon, 'CSIDL_%s' % failing_name)
276+ if name == fail:
277+ raise SomeError('The parameter is incorrect.')
278+ else:
279+ return original_func(code, name, parent, flag)
280+
281+ self.patch(utils.windows.shell, 'SHGetFolderPath', fail_for_some)
282+ self.test_special_folders(names=('PERSONAL', 'MYMUSIC', 'MYPICTURES'))
283+
284+
285 class GetPathTestCase(TestCase):
286 """Test the code that is used for the auto update process."""
287
288
289=== modified file 'ubuntuone/controlpanel/utils/windows.py'
290--- ubuntuone/controlpanel/utils/windows.py 2012-03-09 00:47:47 +0000
291+++ ubuntuone/controlpanel/utils/windows.py 2012-03-16 20:00:25 +0000
292@@ -23,13 +23,15 @@
293 # Avoid pylint error on Linux
294 # pylint: disable=F0401
295 import win32api
296+
297+from win32com.shell import shell, shellcon
298 # pylint: enable=F0401
299 from twisted.internet import defer
300 from twisted.internet.utils import getProcessValue
301
302 from ubuntuone.controlpanel.logger import setup_logging
303
304-logger = setup_logging('utils')
305+logger = setup_logging('utils.windows')
306 AUTOUPDATE_EXE_NAME = 'autoupdate-windows.exe'
307
308
309@@ -62,6 +64,30 @@
310 defer.returnValue(result)
311
312
313+def default_folders(user_home=None):
314+ """Return a list of the folders to add by default."""
315+ # as per http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181,
316+ # SHGetFolderPath is deprecated, we should migrate to SHGetKnownFolderPath
317+ # (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188)
318+ # but the latter does not support XP
319+ # (Minimum supported client: Windows Vista)
320+ get_path = lambda name: shell.SHGetFolderPath(
321+ 0, getattr(shellcon, name), None, 0).encode('utf8')
322+
323+ folders = []
324+ # More information on these constants at
325+ # http://msdn.microsoft.com/en-us/library/bb762494
326+ for name in (u'PERSONAL', u'MYMUSIC', u'MYPICTURES', u'MYVIDEO'):
327+ name = u'CSIDL_%s' % name
328+ try:
329+ folders.append(get_path(name))
330+ except: # pylint: disable=W0702
331+ logger.exception('default_folders: could not retrieve folder info '
332+ 'for name %r.', name)
333+
334+ return folders
335+
336+
337 def perform_update():
338 """Spawn the autoupdate process and call the stop function."""
339 update_path = _get_update_path()

Subscribers

People subscribed via source and target branches