Merge lp:~mandel/ubuntuone-windows-installer/uninstall-old-app into lp:ubuntuone-windows-installer
- uninstall-old-app
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Manuel de la Peña |
Approved revision: | 89 |
Merged at revision: | 78 |
Proposed branch: | lp:~mandel/ubuntuone-windows-installer/uninstall-old-app |
Merge into: | lp:ubuntuone-windows-installer |
Prerequisite: | lp:~mandel/ubuntuone-windows-installer/check-old-app |
Diff against target: |
326 lines (+194/-43) 3 files modified
ubuntuone_installer/gui/qt/utils/__init__.py (+2/-0) ubuntuone_installer/gui/qt/utils/tests/test_windows.py (+131/-18) ubuntuone_installer/gui/qt/utils/windows.py (+61/-25) |
To merge this branch: | bzr merge lp:~mandel/ubuntuone-windows-installer/uninstall-old-app |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Diego Sarmentero (community) | Approve | ||
Natalia Bidart (community) | Approve | ||
Review via email: mp+77225@code.launchpad.net |
Commit message
Provides a function that can be used to uninstall the old beta of ubuntu one.
Description of the change
Provides a function that can be used to uninstall the old beta of ubuntu one.
- 76. By Manuel de la Peña
-
Merged with parent.
- 77. By Manuel de la Peña
-
Re-merged with parent.
- 78. By Manuel de la Peña
-
Remerged with parent.
- 79. By Manuel de la Peña
-
Merged with parent.
- 80. By Manuel de la Peña
-
Merged with parent.
- 81. By Manuel de la Peña
-
Remerged with parent for lint issues.
- 82. By Manuel de la Peña
-
Fixed lint issues.
- 83. By Manuel de la Peña
-
Re-merged with parent and fixed conflicts.
- 84. By Manuel de la Peña
-
Fixed some lint issues.
- 85. By Manuel de la Peña
-
Merged with parent.
- 86. By Manuel de la Peña
-
Fixed lint issues and pep8 ones.
- 87. By Manuel de la Peña
-
Reuse the method used to get a property.
Natalia Bidart (nataliabidart) wrote : | # |
(11:51:59 AM) nessita: mandel: I'm re-reviewing uninstall-old-app. I guess that all the comments inside is_old_
(11:52:09 AM) nessita: mandel: can you please change that?
Natalia Bidart (nataliabidart) wrote : | # |
Also, this docstring:
"""Retruns the value of a fiven property from a product."""
should be written as a command (Returm the value...). Same for:
"""Will remove the old beta from the users machine."""
should be """Remove the old beta from the user's machine.""".
- 88. By Manuel de la Peña
-
Fixed docstrings and move the funtion so that is more clear for other developers.
- 89. By Manuel de la Peña
-
Fixed pep8 issues.
Natalia Bidart (nataliabidart) wrote : | # |
Looks good now!
Preview Diff
1 | === modified file 'ubuntuone_installer/gui/qt/utils/__init__.py' |
2 | --- ubuntuone_installer/gui/qt/utils/__init__.py 2011-09-29 13:26:44 +0000 |
3 | +++ ubuntuone_installer/gui/qt/utils/__init__.py 2011-10-10 15:22:19 +0000 |
4 | @@ -31,6 +31,7 @@ |
5 | is_old_beta_installed = windows.is_old_beta_installed |
6 | are_updates_present = windows.are_updates_present |
7 | perform_update = windows.perform_update |
8 | + uninstall_old_beta = windows.uninstall_old_beta |
9 | else: |
10 | from ubuntuone_installer.gui.qt.utils import linux |
11 | uninstall_application = linux.uninstall_application |
12 | @@ -40,3 +41,4 @@ |
13 | is_old_beta_installed = lambda: False |
14 | are_updates_present = lambda *args, **kwargs: False |
15 | perform_update = lambda *args, **kwargs: None |
16 | + uninstall_old_beta = lambda *args, **kwargs: None |
17 | |
18 | === modified file 'ubuntuone_installer/gui/qt/utils/tests/test_windows.py' |
19 | --- ubuntuone_installer/gui/qt/utils/tests/test_windows.py 2011-10-04 10:43:26 +0000 |
20 | +++ ubuntuone_installer/gui/qt/utils/tests/test_windows.py 2011-10-10 15:22:19 +0000 |
21 | @@ -1,6 +1,7 @@ |
22 | # -*- coding: utf-8 -*- |
23 | |
24 | # Authors: Diego Sarmentero <diego.sarmentero@canonical.com> |
25 | +# Manuel de la Pena <manuel@canonical.com> |
26 | # |
27 | # Copyright 2011 Canonical Ltd. |
28 | # |
29 | @@ -237,41 +238,153 @@ |
30 | |
31 | """Test the update from the old beta.""" |
32 | |
33 | - def _assert_is_old_beta_installed(self, return_code, expected_result): |
34 | + def _assert_is_old_beta_installed(self, return_value, expected_result): |
35 | """Helper to test the is_old_beta_installed.""" |
36 | # vars used to know that we did indeed call the msi lib correctly |
37 | called = {} |
38 | |
39 | - def fake_get_product_info(uid, property_name, property_buffer, |
40 | - size): |
41 | + def fake_get_product_property(uid, property_name): |
42 | """Fake call to the msi lib.""" |
43 | called['uid'] = uid |
44 | - called['property'] = property_name |
45 | - called['property_buffer'] = property_buffer |
46 | - called['size'] = size |
47 | - return return_code |
48 | + called['property_name'] = property_name |
49 | + return return_value |
50 | |
51 | - self.patch(utils.windows.ctypes.windll.msi, 'MsiGetProductInfoW', |
52 | - fake_get_product_info) |
53 | + self.patch(utils.windows, 'get_property_for_product', |
54 | + fake_get_product_property) |
55 | self.assertEqual(utils.is_old_beta_installed(), expected_result) |
56 | # assert that we indeed called the ctypes with the correct data |
57 | self.assertEqual(utils.windows.OLD_BETA_UID_KEY, |
58 | - called['uid'].value) |
59 | + called['uid']) |
60 | self.assertEqual(utils.windows.VERSION_STRING_PROPERTY, |
61 | - called['property']) |
62 | + called['property_name']) |
63 | |
64 | def test_is_old_beta_installed_true(self): |
65 | """Test the function used to know if it is installed.""" |
66 | - self._assert_is_old_beta_installed(utils.windows.ERROR_SUCCESS, True) |
67 | - |
68 | - def test_is_old_beta_installed_more_data(self): |
69 | - """Test the function used to know if it is installed.""" |
70 | - self._assert_is_old_beta_installed(utils.windows.ERROR_MORE_DATA, True) |
71 | + self._assert_is_old_beta_installed('SomeString', True) |
72 | |
73 | def test_is_old_beta_installed_false(self): |
74 | """Test the function used to know if it is installed.""" |
75 | - self._assert_is_old_beta_installed(utils.windows.ERROR_UNKNOWN_PRODUCT, |
76 | - False) |
77 | + self._assert_is_old_beta_installed(None, False) |
78 | + |
79 | + def _assert_get_product_property(self, uid, property_name, return_code, |
80 | + expected_result): |
81 | + """Generic test for getting the property.""" |
82 | + # vars used to know that we did indeed call the msi lib correctly |
83 | + called = {} |
84 | + |
85 | + def fake_get_product_info(uid, property_name, property_buffer, size): |
86 | + """Fake call to the msi lib.""" |
87 | + called['called_uid_buffer'] = uid |
88 | + called['called_property_name'] = property_name |
89 | + if expected_result: |
90 | + property_buffer.value = expected_result |
91 | + called['called_property_buffer'] = expected_result |
92 | + called['called_size'] = size |
93 | + return return_code |
94 | + |
95 | + self.patch(utils.windows.ctypes.windll.msi, 'MsiGetProductInfoW', |
96 | + fake_get_product_info) |
97 | + self.assertEqual(expected_result, |
98 | + utils.windows.get_property_for_product(uid, |
99 | + property_name)) |
100 | + self.assertEqual(uid, called['called_uid_buffer'], uid) |
101 | + self.assertEqual(property_name, called['called_property_name']) |
102 | + |
103 | + def test_get_product_property_present(self): |
104 | + """Test that we do indeed get the correct value.""" |
105 | + uid = u'{som-uid}' |
106 | + property_name = u'VersionString' |
107 | + return_code = 0 |
108 | + self._assert_get_product_property(uid, property_name, return_code, |
109 | + property_name) |
110 | + |
111 | + def test_get_product_property_not_present(self): |
112 | + """Test that we do get None.""" |
113 | + uid = u'{som-uid}' |
114 | + property_name = u'VersionString' |
115 | + return_code = 1212 |
116 | + self._assert_get_product_property(uid, property_name, return_code, |
117 | + None) |
118 | + |
119 | + def test_uninstall_present(self): |
120 | + """Test uninstalling when the msi is present.""" |
121 | + called = {} |
122 | + path = 'path/to/msi' |
123 | + |
124 | + def fake_get_property_for_product(uid, property_name, buf_size=None): |
125 | + """A fake method.""" |
126 | + called['property_uid'] = uid |
127 | + called['property_name'] = property_name |
128 | + return path |
129 | + |
130 | + def fake_msi_install_product(callable_fn, path, cmd): |
131 | + """A fake install function.""" |
132 | + called['callable_fn'] = callable_fn |
133 | + called['install_path'] = path |
134 | + called['cmd'] = cmd |
135 | + return defer.succeed(0) |
136 | + |
137 | + self.patch(utils.windows, 'get_property_for_product', |
138 | + fake_get_property_for_product) |
139 | + self.patch(utils.windows, 'deferToThread', |
140 | + fake_msi_install_product) |
141 | + utils.uninstall_old_beta() |
142 | + self.assertEqual(called['property_uid'], |
143 | + utils.windows.OLD_BETA_UID_KEY) |
144 | + self.assertEqual(called['property_name'], u'LocalPackage') |
145 | + self.assertEqual(utils.windows.ctypes.windll.msi.MsiInstallProductW, |
146 | + called['callable_fn']) |
147 | + self.assertEqual(called['install_path'], path) |
148 | + self.assertEqual(called['cmd'], u'REMOVE=ALL') |
149 | + |
150 | + def test_uninstall_error(self): |
151 | + """Test uninstalling with an error.""" |
152 | + called = {} |
153 | + path = 'path/to/somewhere' |
154 | + |
155 | + def fake_get_property_for_product(uid, property_name, buf_size=None): |
156 | + """A fake method.""" |
157 | + called['property_uid'] = uid |
158 | + called['property_name'] = property_name |
159 | + return path |
160 | + |
161 | + def fake_msi_install_product(callable_fn, path, cmd): |
162 | + """A fake install function.""" |
163 | + called['callable_fn'] = callable_fn |
164 | + called['install_path'] = path |
165 | + called['cmd'] = cmd |
166 | + return defer.succeed(1) |
167 | + |
168 | + self.patch(utils.windows, 'get_property_for_product', |
169 | + fake_get_property_for_product) |
170 | + self.patch(utils.windows, 'deferToThread', |
171 | + fake_msi_install_product) |
172 | + self.assertFailure(utils.uninstall_old_beta(), |
173 | + utils.windows.MsiException) |
174 | + self.assertEqual(called['property_uid'], |
175 | + utils.windows.OLD_BETA_UID_KEY) |
176 | + self.assertEqual(called['property_name'], u'LocalPackage') |
177 | + self.assertEqual(utils.windows.ctypes.windll.msi.MsiInstallProductW, |
178 | + called['callable_fn']) |
179 | + self.assertEqual(called['install_path'], path) |
180 | + self.assertEqual(called['cmd'], u'REMOVE=ALL') |
181 | + |
182 | + def test_uninstall_not_present(self): |
183 | + """Test uninstalling when the msi is missing.""" |
184 | + called = {} |
185 | + |
186 | + def fake_get_property_for_product(uid, property_name): |
187 | + """A fake method.""" |
188 | + called['uid'] = uid |
189 | + called['property_name'] = property_name |
190 | + return None |
191 | + |
192 | + self.patch(utils.windows, 'get_property_for_product', |
193 | + fake_get_property_for_product) |
194 | + self.assertFailure(utils.uninstall_old_beta(), |
195 | + utils.windows.MsiException) |
196 | + self.assertEqual(called['uid'], utils.windows.OLD_BETA_UID_KEY) |
197 | + self.assertEqual(called['property_name'], u'LocalPackage') |
198 | |
199 | |
200 | class FakeLogger(object): |
201 | |
202 | === modified file 'ubuntuone_installer/gui/qt/utils/windows.py' |
203 | --- ubuntuone_installer/gui/qt/utils/windows.py 2011-10-03 14:21:48 +0000 |
204 | +++ ubuntuone_installer/gui/qt/utils/windows.py 2011-10-10 15:22:19 +0000 |
205 | @@ -1,6 +1,7 @@ |
206 | # -*- coding: utf-8 -*- |
207 | |
208 | # Authors: Diego Sarmentero <diego.sarmentero@canonical.com> |
209 | +# Manuel de la Pena <manuel@canonical.com> |
210 | # |
211 | # Copyright 2011 Canonical Ltd. |
212 | # |
213 | @@ -34,6 +35,7 @@ |
214 | |
215 | from twisted.internet import defer |
216 | from twisted.internet.utils import getProcessValue |
217 | +from twisted.internet.threads import deferToThread |
218 | |
219 | AUTORUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run" |
220 | |
221 | @@ -41,6 +43,7 @@ |
222 | # beta is indeed present in the system, can be found in |
223 | # lp:ubuntuone-windows-installed/beta/install/UbuntuOne7.wxs |
224 | OLD_BETA_UID_KEY = '{13b7ee30-849c-11df-8395-0800200c9a66}' |
225 | +PROPERTY_BUFFER_SIZE = 256 |
226 | # error code used by ctypes.windll.msi |
227 | ERROR_MORE_DATA = 234 |
228 | ERROR_SUCCESS = 0 |
229 | @@ -49,6 +52,10 @@ |
230 | VERSION_STRING_PROPERTY = u'VersionString' |
231 | |
232 | |
233 | +class MsiException(Exception): |
234 | + """Raised when there are msi issues.""" |
235 | + |
236 | + |
237 | def uninstall_application(): |
238 | """Uninstall Ubuntu One.""" |
239 | if hasattr(sys, "frozen"): |
240 | @@ -156,32 +163,61 @@ |
241 | return [docs, music, pictures] |
242 | |
243 | |
244 | +def get_property_for_product(product, property_name, |
245 | + buf_size=PROPERTY_BUFFER_SIZE): |
246 | + """Return the value of a given property from a product.""" |
247 | + property_buffer = ctypes.create_unicode_buffer(buf_size) |
248 | + size = wintypes.DWORD(buf_size) |
249 | + result = ctypes.windll.msi.MsiGetProductInfoW(product, property_name, |
250 | + property_buffer, |
251 | + ctypes.byref(size)) |
252 | + if result == ERROR_MORE_DATA: |
253 | + return get_property_for_product(product, property_name, |
254 | + 2 * buf_size) |
255 | + elif result == ERROR_SUCCESS: |
256 | + return property_buffer.value |
257 | + else: |
258 | + # The logic of the following statement is the following. The above code |
259 | + # queried the msi system for a property regarding the msi uid. This |
260 | + # uid is the code that uniquely identifies an app in the system. When |
261 | + # working with COM properties there are different errors to take into |
262 | + # account: |
263 | + # ERROR_SUCCESS: We managed to get the data from the property. |
264 | + # ERROR_MORE_DATA: The property does exist but the buffer allocated for |
265 | + # the result is too small. |
266 | + # |
267 | + # ERROR_UNKNOWN_PROPERTY: The property does not exist. |
268 | + # To the above errors we need to add ERROR_UNKNOWN_PRODUCT which is |
269 | + # particular to msi and states that the product does not exist. |
270 | + # |
271 | + # knowing the above we can deduce that if we do not get any of the |
272 | + # above error it means that such a property does not exists and |
273 | + # therefore we return None. |
274 | + return None |
275 | + |
276 | + |
277 | def is_old_beta_installed(): |
278 | """Return whether the old beta is installed in the system.""" |
279 | # we try to get the VersisonString for the uid, if we get an error it means |
280 | # that the product is not installed in the system. |
281 | - buf_size = 256 |
282 | - uid_buffer = ctypes.create_unicode_buffer(OLD_BETA_UID_KEY) |
283 | - property_buffer = ctypes.create_unicode_buffer(buf_size) |
284 | - size = wintypes.DWORD(buf_size) |
285 | - result = ctypes.windll.msi.MsiGetProductInfoW(uid_buffer, |
286 | - VERSION_STRING_PROPERTY, |
287 | - property_buffer, |
288 | - ctypes.byref(size)) |
289 | - # The logic of the following statement is the following. The above code |
290 | - # queried the msi system for a property regarding the old beta uid. This |
291 | - # uid is the code that uniquely identifies an app in the system. When |
292 | - # working with COM properties there are different errors to take into |
293 | - # account: |
294 | - # ERROR_SUCCESS: We managed to get the data from the property. |
295 | - # ERROR_MORE_DATA: The property does exist but the buffer allocated for the |
296 | - # result is too small. |
297 | - # ERROR_UNKNOWN_PROPERTY: The property does not exist. |
298 | - # To the above errors we need to add ERROR_UNKNOWN_PRODUCT which is |
299 | - # particular to msi and states that the product does not exist. |
300 | - # |
301 | - # knowing the above we can deduce the existence of the application by |
302 | - # checking if the ERROR_SUCCESS or ERROR_MORE_DATA were returned. This |
303 | - # is because the fact that those errors are given state that the uid of the |
304 | - # msi is present and that we can get the info. |
305 | - return result in (ERROR_SUCCESS, ERROR_MORE_DATA) |
306 | + version_string = get_property_for_product(OLD_BETA_UID_KEY, |
307 | + VERSION_STRING_PROPERTY) |
308 | + return version_string is not None |
309 | + |
310 | + |
311 | +@defer.inlineCallbacks |
312 | +def uninstall_old_beta(): |
313 | + """Remove the old beta from the user's machine.""" |
314 | + # we use the msi lib to be able to uninstall apps |
315 | + property_name = u'LocalPackage' |
316 | + uninstall_path = get_property_for_product(OLD_BETA_UID_KEY, property_name) |
317 | + if uninstall_path is not None: |
318 | + # lets remove the package. |
319 | + command_line = u'REMOVE=ALL' |
320 | + result = yield deferToThread(ctypes.windll.msi.MsiInstallProductW, |
321 | + uninstall_path, command_line) |
322 | + if result != ERROR_SUCCESS: |
323 | + raise MsiException('Could not remove old beta.') |
324 | + else: |
325 | + # the local file is missing so we cannot un install it :( |
326 | + raise MsiException('Could not remove old beta.') |
The code in the existent is_old_ beta_installed should reuse the newly added get_property_ for_product, so we do not add code duplication. Likewise, tests should be refactored so there is no duplication there (I'm talking about _assert_ get_product_ property and _assert_ is_old_ beta_installed) .