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

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 46
Merged at revision: 36
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/devices
Merge into: lp:ubuntuone-control-panel
Prerequisite: lp:~nataliabidart/ubuntuone-control-panel/more-logging-and-ids
Diff against target: 927 lines (+711/-22)
5 files modified
data/device.ui (+201/-0)
data/devices.ui (+31/-1)
data/folders.ui (+1/-1)
ubuntuone/controlpanel/gtk/gui.py (+172/-9)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+306/-11)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/devices
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Roman Yepishev (community) fieldtest Approve
Review via email: mp+43975@code.launchpad.net

Commit message

* Implemented devices tab (LP: #690649).

* Maximun size is set using geometry hints (LP: #683164).

Description of the change

To run the tests, do:

./run-tests

To test IRL, open 3 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. Note: the 'Remove' device button so far does nothing. Throttling settings are properly updated, you can check by looking the ~/.config/ubuntuone/syncdaemon.conf file.

To post a comment you must log in.
Revision history for this message
Martin Albisetti (beuno) wrote :

I am probably missing a lot of context, but when blindly trying to test this, I get:

beuno@beuno-laptop:~$ bzr branch lp:~nataliabidart/ubuntuone-control-panel/devices
Branched 44 revision(s).
beuno@beuno-laptop:~$ cd devices/
beuno@beuno-laptop:~/devices$ EBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend
Traceback (most recent call last):
  File "./bin/ubuntuone-control-panel-backend", line 24, in <module>
    from ubuntuone.controlpanel import dbus_service
  File "/home/beuno/devices/ubuntuone/controlpanel/dbus_service.py", line 32, in <module>
    from ubuntuone.controlpanel.backend import (
  File "/home/beuno/devices/ubuntuone/controlpanel/backend.py", line 24, in <module>
    from ubuntuone.controlpanel import dbus_client
  File "/home/beuno/devices/ubuntuone/controlpanel/dbus_client.py", line 28, in <module>
    from ubuntuone.platform.linux import dbus_interface as sd_dbus_iface
ImportError: No module named platform.linux

Revision history for this message
Roman Yepishev (rye) wrote :

When an attempt is made to change max upload/download speed or enable/disable bandwidth usage limits some kind of flickering occurs which is not present in ubuntuone-preferences. It is pretty minor but it looks like it redraws the whole device entry after every change of widget values.

This can be seen here - http://ubuntuone.com/p/U8u/

review: Needs Fixing
45. By Natalia Bidart

Merged trunk in.

46. By Natalia Bidart

UI is not disabled while changing setting for a device.

Revision history for this message
Roman Yepishev (rye) wrote :

Yep, no flickering occurs.

review: Approve (fieldtest)
Revision history for this message
Roberto Alsina (ralsina) wrote :

Approved, fieldtested on narwhal.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/device.ui'
2--- data/device.ui 1970-01-01 00:00:00 +0000
3+++ data/device.ui 2010-12-17 17:27:34 +0000
4@@ -0,0 +1,201 @@
5+<?xml version="1.0" encoding="UTF-8"?>
6+<interface>
7+ <requires lib="gtk+" version="2.16"/>
8+ <!-- interface-naming-policy project-wide -->
9+ <object class="GtkVBox" id="itself">
10+ <property name="visible">True</property>
11+ <property name="spacing">5</property>
12+ <child>
13+ <object class="GtkHBox" id="hbox1">
14+ <property name="visible">True</property>
15+ <property name="spacing">15</property>
16+ <child>
17+ <object class="GtkVBox" id="vbox1">
18+ <property name="visible">True</property>
19+ <property name="spacing">5</property>
20+ <child>
21+ <object class="GtkHBox" id="hbox2">
22+ <property name="visible">True</property>
23+ <child>
24+ <object class="GtkImage" id="device_type">
25+ <property name="visible">True</property>
26+ <property name="icon_name">computer</property>
27+ </object>
28+ <packing>
29+ <property name="expand">False</property>
30+ <property name="position">0</property>
31+ </packing>
32+ </child>
33+ <child>
34+ <object class="GtkLabel" id="device_name">
35+ <property name="visible">True</property>
36+ <property name="xalign">0</property>
37+ <property name="xpad">5</property>
38+ <property name="label">My Laptop</property>
39+ </object>
40+ <packing>
41+ <property name="position">1</property>
42+ </packing>
43+ </child>
44+ <child>
45+ <object class="GtkLabel" id="device_id">
46+ <property name="label" translatable="yes">device id (hidden)</property>
47+ </object>
48+ <packing>
49+ <property name="expand">False</property>
50+ <property name="position">2</property>
51+ </packing>
52+ </child>
53+ <child>
54+ <object class="GtkLabel" id="date_added">
55+ <property name="label">30.02.09</property>
56+ </object>
57+ <packing>
58+ <property name="expand">False</property>
59+ <property name="position">3</property>
60+ </packing>
61+ </child>
62+ </object>
63+ <packing>
64+ <property name="position">0</property>
65+ </packing>
66+ </child>
67+ <child>
68+ <object class="GtkTable" id="throttling">
69+ <property name="visible">True</property>
70+ <property name="n_rows">3</property>
71+ <property name="n_columns">2</property>
72+ <property name="column_spacing">3</property>
73+ <property name="row_spacing">3</property>
74+ <child>
75+ <object class="GtkCheckButton" id="limit_bandwidth">
76+ <property name="label" translatable="yes">Limit bandwidth usage</property>
77+ <property name="visible">True</property>
78+ <property name="can_focus">True</property>
79+ <property name="receives_default">False</property>
80+ <property name="draw_indicator">True</property>
81+ <signal name="toggled" handler="on_limit_bandwidth_toggled"/>
82+ </object>
83+ </child>
84+ <child>
85+ <object class="GtkLabel" id="max_upload_speed_label">
86+ <property name="visible">True</property>
87+ <property name="xalign">1</property>
88+ <property name="xpad">5</property>
89+ <property name="label" translatable="yes">Max upload speed (KiB/s)</property>
90+ </object>
91+ <packing>
92+ <property name="top_attach">1</property>
93+ <property name="bottom_attach">2</property>
94+ </packing>
95+ </child>
96+ <child>
97+ <object class="GtkLabel" id="max_download_speed_label">
98+ <property name="visible">True</property>
99+ <property name="xalign">1</property>
100+ <property name="xpad">5</property>
101+ <property name="label" translatable="yes">Max download speed (KiB/s)</property>
102+ </object>
103+ <packing>
104+ <property name="top_attach">2</property>
105+ <property name="bottom_attach">3</property>
106+ </packing>
107+ </child>
108+ <child>
109+ <object class="GtkSpinButton" id="max_upload_speed">
110+ <property name="visible">True</property>
111+ <property name="can_focus">True</property>
112+ <property name="invisible_char">•</property>
113+ <property name="activates_default">True</property>
114+ <property name="adjustment">adjustment1</property>
115+ <signal name="value_changed" handler="on_max_upload_speed_value_changed"/>
116+ </object>
117+ <packing>
118+ <property name="left_attach">1</property>
119+ <property name="right_attach">2</property>
120+ <property name="top_attach">1</property>
121+ <property name="bottom_attach">2</property>
122+ <property name="x_options">GTK_FILL</property>
123+ <property name="y_options">GTK_FILL</property>
124+ </packing>
125+ </child>
126+ <child>
127+ <object class="GtkSpinButton" id="max_download_speed">
128+ <property name="visible">True</property>
129+ <property name="can_focus">True</property>
130+ <property name="invisible_char">•</property>
131+ <property name="activates_default">True</property>
132+ <property name="adjustment">adjustment2</property>
133+ <signal name="value_changed" handler="on_max_download_speed_value_changed"/>
134+ </object>
135+ <packing>
136+ <property name="left_attach">1</property>
137+ <property name="right_attach">2</property>
138+ <property name="top_attach">2</property>
139+ <property name="bottom_attach">3</property>
140+ </packing>
141+ </child>
142+ <child>
143+ <placeholder/>
144+ </child>
145+ </object>
146+ <packing>
147+ <property name="expand">False</property>
148+ <property name="position">1</property>
149+ </packing>
150+ </child>
151+ </object>
152+ <packing>
153+ <property name="expand">False</property>
154+ <property name="position">0</property>
155+ </packing>
156+ </child>
157+ <child>
158+ <object class="GtkVButtonBox" id="vbuttonbox1">
159+ <property name="visible">True</property>
160+ <property name="layout_style">start</property>
161+ <child>
162+ <object class="GtkButton" id="remove">
163+ <property name="label">gtk-remove</property>
164+ <property name="visible">True</property>
165+ <property name="can_focus">True</property>
166+ <property name="receives_default">True</property>
167+ <property name="use_stock">True</property>
168+ </object>
169+ <packing>
170+ <property name="expand">False</property>
171+ <property name="fill">False</property>
172+ <property name="position">0</property>
173+ </packing>
174+ </child>
175+ </object>
176+ <packing>
177+ <property name="expand">False</property>
178+ <property name="pack_type">end</property>
179+ <property name="position">1</property>
180+ </packing>
181+ </child>
182+ </object>
183+ <packing>
184+ <property name="expand">False</property>
185+ <property name="position">0</property>
186+ </packing>
187+ </child>
188+ <child>
189+ <object class="GtkLabel" id="warning_label">
190+ <property name="visible">True</property>
191+ </object>
192+ <packing>
193+ <property name="position">1</property>
194+ </packing>
195+ </child>
196+ </object>
197+ <object class="GtkAdjustment" id="adjustment1">
198+ <property name="upper">10000</property>
199+ <property name="step_increment">1</property>
200+ </object>
201+ <object class="GtkAdjustment" id="adjustment2">
202+ <property name="upper">10000</property>
203+ <property name="step_increment">1</property>
204+ </object>
205+</interface>
206
207=== modified file 'data/devices.ui'
208--- data/devices.ui 2010-10-21 21:14:24 +0000
209+++ data/devices.ui 2010-12-17 17:27:34 +0000
210@@ -7,7 +7,37 @@
211 <property name="border_width">10</property>
212 <property name="spacing">10</property>
213 <child>
214- <placeholder/>
215+ <object class="GtkScrolledWindow" id="scrolledwindow1">
216+ <property name="visible">True</property>
217+ <property name="can_focus">True</property>
218+ <property name="hscrollbar_policy">automatic</property>
219+ <property name="vscrollbar_policy">automatic</property>
220+ <child>
221+ <object class="GtkViewport" id="viewport1">
222+ <property name="visible">True</property>
223+ <property name="resize_mode">queue</property>
224+ <property name="shadow_type">none</property>
225+ <child>
226+ <object class="GtkAlignment" id="alignment1">
227+ <property name="visible">True</property>
228+ <property name="xscale">0</property>
229+ <property name="yscale">0</property>
230+ <child>
231+ <object class="GtkVBox" id="devices">
232+ <property name="visible">True</property>
233+ <child>
234+ <placeholder/>
235+ </child>
236+ </object>
237+ </child>
238+ </object>
239+ </child>
240+ </object>
241+ </child>
242+ </object>
243+ <packing>
244+ <property name="position">0</property>
245+ </packing>
246 </child>
247 </object>
248 </interface>
249
250=== modified file 'data/folders.ui'
251--- data/folders.ui 2010-12-14 15:54:29 +0000
252+++ data/folders.ui 2010-12-17 17:27:34 +0000
253@@ -31,7 +31,7 @@
254 <property name="resize_mode">queue</property>
255 <property name="shadow_type">none</property>
256 <child>
257- <object class="GtkAlignment" id="folders_alignment">
258+ <object class="GtkAlignment" id="folders">
259 <property name="visible">True</property>
260 <property name="xscale">0</property>
261 <property name="yscale">0</property>
262
263=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
264--- ubuntuone/controlpanel/gtk/gui.py 2010-12-14 19:16:25 +0000
265+++ ubuntuone/controlpanel/gtk/gui.py 2010-12-17 17:27:34 +0000
266@@ -47,6 +47,8 @@
267
268 from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
269 DBUS_PREFERENCES_IFACE)
270+from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
271+ DEVICE_TYPE_COMPUTER)
272 from ubuntuone.controlpanel.logger import setup_logging, log_call
273 from ubuntuone.controlpanel.utils import get_data_file
274
275@@ -69,6 +71,12 @@
276 _('Sync data between computers.\n'
277 '<small>«this is custom text #<i>N</i>»</small>')]
278 WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
279+KILOBYTES = 1024
280+
281+
282+def bool_str(value):
283+ """Return the string representation of a bool (dbus-compatible)."""
284+ return 'True' if value else ''
285
286
287 def filter_by_app_name(f):
288@@ -151,6 +159,8 @@
289 self.set_title(self.TITLE % {'app_name': U1_APP_NAME})
290 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
291 self.set_icon_name('ubuntuone')
292+ self.set_geometry_hints(max_width=736, max_height=525) # bug #683164
293+
294 self.connect('delete-event', lambda w, e: gtk.main_quit())
295 self.show()
296
297@@ -377,7 +387,7 @@
298 def __init__(self):
299 UbuntuOneBin.__init__(self)
300 ControlPanelMixin.__init__(self, filename='account.ui')
301- self.pack_start(self.itself)
302+ self.add(self.itself)
303 self.show()
304
305 self.backend.connect_to_signal('AccountInfoReady',
306@@ -421,7 +431,7 @@
307 def __init__(self):
308 UbuntuOneBin.__init__(self)
309 ControlPanelMixin.__init__(self, filename='folders.ui')
310- self.pack_start(self.itself)
311+ self.add(self.itself)
312 self.show_all()
313
314 self.backend.connect_to_signal('VolumesInfoReady',
315@@ -433,13 +443,12 @@
316 self.label_alignment.add(self.volumes_label)
317 self._subscribed = []
318
319- @log_call(logger.debug)
320 def on_volumes_info_ready(self, info):
321 """Backend notifies of volumes info."""
322 self.volumes_label.stop()
323
324 if self.volumes is not None:
325- self.folders_alignment.remove(self.volumes)
326+ self.folders.remove(self.volumes)
327 self.volumes = None
328
329 if not info:
330@@ -473,7 +482,7 @@
331 self._subscribed.append(subscribed)
332 self.volumes.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0)
333
334- self.folders_alignment.add(self.volumes)
335+ self.folders.add(self.volumes)
336
337 @log_call(logger.error)
338 def on_volumes_info_error(self, error_dict=None):
339@@ -485,7 +494,7 @@
340 def on_subscribed_clicked(self, checkbutton):
341 """The user toggled 'checkbutton'."""
342 volume_id = checkbutton.get_label()
343- subscribed = 'True' if checkbutton.get_active() else ''
344+ subscribed = bool_str(checkbutton.get_active())
345 self.backend.change_volume_settings(volume_id,
346 {'subscribed': subscribed})
347
348@@ -495,6 +504,140 @@
349 self.volumes_label.start()
350
351
352+class Device(gtk.VBox, ControlPanelMixin):
353+ """The devices panel."""
354+
355+ DEVICE_CHANGE_ERROR = _('The settings could not be changed,\n'
356+ 'previous values were restored.')
357+
358+ def __init__(self):
359+ gtk.VBox.__init__(self)
360+ ControlPanelMixin.__init__(self, filename='device.ui')
361+
362+ self._updating = False
363+ self._last_settings = {}
364+ self.configurable = False
365+
366+ self.update(device_id='', device_name='', limit_bandwidth=False,
367+ max_upload_speed=0, max_download_speed=0)
368+
369+ self.add(self.itself)
370+ self.device_id.hide()
371+ self.show()
372+
373+ self.backend.connect_to_signal('DeviceSettingsChanged',
374+ self.on_device_settings_changed)
375+ self.backend.connect_to_signal('DeviceSettingsChangeError',
376+ self.on_device_settings_change_error)
377+
378+ def _change_device_settings(self, *args):
379+ """Update backend settings for this device."""
380+ if self._updating:
381+ return
382+
383+ # Not disabling the GUI to avoid annyong twitchings
384+ #self.set_sensitive(False)
385+ self.warning_label.set_text('')
386+ self.backend.change_device_settings(self.device_id.get_text(),
387+ self.__dict__)
388+
389+ def _block_signals(f):
390+ """Execute 'f' while having the _updating flag set."""
391+
392+ # pylint: disable=E0213,W0212,E1102
393+
394+ @wraps(f)
395+ def inner(self, *args, **kwargs):
396+ """Execute 'f' while having the _updating flag set."""
397+ old = self._updating
398+ self._updating = True
399+
400+ result = f(self, *args, **kwargs)
401+
402+ self._updating = old
403+ return result
404+
405+ return inner
406+
407+ on_limit_bandwidth_toggled = _change_device_settings
408+ on_max_upload_speed_value_changed = _change_device_settings
409+ on_max_download_speed_value_changed = _change_device_settings
410+
411+ @_block_signals
412+ def update(self, **kwargs):
413+ """Update according to named parameters.
414+
415+ Possible settings are:
416+ * device_id (string, not shown to the user)
417+ * device_name (string)
418+ * type (either DEVICE_TYPE_PHONE or DEVICE_TYPE_COMPUTER)
419+ * configurable (True/False)
420+ * if configurable, the following can be set:
421+ * limit_bandwidth (True/False)
422+ * max_upload_speed (bytes)
423+ * max_download_speed (bytes)
424+
425+ """
426+ if 'device_id' in kwargs:
427+ self.device_id.set_text(kwargs['device_id'])
428+
429+ if 'device_name' in kwargs:
430+ self.device_name.set_markup('<b>%s</b>' % kwargs['device_name'])
431+
432+ if 'device_type' in kwargs:
433+ dtype = kwargs['device_type']
434+ if dtype in (DEVICE_TYPE_COMPUTER, DEVICE_TYPE_PHONE):
435+ self.device_type.set_from_icon_name(dtype.lower(),
436+ gtk.ICON_SIZE_BUTTON)
437+
438+ if 'configurable' in kwargs:
439+ self.configurable = bool(kwargs['configurable'])
440+ self.throttling.set_visible(self.configurable)
441+
442+ if 'limit_bandwidth' in kwargs:
443+ self.limit_bandwidth.set_active(bool(kwargs['limit_bandwidth']))
444+
445+ for speed in ('max_upload_speed', 'max_download_speed'):
446+ if speed in kwargs:
447+ value = int(kwargs[speed]) // KILOBYTES
448+ getattr(self, speed).set_value(value)
449+
450+ self._last_settings = self.__dict__
451+
452+ @property
453+ def __dict__(self):
454+ result = {
455+ 'device_id': self.device_id.get_text(),
456+ 'device_name': self.device_name.get_text(),
457+ 'device_type': self.device_type.get_icon_name()[0].capitalize(),
458+ 'configurable': bool_str(self.configurable),
459+ 'limit_bandwidth': bool_str(self.limit_bandwidth.get_active()),
460+ 'max_upload_speed': \
461+ str(self.max_upload_speed.get_value_as_int() * KILOBYTES),
462+ 'max_download_speed': \
463+ str(self.max_download_speed.get_value_as_int() * KILOBYTES),
464+ }
465+ return result
466+
467+ @log_call(logger.info)
468+ def on_device_settings_changed(self, device_id):
469+ """The change of this device settings succeded."""
470+ if device_id != self.device_id.get_text():
471+ return
472+ self.set_sensitive(True)
473+ self.warning_label.set_text('')
474+ self._last_settings = self.__dict__
475+
476+ @log_call(logger.error)
477+ def on_device_settings_change_error(self, device_id, error_dict=None):
478+ """The change of this device settings failed."""
479+ if device_id != self.device_id.get_text():
480+ return
481+ self.update(**self._last_settings)
482+ self._set_warning(self.DEVICE_CHANGE_ERROR, self.warning_label)
483+ self.set_sensitive(True)
484+
485+
486 class DevicesPanel(UbuntuOneBin, ControlPanelMixin):
487 """The devices panel."""
488
489@@ -504,9 +647,29 @@
490 def __init__(self):
491 UbuntuOneBin.__init__(self)
492 ControlPanelMixin.__init__(self, filename='devices.ui')
493- self.pack_start(self.itself)
494+ self.add(self.itself)
495 self.show()
496
497+ self.backend.connect_to_signal('DevicesInfoReady',
498+ self.on_devices_info_ready)
499+ self.backend.connect_to_signal('DevicesInfoError',
500+ self.on_devices_info_error)
501+ self.backend.devices_info()
502+
503+ def on_devices_info_ready(self, info):
504+ """Backend notifies of devices info."""
505+ for device_info in info:
506+ device = Device()
507+ device_info['device_name'] = device_info.pop('name', '')
508+ device_info['device_type'] = device_info.pop('type',
509+ DEVICE_TYPE_COMPUTER)
510+ device.update(**device_info)
511+ self.devices.pack_start(device)
512+
513+ @log_call(logger.error)
514+ def on_devices_info_error(self, error_dict=None):
515+ """Backend notifies of an error when fetching volumes info."""
516+
517
518 class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
519 """The applications panel."""
520@@ -517,7 +680,7 @@
521 def __init__(self):
522 UbuntuOneBin.__init__(self)
523 ControlPanelMixin.__init__(self, filename='applications.ui')
524- self.pack_start(self.itself)
525+ self.add(self.itself)
526 self.show()
527
528
529@@ -543,7 +706,7 @@
530 def __init__(self):
531 gtk.VBox.__init__(self)
532 ControlPanelMixin.__init__(self, filename='management.ui')
533- self.pack_start(self.itself)
534+ self.add(self.itself)
535 self.show()
536
537 self.backend.connect_to_signal('AccountInfoReady',
538
539=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
540--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-14 19:16:25 +0000
541+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-17 17:27:34 +0000
542@@ -45,6 +45,23 @@
543 {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
544 ]
545
546+FAKE_DEVICE_INFO = {
547+ 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
548+ 'configurable': 'True', 'limit_bandwidth': 'True',
549+ 'max_upload_speed': '1000', 'max_download_speed': '72548',
550+}
551+
552+FAKE_DEVICES_INFO = [
553+ {'device_id': '0', 'name': 'Foo', 'type': 'Computer', 'configurable': ''},
554+ {'device_id': '1', 'name': 'Bar', 'type': 'Phone', 'configurable': ''},
555+ {'device_id': '2', 'name': 'Z', 'type': 'Computer',
556+ 'configurable': 'True', 'limit_bandwidth': '',
557+ 'max_upload_speed': '0', 'max_download_speed': '0'},
558+ {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
559+ 'configurable': 'True', 'limit_bandwidth': 'True',
560+ 'max_upload_speed': '1000', 'max_download_speed': '72548'},
561+]
562+
563
564 class FakedObject(object):
565 """Fake an object, record every call."""
566@@ -108,8 +125,10 @@
567 bus_name = gui.DBUS_BUS_NAME
568 object_path = gui.DBUS_PREFERENCES_PATH
569 iface = gui.DBUS_PREFERENCES_IFACE
570- exposed_methods = ['account_info', 'devices_info', 'volumes_info',
571- 'file_sync_status', 'change_volume_settings']
572+ exposed_methods = [
573+ 'account_info', 'devices_info', 'change_device_settings',
574+ 'volumes_info', 'change_volume_settings', 'file_sync_status',
575+ ]
576
577
578 class FakedSessionBus(object):
579@@ -268,8 +287,8 @@
580 self.assertEqual(self.ui.get_icon_name(), 'ubuntuone')
581
582 def test_max_size(self):
583- """Max size is not bigger than 966x576 (LP: #645526)."""
584- self.assertTrue(self.ui.get_size_request() <= (966, 576))
585+ """Max size is not bigger than 736x525 (LP: #645526, LP: #683164)."""
586+ self.assertTrue(self.ui.get_size_request() <= (736, 525))
587
588
589 class ControlPanelTestCase(ControlPanelMixinTestCase):
590@@ -801,8 +820,7 @@
591 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
592
593 self.assertFalse(self.ui.label_alignment.get_visible())
594- self.assertEqual(self.ui.folders_alignment.get_children(),
595- [self.ui.volumes])
596+ self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes])
597
598 volumes = self.ui.volumes.get_children()
599 volumes.reverse()
600@@ -828,8 +846,8 @@
601 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
602 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
603
604- self.assertEqual(len(self.ui.folders_alignment.get_children()), 1)
605- child = self.ui.folders_alignment.get_children()[0]
606+ self.assertEqual(len(self.ui.folders.get_children()), 1)
607+ child = self.ui.folders.get_children()[0]
608 self.assertEqual(child, self.ui.volumes)
609
610 volumes = filter(lambda w: isinstance(w, gui.gtk.CheckButton),
611@@ -840,7 +858,7 @@
612 """When there are no volumes, a notification is shown."""
613 self.ui.on_volumes_info_ready([])
614 # no volumes table
615- self.assertEqual(len(self.ui.folders_alignment.get_children()), 0)
616+ self.assertEqual(len(self.ui.folders.get_children()), 0)
617 self.assertTrue(self.ui.volumes is None)
618
619 def test_on_subscribed_clicked(self):
620@@ -852,14 +870,14 @@
621 checkbutton.clicked()
622 fid = checkbutton.get_label()
623
624- subscribed = 'True' if checkbutton.get_active() else ''
625+ subscribed = gui.bool_str(checkbutton.get_active())
626 self.assert_backend_called(method,
627 (fid, {'subscribed': subscribed}))
628 # clean backend calls
629 self.ui.backend._called.pop(method)
630
631 checkbutton.clicked()
632- subscribed = 'True' if checkbutton.get_active() else ''
633+ subscribed = gui.bool_str(checkbutton.get_active())
634 self.assert_backend_called('change_volume_settings',
635 (fid, {'subscribed': subscribed}))
636
637@@ -881,6 +899,239 @@
638 self.test_on_volumes_info_ready_with_no_volumes()
639
640
641+class DeviceTestCase(ControlPanelMixinTestCase):
642+ """The test suite for the device widget."""
643+
644+ klass = gui.Device
645+ ui_filename = 'device.ui'
646+
647+ def assert_device_equal(self, device, expected):
648+ """Assert that the device has the values from expected."""
649+ self.assertEqual(device.device_id.get_text(),
650+ expected['device_id'])
651+ self.assertEqual(device.device_name.get_text(),
652+ expected['device_name'])
653+ self.assertEqual(device.device_type.get_icon_name()[0],
654+ expected['device_type'].lower())
655+ self.assertEqual(device.configurable,
656+ bool(expected['configurable']))
657+ self.assertEqual(device.limit_bandwidth.get_active(),
658+ bool(expected['limit_bandwidth']))
659+
660+ value = int(expected['max_upload_speed']) // gui.KILOBYTES
661+ self.assertEqual(device.max_upload_speed.get_value_as_int(), value)
662+ value = int(expected['max_download_speed']) // gui.KILOBYTES
663+ self.assertEqual(device.max_download_speed.get_value_as_int(), value)
664+
665+ def assert_device_settings_changed(self):
666+ """Changing throttling settings updates the backend properly."""
667+ expected = self.ui.__dict__
668+ self.assert_backend_called('change_device_settings',
669+ (self.ui.device_id.get_text(), expected))
670+ self.assertEqual(self.ui.warning_label.get_text(), '')
671+
672+ def modify_settings(self):
673+ """Modify settings so values actually change."""
674+ new_val = not self.ui.limit_bandwidth.get_active()
675+ self.ui.limit_bandwidth.set_active(new_val)
676+
677+ new_val = self.ui.max_upload_speed.get_value_as_int() + 1
678+ self.ui.max_upload_speed.set_value(new_val)
679+
680+ new_val = self.ui.max_download_speed.get_value_as_int() + 1
681+ self.ui.max_download_speed.set_value(new_val)
682+
683+ def test_is_a_vbox(self):
684+ """Inherits from VBox."""
685+ self.assertIsInstance(self.ui, gui.gtk.VBox)
686+
687+ def test_inner_widget_is_packed(self):
688+ """The 'itself' vbox is packed into the widget."""
689+ self.assertIn(self.ui.itself, self.ui.get_children())
690+
691+ def test_is_visible(self):
692+ """Is visible."""
693+ self.assertTrue(self.ui.get_visible())
694+
695+ def test_is_sensitive(self):
696+ """Is sensitive."""
697+ self.assertTrue(self.ui.get_sensitive())
698+
699+ def test_warning_label_is_cleared(self):
700+ """The warning label is cleared."""
701+ self.assertEqual(self.ui.warning_label.get_text(), '')
702+
703+ def test_device_id_is_hidden(self):
704+ """The device id label is hidden."""
705+ self.assertFalse(self.ui.device_id.get_visible())
706+
707+ def test_default_values(self):
708+ """Default values are correct."""
709+ self.assertEqual(self.ui.device_id.get_text(), '')
710+ self.assertEqual(self.ui.device_name.get_text(), '')
711+ self.assertEqual(self.ui.device_type.get_icon_name()[0],
712+ gui.DEVICE_TYPE_COMPUTER.lower())
713+ self.assertEqual(self.ui.configurable, False)
714+ self.assertEqual(self.ui.limit_bandwidth.get_active(), False)
715+ self.assertEqual(self.ui.max_upload_speed.get_value_as_int(), 0)
716+ self.assertEqual(self.ui.max_download_speed.get_value_as_int(), 0)
717+
718+ def test_init_does_not_call_backend(self):
719+ """When updating, the backend is not called."""
720+ self.assertEqual(self.ui.backend._called, {})
721+
722+ def test_update_device_id(self):
723+ """A device can be updated from a dict."""
724+ value = '741-822-963'
725+ self.ui.update(device_id=value)
726+ self.assertEqual(value, self.ui.device_id.get_text())
727+
728+ def test_update_device_name(self):
729+ """A device can be updated from a dict."""
730+ value = 'The death star'
731+ self.ui.update(device_name=value)
732+ self.assertEqual(value, self.ui.device_name.get_text())
733+
734+ def test_update_unicode_device_name(self):
735+ """A device can be updated from a dict."""
736+ value = u'Ñoño Ñandú'
737+ self.ui.update(device_name=value)
738+ self.assertEqual(value, self.ui.device_name.get_text())
739+
740+ def test_update_device_type_computer(self):
741+ """A device can be updated from a dict."""
742+ dtype = gui.DEVICE_TYPE_COMPUTER
743+ self.ui.update(device_type=dtype)
744+ self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON),
745+ self.ui.device_type.get_icon_name())
746+
747+ def test_update_device_type_phone(self):
748+ """A device can be updated from a dict."""
749+ dtype = gui.DEVICE_TYPE_PHONE
750+ self.ui.update(device_type=dtype)
751+ self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON),
752+ self.ui.device_type.get_icon_name())
753+
754+ def test_update_configurable(self):
755+ """A device can be updated from a dict."""
756+ self.ui.update(configurable='')
757+ self.assertFalse(self.ui.configurable)
758+ self.assertFalse(self.ui.throttling.get_visible())
759+
760+ def test_update_non_configurable(self):
761+ """A device can be updated from a dict."""
762+ self.ui.update(configurable='True')
763+ self.assertTrue(self.ui.configurable)
764+ self.assertTrue(self.ui.throttling.get_visible())
765+
766+ def test_update_limit_bandwidth(self):
767+ """A device can be updated from a dict."""
768+ self.ui.update(limit_bandwidth='')
769+ self.assertFalse(self.ui.limit_bandwidth.get_active())
770+
771+ self.ui.update(limit_bandwidth='True')
772+ self.assertTrue(self.ui.limit_bandwidth.get_active())
773+
774+ def test_update_upload_speed(self):
775+ """A device can be updated from a dict."""
776+ value = '12345'
777+ self.ui.update(max_upload_speed=value)
778+ self.assertEqual(int(value) // gui.KILOBYTES,
779+ self.ui.max_upload_speed.get_value_as_int())
780+
781+ def test_update_download_speed(self):
782+ """A device can be updated from a dict."""
783+ value = '987654'
784+ self.ui.update(max_download_speed=value)
785+ self.assertEqual(int(value) // gui.KILOBYTES,
786+ self.ui.max_download_speed.get_value_as_int())
787+
788+ def test_update_does_not_call_backend(self):
789+ """When updating, the backend is not called."""
790+ self.ui.update(**FAKE_DEVICE_INFO)
791+ self.assertEqual(self.ui.backend._called, {})
792+ self.assert_device_equal(self.ui, FAKE_DEVICE_INFO)
793+
794+ def test_on_limit_bandwidth_toggled(self):
795+ """When toggling limit_bandwidth, backend is updated."""
796+ self.ui.limit_bandwidth.toggled()
797+ self.assert_device_settings_changed()
798+
799+ def test_on_max_upload_speed_value_changed(self):
800+ """When setting max_upload_speed, backend is updated."""
801+ self.ui.max_upload_speed.set_value(25)
802+ self.assert_device_settings_changed()
803+
804+ def test_on_max_download_speed_value_changed(self):
805+ """When setting max_download_speed, backend is updated."""
806+ self.ui.max_download_speed.set_value(52)
807+ self.assert_device_settings_changed()
808+
809+ def test_backend_signals(self):
810+ """The proper signals are connected to the backend."""
811+ self.assertEqual(self.ui.backend._signals['DeviceSettingsChanged'],
812+ [self.ui.on_device_settings_changed])
813+ self.assertEqual(self.ui.backend._signals['DeviceSettingsChangeError'],
814+ [self.ui.on_device_settings_change_error])
815+
816+ def test_on_device_settings_changed(self):
817+ """When settings were changed for this device, enable it."""
818+ self.modify_settings()
819+ did = self.ui.device_id.get_text()
820+ self.ui.on_device_settings_changed(device_id=did)
821+
822+ self.assertTrue(self.ui.get_sensitive())
823+ self.assertEqual(self.ui.warning_label.get_text(), '')
824+ self.assertEqual(self.ui.__dict__, self.ui._last_settings)
825+
826+ def test_on_device_settings_change_after_error(self):
827+ """Change success after error."""
828+ self.modify_settings()
829+ did = self.ui.device_id.get_text()
830+ self.ui.on_device_settings_change_error(device_id=did) # change failed
831+
832+ self.test_on_device_settings_changed()
833+
834+ def test_on_device_settings_changed_different_id(self):
835+ """When settings were changed for other device, nothing changes."""
836+ self.modify_settings()
837+ self.ui.on_device_settings_changed(device_id='yadda')
838+
839+ self.assertEqual(self.ui.warning_label.get_text(), '')
840+
841+ def test_on_device_settings_change_error(self):
842+ """When settings were not changed for this device, notify the user.
843+
844+ Also, confirm that old values were restored.
845+
846+ """
847+ self.ui.update(**FAKE_DEVICE_INFO) # use known values
848+
849+ self.modify_settings()
850+
851+ did = self.ui.device_id.get_text()
852+ self.ui.on_device_settings_change_error(device_id=did) # change failed
853+
854+ self.assertTrue(self.ui.get_sensitive())
855+ self.assert_warning_correct(self.ui.warning_label,
856+ self.ui.DEVICE_CHANGE_ERROR)
857+ self.assert_device_equal(self.ui, FAKE_DEVICE_INFO) # restored info
858+
859+ def test_on_device_settings_change_error_after_success(self):
860+ """Change error after success."""
861+ self.modify_settings()
862+ did = self.ui.device_id.get_text()
863+ self.ui.on_device_settings_changed(device_id=did)
864+
865+ self.test_on_device_settings_change_error()
866+
867+ def test_on_device_settings_change_error_different_id(self):
868+ """When settings were not changed for other device, do nothing."""
869+ self.modify_settings()
870+ self.ui.on_device_settings_change_error(device_id='yudo')
871+ self.assertEqual(self.ui.warning_label.get_text(), '')
872+
873+
874 class DevicesTestCase(ControlPanelMixinTestCase):
875 """The test suite for the devices panel."""
876
877@@ -899,6 +1150,50 @@
878 """Is visible."""
879 self.assertTrue(self.ui.get_visible())
880
881+ def test_backend_signals(self):
882+ """The proper signals are connected to the backend."""
883+ self.assertEqual(self.ui.backend._signals['DevicesInfoReady'],
884+ [self.ui.on_devices_info_ready])
885+ self.assertEqual(self.ui.backend._signals['DevicesInfoError'],
886+ [self.ui.on_devices_info_error])
887+
888+ def test_devices_info_is_requested(self):
889+ """The devices info is requested to the backend."""
890+ self.assert_backend_called('devices_info', ())
891+
892+ def test_on_devices_info_ready(self):
893+ """The devices info is processed when ready."""
894+ self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
895+
896+ children = self.ui.devices.get_children()
897+ self.assertEqual(len(children), len(FAKE_DEVICES_INFO))
898+
899+ for child, device in zip(children, FAKE_DEVICES_INFO):
900+ self.assertIsInstance(child, gui.Device)
901+
902+ self.assertEqual(device['device_id'],
903+ child.device_id.get_text())
904+ self.assertEqual(device['device_name'],
905+ child.device_name.get_text())
906+ self.assertEqual(device['device_type'].lower(),
907+ child.device_type.get_icon_name()[0])
908+ self.assertEqual(bool(device['configurable']),
909+ child.configurable)
910+
911+ if bool(device['configurable']):
912+ self.assertEqual(bool(device['limit_bandwidth']),
913+ child.limit_bandwidth.get_active())
914+ value = int(device['max_upload_speed']) // gui.KILOBYTES
915+ self.assertEqual(value,
916+ child.max_upload_speed.get_value_as_int())
917+ value = int(device['max_download_speed']) // gui.KILOBYTES
918+ self.assertEqual(value,
919+ child.max_download_speed.get_value_as_int())
920+
921+ def test_on_devices_info_error(self):
922+ """The devices info couldn't be retrieved."""
923+ self.ui.on_devices_info_error()
924+
925
926 class ApplicationsTestCase(ControlPanelMixinTestCase):
927 """The test suite for the applications panel."""

Subscribers

People subscribed via source and target branches