Merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.92 into lp:ubuntuone-control-panel/stable-3-0

Proposed by Natalia Bidart on 2012-04-03
Status: Merged
Approved by: Natalia Bidart on 2012-04-03
Approved revision: 258
Merged at revision: 257
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.92
Merge into: lp:ubuntuone-control-panel/stable-3-0
Diff against target: 1335 lines (+334/-437)
19 files modified
ubuntuone/controlpanel/backend.py (+13/-31)
ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py (+0/-94)
ubuntuone/controlpanel/gui/__init__.py (+1/-1)
ubuntuone/controlpanel/gui/qt/controlpanel.py (+3/-4)
ubuntuone/controlpanel/gui/qt/folders.py (+3/-2)
ubuntuone/controlpanel/gui/qt/gui.py (+1/-2)
ubuntuone/controlpanel/gui/qt/main/__init__.py (+2/-1)
ubuntuone/controlpanel/gui/qt/main/tests/test_main.py (+10/-1)
ubuntuone/controlpanel/gui/qt/tests/test_folders.py (+13/-1)
ubuntuone/controlpanel/gui/qt/tests/test_preferences.py (+15/-0)
ubuntuone/controlpanel/gui/qt/tests/test_wizard.py (+12/-1)
ubuntuone/controlpanel/gui/qt/wizard.py (+4/-8)
ubuntuone/controlpanel/sd_client/__init__.py (+10/-13)
ubuntuone/controlpanel/sd_client/linux.py (+0/-46)
ubuntuone/controlpanel/tests/test_backend.py (+66/-105)
ubuntuone/controlpanel/tests/test_sd_client.py (+23/-17)
ubuntuone/controlpanel/utils/__init__.py (+8/-4)
ubuntuone/controlpanel/utils/tests/test_windows.py (+106/-71)
ubuntuone/controlpanel/utils/windows.py (+44/-35)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.92
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) 2012-04-03 Approve on 2012-04-03
Review via email: mp+100599@code.launchpad.net

Commit message

- Updating from trunk up to revno 308:

[ Brian Curtin <email address hidden> ]
  - Match test behavior with updated functionality.

