Merge lp:~nataliabidart/ubuntuone-control-panel/uninstall into lp:ubuntuone-control-panel

Proposed by Natalia Bidart on 2012-03-29
Status: Merged
Approved by: Natalia Bidart on 2012-03-30
Approved revision: 310
Merged at revision: 307
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/uninstall
Merge into: lp:ubuntuone-control-panel
Diff against target: 475 lines (+174/-122)
7 files modified
ubuntuone/controlpanel/gui/qt/controlpanel.py (+3/-4)
ubuntuone/controlpanel/gui/qt/gui.py (+1/-2)
ubuntuone/controlpanel/gui/qt/tests/test_wizard.py (+12/-1)
ubuntuone/controlpanel/gui/qt/wizard.py (+4/-8)
ubuntuone/controlpanel/utils/__init__.py (+8/-4)
ubuntuone/controlpanel/utils/tests/test_windows.py (+103/-69)
ubuntuone/controlpanel/utils/windows.py (+43/-34)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/uninstall
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve on 2012-03-30
Brian Curtin (community) 2012-03-29 Approve on 2012-03-30
Review via email: mp+100012@code.launchpad.net

Commit message

- When user rejects the License, also uninstall the application (LP: #968327).

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

Merged trunk in.

308. By Natalia Bidart on 2012-03-30

Merged trunk in.

309. By Natalia Bidart on 2012-03-30

- No more runas as parameter to shellexecute.

310. By Natalia Bidart on 2012-03-30

Restoring the 'runas' param for autoupdate.

Brian Curtin (brian.curtin) wrote :

Looks fine, and IRL tests of uninstallation on Windows XP and Windows 7 both function properly with these changes.

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

Everything looks good from spain.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py'
2--- ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-21 19:17:21 +0000
3+++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-30 17:37:31 +0000
4@@ -1,5 +1,5 @@
5 # -*- coding: utf-8 -*-
6-
7+#
8 # Copyright 2012 Canonical Ltd.
9 #
10 # This program is free software: you can redistribute it and/or modify it
11@@ -174,7 +174,6 @@
12 @log_call(logger.info)
13 def start_from_license(self):
14 """Use the license page as first page."""
15- # license
16- self.ui.wizard.setStartId(self.ui.wizard.pages[
17- self.ui.wizard.license_page])
18+ license_id = self.ui.wizard.pages[self.ui.wizard.license_page]
19+ self.ui.wizard.setStartId(license_id)
20 self.ui.wizard.restart()
21
22=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
23--- ubuntuone/controlpanel/gui/qt/gui.py 2012-03-26 13:23:57 +0000
24+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-03-30 17:37:31 +0000
25@@ -43,8 +43,7 @@
26 self.ui.setupUi(self)
27 self.close_callback = close_callback
28 self._setup()
29- self.quit_action = QtGui.QAction(self,
30- triggered=self.close)
31+ self.quit_action = QtGui.QAction(self, triggered=self.close)
32 self.quit_action.setShortcuts(["Ctrl+q", "Ctrl+w"])
33 self.addAction(self.quit_action)
34 self.installer = installer
35
36=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_wizard.py'
37--- ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-26 21:00:47 +0000
38+++ ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-30 17:37:31 +0000
39@@ -157,6 +157,7 @@
40 @defer.inlineCallbacks
41 def setUp(self):
42 yield super(UbuntuOneWizardTestCase, self).setUp()
43+ self.patch(gui.utils, 'uninstall_application', self.fail)
44 self.patch(self.ui.confirm_dialog, 'exec_',
45 lambda: self.confirm_response)
46
47@@ -260,7 +261,7 @@
48 button.click()
49
50 self.assertEqual(self._called, (signal_args, {}),
51- msg % (name, signal, signal_args))
52+ msg % (name, signal, signal_args))
53 self._called = False
54
55 def test_done_rejected(self):
56@@ -336,6 +337,16 @@
57 page_name = 'license'
58 stage_name = 'install'
59
60+ @defer.inlineCallbacks
61+ def setUp(self):
62+ yield super(UbuntuOneWizardLicensePage, self).setUp()
63+ self.patch(gui.utils, 'uninstall_application', self._set_called)
64+
65+ def test_done_rejected(self):
66+ """When the wizard is closed on the final page, emit rejected."""
67+ super(UbuntuOneWizardLicensePage, self).test_done_rejected()
68+ self.assertEqual(self._called, ((), {}))
69+
70
71 class UbuntuOneWizardLoginTestCase(UbuntuOneWizardTestCase):
72 """Test the login through the wizard."""
73
74=== modified file 'ubuntuone/controlpanel/gui/qt/wizard.py'
75--- ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-26 20:15:58 +0000
76+++ ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-30 17:37:31 +0000
77@@ -23,7 +23,7 @@
78 from ubuntu_sso.qt.sso_wizard_page import BaseWizardPage
79 from ubuntu_sso.utils.ui import CLOSE_AND_SETUP_LATER
80
81-from ubuntuone.controlpanel import cache
82+from ubuntuone.controlpanel import cache, utils
83 from ubuntuone.controlpanel.logger import log_call, setup_logging
84 from ubuntuone.controlpanel.gui import (
85 APP_NAME,
86@@ -342,13 +342,9 @@
87 response = self.confirm_dialog.exec_()
88 if response == QtGui.QDialog.Accepted:
89 logger.warning('UbuntuOneWizard: user canceled setup.')
90- self.rejected.emit()
91- elif (self.currentId() == self.pages[self.license_page]):
92- response = self.confirm_dialog.exec_()
93- if response == QtGui.QDialog.Accepted:
94- logger.warning('UbuntuOneWizard: user wants to uninstall.')
95- # TODO: needs implementation in this project
96- ##qt.utils.uninstall_application()
97+ if self.currentId() == self.pages[self.license_page]:
98+ logger.warning('UbuntuOneWizard: user wants to uninstall.')
99+ utils.uninstall_application()
100 self.rejected.emit()
101 else:
102 super(UbuntuOneWizard, self).done(result)
103
104=== modified file 'ubuntuone/controlpanel/utils/__init__.py'
105--- ubuntuone/controlpanel/utils/__init__.py 2012-03-22 23:28:19 +0000
106+++ ubuntuone/controlpanel/utils/__init__.py 2012-03-30 17:37:31 +0000
107@@ -32,19 +32,23 @@
108 # ignore issues with the name of the method
109 # pylint: disable=C0103
110
111+no_op = lambda *args, **kwargs: None
112+
113 # import the platform dependent code.
114 if sys.platform == 'win32':
115 from ubuntuone.controlpanel.utils import windows
116+ add_to_autostart = windows.add_to_autostart
117 are_updates_present = windows.are_updates_present
118 default_folders = windows.default_folders
119 perform_update = windows.perform_update
120- add_to_autostart = windows.add_to_autostart
121+ uninstall_application = windows.uninstall_application
122 else:
123 from ubuntuone.controlpanel.utils import linux
124- are_updates_present = lambda *args, **kwargs: False
125+ add_to_autostart = no_op
126+ are_updates_present = no_op
127 default_folders = linux.default_folders
128- perform_update = lambda *args, **kwargs: None
129- add_to_autostart = lambda *args, **kwargs: None
130+ perform_update = no_op
131+ uninstall_application = no_op
132
133 # pylint: enable=C0103
134
135
136=== modified file 'ubuntuone/controlpanel/utils/tests/test_windows.py'
137--- ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-27 18:55:37 +0000
138+++ ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-30 17:37:31 +0000
139@@ -1,7 +1,6 @@
140 # -*- coding: utf-8 -*-
141-
142 #
143-# Copyright 2011 Canonical Ltd.
144+# Copyright 2011-2012 Canonical Ltd.
145 #
146 # This program is free software: you can redistribute it and/or modify it
147 # under the terms of the GNU General Public License version 3, as published
148@@ -28,6 +27,34 @@
149 # let me use protected methods
150 # pylint: disable=W0212
151
152+SOME_EXE_NAME = 'foo.exe'
153+
154+
155+class FrozenTestCase(TestCase):
156+ """A base test case for handling frozen/not frozen systems."""
157+
158+ frozen = True
159+ executable = 'path/to/current/exe/ubuntuone/dist/executable.exe'
160+
161+ @defer.inlineCallbacks
162+ def setUp(self):
163+ """Set the different tests."""
164+ yield super(FrozenTestCase, self).setUp()
165+
166+ for attr_name in ('frozen', 'executable'):
167+ value_not_set = object()
168+ value = getattr(utils.windows.sys, attr_name, value_not_set)
169+
170+ if self.frozen is not None:
171+ setattr(utils.windows.sys, attr_name, getattr(self, attr_name))
172+ elif self.frozen is None and value is not value_not_set:
173+ delattr(utils.windows.sys, attr_name)
174+
175+ if self.frozen is not None and value is value_not_set:
176+ self.addCleanup(delattr, utils.windows.sys, attr_name)
177+ elif value is not value_not_set:
178+ self.addCleanup(setattr, utils.windows.sys, attr_name, value)
179+
180
181 class AutoupdaterTestCase(TestCase):
182 """Test the code that is used for the auto update process."""
183@@ -36,7 +63,9 @@
184 def setUp(self):
185 """Prepare for the diff tests."""
186 yield super(AutoupdaterTestCase, self).setUp()
187- self.auto_update_path = r'path\to\exe'
188+ self._base_path = r'path\to\exe'
189+ self.auto_update_path = os.path.join(self._base_path,
190+ utils.windows.AUTOUPDATE_EXE_NAME)
191 self.return_from_call = 0
192 self.command = None
193 self.args = []
194@@ -48,8 +77,8 @@
195 return self.return_from_call
196
197 self.patch(utils.windows, 'getProcessValue', fake_execute_process)
198- self.patch(utils.windows, '_get_update_path',
199- lambda: self.auto_update_path)
200+ self.patch(utils.windows, 'get_exe_path',
201+ lambda exe_name: os.path.join(self._base_path, exe_name))
202
203 @defer.inlineCallbacks
204 def test_are_updates_present_true(self):
205@@ -81,11 +110,9 @@
206 """Test the method that performs the update."""
207 self.patch(utils.windows.win32api, 'ShellExecute', self._set_called)
208 utils.perform_update()
209- self.assertIn(self.auto_update_path, self._called[0][2])
210- self.assertEqual('runas', self._called[0][1])
211- self.assertEqual('--unattendedmodeui none', self._called[0][3])
212- self.assertEqual('', self._called[0][4])
213- self.assertEqual(0, self._called[0][5])
214+ args = (None, 'runas', self.auto_update_path,
215+ '--unattendedmodeui none', '', 0)
216+ self.assertEqual(self._called, (args, {}))
217
218
219 class FakeOpenKey(object):
220@@ -217,15 +244,14 @@
221 self.test_special_folders(names=('PERSONAL', 'MYMUSIC', 'MYPICTURES'))
222
223
224-class GetPathTestCase(TestCase):
225- """Test the code that is used for the auto update process."""
226+class GetExePathTestCase(FrozenTestCase):
227+ """Test the path calculator when sys is frozen."""
228
229 @defer.inlineCallbacks
230 def setUp(self):
231 """Set the different tests."""
232- yield super(GetPathTestCase, self).setUp()
233+ yield super(GetExePathTestCase, self).setUp()
234 self.called = []
235- self.exec_path = 'path/to/current/exe'
236 self.exists = True
237
238 def fake_abspath(path):
239@@ -248,62 +274,70 @@
240 self.patch(utils.windows.os.path, 'dirname', fake_dirname)
241 self.patch(utils.windows.os.path, 'exists', fake_exists)
242
243- def _delete_frozen_state(self):
244- """Delete the frozen state."""
245- del utils.windows.sys.frozen
246- del utils.windows.sys.executable
247-
248- def test_get_auto_update_path_frozen(self):
249- """Test the method used to get the autoupdate."""
250- # patch the diff parts of sys so that we get fake paths
251- is_frozen = hasattr(utils.windows.sys, 'frozen')
252- if not is_frozen:
253- utils.windows.sys.frozen = True
254- utils.windows.sys.executable = self.exec_path
255- self.addCleanup(self._delete_frozen_state)
256-
257- # called method and assert that we have the correct result
258- path = utils.windows._get_update_path()
259- self.assertEqual(os.path.join(self.exec_path,
260- utils.windows.AUTOUPDATE_EXE_NAME), path)
261- self.assertTrue('os.path.abspath' in self.called)
262- self.assertTrue('os.path.dirname' in self.called)
263- self.assertTrue('os.path.exists' in self.called)
264-
265- def _reset_frozen_state(self, old_frozen, old_exec_path):
266- """Reset the frozen state."""
267- utils.windows.sys.frozen = old_frozen
268- utils.windows.sys.executable = old_exec_path
269-
270- def _reset__file__(self, path):
271- """Reset the value of __file__."""
272- utils.windows.__file__ = path
273-
274- def test_get_auto_update_path_not_frozen(self):
275- """Test the method used to get the autoupdate."""
276- is_frozen = hasattr(utils.windows.sys, 'frozen')
277- if is_frozen:
278- old_frozen = utils.windows.sys.frozen
279- old_exec_path = utils.windows.sys.executable
280- del utils.windows.sys.frozen
281- del utils.windows.sys.executable
282- self.addCleanup(self._reset_frozen_state, old_frozen,
283- old_exec_path)
284- # set a fake __file__ for the module
285- old_file = utils.windows.__file__
286- utils.windows.__file__ = self.exec_path
287- self.addCleanup(self._reset__file__, old_file)
288-
289- path = utils.windows._get_update_path()
290- self.assertEqual(os.path.join(self.exec_path,
291- utils.windows.AUTOUPDATE_EXE_NAME), path)
292- self.assertEqual(2, self.called.count('os.path.dirname'))
293- self.assertTrue('os.path.exists' in self.called)
294-
295- def test_get_auto_update_path_not_present(self):
296+ def test_get_exe_path(self):
297+ """Test the method used to get the autoupdate."""
298+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
299+
300+ self.assertEqual(os.path.join(self.executable, SOME_EXE_NAME), path)
301+
302+ expected = ['os.path.abspath', 'os.path.dirname', 'os.path.dirname',
303+ 'os.path.exists']
304+ self.assertEqual(expected, self.called)
305+
306+ def test_get_exe_path_not_present(self):
307 """Test the method used to get the autoupdate."""
308 self.exists = False
309
310 # called method and assert that we have the correct result
311- path = utils.windows._get_update_path()
312- self.assertEqual(None, path)
313+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
314+ self.assertTrue(path is None)
315+
316+
317+class GetExePathNotFrozenTestCase(GetExePathTestCase):
318+ """Test the path calculator when sys is not frozen."""
319+
320+ frozen = None
321+
322+ def test_get_exe_path(self):
323+ """Test the method used to get the autoupdate."""
324+ self.patch(utils.windows, '__file__', self.executable)
325+
326+ path = utils.windows.get_exe_path(exe_name=SOME_EXE_NAME)
327+ self.assertEqual(os.path.join(self.executable, SOME_EXE_NAME), path)
328+
329+ expected = ['os.path.dirname', 'os.path.dirname', 'os.path.dirname',
330+ 'os.path.exists']
331+ self.assertEqual(expected, self.called)
332+
333+
334+class UninstallApplicationTestCase(FrozenTestCase):
335+ """Test the uninstall_application helper when sys is frozen."""
336+
337+ @defer.inlineCallbacks
338+ def setUp(self):
339+ yield super(UninstallApplicationTestCase, self).setUp()
340+ self.patch(utils.windows.win32api, "ShellExecute", self._set_called)
341+ self.patch(os.path, "exists", lambda path: True)
342+
343+ def test_uninstall(self):
344+ """The uninstaller is run."""
345+ utils.uninstall_application()
346+
347+ exe_name = utils.windows.UNINSTALL_EXE_NAME
348+ uninstall_path = utils.windows.get_exe_path(exe_name=exe_name)
349+ self.assertEqual(self._called,
350+ ((None, '', uninstall_path, '--mode win32', '', 0), {}))
351+
352+ def test_uninstall_exe_not_present(self):
353+ """The uninstaller is not run if not available."""
354+ self.patch(os.path, "exists", lambda path: False)
355+
356+ utils.uninstall_application()
357+
358+ self.assertFalse(self._called)
359+
360+
361+class UninstallApplicationNotFrozenTestCase(UninstallApplicationTestCase):
362+ """Test the uninstall_application helper when sys is not frozen."""
363+
364+ frozen = None
365
366=== modified file 'ubuntuone/controlpanel/utils/windows.py'
367--- ubuntuone/controlpanel/utils/windows.py 2012-03-23 21:48:53 +0000
368+++ ubuntuone/controlpanel/utils/windows.py 2012-03-30 17:37:31 +0000
369@@ -1,7 +1,6 @@
370 # -*- coding: utf-8 -*-
371-
372 #
373-# Copyright 2011 Canonical Ltd.
374+# Copyright 2011-2012 Canonical Ltd.
375 #
376 # This program is free software: you can redistribute it and/or modify it
377 # under the terms of the GNU General Public License version 3, as published
378@@ -35,19 +34,41 @@
379 logger = setup_logging('utils.windows')
380 AUTOUPDATE_EXE_NAME = 'autoupdate-windows.exe'
381 AUTORUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
382-
383-
384-def _get_update_path():
385+UNINSTALL_EXE_NAME = 'uninstall.exe'
386+
387+
388+def get_exe_path(exe_name):
389 """Return the path in which the autoupdate command is found."""
390- if hasattr(sys, 'frozen'):
391+ if getattr(sys, 'frozen', False):
392 exec_path = os.path.abspath(sys.executable)
393 else:
394 exec_path = os.path.dirname(__file__)
395- folder = os.path.dirname(exec_path)
396- update_path = os.path.join(folder, AUTOUPDATE_EXE_NAME)
397- if os.path.exists(update_path):
398- return update_path
399- return None
400+
401+ result = None
402+ folder = os.path.dirname(os.path.dirname(exec_path))
403+ exe_path = os.path.join(folder, exe_name)
404+ if os.path.exists(exe_path):
405+ result = exe_path
406+
407+ return result
408+
409+
410+def add_to_autostart():
411+ """Add syncdaemon to the session's autostart."""
412+ if getattr(sys, "frozen", False):
413+ sd_path = '"%s"' % os.path.join(os.path.dirname(
414+ os.path.abspath(sys.executable)),
415+ "ubuntuone-syncdaemon.exe")
416+ u1cp_path = '"%s"' % os.path.join(os.path.dirname(
417+ os.path.abspath(sys.executable)),
418+ "ubuntuone-control-panel-qt.exe")
419+
420+ with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, AUTORUN_KEY,
421+ 0, _winreg.KEY_ALL_ACCESS) as key:
422+ # pylint: disable=E0602
423+ _winreg.SetValueEx(key, "Ubuntu One", 0, _winreg.REG_SZ, sd_path)
424+ _winreg.SetValueEx(key, "Ubuntu One Icon", 0, _winreg.REG_SZ,
425+ u1cp_path + " --minimized --with-icon")
426
427
428 @defer.inlineCallbacks
429@@ -55,7 +76,7 @@
430 """Return if there are updates for Ubuntu One."""
431 result = False
432 retcode = None
433- update_path = _get_update_path()
434+ update_path = get_exe_path(exe_name=AUTOUPDATE_EXE_NAME)
435 if update_path is not None:
436 # If there is an update present we will get 0, non-zero otherwise
437 retcode = yield getProcessValue(update_path, args=('--mode',
438@@ -92,27 +113,15 @@
439
440 def perform_update():
441 """Spawn the autoupdate process and call the stop function."""
442- update_path = _get_update_path()
443+ update_path = get_exe_path(exe_name=AUTOUPDATE_EXE_NAME)
444 if update_path is not None:
445 # lets call the updater with the commands that are required,
446- win32api.ShellExecute(None, 'runas',
447- update_path,
448- '--unattendedmodeui none', '', 0)
449-
450-
451-def add_to_autostart():
452- """Add syncdaemon to the session's autostart."""
453- if getattr(sys, "frozen", False):
454- sd_path = '"%s"' % os.path.join(os.path.dirname(
455- os.path.abspath(sys.executable)),
456- "ubuntuone-syncdaemon.exe")
457- u1cp_path = '"%s"' % os.path.join(os.path.dirname(
458- os.path.abspath(sys.executable)),
459- "ubuntuone-control-panel-qt.exe")
460-
461- with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, AUTORUN_KEY,
462- 0, _winreg.KEY_ALL_ACCESS) as key:
463- # pylint: disable=E0602
464- _winreg.SetValueEx(key, "Ubuntu One", 0, _winreg.REG_SZ, sd_path)
465- _winreg.SetValueEx(key, "Ubuntu One Icon", 0, _winreg.REG_SZ,
466- u1cp_path + " --minimized --with-icon")
467+ win32api.ShellExecute(None, 'runas', update_path,
468+ '--unattendedmodeui none', '', 0)
469+
470+
471+def uninstall_application():
472+ """Uninstall Ubuntu One."""
473+ uninstall_path = get_exe_path(exe_name=UNINSTALL_EXE_NAME)
474+ if uninstall_path is not None:
475+ win32api.ShellExecute(None, '', uninstall_path, '--mode win32', '', 0)

Subscribers

People subscribed via source and target branches