Merge lp:~nataliabidart/ubuntuone-control-panel/remove-devices into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: dobey
Approved revision: 44
Merged at revision: 38
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/remove-devices
Merge into: lp:ubuntuone-control-panel
Prerequisite: lp:~nataliabidart/ubuntuone-control-panel/new-sso-iface
Diff against target: 857 lines (+301/-79)
7 files modified
data/device.ui (+13/-11)
ubuntuone/controlpanel/__init__.py (+1/-1)
ubuntuone/controlpanel/backend.py (+37/-12)
ubuntuone/controlpanel/gtk/gui.py (+75/-18)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+142/-31)
ubuntuone/controlpanel/tests/test_backend.py (+26/-1)
ubuntuone/controlpanel/webclient.py (+7/-5)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/remove-devices
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Roberto Alsina (community) fieldtest Approve
Review via email: mp+44139@code.launchpad.net

Commit message

* Devices can now be removed (LP: #691295).

Description of the change

To run the tests, do:

./run-tests

To test IRL, open 2 terminals pointing to this branch, and run:

terminal 1: DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend
terminal 2: DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-gtk

and play with the 3rd tab. The 'Remove' button is fully functional so be careful.

Among the suggested tests if removing the local device (ie the computer you're testing on). You should be taken to the overview panel after that to re-add your account.

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

It works.

review: Approve (fieldtest)
Revision history for this message
Manuel de la Peña (mandel) wrote :

+1

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (23.0 KiB)

The attempt to merge lp:~nataliabidart/ubuntuone-control-panel/remove-devices into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.

Running test suite for ubuntuone/controlpanel/tests
ubuntuone.controlpanel.tests.test_backend
  BackendAccountTestCase
    test_account_info ... [OK]
    test_account_info_fails ... [OK]
    test_backend_creation ... [OK]
    test_device_is_local ... [OK]
    test_get_token ... [OK]
  BackendBasicTestCase
    test_backend_creation ... [OK]
    test_device_is_local ... [OK]
    test_get_token ... [OK]
  BackendDevicesTestCase
    test_backend_creation ... [OK]
    test_change_download_speed_limit ... [OK]
    test_change_limit_bandwidth ... [OK]
    test_change_upload_speed_limit ... [OK]
    test_changing_settings_for_wrong_id_has_no_effect ... [OK]
    test_device_is_local ... [OK]
    test_devices_info ... [OK]
    test_devices_info_fails ... [OK]
    test_get_token ... [OK]
    test_remove_device ... [OK]
    test_remove_device_clear_credentials_if_local_device ... [OK]
    test_remove_device_fails ... [OK]
  BackendSyncStatusTestCase
    test_backend_creation ... [OK]
    test_device_is_local ... [OK]
    test_disabled ... [OK]
    test_disconnected ... [OK]
    test_error ... [OK]
    test_get_token ... [OK]
    test_idle ... [OK]
    test_starting_when_init_not_user ... [OK]
    test_starting_when_init_with_user ... [OK]
    test_starting_when_local_rescan_not_user ... [OK]
    test_starting_when_local_rescan_with_user ... [OK]
    test_starting_when_ready_with_user ... [OK]
    test_status_changed ... [OK]
    test_syncing_even_if_not_online ... [OK]
    test_syncing_if_online ... [OK]
    test_unknown ... ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/device.ui'
2--- data/device.ui 2010-12-16 20:56:57 +0000
3+++ data/device.ui 2010-12-18 17:42:12 +0000
4@@ -2,6 +2,14 @@
5 <interface>
6 <requires lib="gtk+" version="2.16"/>
7 <!-- interface-naming-policy project-wide -->
8+ <object class="GtkAdjustment" id="adjustment2">
9+ <property name="upper">10000</property>
10+ <property name="step_increment">1</property>
11+ </object>
12+ <object class="GtkAdjustment" id="adjustment1">
13+ <property name="upper">10000</property>
14+ <property name="step_increment">1</property>
15+ </object>
16 <object class="GtkVBox" id="itself">
17 <property name="visible">True</property>
18 <property name="spacing">5</property>
19@@ -74,7 +82,7 @@
20 <property name="can_focus">True</property>
21 <property name="receives_default">False</property>
22 <property name="draw_indicator">True</property>
23- <signal name="toggled" handler="on_limit_bandwidth_toggled"/>
24+ <signal name="toggled" handler="on_limit_bandwidth_toggled" swapped="no"/>
25 </object>
26 </child>
27 <child>
28@@ -108,7 +116,7 @@
29 <property name="invisible_char">•</property>
30 <property name="activates_default">True</property>
31 <property name="adjustment">adjustment1</property>
32- <signal name="value_changed" handler="on_max_upload_speed_value_changed"/>
33+ <signal name="value-changed" handler="on_max_upload_speed_value_changed" swapped="no"/>
34 </object>
35 <packing>
36 <property name="left_attach">1</property>
37@@ -126,7 +134,7 @@
38 <property name="invisible_char">•</property>
39 <property name="activates_default">True</property>
40 <property name="adjustment">adjustment2</property>
41- <signal name="value_changed" handler="on_max_download_speed_value_changed"/>
42+ <signal name="value-changed" handler="on_max_download_speed_value_changed" swapped="no"/>
43 </object>
44 <packing>
45 <property name="left_attach">1</property>
46@@ -161,6 +169,8 @@
47 <property name="can_focus">True</property>
48 <property name="receives_default">True</property>
49 <property name="use_stock">True</property>
50+ <signal name="activate" handler="on_remove_clicked" swapped="no"/>
51+ <signal name="clicked" handler="on_remove_clicked" swapped="no"/>
52 </object>
53 <packing>
54 <property name="expand">False</property>
55@@ -190,12 +200,4 @@
56 </packing>
57 </child>
58 </object>
59- <object class="GtkAdjustment" id="adjustment1">
60- <property name="upper">10000</property>
61- <property name="step_increment">1</property>
62- </object>
63- <object class="GtkAdjustment" id="adjustment2">
64- <property name="upper">10000</property>
65- <property name="step_increment">1</property>
66- </object>
67 </interface>
68
69=== modified file 'ubuntuone/controlpanel/__init__.py'
70--- ubuntuone/controlpanel/__init__.py 2010-10-08 01:31:34 +0000
71+++ ubuntuone/controlpanel/__init__.py 2010-12-18 17:42:12 +0000
72@@ -29,4 +29,4 @@
73 DBUS_PREFERENCES_PATH = "/preferences"
74 DBUS_PREFERENCES_IFACE = "com.ubuntuone.controlpanel.Preferences"
75
76-WEBSERVICE_BASE_URL = "https://one.ubuntu.com/api/"
77+WEBSERVICE_BASE_URL = u"https://one.ubuntu.com/api/"
78
79=== modified file 'ubuntuone/controlpanel/backend.py'
80--- ubuntuone/controlpanel/backend.py 2010-12-18 17:42:11 +0000
81+++ ubuntuone/controlpanel/backend.py 2010-12-18 17:42:12 +0000
82@@ -49,6 +49,11 @@
83 STATUS_KEY = 'status'
84
85
86+def bool_str(value):
87+ """Return the string representation of a bool (dbus-compatible)."""
88+ return 'True' if value else ''
89+
90+
91 class ControlBackend(object):
92 """The control panel backend."""
93
94@@ -124,6 +129,15 @@
95 credentials = yield dbus_client.get_credentials()
96 returnValue(credentials["token"])
97
98+ @inlineCallbacks
99+ def device_is_local(self, device_id):
100+ """Return whether 'device_id' is the local devicew or not."""
101+ dtype, did = self.type_n_id(device_id)
102+ local_token = yield self.get_token()
103+ is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)
104+ logger.info('device_is_local: result %r, ', is_local)
105+ returnValue(is_local)
106+
107 @log_call(logger.debug)
108 @inlineCallbacks
109 def account_info(self):
110@@ -146,7 +160,6 @@
111 def devices_info(self):
112 """Get the user devices info."""
113 result = []
114- this_token = yield self.get_token()
115 limit_bw = yield dbus_client.bandwidth_throttling_enabled()
116 limits = yield dbus_client.get_throttling_limits()
117
118@@ -159,16 +172,22 @@
119 di["configurable"] = ''
120 if di["type"] == DEVICE_TYPE_COMPUTER:
121 di["device_id"] = di["type"] + d["token"]
122- if d["token"] == this_token:
123- di["configurable"] = 'True'
124- di["limit_bandwidth"] = 'True' if limit_bw else ''
125- upload = limits["upload"]
126- download = limits["download"]
127- di[UPLOAD_KEY] = str(upload)
128- di[DOWNLOAD_KEY] = str(download)
129 if di["type"] == DEVICE_TYPE_PHONE:
130 di["device_id"] = di["type"] + str(d["id"])
131
132+ is_local = yield self.device_is_local(di["device_id"])
133+ di["is_local"] = bool_str(is_local)
134+ # currently, only local devices are configurable.
135+ # eventually, more the devices will be configurable.
136+ di["configurable"] = bool_str(is_local)
137+
138+ if bool(di["configurable"]):
139+ di["limit_bandwidth"] = bool_str(limit_bw)
140+ upload = limits["upload"]
141+ download = limits["download"]
142+ di[UPLOAD_KEY] = str(upload)
143+ di[DOWNLOAD_KEY] = str(download)
144+
145 # date_added is not in the webservice yet (LP: #673668)
146 # di["date_added"] = ""
147
148@@ -190,9 +209,7 @@
149 @inlineCallbacks
150 def change_device_settings(self, device_id, settings):
151 """Change the settings for the given device."""
152- dtype, did = self.type_n_id(device_id)
153- local_token = yield self.get_token()
154- is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)
155+ is_local = yield self.device_is_local(device_id)
156
157 if is_local and "limit_bandwidth" in settings:
158 limit_bw = bool(settings["limit_bandwidth"])
159@@ -222,8 +239,16 @@
160 def remove_device(self, device_id):
161 """Remove a device's tokens from the sso server."""
162 dtype, did = self.type_n_id(device_id)
163- api = DEVICE_REMOVE_API % (dtype, did)
164+ is_local = yield self.device_is_local(device_id)
165+
166+ api = DEVICE_REMOVE_API % (dtype.lower(), did)
167 yield self.wc.call_api(api)
168+
169+ if is_local:
170+ logger.warning('remove_device: device is local, id %r, '
171+ 'clearing credentials.', device_id)
172+ yield dbus_client.clear_credentials()
173+
174 returnValue(device_id)
175
176 @log_call(logger.debug)
177
178=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
179--- ubuntuone/controlpanel/gtk/gui.py 2010-12-17 17:25:10 +0000
180+++ ubuntuone/controlpanel/gtk/gui.py 2010-12-18 17:42:12 +0000
181@@ -48,7 +48,7 @@
182 from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
183 DBUS_PREFERENCES_IFACE)
184 from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
185- DEVICE_TYPE_COMPUTER)
186+ DEVICE_TYPE_COMPUTER, bool_str)
187 from ubuntuone.controlpanel.logger import setup_logging, log_call
188 from ubuntuone.controlpanel.utils import get_data_file
189
190@@ -74,11 +74,6 @@
191 KILOBYTES = 1024
192
193
194-def bool_str(value):
195- """Return the string representation of a bool (dbus-compatible)."""
196- return 'True' if value else ''
197-
198-
199 def filter_by_app_name(f):
200 """Excecute 'f' filtering by app_name."""
201
202@@ -187,15 +182,23 @@
203 self._window_id = window_id
204
205 self.management = None
206- self.overview = OverviewPanel(window_id=window_id)
207+ self.overview = None
208+ self.on_show_overview_panel()
209+ self.show()
210+
211+ def on_show_overview_panel(self, widget=None):
212+ """Show the overview panel."""
213+ for child in self.get_children():
214+ self.remove(child)
215+
216+ self.overview = OverviewPanel(window_id=self._window_id)
217 self.overview.connect('credentials-found',
218 self.on_show_management_panel)
219 self.add(self.overview)
220- self.show()
221
222 def on_show_management_panel(self, widget=None,
223 credentials_are_new=False, token=None):
224- """Show the netbook (main panel)."""
225+ """Show the notebook (main panel)."""
226 if self.overview in self.get_children():
227 self.remove(self.overview)
228
229@@ -206,6 +209,9 @@
230
231 self.add(self.management)
232
233+ self.management.connect('local-device-removed',
234+ self.on_show_overview_panel)
235+
236
237 class UbuntuOneBin(gtk.VBox):
238 """A Ubuntu One bin."""
239@@ -505,10 +511,11 @@
240
241
242 class Device(gtk.VBox, ControlPanelMixin):
243- """The devices panel."""
244+ """The device widget."""
245
246 DEVICE_CHANGE_ERROR = _('The settings could not be changed,\n'
247 'previous values were restored.')
248+ DEVICE_REMOVAL_ERROR = _('The device could not be removed.')
249
250 def __init__(self):
251 gtk.VBox.__init__(self)
252@@ -516,19 +523,25 @@
253
254 self._updating = False
255 self._last_settings = {}
256+ self.id = None
257+ self.is_local = False
258 self.configurable = False
259
260- self.update(device_id='', device_name='', limit_bandwidth=False,
261+ self.update(device_id=None, device_name='',
262+ is_local=False, configurable=False, limit_bandwidth=False,
263 max_upload_speed=0, max_download_speed=0)
264
265 self.add(self.itself)
266- self.device_id.hide()
267 self.show()
268
269 self.backend.connect_to_signal('DeviceSettingsChanged',
270 self.on_device_settings_changed)
271 self.backend.connect_to_signal('DeviceSettingsChangeError',
272 self.on_device_settings_change_error)
273+ self.backend.connect_to_signal('DeviceRemoved',
274+ self.on_device_removed)
275+ self.backend.connect_to_signal('DeviceRemovalError',
276+ self.on_device_removal_error)
277
278 def _change_device_settings(self, *args):
279 """Update backend settings for this device."""
280@@ -538,8 +551,7 @@
281 # Not disabling the GUI to avoid annyong twitchings
282 #self.set_sensitive(False)
283 self.warning_label.set_text('')
284- self.backend.change_device_settings(self.device_id.get_text(),
285- self.__dict__)
286+ self.backend.change_device_settings(self.id, self.__dict__)
287
288 def _block_signals(f):
289 """Execute 'f' while having the _updating flag set."""
290@@ -563,6 +575,11 @@
291 on_max_upload_speed_value_changed = _change_device_settings
292 on_max_download_speed_value_changed = _change_device_settings
293
294+ def on_remove_clicked(self, widget):
295+ """Remove button was clicked or activated."""
296+ self.backend.remove_device(self.id)
297+ self.set_sensitive(False)
298+
299 @_block_signals
300 def update(self, **kwargs):
301 """Update according to named parameters.
302@@ -571,6 +588,7 @@
303 * device_id (string, not shown to the user)
304 * device_name (string)
305 * type (either DEVICE_TYPE_PHONE or DEVICE_TYPE_COMPUTER)
306+ * is_local (True/False)
307 * configurable (True/False)
308 * if configurable, the following can be set:
309 * limit_bandwidth (True/False)
310@@ -579,7 +597,7 @@
311
312 """
313 if 'device_id' in kwargs:
314- self.device_id.set_text(kwargs['device_id'])
315+ self.id = kwargs['device_id']
316
317 if 'device_name' in kwargs:
318 self.device_name.set_markup('<b>%s</b>' % kwargs['device_name'])
319@@ -590,6 +608,9 @@
320 self.device_type.set_from_icon_name(dtype.lower(),
321 gtk.ICON_SIZE_BUTTON)
322
323+ if 'is_local' in kwargs:
324+ self.is_local = bool(kwargs['is_local'])
325+
326 if 'configurable' in kwargs:
327 self.configurable = bool(kwargs['configurable'])
328 self.throttling.set_visible(self.configurable)
329@@ -607,9 +628,10 @@
330 @property
331 def __dict__(self):
332 result = {
333- 'device_id': self.device_id.get_text(),
334+ 'device_id': self.id,
335 'device_name': self.device_name.get_text(),
336 'device_type': self.device_type.get_icon_name()[0].capitalize(),
337+ 'is_local': bool_str(self.is_local),
338 'configurable': bool_str(self.configurable),
339 'limit_bandwidth': bool_str(self.limit_bandwidth.get_active()),
340 'max_upload_speed': \
341@@ -622,7 +644,7 @@
342 @log_call(logger.info)
343 def on_device_settings_changed(self, device_id):
344 """The change of this device settings succeded."""
345- if device_id != self.device_id.get_text():
346+ if device_id != self.id:
347 return
348 self.set_sensitive(True)
349 self.warning_label.set_text('')
350@@ -631,12 +653,27 @@
351 @log_call(logger.error)
352 def on_device_settings_change_error(self, device_id, error_dict=None):
353 """The change of this device settings failed."""
354- if device_id != self.device_id.get_text():
355+ if device_id != self.id:
356 return
357 self.update(**self._last_settings)
358 self._set_warning(self.DEVICE_CHANGE_ERROR, self.warning_label)
359 self.set_sensitive(True)
360
361+ @log_call(logger.warning)
362+ def on_device_removed(self, device_id):
363+ """The removal of this device succeded."""
364+ if device_id != self.id:
365+ return
366+ self.hide()
367+
368+ @log_call(logger.error)
369+ def on_device_removal_error(self, device_id, error_dict=None):
370+ """The removal of this device failed."""
371+ if device_id != self.id:
372+ return
373+ self._set_warning(self.DEVICE_REMOVAL_ERROR, self.warning_label)
374+ self.set_sensitive(True)
375+
376
377 class DevicesPanel(UbuntuOneBin, ControlPanelMixin):
378 """The devices panel."""
379@@ -650,10 +687,14 @@
380 self.add(self.itself)
381 self.show()
382
383+ self._devices = {}
384+
385 self.backend.connect_to_signal('DevicesInfoReady',
386 self.on_devices_info_ready)
387 self.backend.connect_to_signal('DevicesInfoError',
388 self.on_devices_info_error)
389+ self.backend.connect_to_signal('DeviceRemoved',
390+ self.on_device_removed)
391 self.backend.devices_info()
392
393 def on_devices_info_ready(self, info):
394@@ -665,11 +706,22 @@
395 DEVICE_TYPE_COMPUTER)
396 device.update(**device_info)
397 self.devices.pack_start(device)
398+ self._devices[device.id] = device
399
400 @log_call(logger.error)
401 def on_devices_info_error(self, error_dict=None):
402 """Backend notifies of an error when fetching volumes info."""
403
404+ @log_call(logger.warning)
405+ def on_device_removed(self, device_id):
406+ """The removal of a device succeded."""
407+ if device_id in self._devices:
408+ child = self._devices.pop(device_id)
409+ self.devices.remove(child)
410+
411+ if child.is_local:
412+ self.emit('local-device-removed')
413+
414
415 class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
416 """The applications panel."""
417@@ -750,6 +802,8 @@
418 self.notebook.insert_page(getattr(self, tab), position=page_num)
419
420 self.folders_button.connect('clicked', lambda b: self.folders.load())
421+ sig = 'local-device-removed'
422+ self.devices.connect(sig, lambda widget: self.emit(sig))
423
424 self.backend.account_info()
425 self.backend.file_sync_status()
426@@ -818,3 +872,6 @@
427 gobject.signal_new('credentials-found', OverviewPanel,
428 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
429 (gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT))
430+
431+gobject.signal_new('local-device-removed', DevicesPanel,
432+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
433
434=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
435--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-17 17:25:10 +0000
436+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-18 17:42:12 +0000
437@@ -47,19 +47,21 @@
438
439 FAKE_DEVICE_INFO = {
440 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
441- 'configurable': 'True', 'limit_bandwidth': 'True',
442+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
443 'max_upload_speed': '1000', 'max_download_speed': '72548',
444 }
445
446 FAKE_DEVICES_INFO = [
447- {'device_id': '0', 'name': 'Foo', 'type': 'Computer', 'configurable': ''},
448- {'device_id': '1', 'name': 'Bar', 'type': 'Phone', 'configurable': ''},
449+ {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
450+ 'is_local': '', 'configurable': ''},
451+ {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
452+ 'is_local': '', 'configurable': ''},
453 {'device_id': '2', 'name': 'Z', 'type': 'Computer',
454- 'configurable': 'True', 'limit_bandwidth': '',
455+ 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
456 'max_upload_speed': '0', 'max_download_speed': '0'},
457 {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
458- 'configurable': 'True', 'limit_bandwidth': 'True',
459- 'max_upload_speed': '1000', 'max_download_speed': '72548'},
460+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
461+ 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
462 ]
463
464
465@@ -128,6 +130,7 @@
466 exposed_methods = [
467 'account_info', 'devices_info', 'change_device_settings',
468 'volumes_info', 'change_volume_settings', 'file_sync_status',
469+ 'remove_device',
470 ]
471
472
473@@ -356,6 +359,15 @@
474 self.assertEqual(self.ui.management.notebook.get_current_page(),
475 self.ui.management.FOLDERS_PAGE)
476
477+ def test_local_device_removed_shows_overview_panel(self):
478+ """On 'local-device-removed' signal, the overview panel is shown."""
479+ self.ui.overview.emit('credentials-found', True, object())
480+ self.ui.management.emit('local-device-removed')
481+
482+ children = self.ui.get_children()
483+ self.assertEqual(1, len(children))
484+ self.assertTrue(children[0] is self.ui.overview)
485+
486
487 class UbuntuOneBinTestCase(BaseTestCase):
488 """The test suite for a Ubuntu One panel."""
489@@ -907,12 +919,14 @@
490
491 def assert_device_equal(self, device, expected):
492 """Assert that the device has the values from expected."""
493- self.assertEqual(device.device_id.get_text(),
494+ self.assertEqual(device.id,
495 expected['device_id'])
496 self.assertEqual(device.device_name.get_text(),
497 expected['device_name'])
498 self.assertEqual(device.device_type.get_icon_name()[0],
499 expected['device_type'].lower())
500+ self.assertEqual(device.is_local,
501+ bool(expected['is_local']))
502 self.assertEqual(device.configurable,
503 bool(expected['configurable']))
504 self.assertEqual(device.limit_bandwidth.get_active(),
505@@ -927,7 +941,7 @@
506 """Changing throttling settings updates the backend properly."""
507 expected = self.ui.__dict__
508 self.assert_backend_called('change_device_settings',
509- (self.ui.device_id.get_text(), expected))
510+ (self.ui.id, expected))
511 self.assertEqual(self.ui.warning_label.get_text(), '')
512
513 def modify_settings(self):
514@@ -961,16 +975,13 @@
515 """The warning label is cleared."""
516 self.assertEqual(self.ui.warning_label.get_text(), '')
517
518- def test_device_id_is_hidden(self):
519- """The device id label is hidden."""
520- self.assertFalse(self.ui.device_id.get_visible())
521-
522 def test_default_values(self):
523 """Default values are correct."""
524- self.assertEqual(self.ui.device_id.get_text(), '')
525+ self.assertEqual(self.ui.id, None)
526 self.assertEqual(self.ui.device_name.get_text(), '')
527 self.assertEqual(self.ui.device_type.get_icon_name()[0],
528 gui.DEVICE_TYPE_COMPUTER.lower())
529+ self.assertEqual(self.ui.is_local, False)
530 self.assertEqual(self.ui.configurable, False)
531 self.assertEqual(self.ui.limit_bandwidth.get_active(), False)
532 self.assertEqual(self.ui.max_upload_speed.get_value_as_int(), 0)
533@@ -980,12 +991,6 @@
534 """When updating, the backend is not called."""
535 self.assertEqual(self.ui.backend._called, {})
536
537- def test_update_device_id(self):
538- """A device can be updated from a dict."""
539- value = '741-822-963'
540- self.ui.update(device_id=value)
541- self.assertEqual(value, self.ui.device_id.get_text())
542-
543 def test_update_device_name(self):
544 """A device can be updated from a dict."""
545 value = 'The death star'
546@@ -1012,13 +1017,23 @@
547 self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON),
548 self.ui.device_type.get_icon_name())
549
550- def test_update_configurable(self):
551+ def test_update_is_not_local(self):
552+ """A device can be updated from a dict."""
553+ self.ui.update(is_local='')
554+ self.assertFalse(self.ui.is_local)
555+
556+ def test_update_is_local(self):
557+ """A device can be updated from a dict."""
558+ self.ui.update(is_local='True')
559+ self.assertTrue(self.ui.is_local)
560+
561+ def test_update_non_configurable(self):
562 """A device can be updated from a dict."""
563 self.ui.update(configurable='')
564 self.assertFalse(self.ui.configurable)
565 self.assertFalse(self.ui.throttling.get_visible())
566
567- def test_update_non_configurable(self):
568+ def test_update_configurable(self):
569 """A device can be updated from a dict."""
570 self.ui.update(configurable='True')
571 self.assertTrue(self.ui.configurable)
572@@ -1073,12 +1088,15 @@
573 [self.ui.on_device_settings_changed])
574 self.assertEqual(self.ui.backend._signals['DeviceSettingsChangeError'],
575 [self.ui.on_device_settings_change_error])
576+ self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
577+ [self.ui.on_device_removed])
578+ self.assertEqual(self.ui.backend._signals['DeviceRemovalError'],
579+ [self.ui.on_device_removal_error])
580
581 def test_on_device_settings_changed(self):
582 """When settings were changed for this device, enable it."""
583 self.modify_settings()
584- did = self.ui.device_id.get_text()
585- self.ui.on_device_settings_changed(device_id=did)
586+ self.ui.on_device_settings_changed(device_id=self.ui.id)
587
588 self.assertTrue(self.ui.get_sensitive())
589 self.assertEqual(self.ui.warning_label.get_text(), '')
590@@ -1087,8 +1105,7 @@
591 def test_on_device_settings_change_after_error(self):
592 """Change success after error."""
593 self.modify_settings()
594- did = self.ui.device_id.get_text()
595- self.ui.on_device_settings_change_error(device_id=did) # change failed
596+ self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
597
598 self.test_on_device_settings_changed()
599
600@@ -1109,8 +1126,7 @@
601
602 self.modify_settings()
603
604- did = self.ui.device_id.get_text()
605- self.ui.on_device_settings_change_error(device_id=did) # change failed
606+ self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
607
608 self.assertTrue(self.ui.get_sensitive())
609 self.assert_warning_correct(self.ui.warning_label,
610@@ -1120,8 +1136,7 @@
611 def test_on_device_settings_change_error_after_success(self):
612 """Change error after success."""
613 self.modify_settings()
614- did = self.ui.device_id.get_text()
615- self.ui.on_device_settings_changed(device_id=did)
616+ self.ui.on_device_settings_changed(device_id=self.ui.id)
617
618 self.test_on_device_settings_change_error()
619
620@@ -1131,6 +1146,49 @@
621 self.ui.on_device_settings_change_error(device_id='yudo')
622 self.assertEqual(self.ui.warning_label.get_text(), '')
623
624+ def test_remove(self):
625+ """Clicking on remove calls the backend properly."""
626+ self.ui.is_local = False
627+ self.ui.remove.clicked()
628+
629+ self.assert_backend_called('remove_device', (self.ui.id,))
630+ self.assertFalse(self.ui.get_sensitive(),
631+ 'Must be disabled while removing.')
632+
633+ def test_on_device_removed(self):
634+ """On this device removed, hide and destroy."""
635+ self.ui.remove.clicked()
636+ self.ui.on_device_removed(device_id=self.ui.id)
637+
638+ self.assertFalse(self.ui.get_visible(),
639+ 'Must not be visible after removed.')
640+
641+ def test_on_device_removed_other_id(self):
642+ """On other device removed, do nothing."""
643+ self.ui.remove.clicked()
644+ self.ui.on_device_removed(device_id='foo')
645+
646+ self.assertTrue(self.ui.get_visible(),
647+ 'Must be visible after other device was removed.')
648+
649+ def test_on_device_removal_error(self):
650+ """On this device removal error, re-enable and show error."""
651+ self.ui.remove.clicked()
652+ self.ui.on_device_removal_error(device_id=self.ui.id)
653+
654+ self.assertTrue(self.ui.get_sensitive(),
655+ 'Must be enabled after removal error.')
656+ self.assert_warning_correct(self.ui.warning_label,
657+ self.ui.DEVICE_REMOVAL_ERROR)
658+
659+ def test_on_device_removal_error_other_id(self):
660+ """On other device removal error, do nothing."""
661+ self.ui.remove.clicked()
662+ self.ui.on_device_removal_error(device_id='foo')
663+
664+ self.assertFalse(self.ui.get_sensitive(),
665+ 'Must be disabled after other device removal error.')
666+
667
668 class DevicesTestCase(ControlPanelMixinTestCase):
669 """The test suite for the devices panel."""
670@@ -1156,6 +1214,8 @@
671 [self.ui.on_devices_info_ready])
672 self.assertEqual(self.ui.backend._signals['DevicesInfoError'],
673 [self.ui.on_devices_info_error])
674+ self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
675+ [self.ui.on_device_removed])
676
677 def test_devices_info_is_requested(self):
678 """The devices info is requested to the backend."""
679@@ -1171,12 +1231,13 @@
680 for child, device in zip(children, FAKE_DEVICES_INFO):
681 self.assertIsInstance(child, gui.Device)
682
683- self.assertEqual(device['device_id'],
684- child.device_id.get_text())
685+ self.assertEqual(device['device_id'], child.id)
686 self.assertEqual(device['device_name'],
687 child.device_name.get_text())
688 self.assertEqual(device['device_type'].lower(),
689 child.device_type.get_icon_name()[0])
690+ self.assertEqual(bool(device['is_local']),
691+ child.is_local)
692 self.assertEqual(bool(device['configurable']),
693 child.configurable)
694
695@@ -1190,9 +1251,51 @@
696 self.assertEqual(value,
697 child.max_download_speed.get_value_as_int())
698
699+ def test_on_devices_info_ready_have_devices_cached(self):
700+ """The devices are cached for further removal."""
701+ self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
702+
703+ for child in self.ui.devices.get_children():
704+ self.assertTrue(self.ui._devices[child.id] is child)
705+
706 def test_on_devices_info_error(self):
707 """The devices info couldn't be retrieved."""
708 self.ui.on_devices_info_error()
709+ # add test!
710+
711+ def test_on_device_removed(self):
712+ """When a child device was removed, remove and destroy."""
713+ self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
714+ did = FAKE_DEVICES_INFO[0]['device_id']
715+ device = self.ui._devices[did]
716+ self.ui.on_device_removed(device_id=did)
717+
718+ self.assertTrue(device not in self.ui.devices.get_children())
719+ self.assertTrue(did not in self.ui._devices)
720+
721+ def test_on_local_device_removed(self):
722+ """Removing the local device emits local-device-removed."""
723+ self.ui.connect('local-device-removed', self._set_called)
724+
725+ self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
726+ local_device = FAKE_DEVICES_INFO[-1]
727+ assert bool(local_device['is_local'])
728+ local_device_id = local_device['device_id']
729+ assert self.ui._devices[local_device_id].is_local
730+
731+ self.ui.on_device_removed(device_id=local_device_id)
732+
733+ self.assertEqual(self._called, ((self.ui,), {}))
734+
735+ def test_on_device_removed_for_no_child_device(self):
736+ """On other device removed, do nothing."""
737+ self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
738+ old_devices = self.ui.devices.get_children()
739+
740+ self.ui.on_device_removed(device_id='foo')
741+
742+ new_devices = self.ui.devices.get_children()
743+ self.assertEqual(new_devices, old_devices)
744
745
746 class ApplicationsTestCase(ControlPanelMixinTestCase):
747@@ -1413,3 +1516,11 @@
748 self.assert_warning_correct(self.ui.status_label,
749 self.ui.FILE_SYNC_ERROR)
750 self.assertFalse(self.ui.status_label.active)
751+
752+ def test_local_device_removed_is_emitted(self):
753+ """Signal local-device-removed is sent when DevicesPanel emits it."""
754+ self.ui.connect('local-device-removed', self._set_called)
755+
756+ self.ui.devices.emit('local-device-removed')
757+
758+ self.assertEqual(self._called, ((self.ui,), {}))
759
760=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
761--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-18 17:42:11 +0000
762+++ ubuntuone/controlpanel/tests/test_backend.py 2010-12-18 17:42:12 +0000
763@@ -114,9 +114,11 @@
764 "device_id": "ComputerABCDEF01234token",
765 "name": "Ubuntu One @ darkstar",
766 "type": "Computer",
767+ "is_local": '',
768 "configurable": '',
769 },
770 {
771+ 'is_local': 'True',
772 'configurable': 'True',
773 'device_id': 'ComputerABC1234DEF',
774 'limit_bandwidth': '',
775@@ -130,6 +132,7 @@
776 "name": "Nokia E65",
777 "type": "Phone",
778 "configurable": '',
779+ "is_local": '',
780 },
781 ]
782
783@@ -301,6 +304,16 @@
784 token = yield self.be.get_token()
785 self.assertEqual(token, SAMPLE_CREDENTIALS["token"])
786
787+ @inlineCallbacks
788+ def test_device_is_local(self):
789+ """The device_is_local returns the right result."""
790+ result = yield self.be.device_is_local(self.local_token)
791+ self.assertTrue(result)
792+
793+ did = EXPECTED_DEVICES_INFO[0]['device_id']
794+ result = yield self.be.device_is_local(did)
795+ self.assertFalse(result)
796+
797
798 class BackendAccountTestCase(BackendBasicTestCase):
799 """Account tests for the backend."""
800@@ -345,11 +358,23 @@
801 """The remove_device method calls the right api."""
802 dtype, did = "Computer", "SAMPLE-TOKEN"
803 device_id = dtype + did
804- apiurl = DEVICE_REMOVE_API % (dtype, did)
805+ apiurl = DEVICE_REMOVE_API % (dtype.lower(), did)
806 # pylint: disable=E1101
807 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
808 result = yield self.be.remove_device(device_id)
809 self.assertEqual(result, device_id)
810+ # credentials were not cleared
811+ self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)
812+
813+ @inlineCallbacks
814+ def test_remove_device_clear_credentials_if_local_device(self):
815+ """The remove_device method clears the credentials if is local."""
816+ apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])
817+ # pylint: disable=E1101
818+ self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
819+ yield self.be.remove_device(self.local_token)
820+ # credentials were cleared
821+ self.assertEqual(MockDBusClient.creds, None)
822
823 @inlineCallbacks
824 def test_remove_device_fails(self):
825
826=== modified file 'ubuntuone/controlpanel/webclient.py'
827--- ubuntuone/controlpanel/webclient.py 2010-12-02 16:23:03 +0000
828+++ ubuntuone/controlpanel/webclient.py 2010-12-18 17:42:12 +0000
829@@ -48,22 +48,24 @@
830
831 def _handler(self, session, msg, d):
832 """Handle the result of an http message."""
833- logger.debug("WebClient: got http response: %d", msg.status_code)
834+ logger.debug("WebClient: got http response %d for uri %r",
835+ msg.status_code, msg.get_uri().to_string(False))
836+ data = msg.response_body.data
837 if msg.status_code == 200:
838- result = simplejson.loads(msg.response_body.data)
839+ result = simplejson.loads(data)
840 d.callback(result)
841 else:
842- e = WebClientError(msg.status_code, msg)
843+ e = WebClientError(msg.status_code, data)
844 d.errback(e)
845
846 def _call_api_with_creds(self, credentials, api_name):
847 """Get a given url from the webservice with credentials."""
848- url = self.base_url + api_name
849+ url = (self.base_url + api_name).encode('utf-8')
850 method = "GET"
851+ logger.debug("WebClient: getting url: %s, %s", method, url)
852 msg = Soup.Message.new(method, url)
853 add_oauth_headers(msg.request_headers.append, method, url, credentials)
854 d = defer.Deferred()
855- logger.debug("WebClient: getting url: %s", url)
856 self.session.queue_message(msg, self._handler, d)
857 return d
858

Subscribers

People subscribed via source and target branches