[ Diego Sarmentero <email address hidden> ]
  - Removing encoding to return unicode (LP: #966513).
  - Using limit_bandwidth attribute to properly process the info dict
    for preferences (LP: #944256).

[ Natalia B. Bidart <email address hidden> ]
  - When user rejects the License, also uninstall the application
    when in Windows (LP: #968327).
  - Remove custom path validation and use the one provided by syncdaemon
    (LP: #824252).
  - Make use of the new feature from syncdaemon where 'refresh_volumes'
    returns a deferred that gets fired when the server info is ready
    (LP: #851810).
  - Ensured that Folders' tree view has proper texts in the columns to
    have proper column widths (LP: #965175).

[ Roberto Alsina <email address hidden> ]
   - Fixed the combination of --minimized --with-icon options
    (LP: #968369).

To post a comment you must log in.
258. By Natalia Bidart on 2012-04-03

Attaching bug numbers.

Alejandro J. Cura (alecu) wrote :

Approving

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/backend.py'
2--- ubuntuone/controlpanel/backend.py 2012-03-01 22:05:51 +0000
3+++ ubuntuone/controlpanel/backend.py 2012-04-03 13:15:23 +0000
4@@ -1,9 +1,6 @@
5 # -*- coding: utf-8 -*-
6-
7-# Authors: Alejandro J. Cura <alecu@canonical.com>
8-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
9 #
10-# Copyright 2010 Canonical Ltd.
11+# Copyright 2010-2012 Canonical Ltd.
12 #
13 # This program is free software: you can redistribute it and/or modify it
14 # under the terms of the GNU General Public License version 3, as published
15@@ -26,11 +23,7 @@
16 from functools import wraps
17
18 from twisted.internet.defer import inlineCallbacks, returnValue
19-# No name 'is_link' in module 'ubuntuone.platform'
20-# pylint: disable=E0611, F0401
21-from ubuntuone.platform import is_link
22 from ubuntuone.platform.credentials import CredentialsManagementTool
23-# pylint: enable=E0611, F0401
24
25 from ubuntuone.controlpanel import sd_client, replication_client
26 from ubuntuone.controlpanel.logger import setup_logging, log_call
27@@ -599,7 +592,7 @@
28
29 @log_call(logger.debug)
30 @inlineCallbacks
31- def volumes_info(self, with_storage_info=True):
32+ def volumes_info(self, with_storage_info=True, refresh=False):
33 """Get the volumes info."""
34 self._volumes = {}
35
36@@ -612,6 +605,9 @@
37 else:
38 free_bytes = account['quota_total'] - account['quota_used']
39
40+ if refresh:
41+ yield self.sd_client.refresh_volumes()
42+
43 root_dir = yield self.sd_client.get_root_dir()
44 shares_dir = yield self.sd_client.get_shares_dir()
45 shares_dir_link = yield self.sd_client.get_shares_dir_link()
46@@ -717,28 +713,9 @@
47 yield self.sd_client.create_folder(path=folder_path)
48
49 @log_call(logger.debug)
50- @inlineCallbacks
51 def validate_path_for_folder(self, folder_path):
52 """Validate 'folder_path' for folder creation."""
53- user_home = yield self.get_home_dir()
54- folder_path = append_path_sep(folder_path)
55-
56- # handle folder_path not within '~' or links
57- # XXX is_link expects bytes, see bug #824252
58- if not folder_path.startswith(user_home) or is_link(
59- folder_path.encode('utf-8')):
60- returnValue(False)
61-
62- # handle folder_path nested with a existing cloud folder
63- volumes = yield self.volumes_info(with_storage_info=False)
64- for _, _, data in volumes:
65- for volume in data:
66- cloud_folder = append_path_sep(volume['path'])
67- if (folder_path.startswith(cloud_folder) or
68- cloud_folder.startswith(folder_path)):
69- returnValue(False)
70-
71- returnValue(True)
72+ return self.sd_client.validate_path(folder_path)
73
74 @log_call(logger.debug)
75 @inlineCallbacks
76@@ -787,8 +764,13 @@
77 result[name] = bool(value)
78
79 limits = yield self.sd_client.get_throttling_limits()
80- result[DOWNLOAD_KEY] = limits['download']
81- result[UPLOAD_KEY] = limits['upload']
82+ limits_enabled = yield self.sd_client.bandwidth_throttling_enabled()
83+ if limits_enabled:
84+ result[DOWNLOAD_KEY] = limits['download']
85+ result[UPLOAD_KEY] = limits['upload']
86+ else:
87+ result[DOWNLOAD_KEY] = -1
88+ result[UPLOAD_KEY] = -1
89
90 returnValue(result)
91
92
93=== removed file 'ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py'
94--- ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py 2011-10-24 21:48:27 +0000
95+++ ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py 1970-01-01 00:00:00 +0000
96@@ -1,94 +0,0 @@
97-# -*- coding: utf-8 -*-
98-
99-# Authors: Alejandro J. Cura <alecu@canonical.com>
100-# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
101-#
102-# Copyright 2010 Canonical Ltd.
103-#
104-# This program is free software: you can redistribute it and/or modify it
105-# under the terms of the GNU General Public License version 3, as published
106-# by the Free Software Foundation.
107-#
108-# This program is distributed in the hope that it will be useful, but
109-# WITHOUT ANY WARRANTY; without even the implied warranties of
110-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
111-# PURPOSE. See the GNU General Public License for more details.
112-#
113-# You should have received a copy of the GNU General Public License along
114-# with this program. If not, see <http://www.gnu.org/licenses/>.
115-
116-"""Tests for the DBus service when accessing SyncDaemon."""
117-
118-import dbus
119-
120-from twisted.internet import defer
121-
122-from ubuntuone.platform.linux import dbus_interface as sd_dbus_iface
123-
124-from ubuntuone.controlpanel import sd_client
125-from ubuntuone.controlpanel.dbustests import DBusClientTestCase
126-
127-
128-SD_DBUS_IFACE_NAME = sd_dbus_iface.DBUS_IFACE_NAME
129-SD_DBUS_IFACE_STATUS_NAME = sd_dbus_iface.DBUS_IFACE_STATUS_NAME
130-
131-# pylint, you have to go to decorator's school
132-# pylint: disable=C0322
133-
134-# Access to a protected member of a client class
135-# pylint: disable=W0212
136-
137-
138-class StatusMockDBusSyncDaemon(dbus.service.Object):
139- """A mock object that mimicks syncdaemon regarding the Status iface."""
140-
141- state_dict = {
142- 'name': 'TEST',
143- 'description': 'Some test state, nothing else.',
144- 'is_error': '',
145- 'is_connected': 'True',
146- 'is_online': '',
147- 'queues': 'GORGEOUS',
148- 'connection': '',
149- }
150-
151- def _get_current_state(self):
152- """Get the current status of the system."""
153- return self.state_dict
154-
155- @dbus.service.method(SD_DBUS_IFACE_STATUS_NAME,
156- in_signature='', out_signature='a{ss}')
157- def current_status(self):
158- """Return the current faked status of the system."""
159- return self._get_current_state()
160-
161- # pylint: disable=C0103
162- # Invalid name "StatusChanged"
163-
164- @dbus.service.signal(SD_DBUS_IFACE_STATUS_NAME)
165- def StatusChanged(self, status):
166- """Fire a signal to notify that the status of the system changed."""
167-
168- def emit_status_changed(self, state=None):
169- """Emit StatusChanged."""
170- self.StatusChanged(self._get_current_state())
171-
172-
173-class StatusTestCase(DBusClientTestCase):
174- """Test for the status dbus client methods."""
175-
176- @defer.inlineCallbacks
177- def setUp(self):
178- yield super(StatusTestCase, self).setUp()
179- self.register_mockserver(SD_DBUS_IFACE_NAME,
180- "/status", StatusMockDBusSyncDaemon)
181-
182- def test_set_status_changed_handler(self):
183- """A proper callback can be connected to StatusChanged signal."""
184- client = sd_client.SyncDaemonClient()
185- _, sig = client.set_status_changed_handler(self._set_called)
186-
187- self.assertEqual(sig._handler, self._set_called)
188- self.assertEqual(sig._member, 'StatusChanged')
189- self.assertEqual(sig._path, '/status')
190- self.assertEqual(sig._interface, SD_DBUS_IFACE_STATUS_NAME)
191
192=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
193--- ubuntuone/controlpanel/gui/__init__.py 2012-03-20 15:11:56 +0000
194+++ ubuntuone/controlpanel/gui/__init__.py 2012-04-03 13:15:23 +0000
195@@ -204,7 +204,7 @@
196 MAIN_PREFERENCES_TAB = _('Settings')
197 MAIN_WINDOW_TITLE = _('%(app_name)s Control Panel')
198 MUSIC_DISPLAY_NAME = _('Purchased Music')
199-MUSIC_REAL_PATH = '.ubuntuone/Purchased from Ubuntu One'
200+MUSIC_REAL_PATH = u'.ubuntuone/Purchased from Ubuntu One'
201 MY_FOLDERS = _('My folders')
202 NAME_NOT_SET = _('[unknown user name]')
203 NETWORK_OFFLINE = _('An internet connection is required to join or sign '
204
205=== modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py'
206--- ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-21 19:17:21 +0000
207+++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-04-03 13:15:23 +0000
208@@ -1,5 +1,5 @@
209 # -*- coding: utf-8 -*-
210-
211+#
212 # Copyright 2012 Canonical Ltd.
213 #
214 # This program is free software: you can redistribute it and/or modify it
215@@ -174,7 +174,6 @@
216 @log_call(logger.info)
217 def start_from_license(self):
218 """Use the license page as first page."""
219- # license
220- self.ui.wizard.setStartId(self.ui.wizard.pages[
221- self.ui.wizard.license_page])
222+ license_id = self.ui.wizard.pages[self.ui.wizard.license_page]
223+ self.ui.wizard.setStartId(license_id)
224 self.ui.wizard.restart()
225
226=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
227--- ubuntuone/controlpanel/gui/qt/folders.py 2012-03-20 13:05:20 +0000
228+++ ubuntuone/controlpanel/gui/qt/folders.py 2012-04-03 13:15:23 +0000
229@@ -123,7 +123,7 @@
230 self.ui.folders.headerItem().setText(FOLDER_NAME_COL,
231 FOLDERS_COLUMN_NAME)
232 self.ui.folders.headerItem().setText(SUBSCRIPTION_COL,
233- FOLDERS_COLUMN_SYNC_LOCALLY)
234+ ALWAYS_SUBSCRIBED)
235 self.ui.folders.headerItem().setText(EXPLORE_COL,
236 FOLDERS_COLUMN_EXPLORE)
237 headers = self.ui.folders.header()
238@@ -153,7 +153,8 @@
239 def load(self):
240 """Load specific tab info."""
241 self.is_processing = True
242- info = yield self.backend.volumes_info(with_storage_info=False)
243+ info = yield self.backend.volumes_info(with_storage_info=False,
244+ refresh=self.remote_folders)
245 self.process_info(info)
246
247 @handle_errors(logger=logger)
248
249=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
250--- ubuntuone/controlpanel/gui/qt/gui.py 2012-03-26 13:23:57 +0000
251+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-04-03 13:15:23 +0000
252@@ -43,8 +43,7 @@
253 self.ui.setupUi(self)
254 self.close_callback = close_callback
255 self._setup()
256- self.quit_action = QtGui.QAction(self,
257- triggered=self.close)
258+ self.quit_action = QtGui.QAction(self, triggered=self.close)
259 self.quit_action.setShortcuts(["Ctrl+q", "Ctrl+w"])
260 self.addAction(self.quit_action)
261 self.installer = installer
262
263=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
264--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-03-27 13:41:19 +0000
265+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-04-03 13:15:23 +0000
266@@ -98,7 +98,8 @@
267 icon, window = start(lambda: source.main_quit(app),
268 minimized=minimized, with_icon=with_icon,
269 installer=installer)
270- window.switch_to(switch_to)
271+ if window:
272+ window.switch_to(switch_to)
273 # pylint: enable=W0612
274 if icon:
275 app.new_instance.connect(icon.restore_window)
276
277=== modified file 'ubuntuone/controlpanel/gui/qt/main/tests/test_main.py'
278--- ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-03-27 13:49:49 +0000
279+++ ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-04-03 13:15:23 +0000
280@@ -96,8 +96,11 @@
281 self.window = None
282
283 def __call__(self, *args, **kwargs):
284+ if kwargs.get('minimized', False):
285+ self.window = None
286+ else:
287+ self.window = FakeMainWindow()
288 self.args = (args, kwargs)
289- self.window = FakeMainWindow()
290 return None, self.window
291
292
293@@ -166,6 +169,12 @@
294 self.assertEqual(self.start.args[1],
295 {'minimized': False, 'with_icon': False, 'installer': True})
296
297+ def test_minimized_with_icon_options(self):
298+ """Ensure you can be minimized and with icon at the same time."""
299+ main.main([sys.argv[0], "--minimized", "--with-icon"])
300+ self.assertEqual(self.start.args[1],
301+ {'minimized': True, 'with_icon': True, 'installer': False})
302+
303 def test_translator(self):
304 """Ensure the Qt translator is loaded."""
305 main.main([sys.argv[0]])
306
307=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_folders.py'
308--- ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-03-23 14:04:57 +0000
309+++ ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-04-03 13:15:23 +0000
310@@ -187,7 +187,8 @@
311
312 def test_info_is_requested_on_load(self):
313 """The volumes info is requested to the backend."""
314- self.assert_backend_called('volumes_info', with_storage_info=False)
315+ self.assert_backend_called('volumes_info', with_storage_info=False,
316+ refresh=self.ui.remote_folders)
317
318 def test_process_info(self):
319 """The volumes info is processed when ready."""
320@@ -409,6 +410,17 @@
321 item)
322 it += 1
323
324+ def test_tree_view_header(self):
325+ """The tree view header is hidden but the text is correct."""
326+ self.assertFalse(self.ui.ui.folders.header().isVisible())
327+
328+ name = self.ui.ui.folders.headerItem().text(gui.FOLDER_NAME_COL)
329+ self.assertEqual(name, gui.FOLDERS_COLUMN_NAME)
330+ name = self.ui.ui.folders.headerItem().text(gui.SUBSCRIPTION_COL)
331+ self.assertEqual(name, gui.ALWAYS_SUBSCRIBED)
332+ name = self.ui.ui.folders.headerItem().text(gui.EXPLORE_COL)
333+ self.assertEqual(name, gui.FOLDERS_COLUMN_EXPLORE)
334+
335 def test_share_publish_button(self):
336 """When clicking the share/publish button, the proper url is opened."""
337 self.assertTrue(self.ui.ui.share_publish_button.isVisible())
338
339=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_preferences.py'
340--- ubuntuone/controlpanel/gui/qt/tests/test_preferences.py 2012-03-01 22:05:51 +0000
341+++ ubuntuone/controlpanel/gui/qt/tests/test_preferences.py 2012-04-03 13:15:23 +0000
342@@ -76,6 +76,21 @@
343
344 self.assertFalse(self.ui.is_processing)
345
346+ def test_process_info_limit_bandwidth(self):
347+ """The ui is not processing when contents are load."""
348+ self.ui.process_info(SAMPLE_SETTINGS)
349+
350+ self.assertTrue(self.ui.ui.limit_uploads_checkbox.isChecked())
351+ self.assertTrue(self.ui.ui.limit_downloads_checkbox.isChecked())
352+
353+ settings = SAMPLE_SETTINGS.copy()
354+ settings[gui.backend.DOWNLOAD_KEY] = -1
355+ settings[gui.backend.UPLOAD_KEY] = -1
356+ self.ui.process_info(settings)
357+
358+ self.assertFalse(self.ui.ui.limit_uploads_checkbox.isChecked())
359+ self.assertFalse(self.ui.ui.limit_downloads_checkbox.isChecked())
360+
361 def test_info_is_requested_on_load(self):
362 """The info is requested to the backend."""
363 self.assert_backend_called('file_sync_settings_info')
364
365=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_wizard.py'
366--- ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-26 21:00:47 +0000
367+++ ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-04-03 13:15:23 +0000
368@@ -157,6 +157,7 @@
369 @defer.inlineCallbacks
370 def setUp(self):
371 yield super(UbuntuOneWizardTestCase, self).setUp()
372+ self.patch(gui.utils, 'uninstall_application', self.fail)
373 self.patch(self.ui.confirm_dialog, 'exec_',
374 lambda: self.confirm_response)
375
376@@ -260,7 +261,7 @@
377 button.click()
378
379 self.assertEqual(self._called, (signal_args, {}),
380- msg % (name, signal, signal_args))
381+ msg % (name, signal, signal_args))
382 self._called = False
383
384 def test_done_rejected(self):
385@@ -336,6 +337,16 @@
386 page_name = 'license'
387 stage_name = 'install'
388
389+ @defer.inlineCallbacks
390+ def setUp(self):
391+ yield super(UbuntuOneWizardLicensePage, self).setUp()
392+ self.patch(gui.utils, 'uninstall_application', self._set_called)
393+
394+ def test_done_rejected(self):
395+ """When the wizard is closed on the final page, emit rejected."""
396+ super(UbuntuOneWizardLicensePage, self).test_done_rejected()
397+ self.assertEqual(self._called, ((), {}))
398+
399
400 class UbuntuOneWizardLoginTestCase(UbuntuOneWizardTestCase):
401 """Test the login through the wizard."""
402
403=== modified file 'ubuntuone/controlpanel/gui/qt/wizard.py'
404--- ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-26 20:15:58 +0000
405+++ ubuntuone/controlpanel/gui/qt/wizard.py 2012-04-03 13:15:23 +0000
406@@ -23,7 +23,7 @@
407 from ubuntu_sso.qt.sso_wizard_page import BaseWizardPage
408 from ubuntu_sso.utils.ui import CLOSE_AND_SETUP_LATER
409
410-from ubuntuone.controlpanel import cache
411+from ubuntuone.controlpanel import cache, utils
412 from ubuntuone.controlpanel.logger import log_call, setup_logging
413 from ubuntuone.controlpanel.gui import (
414 APP_NAME,
415@@ -342,13 +342,9 @@
416 response = self.confirm_dialog.exec_()
417 if response == QtGui.QDialog.Accepted:
418 logger.warning('UbuntuOneWizard: user canceled setup.')
419- self.rejected.emit()
420- elif (self.currentId() == self.pages[self.license_page]):
421- response = self.confirm_dialog.exec_()
422- if response == QtGui.QDialog.Accepted:
423- logger.warning('UbuntuOneWizard: user wants to uninstall.')
424- # TODO: needs implementation in this project
425- ##qt.utils.uninstall_application()
426+ if self.currentId() == self.pages[self.license_page]:
427+ logger.warning('UbuntuOneWizard: user wants to uninstall.')
428+ utils.uninstall_application()
429 self.rejected.emit()
430 else:
431 super(UbuntuOneWizard, self).done(result)
432
433=== modified file 'ubuntuone/controlpanel/sd_client/__init__.py'
434--- ubuntuone/controlpanel/sd_client/__init__.py 2012-01-18 14:06:35 +0000
435+++ ubuntuone/controlpanel/sd_client/__init__.py 2012-04-03 13:15:23 +0000
436@@ -1,8 +1,6 @@
437 # -*- coding: utf-8 -*-
438-
439-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
440 #
441-# Copyright 2011 Canonical Ltd.
442+# Copyright 2012 Canonical Ltd.
443 #
444 # This program is free software: you can redistribute it and/or modify it
445 # under the terms of the GNU General Public License version 3, as published
446@@ -18,7 +16,6 @@
447
448 """The syncdaemon client."""
449
450-import sys
451 import warnings
452
453 # pylint: disable=E0611
454@@ -35,7 +32,6 @@
455
456 def __init__(self):
457 """Get a proxy for the SyncDaemonTool."""
458- self.status_changed_handler = None
459 self.proxy = tools.SyncDaemonTool()
460
461 def get_throttling_limits(self):
462@@ -128,6 +124,10 @@
463 """Retrieve the folders information from syncdaemon."""
464 return self.proxy.get_folders()
465
466+ def validate_path(self, path):
467+ """Validates 'path' to create a new folder through syncdaemon."""
468+ return self.proxy.validate_path(path)
469+
470 def create_folder(self, path):
471 """Create a new folder through syncdaemon."""
472 return self.proxy.create_folder(path)
473@@ -200,11 +200,8 @@
474
475 def set_status_changed_handler(self, handler):
476 """Set the status handler function."""
477- self.status_changed_handler = handler
478- if sys.platform.startswith("linux"):
479- # pylint: disable=W0404
480- from ubuntuone.controlpanel.sd_client import linux
481- result = linux.set_status_changed_handler(handler)
482- else:
483- result = self.proxy.set_status_changed_handler(handler)
484- return result
485+ return self.proxy.connect_signal('StatusChanged', handler)
486+
487+ def refresh_volumes(self):
488+ """Refresh the volumes information from syncdaemon."""
489+ return self.proxy.refresh_volumes()
490
491=== removed file 'ubuntuone/controlpanel/sd_client/linux.py'
492--- ubuntuone/controlpanel/sd_client/linux.py 2011-06-29 17:59:07 +0000
493+++ ubuntuone/controlpanel/sd_client/linux.py 1970-01-01 00:00:00 +0000
494@@ -1,46 +0,0 @@
495-# -*- coding: utf-8 -*-
496-
497-# Authors: Alejandro J. Cura <alecu@canonical.com>
498-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
499-#
500-# Copyright 2010 Canonical Ltd.
501-#
502-# This program is free software: you can redistribute it and/or modify it
503-# under the terms of the GNU General Public License version 3, as published
504-# by the Free Software Foundation.
505-#
506-# This program is distributed in the hope that it will be useful, but
507-# WITHOUT ANY WARRANTY; without even the implied warranties of
508-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
509-# PURPOSE. See the GNU General Public License for more details.
510-#
511-# You should have received a copy of the GNU General Public License along
512-# with this program. If not, see <http://www.gnu.org/licenses/>.
513-
514-"""Client to use other DBus services."""
515-
516-import dbus.service
517-
518-from ubuntuone.controlpanel.logger import setup_logging
519-
520-
521-logger = setup_logging('sd_client')
522-
523-
524-def get_syncdaemon_proxy(object_path, dbus_interface):
525- """Get a DBus proxy for syncdaemon at 'object_path':'dbus_interface'."""
526- logger.debug('get_syncdaemon_proxy: object_path %r, dbus_interface %r',
527- object_path, dbus_interface)
528- bus = dbus.SessionBus()
529- obj = bus.get_object(bus_name='com.ubuntuone.SyncDaemon',
530- object_path=object_path,
531- follow_name_owner_changes=True)
532- proxy = dbus.Interface(object=obj, dbus_interface=dbus_interface)
533- return proxy
534-
535-
536-def set_status_changed_handler(handler):
537- """Connect 'handler' with syncdaemon's StatusChanged signal."""
538- proxy = get_syncdaemon_proxy('/status', 'com.ubuntuone.SyncDaemon.Status')
539- sig = proxy.connect_to_signal('StatusChanged', handler)
540- return proxy, sig
541
542=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
543--- ubuntuone/controlpanel/tests/test_backend.py 2012-02-06 15:23:27 +0000
544+++ ubuntuone/controlpanel/tests/test_backend.py 2012-04-03 13:15:23 +0000
545@@ -1,9 +1,6 @@
546 # -*- coding: utf-8 -*-
547-
548-# Authors: Alejandro J. Cura <alecu@canonical.com>
549-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
550 #
551-# Copyright 2010 Canonical Ltd.
552+# Copyright 2010-2012 Canonical Ltd.
553 #
554 # This program is free software: you can redistribute it and/or modify it
555 # under the terms of the GNU General Public License version 3, as published
556@@ -169,6 +166,7 @@
557 self.actions = []
558 self.shares = []
559 self.folders = []
560+ self.volumes_refreshed = False
561
562 def get_throttling_limits(self):
563 """Return the sample speed limits."""
564@@ -291,6 +289,10 @@
565 """Unsubcribe from 'volume_id'."""
566 self.subscribed_folders.remove(volume_id)
567
568+ def validate_path(self, path):
569+ """Validate a path for folder creation."""
570+ return path != USER_HOME
571+
572 def create_folder(self, path):
573 """Grab list of folders."""
574 self.folders.append(path)
575@@ -319,6 +321,11 @@
576 """Grab list of shared (shares from the user to others)."""
577 return SAMPLE_SHARED
578
579+ def refresh_volumes(self):
580+ """Refresh the volume list."""
581+ self.volumes_refreshed = True
582+ return defer.succeed(None)
583+
584
585 class MockReplicationClient(CallRecorder):
586 """A mock replication_client module."""
587@@ -860,10 +867,19 @@
588 u'display_name': display_name,
589 }
590
591+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
592+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
593+
594 @inlineCallbacks
595- def expected_volumes(self, sample_shares, sample_folders,
596+ def expected_volumes(self, sample_shares=None, sample_folders=None,
597 with_storage_info=True):
598 """Get shares and group by sharing user, get folders and free space."""
599+ if sample_shares is None:
600+ sample_shares = self.be.sd_client.shares
601+
602+ if sample_folders is None:
603+ sample_folders = self.be.sd_client.folders
604+
605 free_bytes = self.be.FREE_BYTES_NOT_AVAILABLE
606 if with_storage_info:
607 try:
608@@ -926,11 +942,19 @@
609 @inlineCallbacks
610 def test_volumes_info(self):
611 """The volumes_info method exercises its callback."""
612- self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
613- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
614-
615- expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
616+ expected = yield self.expected_volumes()
617 result = yield self.be.volumes_info()
618+
619+ self.assertEqual(result, expected)
620+ self.assertFalse(self.be.sd_client.volumes_refreshed)
621+
622+ @inlineCallbacks
623+ def test_volumes_info_can_refresh_volumes(self):
624+ """The volumes_info can be refreshed."""
625+ expected = yield self.expected_volumes()
626+ result = yield self.be.volumes_info(refresh=True)
627+
628+ self.assertTrue(self.be.sd_client.volumes_refreshed)
629 self.assertEqual(result, expected)
630
631 @inlineCallbacks
632@@ -943,7 +967,6 @@
633 path = path[len(USER_HOME) + 1:]
634 item[u'path'] = os.path.join(root_path, path)
635 self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
636- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
637
638 yield self.be.volumes_info()
639 for item in SAMPLE_FOLDERS:
640@@ -956,11 +979,7 @@
641 @inlineCallbacks
642 def test_volumes_info_without_storage_info(self):
643 """The volumes_info method exercises its callback."""
644- self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
645- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
646-
647- expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS,
648- with_storage_info=False)
649+ expected = yield self.expected_volumes(with_storage_info=False)
650 result = yield self.be.volumes_info(with_storage_info=False)
651 self.assertEqual(result, expected)
652
653@@ -971,11 +990,8 @@
654 @inlineCallbacks
655 def test_volumes_are_cached(self):
656 """The volume list is cached."""
657- self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
658- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
659-
660 expected = {}
661- info = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
662+ info = yield self.expected_volumes()
663 for _, _, data in info:
664 for volume in data:
665 sid = volume['volume_id']
666@@ -1024,10 +1040,8 @@
667 ]
668
669 self.patch(self.be.sd_client, 'shares', read_only_shares)
670- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
671
672- expected = yield self.expected_volumes(read_only_shares,
673- SAMPLE_FOLDERS)
674+ expected = yield self.expected_volumes()
675 result = yield self.be.volumes_info()
676 self.assertEqual(result, expected)
677
678@@ -1035,10 +1049,8 @@
679 def test_volumes_info_no_quota_for_root(self):
680 """The volumes_info returns info even if quota call fails."""
681 self.be.wc.failure = 500
682- self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
683- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
684
685- expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
686+ expected = yield self.expected_volumes()
687 result = yield self.be.volumes_info()
688
689 self.assertEqual(len(result), len(expected))
690@@ -1126,29 +1138,15 @@
691 @inlineCallbacks
692 def test_create_folder(self):
693 """New folders can be created."""
694+ self.patch(self.be.sd_client, 'folders', [])
695+
696 folder_path = os.path.join(USER_HOME, 'Test Me')
697 yield self.be.create_folder(folder_path=folder_path)
698
699 self.assertEqual(self.be.sd_client.folders, [folder_path])
700
701-
702-class BackendValidatePathForFolderTestCase(BackendBasicTestCase):
703- """Test for the path validator when creating folders."""
704-
705- msg = '%r must not be a valid path for creating a folder.'
706-
707- @defer.inlineCallbacks
708- def setUp(self):
709- yield super(BackendValidatePathForFolderTestCase, self).setUp()
710- old_home = os.environ['HOME']
711- os.environ['HOME'] = USER_HOME
712- self.addCleanup(os.environ.__setitem__, 'HOME', old_home)
713- self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
714- self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
715- self.patch(backend, 'is_link', lambda p: False)
716-
717- @defer.inlineCallbacks
718- def test_valid_if_folder_inside_home(self):
719+ @defer.inlineCallbacks
720+ def test_validate_path_for_folder(self):
721 """Test proper validation of path for creating folders."""
722 folder_path = os.path.join(USER_HOME, 'Test Me')
723
724@@ -1157,68 +1155,11 @@
725 '%r must be a valid path for creating a folder.' % folder_path)
726
727 @defer.inlineCallbacks
728- def test_valid_if_folder_shares_a_prefix_with_an_udf(self):
729- """Test proper validation of path for creating folders.
730-
731- If the user chooses a folder with the same prefix as an UDF, but
732- outside every UDF, the path is valid.
733-
734- """
735- tricky_path = ROOT_PATH
736- assert not tricky_path.endswith(os.path.sep)
737- tricky_path += ' Suffix'
738- assert tricky_path.startswith(ROOT_PATH)
739-
740- result = yield self.be.validate_path_for_folder(tricky_path)
741- self.assertTrue(result,
742- '%r must be a valid path for creating a folder.' % tricky_path)
743-
744- @inlineCallbacks
745- def test_path_not_valid_if_outside_home(self):
746- """A folder outside ~ is not valid."""
747- outside_home = os.path.abspath(os.path.join(USER_HOME, os.path.pardir))
748-
749- result = yield self.be.validate_path_for_folder(outside_home)
750- self.assertFalse(result, self.msg % outside_home)
751-
752- @defer.inlineCallbacks
753- def test_not_valid_if_folder_inside_root(self):
754- """A folder inside the root is not valid."""
755- root_path = ROOT_PATH
756- # create a valid path inside the root
757- inside_root = os.path.abspath(os.path.join(root_path, 'test'))
758-
759- result = yield self.be.validate_path_for_folder(inside_root)
760- self.assertFalse(result, self.msg % inside_root)
761-
762- @defer.inlineCallbacks
763- def test_not_valid_if_folder_inside_an_udf(self):
764- """A folder inside an UDF is not valid."""
765- udf_path = SAMPLE_FOLDERS[-1]['path']
766- # create a valid path inside an existing UDF
767- inside_udf = os.path.abspath(os.path.join(udf_path, 'test'))
768-
769- result = yield self.be.validate_path_for_folder(inside_udf)
770- self.assertFalse(result, self.msg % inside_udf)
771-
772- @defer.inlineCallbacks
773- def test_not_valid_if_folder_is_parent_of_an_udf(self):
774- """A folder parent of an UDF is not valid."""
775- udf_path = SAMPLE_FOLDERS[-1]['path']
776- # create a valid path that is parent from an existing UDF
777- udf_parent = os.path.abspath(os.path.join(udf_path, os.path.pardir))
778-
779- result = yield self.be.validate_path_for_folder(udf_parent)
780- self.assertFalse(result, self.msg % udf_parent)
781-
782- @defer.inlineCallbacks
783- def test_not_valid_if_folder_is_link(self):
784- """A link path is not valid."""
785- self.patch(backend, 'is_link', lambda p: True)
786- path_link = os.path.join(USER_HOME, 'Test Me')
787-
788- result = yield self.be.validate_path_for_folder(path_link)
789- self.assertFalse(result, self.msg % path_link)
790+ def test_validate_path_for_folder_invalid(self):
791+ """Test proper validation of path for creating folders."""
792+ result = yield self.be.validate_path_for_folder(USER_HOME)
793+ self.assertFalse(result,
794+ '%r must not be a valid path for creating a folder.' % USER_HOME)
795
796
797 class BackendSyncStatusTestCase(BackendBasicTestCase):
798@@ -1598,6 +1539,7 @@
799 @inlineCallbacks
800 def test_file_sync_settings_info(self):
801 """The settings_info method exercises its callback."""
802+ self.patch(self.be.sd_client, "throttling", True)
803 self.be.sd_client.limits = {"download": 1000, "upload": 100}
804 expected = {
805 backend.AUTOCONNECT_KEY: self.be.sd_client.autoconnect,
806@@ -1614,6 +1556,25 @@
807 self.assertEqual(expected, result)
808
809 @inlineCallbacks
810+ def test_file_sync_settings_info_with_limit(self):
811+ """The settings_info method exercises its callback."""
812+ self.patch(self.be.sd_client, "throttling", False)
813+ self.be.sd_client.limits = {"download": 987456, "upload": 125698}
814+ expected = {
815+ backend.AUTOCONNECT_KEY: self.be.sd_client.autoconnect,
816+ backend.SHOW_ALL_NOTIFICATIONS_KEY:
817+ self.be.sd_client.show_all_notifications,
818+ backend.SHARE_AUTOSUBSCRIBE_KEY:
819+ self.be.sd_client.share_autosubscribe,
820+ backend.UDF_AUTOSUBSCRIBE_KEY:
821+ self.be.sd_client.udf_autosubscribe,
822+ backend.DOWNLOAD_KEY: -1,
823+ backend.UPLOAD_KEY: -1,
824+ }
825+ result = yield self.be.file_sync_settings_info()
826+ self.assertEqual(expected, result)
827+
828+ @inlineCallbacks
829 def test_change_file_sync_setting_autoconnect(self):
830 """The settings can be changed for autoconnect."""
831 yield self.assert_boolean_setting_is_correct(backend.AUTOCONNECT_KEY)
832
833=== modified file 'ubuntuone/controlpanel/tests/test_sd_client.py'
834--- ubuntuone/controlpanel/tests/test_sd_client.py 2012-03-19 17:16:43 +0000
835+++ ubuntuone/controlpanel/tests/test_sd_client.py 2012-04-03 13:15:23 +0000
836@@ -1,9 +1,6 @@
837 # -*- coding: utf-8 -*-
838-
839-# Authors: Alejandro J. Cura <alecu@canonical.com>
840-# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
841 #
842-# Copyright 2010 Canonical Ltd.
843+# Copyright 2010-2012 Canonical Ltd.
844 #
845 # This program is free software: you can redistribute it and/or modify it
846 # under the terms of the GNU General Public License version 3, as published
847@@ -24,11 +21,7 @@
848
849 from twisted.internet import defer
850 from twisted.internet.defer import inlineCallbacks, returnValue
851-from ubuntuone.devtools.testcases import skipIfNotOS
852-# No name 'tools' in module 'ubuntuone.platform'
853-# pylint: disable=E0611
854 from ubuntuone.platform import tools
855-# pylint: enable=E0611
856 from ubuntuone.syncdaemon.interaction_interfaces import bool_str
857
858 from ubuntuone.controlpanel import sd_client
859@@ -164,6 +157,10 @@
860 """Get the list of the shares "shared"/created/offered."""
861 self.called['list_shared'] = None
862
863+ def validate_path(self, path):
864+ """Validate a path for folder creation."""
865+ return path not in [f['path'] for f in self.folders.itervalues()]
866+
867 def create_folder(self, path):
868 """Create a user defined folder in the specified path."""
869 if path == '': # simulate an error
870@@ -183,14 +180,12 @@
871 @inlineCallbacks
872 def subscribe_folder(self, folder_id):
873 """Subscribe to a user defined folder given its id."""
874- yield self._set_folder_attr(folder_id,
875- u'subscribed', True)
876+ yield self._set_folder_attr(folder_id, u'subscribed', True)
877
878 @inlineCallbacks
879 def unsubscribe_folder(self, folder_id):
880 """Unsubscribe from a user defined folder given its id."""
881- yield self._set_folder_attr(folder_id,
882- u'subscribed', False)
883+ yield self._set_folder_attr(folder_id, u'subscribed', False)
884
885 @inlineCallbacks
886 def get_folders(self):
887@@ -315,9 +310,9 @@
888 """Return the shares link directory."""
889 return self.shares_dir_link
890
891- def set_status_changed_handler(self, handler):
892+ def connect_signal(self, signal_name, handler):
893 """Set the handler for the status changed signal."""
894- self.called['status_changed_handler'] = handler
895+ self.called[signal_name] = handler
896
897
898 class BaseTestCase(TestCase):
899@@ -519,6 +514,19 @@
900 yield self.assertFailure(self.sd.get_folders(), CustomError)
901
902 @inlineCallbacks
903+ def test_validate_path(self):
904+ """Check if a folder path is valid."""
905+ path = '~/bar/baz'
906+ result = yield self.sd.validate_path(path)
907+
908+ self.assertTrue(result)
909+
910+ yield self.sd.create_folder(path)
911+ result = yield self.sd.validate_path(path)
912+
913+ self.assertFalse(result)
914+
915+ @inlineCallbacks
916 def test_create_folder(self):
917 """Create a new folder."""
918 path = '~/bar/baz'
919@@ -694,14 +702,12 @@
920
921 self.assertEqual(self.sd.proxy.called['quit'], None)
922
923- @skipIfNotOS('win32', 'The tested function is only defined on windows')
924 @inlineCallbacks
925 def test_set_status_changed_handler(self):
926 """Connect a handler to the status changed signal."""
927 sample_handler = object()
928 yield self.sd.set_status_changed_handler(sample_handler)
929- self.assertEqual(sample_handler,
930- self.sd.proxy.called['status_changed_handler'])
931+ self.assertEqual(sample_handler, self.sd.proxy.called['StatusChanged'])
932
933
934 class BasicTestCase(BaseTestCase):
935
936=== modified file 'ubuntuone/controlpanel/utils/__init__.py'
937--- ubuntuone/controlpanel/utils/__init__.py 2012-03-22 23:28:19 +0000
938+++ ubuntuone/controlpanel/utils/__init__.py 2012-04-03 13:15:23 +0000
939@@ -32,19 +32,23 @@
940 # ignore issues with the name of the method
941 # pylint: disable=C0103
942
943+no_op = lambda *args, **kwargs: None
944+
945 # import the platform dependent code.
946 if sys.platform == 'win32':
947 from ubuntuone.controlpanel.utils import windows
948+ add_to_autostart = windows.add_to_autostart
949 are_updates_present = windows.are_updates_present
950 default_folders = windows.default_folders
951 perform_update = windows.perform_update
952- add_to_autostart = windows.add_to_autostart
953+ uninstall_application = windows.uninstall_application
954 else:
955 from ubuntuone.controlpanel.utils import linux
956- are_updates_present = lambda *args, **kwargs: False
957+ add_to_autostart = no_op
958+ are_updates_present = no_op
959 default_folders = linux.default_folders
960- perform_update = lambda *args, **kwargs: None
961- add_to_autostart = lambda *args, **kwargs: None
962+ perform_update = no_op
963+ uninstall_application = no_op
964
965 # pylint: enable=C0103
966
967
968=== modified file 'ubuntuone/controlpanel/utils/tests/test_windows.py'
969--- ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-23 21:20:18 +0000
970+++ ubuntuone/controlpanel/utils/tests/test_windows.py 2012-04-03 13:15:23 +0000
971@@ -1,7 +1,6 @@
972 # -*- coding: utf-8 -*-
973-
974 #
975-# Copyright 2011 Canonical Ltd.
976+# Copyright 2011-2012 Canonical Ltd.
977 #
978 # This program is free software: you can redistribute it and/or modify it
979 # under the terms of the GNU General Public License version 3, as published
980@@ -28,6 +27,34 @@
981 # let me use protected methods
982 # pylint: disable=W0212
983
984+SOME_EXE_NAME = 'foo.exe'
985+
986+
987+class FrozenTestCase(TestCase):
988+ """A base test case for handling frozen/not frozen systems."""
989+
990+ frozen = True
991+ executable = 'path/to/current/exe/ubuntuone/dist/executable.exe'
992+
993+ @defer.inlineCallbacks
994+ def setUp(self):
995+ """Set the different tests."""
996+ yield super(FrozenTestCase, self).setUp()
997+
998+ for attr_name in ('frozen', 'executable'):
999+ value_not_set = object()
1000+ value = getattr(utils.windows.sys, attr_name, value_not_set)
1001+
1002+ if self.frozen is not None:
1003+ setattr(utils.windows.sys, attr_name, getattr(self, attr_name))
1004+ elif self.frozen is None and value is not value_not_set:
1005+ delattr(utils.windows.sys, attr_name)
1006+
1007+ if self.frozen is not None and value is value_not_set:
1008+ self.addCleanup(delattr, utils.windows.sys, attr_name)
1009+ elif value is not value_not_set:
1010+ self.addCleanup(setattr, utils.windows.sys, attr_name, value)
1011+
1012
1013 class AutoupdaterTestCase(TestCase):
1014 """Test the code that is used for the auto update process."""
1015@@ -36,7 +63,9 @@
1016 def setUp(self):
1017 """Prepare for the diff tests."""
1018 yield super(AutoupdaterTestCase, self).setUp()
1019- self.auto_update_path = r'path\to\exe'
1020+ self._base_path = r'path\to\exe'
1021+ self.auto_update_path = os.path.join(self._base_path,
1022+ utils.windows.AUTOUPDATE_EXE_NAME)
1023 self.return_from_call = 0
1024 self.command = None
1025 self.args = []
1026@@ -48,8 +77,8 @@
1027 return self.return_from_call
1028
1029 self.patch(utils.windows, 'getProcessValue', fake_execute_process)
1030- self.patch(utils.windows, '_get_update_path',
1031- lambda: self.auto_update_path)
1032+ self.patch(utils.windows, 'get_exe_path',
1033+ lambda exe_name: os.path.join(self._base_path, exe_name))
1034
1035 @defer.inlineCallbacks
1036 def test_are_updates_present_true(self):
1037@@ -81,11 +110,9 @@
1038 """Test the method that performs the update."""
1039 self.patch(utils.windows.win32api, 'ShellExecute', self._set_called)
1040 utils.perform_update()
1041- self.assertIn(self.auto_update_path, self._called[0][2])
1042- self.assertEqual('runas', self._called[0][1])
1043- self.assertEqual('--unattendedmodeui none', self._called[0][3])
1044- self.assertEqual('', self._called[0][4])
1045- self.assertEqual(0, self._called[0][5])
1046+ args = (None, 'runas', self.auto_update_path,
1047+ '--unattendedmodeui none', '', 0)
1048+ self.assertEqual(self._called, (args, {}))
1049
1050
1051 class FakeOpenKey(object):
1052@@ -164,7 +191,8 @@
1053 [((self.registry.OpenKey, 'Ubuntu One', 0, 1,
1054 '"%s\\ubuntuone-syncdaemon.exe"' % path), {}),
1055 ((self.registry.OpenKey, 'Ubuntu One Icon', 0, 1,
1056- '"%s\\ubuntuone-control-panel-qt.exe" --minimized' % path),
1057+ '"%s\\ubuntuone-control-panel-qt.exe" '
1058+ '--minimized --with-icon' % path),
1059 {})])
1060
1061 def test_not_added_if_not_frozen(self):
1062@@ -192,7 +220,7 @@
1063 for name in names:
1064 name = getattr(utils.windows.shellcon, 'CSIDL_%s' % name)
1065 folder = utils.windows.shell.SHGetFolderPath(0, name, None, 0)
1066- expected.append(folder.encode('utf8'))
1067+ expected.append(folder)
1068
1069 self.assertEqual(sorted(folders), sorted(expected))
1070
1071@@ -216,15 +244,14 @@
1072 self.test_special_folders(names=('PERSONAL', 'MYMUSIC', 'MYPICTURES'))
1073
1074
1075-class GetPathTestCase(TestCase):
1076- """Test the code that is used for the auto update process."""
1077+class GetExePathTestCase(FrozenTestCase):
1078+ """Test the path calculator when sys is frozen."""
1079
1080 @defer.inlineCallbacks
1081 def setUp(self):
1082 """Set the different tests."""
1083- yield super(GetPathTestCase, self).setUp()
1084+ yield super(GetExePathTestCase, self).setUp()
1085 self.called = []
1086- self.exec_path = 'path/to/current/exe'
1087 self.exists = True
1088
1089 def fake_abspath(path):
1090@@ -247,62 +274,70 @@
1091 self.patch(utils.windows.os.path, 'dirname', fake_dirname)
1092 self.patch(utils.windows.os.path, 'exists', fake_exists)
1093
1094- def _delete_frozen_state(self):
1095- """Delete the frozen state."""
1096- del utils.windows.sys.frozen
1097- del utils.windows.sys.executable
1098-
1099- def test_get_auto_update_path_frozen(self):
1100- """Test the method used to get the autoupdate."""
1101- # patch the diff parts of sys so that we get fake paths
1102- is_frozen = hasattr(utils.windows.sys, 'frozen')
1103- if not is_frozen:
1104- utils.windows.sys.frozen = True
1105- utils.windows.sys.executable = self.exec_path
1106- self.addCleanup(self._delete_frozen_state)
1107-
1108- # called method and assert that we have the correct result
1109- path = utils.windows._get_update_path()
1110- self.assertEqual(os.path.join(self.exec_path,
1111- utils.windows.AUTOUPDATE_EXE_NAME), path)
1112- self.assertTrue('os.path.abspath' in self.called)
1113- self.assertTrue('os.path.dirname' in self.called)
1114- self.assertTrue('os.path.exists' in self.called)
1115-
1116- def _reset_frozen_state(self, old_frozen, old_exec_path):
1117- """Reset the frozen state."""
1118- utils.windows.sys.frozen = old_frozen
1119- utils.windows.sys.executable = old_exec_path
1120-
1121- def _reset__file__(self, path):
1122- """Reset the value of __file__."""
1123- utils.windows.__file__ = path
1124-
1125- def test_get_auto_update_path_not_frozen(self):
1126- """Test the method used to get the autoupdate."""
1127- is_frozen = hasattr(utils.windows.sys, 'frozen')
1128- if is_frozen:
1129- old_frozen = utils.windows.sys.frozen
1130- old_exec_path = utils.windows.sys.executable
1131- del utils.windows.sys.frozen
1132- del utils.windows.sys.executable
1133- self.addCleanup(self._reset_frozen_state, old_frozen,
1134- old_exec_path)
1135- # set a fake __file__ for the module
1136- old_file = utils.windows.__file__
1137- utils.windows.__file__ = self.exec_path
1138- self.addCleanup(self._reset__file__, old_file)
1139-
1140- path = utils.windows._get_update_path()
1141- self.assertEqual(os.path.join(self.exec_path,
1142- utils.windows.AUTOUPDATE_EXE_NAME), path)
1143- self.assertEqual(2, self.called.count('os.path.dirname'))
1144- self.assertTrue('os.path.exists' in self.called)
1145-
1146- def test_get_auto_update_path_not_present(self):
1147+ def test_get_exe_path(self):
1148+ """Test the method used to get the autoupdate."""
1149+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
1150+
1151+ self.assertEqual(os.path.join(self.executable, SOME_EXE_NAME), path)
1152+
1153+ expected = ['os.path.abspath', 'os.path.dirname', 'os.path.dirname',
1154+ 'os.path.exists']
1155+ self.assertEqual(expected, self.called)
1156+
1157+ def test_get_exe_path_not_present(self):
1158 """Test the method used to get the autoupdate."""
1159 self.exists = False
1160
1161 # called method and assert that we have the correct result
1162- path = utils.windows._get_update_path()
1163- self.assertEqual(None, path)
1164+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
1165+ self.assertTrue(path is None)
1166+
1167+
1168+class GetExePathNotFrozenTestCase(GetExePathTestCase):
1169+ """Test the path calculator when sys is not frozen."""
1170+
1171+ frozen = None
1172+
1173+ def test_get_exe_path(self):
1174+ """Test the method used to get the autoupdate."""
1175+ self.patch(utils.windows, '__file__', self.executable)
1176+
1177+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
1178+ self.assertEqual(os.path.join(self.executable, SOME_EXE_NAME), path)
1179+
1180+ expected = ['os.path.dirname', 'os.path.dirname', 'os.path.dirname',
1181+ 'os.path.exists']
1182+ self.assertEqual(expected, self.called)
1183+
1184+
1185+class UninstallApplicationTestCase(FrozenTestCase):
1186+ """Test the uninstall_application helper when sys is frozen."""
1187+
1188+ @defer.inlineCallbacks
1189+ def setUp(self):
1190+ yield super(UninstallApplicationTestCase, self).setUp()
1191+ self.patch(utils.windows.win32api, "ShellExecute", self._set_called)
1192+ self.patch(os.path, "exists", lambda path: True)
1193+
1194+ def test_uninstall(self):
1195+ """The uninstaller is run."""
1196+ utils.uninstall_application()
1197+
1198+ exe_name = utils.windows.UNINSTALL_EXE_NAME
1199+ uninstall_path = utils.windows.get_exe_path(exe_name=exe_name)
1200+ self.assertEqual(self._called,
1201+ ((None, '', uninstall_path, '--mode win32', '', 0), {}))
1202+
1203+ def test_uninstall_exe_not_present(self):
1204+ """The uninstaller is not run if not available."""
1205+ self.patch(os.path, "exists", lambda path: False)
1206+
1207+ utils.uninstall_application()
1208+
1209+ self.assertFalse(self._called)
1210+
1211+
1212+class UninstallApplicationNotFrozenTestCase(UninstallApplicationTestCase):
1213+ """Test the uninstall_application helper when sys is not frozen."""
1214+
1215+ frozen = None
1216
1217=== modified file 'ubuntuone/controlpanel/utils/windows.py'
1218--- ubuntuone/controlpanel/utils/windows.py 2012-03-23 21:48:53 +0000
1219+++ ubuntuone/controlpanel/utils/windows.py 2012-04-03 13:15:23 +0000
1220@@ -1,7 +1,6 @@
1221 # -*- coding: utf-8 -*-
1222-
1223 #
1224-# Copyright 2011 Canonical Ltd.
1225+# Copyright 2011-2012 Canonical Ltd.
1226 #
1227 # This program is free software: you can redistribute it and/or modify it
1228 # under the terms of the GNU General Public License version 3, as published
1229@@ -35,19 +34,41 @@
1230 logger = setup_logging('utils.windows')
1231 AUTOUPDATE_EXE_NAME = 'autoupdate-windows.exe'
1232 AUTORUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
1233-
1234-
1235-def _get_update_path():
1236+UNINSTALL_EXE_NAME = 'uninstall.exe'
1237+
1238+
1239+def get_exe_path(exe_name):
1240 """Return the path in which the autoupdate command is found."""
1241- if hasattr(sys, 'frozen'):
1242+ if getattr(sys, 'frozen', False):
1243 exec_path = os.path.abspath(sys.executable)
1244 else:
1245 exec_path = os.path.dirname(__file__)
1246- folder = os.path.dirname(exec_path)
1247- update_path = os.path.join(folder, AUTOUPDATE_EXE_NAME)
1248- if os.path.exists(update_path):
1249- return update_path
1250- return None
1251+
1252+ result = None
1253+ folder = os.path.dirname(os.path.dirname(exec_path))
1254+ exe_path = os.path.join(folder, exe_name)
1255+ if os.path.exists(exe_path):
1256+ result = exe_path
1257+
1258+ return result
1259+
1260+
1261+def add_to_autostart():
1262+ """Add syncdaemon to the session's autostart."""
1263+ if getattr(sys, "frozen", False):
1264+ sd_path = '"%s"' % os.path.join(os.path.dirname(
1265+ os.path.abspath(sys.executable)),
1266+ "ubuntuone-syncdaemon.exe")
1267+ u1cp_path = '"%s"' % os.path.join(os.path.dirname(
1268+ os.path.abspath(sys.executable)),
1269+ "ubuntuone-control-panel-qt.exe")
1270+
1271+ with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, AUTORUN_KEY,
1272+ 0, _winreg.KEY_ALL_ACCESS) as key:
1273+ # pylint: disable=E0602
1274+ _winreg.SetValueEx(key, "Ubuntu One", 0, _winreg.REG_SZ, sd_path)
1275+ _winreg.SetValueEx(key, "Ubuntu One Icon", 0, _winreg.REG_SZ,
1276+ u1cp_path + " --minimized --with-icon")
1277
1278
1279 @defer.inlineCallbacks
1280@@ -55,7 +76,7 @@
1281 """Return if there are updates for Ubuntu One."""
1282 result = False
1283 retcode = None
1284- update_path = _get_update_path()
1285+ update_path = get_exe_path(exe_name=AUTOUPDATE_EXE_NAME)
1286 if update_path is not None:
1287 # If there is an update present we will get 0, non-zero otherwise
1288 retcode = yield getProcessValue(update_path, args=('--mode',
1289@@ -74,7 +95,7 @@
1290 # but the latter does not support XP
1291 # (Minimum supported client: Windows Vista)
1292 get_path = lambda name: shell.SHGetFolderPath(
1293- 0, getattr(shellcon, name), None, 0).encode('utf8')
1294+ 0, getattr(shellcon, name), None, 0)
1295
1296 folders = []
1297 # More information on these constants at
1298@@ -92,27 +113,15 @@
1299
1300 def perform_update():
1301 """Spawn the autoupdate process and call the stop function."""
1302- update_path = _get_update_path()
1303+ update_path = get_exe_path(exe_name=AUTOUPDATE_EXE_NAME)
1304 if update_path is not None:
1305 # lets call the updater with the commands that are required,
1306- win32api.ShellExecute(None, 'runas',
1307- update_path,
1308- '--unattendedmodeui none', '', 0)
1309-
1310-
1311-def add_to_autostart():
1312- """Add syncdaemon to the session's autostart."""
1313- if getattr(sys, "frozen", False):
1314- sd_path = '"%s"' % os.path.join(os.path.dirname(
1315- os.path.abspath(sys.executable)),
1316- "ubuntuone-syncdaemon.exe")
1317- u1cp_path = '"%s"' % os.path.join(os.path.dirname(
1318- os.path.abspath(sys.executable)),
1319- "ubuntuone-control-panel-qt.exe")
1320-
1321- with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, AUTORUN_KEY,
1322- 0, _winreg.KEY_ALL_ACCESS) as key:
1323- # pylint: disable=E0602
1324- _winreg.SetValueEx(key, "Ubuntu One", 0, _winreg.REG_SZ, sd_path)
1325- _winreg.SetValueEx(key, "Ubuntu One Icon", 0, _winreg.REG_SZ,
1326- u1cp_path + " --minimized --with-icon")
1327+ win32api.ShellExecute(None, 'runas', update_path,
1328+ '--unattendedmodeui none', '', 0)
1329+
1330+
1331+def uninstall_application():
1332+ """Uninstall Ubuntu One."""
1333+ uninstall_path = get_exe_path(exe_name=UNINSTALL_EXE_NAME)
1334+ if uninstall_path is not None:
1335+ win32api.ShellExecute(None, '', uninstall_path, '--mode win32', '', 0)

Subscribers

People subscribed via source and target branches