Merge lp:~nataliabidart/ubuntuone-control-panel/applications-woohoo into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 55
Merged at revision: 42
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/applications-woohoo
Merge into: lp:ubuntuone-control-panel
Diff against target: 1986 lines (+1422/-188)
15 files modified
data/install.ui (+46/-0)
data/management.ui (+7/-7)
data/services.ui (+57/-1)
po/POTFILES.in (+3/-2)
ubuntuone/controlpanel/backend.py (+12/-0)
ubuntuone/controlpanel/dbus_service.py (+47/-0)
ubuntuone/controlpanel/gtk/gui.py (+271/-16)
ubuntuone/controlpanel/gtk/package_manager.py (+60/-0)
ubuntuone/controlpanel/gtk/tests/__init__.py (+150/-0)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+525/-157)
ubuntuone/controlpanel/gtk/tests/test_package_manager.py (+177/-0)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+44/-0)
ubuntuone/controlpanel/logger.py (+1/-1)
ubuntuone/controlpanel/tests/__init__.py (+2/-2)
ubuntuone/controlpanel/tests/test_backend.py (+20/-2)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/applications-woohoo
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
John Lenton (community) Approve
Review via email: mp+44623@code.launchpad.net

Commit message

* renamed Account to Dashboard
* renamed Applications to Services
* added enable_files/disable_files to backend + dbus service
* added a new dbus signal FileSyncStatusChanged
* desktopcouch replication exclusion layer is acceded directly on the GUI. We should later move that to the backend.
* a new module package_manager was added to the gtk package, since we need to do some deeper work to abstract that manager from the toolkit (since it uses a gtk progress bar)(LP: #673673).
* a new widget InstallPackage that provides package installation.
* widgets FilesService and DesktopcouchService that can be re used as needed.
* ServicesPanel checks for depenencies and creates widget as necessary (LP: #673672).

Description of the change

This is an epic branch, I know, please don't hate me. Think on the good things, we have a full featured control panel!

This branch implements all bits needed to list the services offered and install package dependencies if needed.

What was done:

(see commit message for the full list)

All this is tested with code and was tested IRL.

To run the tests, do:

./run-tests

To test IRL:

* uninstall desktopcouch, xul-ext-bindwood, evolution-couchdb
* on terminal 1: DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend
* on terminal 2: DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-gtk

Got to the Services tab, and install desktopcouch using the same UI, and then install (if you want) the bindwood and evocouch packages.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Need to handle the case where the pairing record is not present:

  File "/home/ralsina/canonical/ubuntuone/woo/ubuntuone/controlpanel/logger.py", line 75, in inner
    res = f(*args, **kwargs)
  File "/home/ralsina/canonical/ubuntuone/woo/ubuntuone/controlpanel/gtk/gui.py", line 992, in load_replications
    self.replication_service = self.replication_exclusion_class()
  File "/usr/lib/pymodules/python2.7/desktopcouch/application/replication_services/ubuntuone.py", line 140, in __init__
    raise ValueError("No pairing record for ubuntuone.")
ValueError: No pairing record for ubuntuone.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Code to properly handle ValueError is now in place. The lack of a pairing record with Ubuntu One should be handled in the pairing_ubuntuone plugin of desktopcouch, see bug #694495.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Just filled bug #696782 to make the DC service starts on the backend.

53. By Natalia Bidart

aptdaemon.defer on natty is just defer.

54. By Natalia Bidart

Proper namespace for defer.

55. By Natalia Bidart

Avoiding lint warnings.

Revision history for this message
John Lenton (chipaca) :
review: Approve
Revision history for this message
Roberto Alsina (ralsina) wrote :

Approved.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'data/account.ui' => 'data/dashboard.ui'
2=== added file 'data/install.ui'
3--- data/install.ui 1970-01-01 00:00:00 +0000
4+++ data/install.ui 2011-01-03 14:24:42 +0000
5@@ -0,0 +1,46 @@
6+<?xml version="1.0" encoding="UTF-8"?>
7+<interface>
8+ <requires lib="gtk+" version="2.16"/>
9+ <!-- interface-naming-policy project-wide -->
10+ <object class="GtkVBox" id="itself">
11+ <property name="visible">True</property>
12+ <property name="border_width">10</property>
13+ <property name="spacing">10</property>
14+ <child>
15+ <object class="GtkLabel" id="install_label">
16+ <property name="visible">True</property>
17+ <property name="xalign">0</property>
18+ <property name="label">label</property>
19+ <property name="wrap">True</property>
20+ </object>
21+ <packing>
22+ <property name="expand">False</property>
23+ <property name="position">0</property>
24+ </packing>
25+ </child>
26+ <child>
27+ <object class="GtkHButtonBox" id="install_button_box">
28+ <property name="visible">True</property>
29+ <child>
30+ <object class="GtkButton" id="install_button">
31+ <property name="label">gtk-ok</property>
32+ <property name="visible">True</property>
33+ <property name="can_focus">True</property>
34+ <property name="receives_default">True</property>
35+ <property name="use_stock">True</property>
36+ <signal name="clicked" handler="on_install_button_clicked"/>
37+ </object>
38+ <packing>
39+ <property name="expand">False</property>
40+ <property name="fill">False</property>
41+ <property name="position">0</property>
42+ </packing>
43+ </child>
44+ </object>
45+ <packing>
46+ <property name="expand">False</property>
47+ <property name="position">1</property>
48+ </packing>
49+ </child>
50+ </object>
51+</interface>
52
53=== modified file 'data/management.ui'
54--- data/management.ui 2010-12-20 20:23:57 +0000
55+++ data/management.ui 2011-01-03 14:24:42 +0000
56@@ -64,8 +64,8 @@
57 <property name="visible">True</property>
58 <property name="layout_style">center</property>
59 <child>
60- <object class="GtkRadioButton" id="account_button">
61- <property name="label" translatable="yes">Account</property>
62+ <object class="GtkRadioButton" id="dashboard_button">
63+ <property name="label" translatable="yes">Dashboard</property>
64 <property name="visible">True</property>
65 <property name="can_focus">True</property>
66 <property name="receives_default">False</property>
67@@ -85,7 +85,7 @@
68 <property name="can_focus">True</property>
69 <property name="receives_default">False</property>
70 <property name="draw_indicator">False</property>
71- <property name="group">account_button</property>
72+ <property name="group">dashboard_button</property>
73 </object>
74 <packing>
75 <property name="expand">False</property>
76@@ -100,7 +100,7 @@
77 <property name="can_focus">True</property>
78 <property name="receives_default">False</property>
79 <property name="draw_indicator">False</property>
80- <property name="group">account_button</property>
81+ <property name="group">dashboard_button</property>
82 </object>
83 <packing>
84 <property name="expand">False</property>
85@@ -109,13 +109,13 @@
86 </packing>
87 </child>
88 <child>
89- <object class="GtkRadioButton" id="applications_button">
90- <property name="label" translatable="yes">Applications</property>
91+ <object class="GtkRadioButton" id="services_button">
92+ <property name="label" translatable="yes">Services</property>
93 <property name="visible">True</property>
94 <property name="can_focus">True</property>
95 <property name="receives_default">False</property>
96 <property name="draw_indicator">False</property>
97- <property name="group">account_button</property>
98+ <property name="group">dashboard_button</property>
99 </object>
100 <packing>
101 <property name="expand">False</property>
102
103=== renamed file 'data/applications.ui' => 'data/services.ui'
104--- data/applications.ui 2010-10-21 21:14:24 +0000
105+++ data/services.ui 2011-01-03 14:24:42 +0000
106@@ -7,7 +7,63 @@
107 <property name="border_width">10</property>
108 <property name="spacing">10</property>
109 <child>
110- <placeholder/>
111+ <object class="GtkScrolledWindow" id="scrolledwindow1">
112+ <property name="visible">True</property>
113+ <property name="can_focus">True</property>
114+ <property name="hscrollbar_policy">automatic</property>
115+ <property name="vscrollbar_policy">automatic</property>
116+ <child>
117+ <object class="GtkViewport" id="viewport1">
118+ <property name="visible">True</property>
119+ <property name="resize_mode">queue</property>
120+ <property name="shadow_type">none</property>
121+ <child>
122+ <object class="GtkVBox" id="vbox1">
123+ <property name="visible">True</property>
124+ <child>
125+ <object class="GtkAlignment" id="alignment1">
126+ <property name="visible">True</property>
127+ <child>
128+ <object class="GtkVBox" id="replications">
129+ <property name="visible">True</property>
130+ <property name="spacing">5</property>
131+ <child>
132+ <placeholder/>
133+ </child>
134+ </object>
135+ </child>
136+ </object>
137+ <packing>
138+ <property name="expand">False</property>
139+ <property name="position">0</property>
140+ </packing>
141+ </child>
142+ <child>
143+ <object class="GtkAlignment" id="alignment2">
144+ <property name="visible">True</property>
145+ <child>
146+ <object class="GtkVBox" id="files">
147+ <property name="visible">True</property>
148+ <property name="spacing">5</property>
149+ <child>
150+ <placeholder/>
151+ </child>
152+ </object>
153+ </child>
154+ </object>
155+ <packing>
156+ <property name="expand">False</property>
157+ <property name="position">1</property>
158+ </packing>
159+ </child>
160+ </object>
161+ </child>
162+ </object>
163+ </child>
164+ </object>
165+ <packing>
166+ <property name="position">0</property>
167+ </packing>
168 </child>
169 </object>
170 </interface>
171
172=== modified file 'po/POTFILES.in'
173--- po/POTFILES.in 2010-12-22 13:20:20 +0000
174+++ po/POTFILES.in 2011-01-03 14:24:42 +0000
175@@ -1,6 +1,7 @@
176 ubuntuone/controlpanel/gtk/gui.py
177-[type: gettext/glade] data/account.ui
178-[type: gettext/glade] data/applications.ui
179+[type: gettext/glade] data/dashboard.ui
180+[type: gettext/glade] data/services.ui
181+[type: gettext/glade] data/device.ui
182 [type: gettext/glade] data/devices.ui
183 [type: gettext/glade] data/management.ui
184 [type: gettext/glade] data/overview.ui
185
186=== modified file 'ubuntuone/controlpanel/backend.py'
187--- ubuntuone/controlpanel/backend.py 2010-12-18 20:16:18 +0000
188+++ ubuntuone/controlpanel/backend.py 2011-01-03 14:24:42 +0000
189@@ -273,6 +273,18 @@
190
191 @log_call(logger.debug)
192 @inlineCallbacks
193+ def enable_files(self):
194+ """Enable the files service."""
195+ yield dbus_client.set_files_sync_enabled(True)
196+
197+ @log_call(logger.debug)
198+ @inlineCallbacks
199+ def disable_files(self):
200+ """Enable the files service."""
201+ yield dbus_client.set_files_sync_enabled(False)
202+
203+ @log_call(logger.debug)
204+ @inlineCallbacks
205 def volumes_info(self):
206 """Get the volumes info."""
207 result = yield dbus_client.get_folders()
208
209=== modified file 'ubuntuone/controlpanel/dbus_service.py'
210--- ubuntuone/controlpanel/dbus_service.py 2010-12-16 21:09:46 +0000
211+++ ubuntuone/controlpanel/dbus_service.py 2011-01-03 14:24:42 +0000
212@@ -209,6 +209,8 @@
213 else:
214 self.FileSyncStatusError(error_handler(status_dict))
215
216+ self.FileSyncStatusChanged(status)
217+
218 @log_call(logger.debug)
219 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
220 def file_sync_status(self):
221@@ -242,6 +244,11 @@
222 def FileSyncStatusIdle(self, msg):
223 """The file sync service is idle."""
224
225+ @log_call(logger.debug)
226+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
227+ def FileSyncStatusChanged(self, msg):
228+ """The file sync service status changed."""
229+
230 @log_call(logger.error)
231 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
232 def FileSyncStatusError(self, error):
233@@ -251,6 +258,46 @@
234
235 @log_call(logger.debug)
236 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
237+ def enable_files(self):
238+ """Enable the files service."""
239+ d = self.backend.enable_files()
240+ d.addCallback(lambda _: self.FilesEnabled())
241+ d.addErrback(transform_failure(self.FilesEnableError))
242+
243+ @log_call(logger.debug)
244+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
245+ def FilesEnabled(self):
246+ """The files service is enabled."""
247+
248+ @log_call(logger.error)
249+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
250+ def FilesEnableError(self, error):
251+ """Problem enabling the files service."""
252+
253+ #---
254+
255+ @log_call(logger.debug)
256+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
257+ def disable_files(self):
258+ """Disable the files service."""
259+ d = self.backend.disable_files()
260+ d.addCallback(lambda _: self.FilesDisabled())
261+ d.addErrback(transform_failure(self.FilesDisableError))
262+
263+ @log_call(logger.debug)
264+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
265+ def FilesDisabled(self):
266+ """The files service is disabled."""
267+
268+ @log_call(logger.error)
269+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
270+ def FilesDisableError(self, error):
271+ """Problem disabling the files service."""
272+
273+ #---
274+
275+ @log_call(logger.debug)
276+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
277 def volumes_info(self):
278 """Find out the volumes info for the logged in user."""
279 d = self.backend.volumes_info()
280
281=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
282--- ubuntuone/controlpanel/gtk/gui.py 2010-12-20 21:55:49 +0000
283+++ ubuntuone/controlpanel/gtk/gui.py 2011-01-03 14:24:42 +0000
284@@ -46,12 +46,13 @@
285 from ubuntuone.controlpanel.gtk.widgets import GreyableBin
286
287 from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
288- DBUS_PREFERENCES_IFACE)
289+ DBUS_PREFERENCES_IFACE, backend)
290 from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
291 DEVICE_TYPE_COMPUTER, bool_str)
292 from ubuntuone.controlpanel.logger import setup_logging, log_call
293 from ubuntuone.controlpanel.utils import get_data_file
294
295+from ubuntuone.controlpanel.gtk import package_manager
296
297 logger = setup_logging('gtk.gui')
298 _ = gettext.gettext
299@@ -149,7 +150,7 @@
300 class ControlPanelWindow(gtk.Window):
301 """The main window for the Ubuntu One control panel."""
302
303- TITLE = _('My %(app_name)s Account')
304+ TITLE = _('My %(app_name)s Dashboard')
305
306 def __init__(self):
307 super(ControlPanelWindow, self).__init__()
308@@ -410,8 +411,8 @@
309 self.sso_backend.find_credentials(U1_APP_NAME, {})
310
311
312-class AccountPanel(UbuntuOneBin, ControlPanelMixin):
313- """The account panel. The user can manage the subscription."""
314+class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
315+ """The dashboard panel. The user can manage the subscription."""
316
317 TITLE = _('Welcome to Ubuntu One!')
318 NAME = _('Name')
319@@ -420,7 +421,7 @@
320
321 def __init__(self):
322 UbuntuOneBin.__init__(self)
323- ControlPanelMixin.__init__(self, filename='account.ui')
324+ ControlPanelMixin.__init__(self, filename='dashboard.ui')
325 self.add(self.itself)
326 self.show()
327
328@@ -750,26 +751,276 @@
329 self.message.start()
330
331
332-class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
333- """The applications panel."""
334+class InstallPackage(gtk.VBox, ControlPanelMixin):
335+ """A widget to process the install of a package."""
336+
337+ INSTALL_PACKAGE = _('You need to install the package <i>%(package_name)s'
338+ '</i> in order to enable replication.')
339+ INSTALLING = _('The package <i>%(package_name)s</i> is being installed, '
340+ 'please wait...')
341+ FAILED_INSTALL = _('The installation of <i>%(package_name)s</i> failed.')
342+ SUCCESS_INSTALL = _('The installation of <i>%(package_name)s</i> '
343+ 'was successful.')
344+
345+ def __init__(self, package_name):
346+ gtk.VBox.__init__(self)
347+ ControlPanelMixin.__init__(self, filename='install.ui')
348+ self.add(self.itself)
349+
350+ self.package_name = package_name
351+ self.package_manager = package_manager.PackageManager()
352+ self.args = {'package_name': self.package_name}
353+ self.transaction = None
354+
355+ self.progress_bar = None
356+ self.install_label.set_markup(self.INSTALL_PACKAGE % self.args)
357+
358+ self.show()
359+
360+ @package_manager.inline_callbacks
361+ def on_install_button_clicked(self, button):
362+ """The install button was clicked."""
363+ try:
364+ # create the install transaction
365+ self.transaction = yield self.package_manager.install(
366+ self.package_name)
367+
368+ # create the progress bar and pack it to the box
369+ self.progress_bar = package_manager.PackageManagerProgressBar(
370+ self.transaction)
371+ self.progress_bar.show()
372+
373+ self.itself.remove(self.install_button_box)
374+ self.itself.pack_start(self.progress_bar)
375+
376+ self.transaction.connect('finished', self.on_install_finished)
377+ self.install_label.set_markup(self.INSTALLING % self.args)
378+ yield self.transaction.run()
379+ except: # pylint: disable=W0702
380+ self._set_warning(self.FAILED_INSTALL % self.args,
381+ self.install_label)
382+
383+ @log_call(logger.info)
384+ def on_install_finished(self, transaction, exit_code):
385+ """The installation finished."""
386+ self.progress_bar.set_sensitive(False)
387+
388+ if exit_code != package_manager.aptdaemon.enums.EXIT_SUCCESS:
389+ if hasattr(transaction, 'error'):
390+ logger.error('transaction failed: %r', transaction.error)
391+ self._set_warning(self.FAILED_INSTALL % self.args,
392+ self.install_label)
393+ else:
394+ self.install_label.set_markup(self.SUCCESS_INSTALL % self.args)
395+ self.emit('finished')
396+
397+
398+class Service(gtk.VBox, ControlPanelMixin):
399+ """A service."""
400+
401+ def __init__(self, name, localized_name, *args, **kwargs):
402+ gtk.VBox.__init__(self)
403+ ControlPanelMixin.__init__(self)
404+ self.service_name = name
405+
406+ self.button = gtk.CheckButton(label=localized_name)
407+ self.pack_start(self.button, expand=False)
408+
409+ self.show_all()
410+
411+
412+class FilesService(Service):
413+ """The file sync service."""
414+
415+ FILES_SERVICE_NAME = _('Files')
416+
417+ def __init__(self):
418+ Service.__init__(self, name='files',
419+ localized_name=self.FILES_SERVICE_NAME)
420+
421+ self.set_sensitive(False)
422+ self.backend.connect_to_signal('FileSyncStatusChanged',
423+ self.on_file_sync_status_changed)
424+ self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
425+ self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
426+ self.backend.file_sync_status()
427+
428+ @log_call(logger.debug)
429+ def on_file_sync_status_changed(self, status):
430+ """File sync status changed."""
431+ enabled = status != backend.FILE_SYNC_DISABLED
432+ self.button.set_active(enabled)
433+
434+ if not self.is_sensitive():
435+ # first time we're getting this event
436+ self.button.connect('toggled', self.on_button_toggled)
437+ self.set_sensitive(True)
438+
439+ def on_files_enabled(self):
440+ """Files service was enabled."""
441+ self.on_file_sync_status_changed('enabled!')
442+
443+ def on_files_disabled(self):
444+ """Files service was disabled."""
445+ self.on_file_sync_status_changed(backend.FILE_SYNC_DISABLED)
446+
447+ @log_call(logger.debug)
448+ def on_button_toggled(self, button):
449+ """Button was toggled, exclude/replicate the service properly."""
450+ logger.info('File sync enabled? %r', self.button.get_active())
451+ if self.button.get_active():
452+ self.backend.enable_files()
453+ else:
454+ self.backend.disable_files()
455+
456+
457+class DesktopcouchService(Service):
458+ """A desktopcouch service."""
459+
460+ def __init__(self, name, localized_name,
461+ replication_service, dependency=None):
462+ Service.__init__(self, name, localized_name)
463+ self.replication_service = replication_service
464+ enabled = name not in self.replication_service.all_exclusions()
465+ self.button.set_active(enabled)
466+
467+ self.dependency = None
468+ if dependency is not None:
469+ self.dependency = InstallPackage(dependency)
470+ self.dependency.connect('finished', self.on_depedency_finished)
471+ self.pack_start(self.dependency, expand=False)
472+ self.button.set_sensitive(False)
473+
474+ self.button.connect('toggled', self.on_button_toggled)
475+
476+ def on_depedency_finished(self, widget):
477+ """The dependency was installed."""
478+ self.button.set_sensitive(True)
479+ self.remove(self.dependency)
480+ self.dependency = None
481+
482+ @log_call(logger.debug)
483+ def on_button_toggled(self, button):
484+ """Button was toggled, exclude/replicate the service properly."""
485+ logger.info('Starting replication for %r? %r',
486+ self.service_name, self.button.get_active())
487+ if self.button.get_active():
488+ self.replication_service.replicate(self.service_name)
489+ else:
490+ self.replication_service.exclude(self.service_name)
491+
492+
493+class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
494+ """The services panel."""
495
496 TITLE = _('Ubuntu One services including data sync are enabled for the '
497- 'data types and applications listed below:')
498+ 'data types and services listed below.')
499+ CHOOSE_SERVICES = _('Choose services to synchronize with this computer:')
500+ DESKTOPCOUCH_PKG = 'desktopcouch'
501+ BINDWOOD_PKG = 'xul-ext-bindwood'
502+ EVOCOUCH_PKG = 'evolution-couchdb'
503+ BOOKMARKS = _('Bookmarks (Firefox)')
504+ CONTACTS = _('Contacts (Evolution)')
505+ NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
506
507- def __init__(self):
508+ def __init__(self, replication_exclusion_class=None):
509 UbuntuOneBin.__init__(self)
510- ControlPanelMixin.__init__(self, filename='applications.ui')
511+ ControlPanelMixin.__init__(self, filename='services.ui')
512 self.add(self.itself)
513+
514+ self.replication_exclusion_class = replication_exclusion_class
515+ self.replication_service = None
516+ self.has_desktopcouch = False
517+ self.has_bindwood = False
518+ self.has_evocouch = False
519+ self.package_manager = package_manager.PackageManager()
520+ self.install_box = None
521+ self.bookmarks = None
522+ self.contacts = None
523+
524+ self.files.pack_start(FilesService(), expand=False)
525+
526 self.show()
527
528+ @log_call(logger.debug)
529+ def load(self):
530+ """Load info."""
531+ if self.install_box is not None:
532+ self.itself.remove(self.install_box)
533+ self.install_box = None
534+
535+ self.has_desktopcouch = \
536+ self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
537+ self.has_bindwood = \
538+ self.package_manager.is_installed(self.BINDWOOD_PKG)
539+ self.has_evocouch = \
540+ self.package_manager.is_installed(self.EVOCOUCH_PKG)
541+
542+ logger.info('load: has_desktopcouch? %r has_bindwood? %s '
543+ 'has_evocouch? %s', self.has_desktopcouch,
544+ self.has_bindwood, self.has_evocouch)
545+ if not self.has_desktopcouch:
546+ self.message.set_text('')
547+ self.replications.hide()
548+
549+ self.install_box = InstallPackage(self.DESKTOPCOUCH_PKG)
550+ self.install_box.connect('finished', self.load_replications)
551+ self.itself.pack_start(self.install_box, expand=False)
552+ self.itself.reorder_child(self.install_box, 0)
553+ else:
554+ self.load_replications()
555+
556 self.message.stop()
557- self.message.set_text('Under construction')
558+
559+ @log_call(logger.debug)
560+ def load_replications(self, *args):
561+ """Load replications info."""
562+ self.replications.show()
563+
564+ if self.install_box is not None:
565+ self.itself.remove(self.install_box)
566+ self.install_box = None
567+
568+ self.message.set_text(self.CHOOSE_SERVICES)
569+ for child in self.replications.get_children():
570+ self.replications.remove(child)
571+
572+ # Unable to import 'desktopcouch.application.replication_services'
573+ # pylint: disable=F0401
574+ if self.replication_exclusion_class is None:
575+ from desktopcouch.application.replication_services import \
576+ ubuntuone as u1rep
577+ self.replication_exclusion_class = u1rep.ReplicationExclusion
578+
579+ if self.replication_service is None:
580+ try:
581+ self.replication_service = self.replication_exclusion_class()
582+ except ValueError:
583+ logger.exception('Can not load replications:')
584+ self._set_warning(self.NO_PAIRING_RECORD, self.message)
585+ return
586+
587+ pkg = None
588+ if not self.has_bindwood:
589+ pkg = self.BINDWOOD_PKG
590+ self.bookmarks = DesktopcouchService('bookmarks', self.BOOKMARKS,
591+ self.replication_service,
592+ dependency=pkg)
593+ self.replications.pack_start(self.bookmarks, expand=False)
594+
595+ pkg = None
596+ if not self.has_evocouch:
597+ pkg = self.EVOCOUCH_PKG
598+ self.contacts = DesktopcouchService('contacts', self.CONTACTS,
599+ self.replication_service,
600+ dependency=pkg)
601+ self.replications.pack_start(self.contacts, expand=False)
602
603
604 class ManagementPanel(gtk.VBox, ControlPanelMixin):
605 """The management panel.
606
607- The user can manage account, folders, devices and applications.
608+ The user can manage dashboard, folders, devices and services.
609
610 """
611
612@@ -817,13 +1068,13 @@
613 self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
614 self.status_box.pack_end(self.status_label, expand=False)
615
616- self.account = AccountPanel()
617+ self.dashboard = DashboardPanel()
618 self.folders = FoldersPanel()
619 self.devices = DevicesPanel()
620- self.applications = ApplicationsPanel()
621+ self.services = ServicesPanel()
622
623 cb = lambda button, page_num: self.notebook.set_current_page(page_num)
624- self.tabs = (u'account', u'folders', u'devices', u'applications')
625+ self.tabs = (u'dashboard', u'folders', u'devices', u'services')
626 for page_num, tab in enumerate(self.tabs):
627 setattr(self, ('%s_page' % tab).upper(), page_num)
628 button = getattr(self, '%s_button' % tab)
629@@ -835,6 +1086,7 @@
630
631 self.folders_button.connect('clicked', lambda b: self.folders.load())
632 self.devices_button.connect('clicked', lambda b: self.devices.load())
633+ self.services_button.connect('clicked', lambda b: self.services.load())
634 self.devices.connect('local-device-removed',
635 lambda widget: self.emit('local-device-removed'))
636
637@@ -857,7 +1109,7 @@
638 """Load the account info and file sync status list."""
639 self.backend.account_info()
640 self.backend.file_sync_status()
641- self.account_button.clicked()
642+ self.dashboard_button.clicked()
643
644 @log_call(logger.debug)
645 def on_account_info_ready(self, info):
646@@ -910,3 +1162,6 @@
647
648 gobject.signal_new('local-device-removed', DevicesPanel,
649 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
650+
651+gobject.signal_new('finished', InstallPackage,
652+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
653
654=== added file 'ubuntuone/controlpanel/gtk/package_manager.py'
655--- ubuntuone/controlpanel/gtk/package_manager.py 1970-01-01 00:00:00 +0000
656+++ ubuntuone/controlpanel/gtk/package_manager.py 2011-01-03 14:24:42 +0000
657@@ -0,0 +1,60 @@
658+# -*- coding: utf-8 -*-
659+
660+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
661+#
662+# Copyright 2010 Canonical Ltd.
663+#
664+# This program is free software: you can redistribute it and/or modify it
665+# under the terms of the GNU General Public License version 3, as published
666+# by the Free Software Foundation.
667+#
668+# This program is distributed in the hope that it will be useful, but
669+# WITHOUT ANY WARRANTY; without even the implied warranties of
670+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
671+# PURPOSE. See the GNU General Public License for more details.
672+#
673+# You should have received a copy of the GNU General Public License along
674+# with this program. If not, see <http://www.gnu.org/licenses/>.
675+
676+"""Client to manage packages."""
677+
678+import apt
679+import aptdaemon.client
680+import aptdaemon.enums
681+
682+try:
683+ # Unable to import 'defer', pylint: disable=F0401,E0611
684+ from aptdaemon.defer import inline_callbacks, return_value
685+except ImportError:
686+ # Unable to import 'defer', pylint: disable=F0401,E0611
687+ from defer import inline_callbacks, return_value
688+from aptdaemon.gtkwidgets import AptProgressBar
689+
690+from ubuntuone.controlpanel.logger import setup_logging
691+
692+
693+logger = setup_logging('package_manager')
694+
695+
696+class PackageManagerProgressBar(AptProgressBar):
697+ """A progress bar for a transaction."""
698+
699+
700+class PackageManager(object):
701+ """Manage packages (check if is installed, install)."""
702+
703+ def is_installed(self, package_name):
704+ """Return whether 'package_name' is installed in this system."""
705+ cache = apt.Cache()
706+ result = package_name in cache and cache[package_name].is_installed
707+ return result
708+
709+ @inline_callbacks
710+ def install(self, package_name):
711+ """Install 'package_name' if is not installed in this system."""
712+ if self.is_installed(package_name):
713+ return_value(aptdaemon.enums.EXIT_SUCCESS)
714+
715+ client = aptdaemon.client.AptClient()
716+ transaction = yield client.install_packages([package_name])
717+ return_value(transaction)
718
719=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
720--- ubuntuone/controlpanel/gtk/tests/__init__.py 2010-12-02 16:23:03 +0000
721+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-03 14:24:42 +0000
722@@ -17,3 +17,153 @@
723 # with this program. If not, see <http://www.gnu.org/licenses/>.
724
725 """The test suite for the GTK UI for the control panel for Ubuntu One."""
726+
727+from collections import defaultdict
728+
729+from ubuntuone.controlpanel.gtk import gui
730+from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
731+ FakedTransaction)
732+
733+
734+FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
735+ 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
736+
737+FAKE_VOLUMES_INFO = [
738+ {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},
739+ {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},
740+ {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
741+]
742+
743+FAKE_DEVICE_INFO = {
744+ 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
745+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
746+ 'max_upload_speed': '1000', 'max_download_speed': '72548',
747+}
748+
749+FAKE_DEVICES_INFO = [
750+ {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
751+ 'is_local': '', 'configurable': ''},
752+ {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
753+ 'is_local': '', 'configurable': ''},
754+ {'device_id': '2', 'name': 'Z', 'type': 'Computer',
755+ 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
756+ 'max_upload_speed': '0', 'max_download_speed': '0'},
757+ {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
758+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
759+ 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
760+]
761+
762+
763+class FakedObject(object):
764+ """Fake an object, record every call."""
765+
766+ exposed_methods = []
767+
768+ def __init__(self, *args, **kwargs):
769+ self._args = args
770+ self._kwargs = kwargs
771+ self._called = {}
772+ for i in self.exposed_methods:
773+ setattr(self, i, self._record_call(i))
774+
775+ def _record_call(self, func_name):
776+ """Store values when calling 'func_name'."""
777+
778+ def inner(*args, **kwargs):
779+ """Fake 'func_name'."""
780+ self._called[func_name] = (args, kwargs)
781+
782+ return inner
783+
784+
785+class FakedNMState(FakedObject):
786+ """Fake a NetworkManagerState."""
787+
788+ exposed_methods = ['find_online_state']
789+
790+
791+class FakedDBusBackend(FakedObject):
792+ """Fake a DBus Backend."""
793+
794+ bus_name = None
795+ object_path = None
796+ iface = None
797+
798+ def __init__(self, obj, dbus_interface, *args, **kwargs):
799+ if dbus_interface != self.iface:
800+ raise TypeError()
801+ self._signals = defaultdict(list)
802+ super(FakedDBusBackend, self).__init__(*args, **kwargs)
803+
804+ def connect_to_signal(self, signal, handler):
805+ """Bind 'handler' to be callback'd when 'signal' is fired."""
806+ self._signals[signal].append(handler)
807+
808+
809+class FakedSSOBackend(FakedDBusBackend):
810+ """Fake a SSO Backend, act as a dbus.Interface."""
811+
812+ bus_name = gui.ubuntu_sso.DBUS_BUS_NAME
813+ object_path = gui.ubuntu_sso.DBUS_CREDENTIALS_PATH
814+ iface = gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE
815+ exposed_methods = ['find_credentials', 'clear_credentials',
816+ 'login', 'register']
817+
818+
819+class FakedControlPanelBackend(FakedDBusBackend):
820+ """Fake a Control Panel Backend, act as a dbus.Interface."""
821+
822+ bus_name = gui.DBUS_BUS_NAME
823+ object_path = gui.DBUS_PREFERENCES_PATH
824+ iface = gui.DBUS_PREFERENCES_IFACE
825+ exposed_methods = [
826+ 'account_info', 'devices_info', 'change_device_settings',
827+ 'volumes_info', 'change_volume_settings', 'file_sync_status',
828+ 'remove_device', 'enable_files', 'disable_files',
829+ ]
830+
831+
832+class FakedSessionBus(object):
833+ """Fake a session bus."""
834+
835+ def get_object(self, bus_name, object_path, introspect=True,
836+ follow_name_owner_changes=False, **kwargs):
837+ """Return a faked proxy for the given remote object."""
838+ return None
839+
840+
841+class FakedInterface(object):
842+ """Fake a dbus interface."""
843+
844+ def __new__(cls, obj, dbus_interface, *args, **kwargs):
845+ if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
846+ return FakedControlPanelBackend(obj, dbus_interface,
847+ *args, **kwargs)
848+ if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE:
849+ return FakedSSOBackend(obj, dbus_interface, *args, **kwargs)
850+
851+
852+class FakedPackageManager(object):
853+ """Faked a package manager."""
854+
855+ def __init__(self):
856+ self._installed = {}
857+ self.is_installed = lambda package_name: \
858+ self._installed.setdefault(package_name, False)
859+
860+ @gui.package_manager.inline_callbacks
861+ def install(self, package_name):
862+ """Install 'package_name' if is not installed in this system."""
863+ yield
864+ self._installed[package_name] = True
865+ gui.package_manager.return_value(FakedTransaction([package_name]))
866+
867+
868+class FakedReplication(object):
869+ """Faked a DC replication exclusion."""
870+
871+ def __init__(self):
872+ self._exclusions = set()
873+ self.all_exclusions = lambda: self._exclusions
874+ self.replicate = self._exclusions.remove
875+ self.exclude = self._exclusions.add
876
877=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
878--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-20 20:58:39 +0000
879+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-03 14:24:42 +0000
880@@ -22,136 +22,21 @@
881
882 import logging
883
884-from collections import defaultdict
885-
886 from ubuntuone.devtools.handlers import MementoHandler
887
888 from ubuntuone.controlpanel.gtk import gui
889+from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
890+ FAKE_VOLUMES_INFO, FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
891+ FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
892+ FakedPackageManager, FakedReplication,
893+)
894 from ubuntuone.controlpanel.tests import TOKEN, TestCase
895-
896-# Attribute 'yyy' defined outside __init__
897-# pylint: disable=W0201
898-
899-# Access to a protected member 'yyy' of a client class
900-# pylint: disable=W0212
901-
902-
903-FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
904- 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
905-
906-FAKE_VOLUMES_INFO = [
907- {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},
908- {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},
909- {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
910-]
911-
912-FAKE_DEVICE_INFO = {
913- 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
914- 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
915- 'max_upload_speed': '1000', 'max_download_speed': '72548',
916-}
917-
918-FAKE_DEVICES_INFO = [
919- {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
920- 'is_local': '', 'configurable': ''},
921- {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
922- 'is_local': '', 'configurable': ''},
923- {'device_id': '2', 'name': 'Z', 'type': 'Computer',
924- 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
925- 'max_upload_speed': '0', 'max_download_speed': '0'},
926- {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
927- 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
928- 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
929-]
930-
931-
932-class FakedObject(object):
933- """Fake an object, record every call."""
934-
935- exposed_methods = []
936-
937- def __init__(self, *args, **kwargs):
938- self._args = args
939- self._kwargs = kwargs
940- self._called = {}
941- for i in self.exposed_methods:
942- setattr(self, i, self._record_call(i))
943-
944- def _record_call(self, func_name):
945- """Store values when calling 'func_name'."""
946-
947- def inner(*args, **kwargs):
948- """Fake 'func_name'."""
949- self._called[func_name] = (args, kwargs)
950-
951- return inner
952-
953-
954-class FakedNMState(FakedObject):
955- """Fake a NetworkManagerState."""
956-
957- exposed_methods = ['find_online_state']
958-
959-
960-class FakedDBusBackend(FakedObject):
961- """Fake a DBus Backend."""
962-
963- bus_name = None
964- object_path = None
965- iface = None
966-
967- def __init__(self, obj, dbus_interface, *args, **kwargs):
968- if dbus_interface != self.iface:
969- raise TypeError()
970- self._signals = defaultdict(list)
971- super(FakedDBusBackend, self).__init__(*args, **kwargs)
972-
973- def connect_to_signal(self, signal, handler):
974- """Bind 'handler' to be callback'd when 'signal' is fired."""
975- self._signals[signal].append(handler)
976-
977-
978-class FakedSSOBackend(FakedDBusBackend):
979- """Fake a SSO Backend, act as a dbus.Interface."""
980-
981- bus_name = gui.ubuntu_sso.DBUS_BUS_NAME
982- object_path = gui.ubuntu_sso.DBUS_CREDENTIALS_PATH
983- iface = gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE
984- exposed_methods = ['find_credentials', 'clear_credentials',
985- 'login', 'register']
986-
987-
988-class FakedControlPanelBackend(FakedDBusBackend):
989- """Fake a Control Panel Backend, act as a dbus.Interface."""
990-
991- bus_name = gui.DBUS_BUS_NAME
992- object_path = gui.DBUS_PREFERENCES_PATH
993- iface = gui.DBUS_PREFERENCES_IFACE
994- exposed_methods = [
995- 'account_info', 'devices_info', 'change_device_settings',
996- 'volumes_info', 'change_volume_settings', 'file_sync_status',
997- 'remove_device',
998- ]
999-
1000-
1001-class FakedSessionBus(object):
1002- """Fake a session bus."""
1003-
1004- def get_object(self, bus_name, object_path, introspect=True,
1005- follow_name_owner_changes=False, **kwargs):
1006- """Return a faked proxy for the given remote object."""
1007- return None
1008-
1009-
1010-class FakedInterface(object):
1011- """Fake a dbus interface."""
1012-
1013- def __new__(cls, obj, dbus_interface, *args, **kwargs):
1014- if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
1015- return FakedControlPanelBackend(obj, dbus_interface,
1016- *args, **kwargs)
1017- if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE:
1018- return FakedSSOBackend(obj, dbus_interface, *args, **kwargs)
1019+from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
1020+ SUCCESS, FAILURE)
1021+
1022+
1023+# Attribute 'yyy' defined outside __init__, access to a protected member
1024+# pylint: disable=W0201, W0212
1025
1026
1027 class BaseTestCase(TestCase):
1028@@ -168,6 +53,7 @@
1029 self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
1030 self.patch(gui.dbus, 'Interface', FakedInterface)
1031 self.patch(gui.networkstate, 'NetworkManagerState', FakedNMState)
1032+ self.patch(gui.package_manager, 'PackageManager', FakedPackageManager)
1033
1034 if self.klass is not None:
1035 self.ui = self.klass(**self.kwargs)
1036@@ -339,10 +225,10 @@
1037
1038 self.assert_current_tab_correct(self.ui.management)
1039
1040- def test_credentials_found_shows_account_management_panel(self):
1041+ def test_credentials_found_shows_dashboard_management_panel(self):
1042 """On 'credentials-found' signal, the management panel is shown.
1043
1044- If first signal parameter is False, visible tab should be account.
1045+ If first signal parameter is False, visible tab should be dashboard.
1046
1047 """
1048 self.patch(self.ui.management, 'load', self._set_called)
1049@@ -350,7 +236,7 @@
1050
1051 self.assert_current_tab_correct(self.ui.management)
1052 self.assertEqual(self.ui.management.notebook.get_current_page(),
1053- self.ui.management.ACCOUNT_PAGE)
1054+ self.ui.management.DASHBOARD_PAGE)
1055 self.assertEqual(self._called, ((), {}))
1056
1057 def test_credentials_found_shows_folders_management_panel(self):
1058@@ -736,11 +622,11 @@
1059 messages = ['<small>Test me</small>', 'A <b>little</b> bit more']
1060
1061
1062-class AccountTestCase(ControlPanelMixinTestCase):
1063- """The test suite for the account panel."""
1064+class DashboardTestCase(ControlPanelMixinTestCase):
1065+ """The test suite for the dashboard panel."""
1066
1067- klass = gui.AccountPanel
1068- ui_filename = 'account.ui'
1069+ klass = gui.DashboardPanel
1070+ ui_filename = 'dashboard.ui'
1071
1072 def assert_account_info_correct(self, info):
1073 """Check that the displayed account info matches 'info'."""
1074@@ -1087,7 +973,8 @@
1075
1076 def test_on_limit_bandwidth_toggled(self):
1077 """When toggling limit_bandwidth, backend is updated."""
1078- self.ui.limit_bandwidth.toggled()
1079+ value = not self.ui.limit_bandwidth.get_active()
1080+ self.ui.limit_bandwidth.set_active(value)
1081 self.assert_device_settings_changed()
1082
1083 def test_on_max_upload_speed_value_changed(self):
1084@@ -1365,11 +1252,294 @@
1085 self.assertEqual(new_devices, old_devices)
1086
1087
1088-class ApplicationsTestCase(ControlPanelMixinTestCase):
1089- """The test suite for the applications panel."""
1090-
1091- klass = gui.ApplicationsPanel
1092- ui_filename = 'applications.ui'
1093+class InstallPackageTestCase(ControlPanelMixinTestCase):
1094+ """The test suite for the install widget."""
1095+
1096+ klass = gui.InstallPackage
1097+ ui_filename = 'install.ui'
1098+ kwargs = {'package_name': 'a test package'}
1099+
1100+ def test_is_an_box(self):
1101+ """Inherits from gtk.VBox."""
1102+ self.assertIsInstance(self.ui, gui.gtk.VBox)
1103+
1104+ def test_inner_widget_is_packed(self):
1105+ """The 'itself' vbox is packed into the widget."""
1106+ self.assertIn(self.ui.itself, self.ui.get_children())
1107+
1108+ def test_is_visible(self):
1109+ """Is visible."""
1110+ self.assertTrue(self.ui.get_visible())
1111+
1112+ def test_package_name(self):
1113+ """The package_name is stored."""
1114+ self.assertEqual(self.ui.package_name, self.kwargs['package_name'])
1115+
1116+ def test_children(self):
1117+ """The children is correct."""
1118+ children = self.ui.itself.get_children()
1119+ self.assertEqual(len(children), 2)
1120+ self.assertEqual(self.ui.install_label, children[0])
1121+ self.assertIn(self.ui.install_button, children[1].get_children())
1122+
1123+ def test_install_label(self):
1124+ """The install label is correct."""
1125+ msg = self.ui.INSTALL_PACKAGE % self.kwargs
1126+ self.assertEqual(self.ui.install_label.get_label(), msg)
1127+
1128+ @gui.package_manager.inline_callbacks
1129+ def test_install_button_clicked_shows_progress(self):
1130+ """The install button is correct."""
1131+ yield self.ui.install_button.clicked()
1132+
1133+ children = self.ui.itself.get_children()
1134+ self.assertEqual(len(children), 2)
1135+ self.assertEqual(self.ui.progress_bar, children[1])
1136+ self.assertTrue(self.ui.progress_bar.get_visible())
1137+ self.assertIsInstance(self.ui.progress_bar,
1138+ gui.package_manager.PackageManagerProgressBar)
1139+
1140+ def test_install_button_clicked_install_label(self):
1141+ """The install label is correct."""
1142+ yield self.ui.install_button.clicked()
1143+
1144+ children = self.ui.itself.get_children()
1145+ self.assertEqual(len(children), 2)
1146+ self.assertEqual(self.ui.install_label, children[0])
1147+ msg = self.ui.INSTALLING % self.kwargs
1148+ self.assertEqual(self.ui.install_label.get_label(), msg)
1149+
1150+ @gui.package_manager.inline_callbacks
1151+ def test_install_button_clicked_transaction(self):
1152+ """The install button transaction is correct."""
1153+ yield self.ui.install_button.clicked()
1154+
1155+ transaction = self.ui.transaction
1156+ self.assertTrue(transaction.packages, [self.ui.package_name])
1157+ self.assertIn(self.ui.on_install_finished,
1158+ transaction._signals['finished'])
1159+ self.assertTrue(transaction.was_run)
1160+
1161+ @gui.package_manager.inline_callbacks
1162+ def test_install_button_clicked_fails(self):
1163+ """The install button transaction is correct."""
1164+
1165+ def fail(*args):
1166+ """Simulate an error."""
1167+ raise Exception(args)
1168+
1169+ self.patch(self.ui.package_manager, 'install', fail)
1170+ yield self.ui.install_button.clicked()
1171+
1172+ msg = self.ui.FAILED_INSTALL % self.kwargs
1173+ self.assert_warning_correct(self.ui.install_label, msg)
1174+
1175+ @gui.package_manager.inline_callbacks
1176+ def test_on_install_finished_success(self):
1177+ """The install finished."""
1178+ self.ui.connect('finished', self._set_called)
1179+ yield self.ui.install_button.clicked()
1180+ self.ui.on_install_finished(object(), SUCCESS)
1181+
1182+ self.assertFalse(self.ui.progress_bar.get_sensitive())
1183+ msg = self.ui.SUCCESS_INSTALL % self.kwargs
1184+ self.assertEqual(self.ui.install_label.get_label(), msg)
1185+ self.assertEqual(self._called, ((self.ui,), {}))
1186+
1187+ @gui.package_manager.inline_callbacks
1188+ def test_on_install_finished_failed(self):
1189+ """The install finished."""
1190+ yield self.ui.install_button.clicked()
1191+ self.ui.on_install_finished(object(), FAILURE)
1192+
1193+ self.assertFalse(self.ui.progress_bar.get_sensitive())
1194+ msg = self.ui.FAILED_INSTALL % self.kwargs
1195+ self.assert_warning_correct(self.ui.install_label, msg)
1196+
1197+
1198+class ServiceTestCase(ControlPanelMixinTestCase):
1199+ """The test suite for a service."""
1200+
1201+ klass = gui.Service
1202+ name = 'dc_test'
1203+ localized_name = u'Qué lindo test!'
1204+ kwargs = {'name': 'dc_test', 'localized_name': u'Qué lindo test!'}
1205+
1206+ def test_is_an_box(self):
1207+ """Inherits from gtk.VBox."""
1208+ self.assertIsInstance(self.ui, gui.gtk.VBox)
1209+
1210+ def test_is_visible(self):
1211+ """Is visible."""
1212+ self.assertTrue(self.ui.get_visible())
1213+
1214+ def test_check_button_packed(self):
1215+ """A check button is packed as only child."""
1216+ self.assertIn(self.ui.button, self.ui.get_children())
1217+
1218+ def test_label(self):
1219+ """The label is set."""
1220+ self.assertEqual(self.localized_name, self.ui.button.get_label())
1221+
1222+ def test_service_name(self):
1223+ """The service_name is set."""
1224+ self.assertEqual(self.name, self.ui.service_name)
1225+
1226+
1227+class FilesServiceTestCase(ServiceTestCase):
1228+ """The test suite for the file sync service."""
1229+
1230+ klass = gui.FilesService
1231+ kwargs = {}
1232+
1233+ def setUp(self):
1234+ self.name = 'files'
1235+ self.localized_name = gui.FilesService.FILES_SERVICE_NAME
1236+ super(FilesServiceTestCase, self).setUp()
1237+
1238+ def test_backend_account_signals(self):
1239+ """The proper signals are connected to the backend."""
1240+ self.assertEqual(self.ui.backend._signals['FileSyncStatusChanged'],
1241+ [self.ui.on_file_sync_status_changed])
1242+ self.assertEqual(self.ui.backend._signals['FilesEnabled'],
1243+ [self.ui.on_files_enabled])
1244+ self.assertEqual(self.ui.backend._signals['FilesDisabled'],
1245+ [self.ui.on_files_disabled])
1246+
1247+ def test_file_sync_status_is_requested(self):
1248+ """The file sync status is requested to the backend."""
1249+ self.assert_backend_called('file_sync_status', ())
1250+
1251+ def test_is_disabled(self):
1252+ """Until file sync status is given, the widget is disabled."""
1253+ self.assertFalse(self.ui.get_sensitive())
1254+
1255+ def test_is_enabled_on_file_sync_status_changed(self):
1256+ """When the file sync status is given, the widget is enabled."""
1257+ self.ui.on_file_sync_status_changed('something')
1258+ self.assertTrue(self.ui.get_sensitive())
1259+
1260+ def test_active(self):
1261+ """Is active when file status is anything but 'file-sync-disabled'."""
1262+ self.ui.on_file_sync_status_changed('something not disabled')
1263+ self.assertTrue(self.ui.button.get_active())
1264+
1265+ def test_not_active(self):
1266+ """Is not active when status is exactly but 'file-sync-disabled'."""
1267+ self.ui.on_file_sync_status_changed(gui.backend.FILE_SYNC_DISABLED)
1268+ self.assertFalse(self.ui.button.get_active())
1269+
1270+ def test_on_button_toggled(self):
1271+ """When toggling the button, the file sync service is updated."""
1272+ self.ui.on_file_sync_status_changed('something not disabled')
1273+ assert self.ui.button.get_active()
1274+
1275+ self.ui.button.set_active(not self.ui.button.get_active())
1276+ self.assert_backend_called('disable_files', ())
1277+
1278+ self.ui.button.set_active(not self.ui.button.get_active())
1279+ self.assert_backend_called('enable_files', ())
1280+
1281+ def test_on_file_sync_enabled(self):
1282+ """When file sync is enabled, the button is active."""
1283+ self.ui.on_files_disabled()
1284+ assert not self.ui.button.get_active()
1285+
1286+ self.ui.on_files_enabled()
1287+ self.assertTrue(self.ui.button.get_active())
1288+
1289+ def test_on_file_sync_disabled(self):
1290+ """When file sync is disabled, the button is not active."""
1291+ self.ui.on_files_enabled()
1292+ assert self.ui.button.get_active()
1293+
1294+ self.ui.on_files_disabled()
1295+ self.assertFalse(self.ui.button.get_active())
1296+
1297+
1298+class DesktopcouchServiceTestCase(ServiceTestCase):
1299+ """The test suite for a desktopcouch service."""
1300+
1301+ klass = gui.DesktopcouchService
1302+
1303+ def setUp(self):
1304+ self.replication = FakedReplication()
1305+ self.name = self.kwargs['name']
1306+ self.kwargs['replication_service'] = self.replication
1307+ super(DesktopcouchServiceTestCase, self).setUp()
1308+
1309+ def test_active(self):
1310+ """Is active since replication has an empty database."""
1311+ self.assertTrue(self.ui.button.get_active())
1312+
1313+ def test_not_active(self):
1314+ """Is not active since 'name' is excluded on replication database."""
1315+ self.replication.exclude(self.name)
1316+ self.ui = self.klass(**self.kwargs)
1317+ self.assertFalse(self.ui.button.get_active())
1318+
1319+ def test_on_button_toggled(self):
1320+ """When toggling the button, the DC exclude list is updated."""
1321+ assert self.ui.button.get_active()
1322+ self.ui.button.set_active(not self.ui.button.get_active())
1323+ self.assertEqual(set([self.name]), self.replication.all_exclusions())
1324+
1325+ def test_on_button_toggled_twice(self):
1326+ """When toggling the button twice, the DC exclude list is updated."""
1327+ assert self.ui.button.get_active()
1328+ self.ui.button.set_active(not self.ui.button.get_active())
1329+ self.ui.button.set_active(not self.ui.button.get_active())
1330+ self.assertEqual(set(), self.replication.all_exclusions())
1331+
1332+ def test_dependency(self):
1333+ """The dependency box is None."""
1334+ self.assertTrue(self.ui.dependency is None)
1335+
1336+ def test_button_sensitiveness(self):
1337+ """The check button is sensitive."""
1338+ self.assertTrue(self.ui.button.get_sensitive())
1339+
1340+
1341+class DesktopcouchServiceWithDependencyTestCase(DesktopcouchServiceTestCase):
1342+ """The test suite for a desktopcouch service when it needs a dependency."""
1343+
1344+ def setUp(self):
1345+ self.kwargs['dependency'] = 'a package'
1346+ super(DesktopcouchServiceWithDependencyTestCase, self).setUp()
1347+
1348+ def test_dependency(self):
1349+ """The dependency bos is not hidden."""
1350+ self.assertIsInstance(self.ui.dependency, gui.InstallPackage)
1351+ self.assertEqual(self.ui.dependency.package_name,
1352+ self.kwargs['dependency'])
1353+
1354+ def test_dependency_is_packed(self):
1355+ """The dependency is packed in the ui."""
1356+ self.assertIn(self.ui.dependency, self.ui.get_children())
1357+
1358+ def test_button_sensitiveness(self):
1359+ """The check button is not sensitive until depedency installed."""
1360+ self.assertFalse(self.ui.button.get_sensitive())
1361+
1362+ def test_button_is_enabled_on_dependency_installed(self):
1363+ """The check button is sensitive when depedency is installed."""
1364+ self.ui.dependency.emit('finished')
1365+
1366+ self.assertTrue(self.ui.button.get_sensitive())
1367+
1368+ def test_install_widget_is_removed_on_dependency_installed(self):
1369+ """The install button is removed when depedency is installed."""
1370+ self.ui.dependency.emit('finished')
1371+
1372+ self.assertTrue(self.ui.dependency is None)
1373+ self.assertEqual(self.ui.get_children(), [self.ui.button])
1374+
1375+
1376+class ServicesTestCase(ControlPanelMixinTestCase):
1377+ """The test suite for the services panel."""
1378+
1379+ klass = gui.ServicesPanel
1380+ ui_filename = 'services.ui'
1381
1382 def test_is_an_ubuntuone_bin(self):
1383 """Inherits from UbuntuOneBin."""
1384@@ -1383,6 +1553,200 @@
1385 """Is visible."""
1386 self.assertTrue(self.ui.get_visible())
1387
1388+ def test_package_manager(self):
1389+ """Has a package manager."""
1390+ self.assertIsInstance(self.ui.package_manager,
1391+ gui.package_manager.PackageManager)
1392+
1393+ def test_install_box(self):
1394+ """The install box is None."""
1395+ self.assertTrue(self.ui.install_box is None)
1396+
1397+
1398+class ServicesFilesTestCase(ServicesTestCase):
1399+ """The test suite for the services panel (files section)."""
1400+
1401+ def test_files_is_visible(self):
1402+ """Files section is visible."""
1403+ self.assertTrue(self.ui.files.get_visible())
1404+
1405+ def test_files_is_a_file_sync_service(self):
1406+ """Files contains a FilesService."""
1407+ child, = self.ui.files.get_children()
1408+ self.assertIsInstance(child, gui.FilesService)
1409+
1410+
1411+class ServicesWithoutDesktopcouchTestCase(ServicesTestCase):
1412+ """The test suite for the services panel when DC is not installed."""
1413+
1414+ def setUp(self):
1415+ super(ServicesWithoutDesktopcouchTestCase, self).setUp()
1416+ self.patch(self.ui.package_manager, 'is_installed', lambda *a: False)
1417+ self.ui.load()
1418+
1419+ def test_message(self):
1420+ """Global load message is stopped and cleared."""
1421+ self.assertFalse(self.ui.message.active)
1422+ self.assertEqual(self.ui.message.get_text(), '')
1423+
1424+ def test_replication_service(self):
1425+ """Has a replication service."""
1426+ self.assertEqual(self.ui.replication_service, None)
1427+
1428+ def test_has_desktopcouch(self):
1429+ """Has desktopcouch installed?"""
1430+ self.assertFalse(self.ui.has_desktopcouch)
1431+
1432+ def test_has_bindwood(self):
1433+ """Has bindwood installed?"""
1434+ self.assertFalse(self.ui.has_bindwood)
1435+
1436+ def test_has_evocouch(self):
1437+ """Has evocouch installed?"""
1438+ self.assertFalse(self.ui.has_evocouch)
1439+
1440+ def test_install_box_is_hidden(self):
1441+ """The install box is not hidden."""
1442+ self.assertTrue(self.ui.install_box.get_visible())
1443+
1444+ def test_replications_is_hidden(self):
1445+ """The replications section is disabled."""
1446+ self.assertFalse(self.ui.replications.get_visible())
1447+
1448+ def test_install_box(self):
1449+ """The install box is enabled."""
1450+ self.assertTrue(self.ui.install_box.get_visible())
1451+ self.assertIn(self.ui.install_box, self.ui.itself.get_children())
1452+ self.assertEqual(self.ui.install_box.package_name,
1453+ self.ui.DESKTOPCOUCH_PKG)
1454+
1455+ def test_install_box_finished_connected(self):
1456+ """The install box 'finished' signal is connected."""
1457+ self.patch(self.ui, 'load_replications', self._set_called)
1458+ self.ui.load() # ensure signal connection uses the new method
1459+
1460+ self.ui.install_box.emit('finished')
1461+
1462+ self.assertEqual(self._called, ((self.ui.install_box,), {}))
1463+
1464+
1465+class ServicesWithDesktopcouchTestCase(ServicesTestCase):
1466+ """The test suite for the services panel."""
1467+
1468+ kwargs = {'replication_exclusion_class': FakedReplication}
1469+
1470+ def setUp(self):
1471+ super(ServicesWithDesktopcouchTestCase, self).setUp()
1472+ self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
1473+ self.ui.load()
1474+
1475+ def test_message(self):
1476+ """Global load message is stopped and proper test is shown."""
1477+ self.assertFalse(self.ui.message.active)
1478+ self.assertEqual(self.ui.message.get_text(), self.ui.CHOOSE_SERVICES)
1479+
1480+ def test_replication_service(self):
1481+ """Has a replication service."""
1482+ self.assertIsInstance(self.ui.replication_service, FakedReplication)
1483+
1484+ def test_no_pairing_record(self):
1485+ """The pairing record is not in place."""
1486+
1487+ def no_pairing_record(*a):
1488+ """Fake a ReplicationExclusion with no pairing record."""
1489+ raise ValueError("No pairing record for ubuntuone.")
1490+
1491+ self.ui.replication_exclusion_class = no_pairing_record
1492+ self.ui.replication_service = None
1493+ self.ui.load()
1494+
1495+ self.assertEqual(self.ui.replications.get_children(), [])
1496+ self.assertFalse(self.ui.message.active)
1497+ self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
1498+
1499+ def test_has_desktopcouch(self):
1500+ """Has desktopcouch installed?"""
1501+ self.assertTrue(self.ui.has_desktopcouch)
1502+
1503+ def test_replications(self):
1504+ """Has proper child for each desktopcouch replication available."""
1505+ self.assertTrue(self.ui.replications.get_visible())
1506+
1507+ children = self.ui.replications.get_children()
1508+ self.assertEqual(len(children), 2)
1509+ for child in children:
1510+ self.assertIsInstance(child, gui.DesktopcouchService)
1511+
1512+ self.assertTrue(self.ui.bookmarks is children[0])
1513+ self.assertTrue(self.ui.contacts is children[1])
1514+
1515+ def test_replications_after_loading_twice(self):
1516+ """Has proper child after loading twice."""
1517+ self.ui.load()
1518+ self.test_replications()
1519+
1520+ def test_bookmarks(self):
1521+ """The bookmarks is correct."""
1522+ self.assertEqual(self.ui.bookmarks.service_name, 'bookmarks')
1523+ self.assertEqual(self.ui.bookmarks.button.get_label(),
1524+ self.ui.BOOKMARKS)
1525+ self.assertTrue(self.ui.bookmarks.replication_service is
1526+ self.ui.replication_service)
1527+
1528+ def test_bookmarks_dependency(self):
1529+ """The bookmarks dependency is correct."""
1530+ self.assertTrue(self.ui.bookmarks.dependency is not None)
1531+ self.assertEqual(self.ui.bookmarks.dependency.package_name,
1532+ self.ui.BINDWOOD_PKG)
1533+
1534+ def test_contacts(self):
1535+ """The contacts is correct."""
1536+ self.assertEqual(self.ui.contacts.service_name, 'contacts')
1537+ self.assertEqual(self.ui.contacts.button.get_label(),
1538+ self.ui.CONTACTS)
1539+ self.assertTrue(self.ui.contacts.replication_service is
1540+ self.ui.replication_service)
1541+
1542+ def test_contacts_dependency(self):
1543+ """The contacts dependency is correct."""
1544+ self.assertTrue(self.ui.contacts.dependency is not None)
1545+ self.assertEqual(self.ui.contacts.dependency.package_name,
1546+ self.ui.EVOCOUCH_PKG)
1547+
1548+
1549+class ServicesWithDCAndBindwoodTestCase(ServicesWithDesktopcouchTestCase):
1550+ """The test suite for the services panel."""
1551+
1552+ def setUp(self):
1553+ super(ServicesWithDCAndBindwoodTestCase, self).setUp()
1554+ self.ui.package_manager._installed[self.ui.BINDWOOD_PKG] = True
1555+ self.ui.load()
1556+
1557+ def test_has_bindwood(self):
1558+ """Has bindwood installed?"""
1559+ self.assertTrue(self.ui.has_bindwood)
1560+
1561+ def test_bookmarks_dependency(self):
1562+ """The bookmarks dependency is correct."""
1563+ self.assertTrue(self.ui.bookmarks.dependency is None)
1564+
1565+
1566+class ServicesWithDCAndEvocouchTestCase(ServicesWithDesktopcouchTestCase):
1567+ """The test suite for the services panel."""
1568+
1569+ def setUp(self):
1570+ super(ServicesWithDCAndEvocouchTestCase, self).setUp()
1571+ self.ui.package_manager._installed[self.ui.EVOCOUCH_PKG] = True
1572+ self.ui.load()
1573+
1574+ def test_has_evocouch(self):
1575+ """Has evocoucg installed?"""
1576+ self.assertTrue(self.ui.has_evocouch)
1577+
1578+ def test_contacts_dependency(self):
1579+ """The bookmarks dependency is correct."""
1580+ self.assertTrue(self.ui.contacts.dependency is None)
1581+
1582
1583 class ManagementPanelTestCase(ControlPanelMixinTestCase):
1584 """The test suite for the management panel."""
1585@@ -1420,11 +1784,11 @@
1586 """Tabs are not shown."""
1587 self.assertFalse(self.ui.notebook.get_show_tabs())
1588
1589- def test_default_page_is_account(self):
1590- """The default page is Account."""
1591+ def test_default_page_is_dashboard(self):
1592+ """The default page is Dashboard."""
1593 self.assertEqual(self.ui.notebook.get_current_page(),
1594- self.ui.ACCOUNT_PAGE)
1595- self.assertTrue(self.ui.account_button.get_active())
1596+ self.ui.DASHBOARD_PAGE)
1597+ self.assertTrue(self.ui.dashboard_button.get_active())
1598
1599 def test_buttons_set_notebook_pages(self):
1600 """The notebook pages are set when clicking buttons."""
1601@@ -1449,10 +1813,6 @@
1602 active = getattr(self.ui, '%s_button' % other).get_active()
1603 self.assertFalse(active, msg % (button, other))
1604
1605-
1606-class ManagementPanelAccountTestCase(ManagementPanelTestCase):
1607- """The test suite for the management panel (account tab)."""
1608-
1609 def test_backend_account_signals(self):
1610 """The proper signals are connected to the backend."""
1611 self.assertEqual(self.ui.backend._signals['AccountInfoReady'],
1612@@ -1465,11 +1825,11 @@
1613 self.ui.load()
1614 self.assert_backend_called('account_info', ())
1615
1616- def test_account_panel_is_packed(self):
1617- """The account panel is packed."""
1618- self.assertIsInstance(self.ui.account, gui.AccountPanel)
1619- actual = self.ui.notebook.get_nth_page(self.ui.ACCOUNT_PAGE)
1620- self.assertTrue(self.ui.account is actual)
1621+ def test_dashboard_panel_is_packed(self):
1622+ """The dashboard panel is packed."""
1623+ self.assertIsInstance(self.ui.dashboard, gui.DashboardPanel)
1624+ actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE)
1625+ self.assertTrue(self.ui.dashboard is actual)
1626
1627 def test_folders_panel_is_packed(self):
1628 """The folders panel is packed."""
1629@@ -1483,11 +1843,11 @@
1630 actual = self.ui.notebook.get_nth_page(self.ui.DEVICES_PAGE)
1631 self.assertTrue(self.ui.devices is actual)
1632
1633- def test_applications_panel_is_packed(self):
1634- """The applications panel is packed."""
1635- self.assertIsInstance(self.ui.applications, gui.ApplicationsPanel)
1636- actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE)
1637- self.assertTrue(self.ui.applications is actual)
1638+ def test_services_panel_is_packed(self):
1639+ """The services panel is packed."""
1640+ self.assertIsInstance(self.ui.services, gui.ServicesPanel)
1641+ actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE)
1642+ self.assertTrue(self.ui.services is actual)
1643
1644 def test_entering_folders_tab_loads_content(self):
1645 """The volumes info is loaded when entering the Folders tab."""
1646@@ -1505,6 +1865,14 @@
1647
1648 self.assertEqual(self._called, ((), {}))
1649
1650+ def test_entering_services_tab_loads_content(self):
1651+ """The services info is loaded when entering the Devices tab."""
1652+ self.patch(self.ui.services, 'load', self._set_called)
1653+ # clean backend calls
1654+ self.ui.services_button.clicked()
1655+
1656+ self.assertEqual(self._called, ((), {}))
1657+
1658 def test_quota_placeholder_is_loading(self):
1659 """Placeholders for quota label is a Loading widget."""
1660 self.assertIsInstance(self.ui.quota_label, gui.LabelLoading)
1661
1662=== added file 'ubuntuone/controlpanel/gtk/tests/test_package_manager.py'
1663--- ubuntuone/controlpanel/gtk/tests/test_package_manager.py 1970-01-01 00:00:00 +0000
1664+++ ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-01-03 14:24:42 +0000
1665@@ -0,0 +1,177 @@
1666+# -*- coding: utf-8 -*-
1667+
1668+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1669+#
1670+# Copyright 2010 Canonical Ltd.
1671+#
1672+# This program is free software: you can redistribute it and/or modify it
1673+# under the terms of the GNU General Public License version 3, as published
1674+# by the Free Software Foundation.
1675+#
1676+# This program is distributed in the hope that it will be useful, but
1677+# WITHOUT ANY WARRANTY; without even the implied warranties of
1678+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1679+# PURPOSE. See the GNU General Public License for more details.
1680+#
1681+# You should have received a copy of the GNU General Public License along
1682+# with this program. If not, see <http://www.gnu.org/licenses/>.
1683+
1684+"""Tests for the package manager service."""
1685+
1686+import collections
1687+
1688+try:
1689+ # Unable to import 'defer', pylint: disable=F0401,E0611
1690+ from aptdaemon import defer
1691+except ImportError:
1692+ # Unable to import 'defer', pylint: disable=F0401,E0611
1693+ import defer
1694+
1695+from ubuntuone.controlpanel.gtk import package_manager
1696+from ubuntuone.controlpanel.tests import TestCase
1697+
1698+
1699+FAKED_CACHE = {}
1700+SUCCESS = package_manager.aptdaemon.enums.EXIT_SUCCESS
1701+FAILURE = package_manager.aptdaemon.enums.EXIT_FAILED
1702+
1703+
1704+class FakedPackage(object):
1705+ """Fake a package."""
1706+
1707+ def __init__(self, name=None, is_installed=False):
1708+ self.name = name
1709+ self.is_installed = is_installed
1710+
1711+
1712+class FakedTransaction(object):
1713+ """Fake a transaction."""
1714+
1715+ failure = None
1716+
1717+ def __init__(self, packages, cache=None):
1718+ self._signals = collections.defaultdict(list)
1719+ self._cache = cache
1720+ self.was_run = False
1721+ self.packages = packages
1722+ self.connect = lambda sig, f: self._signals[sig].append(f)
1723+
1724+ def run(self):
1725+ """Run!"""
1726+ self.was_run = True
1727+
1728+ if self._cache is not None:
1729+ for package in self.packages:
1730+ FAKED_CACHE[package].is_installed = True
1731+
1732+ if self.failure is None:
1733+ code = SUCCESS
1734+ else:
1735+ code = FAILURE
1736+
1737+ for listener in self._signals['finished']:
1738+ listener(self, code)
1739+
1740+ d = defer.Deferred()
1741+ d.callback(code)
1742+ return d
1743+
1744+
1745+class FakedClient(object):
1746+ """Fake an apt client."""
1747+
1748+ def install_packages(self, packages_names, **kwargs):
1749+ """Install packages listed in 'package_names'.
1750+
1751+ package_names - a list of package names
1752+ wait - if True run the transaction immediately and return its exit
1753+ state instead of the transaction itself.
1754+ reply_handler - callback function. If specified in combination with
1755+ error_handler the method will be called asynchrounsouly.
1756+ error_handler - in case of an error the given callback gets the
1757+ corresponding DBus exception instance
1758+
1759+ """
1760+ if kwargs.get('wait', False):
1761+ return package_manager.aptdaemon.enums.EXIT_FAILED
1762+ d = defer.Deferred()
1763+ d.callback(FakedTransaction(packages_names, cache=FAKED_CACHE))
1764+ return d
1765+
1766+
1767+class PackageManagerTestCase(TestCase):
1768+ """Test for the package manager."""
1769+
1770+ timeout = 2
1771+
1772+ def setUp(self):
1773+ FAKED_CACHE.clear() # clean cache
1774+ self.patch(package_manager.apt, 'Cache', lambda: FAKED_CACHE)
1775+ self.patch(package_manager.aptdaemon.client, 'AptClient', FakedClient)
1776+ self.obj = package_manager.PackageManager()
1777+
1778+ def test_is_installed(self):
1779+ """Check that a package is installed."""
1780+ name = 'test'
1781+ FAKED_CACHE[name] = FakedPackage(name, is_installed=True)
1782+
1783+ result = self.obj.is_installed(name)
1784+
1785+ self.assertTrue(result, 'must be installed')
1786+
1787+ def test_is_not_installed(self):
1788+ """Check if a package is installed."""
1789+ name = 'test'
1790+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
1791+
1792+ result = self.obj.is_installed(name)
1793+
1794+ self.assertFalse(result, 'must not be installed')
1795+
1796+ def test_is_not_installed_if_key_error(self):
1797+ """Check if a package is installed when cache raises KeyError."""
1798+ name = 'test' # is not in the cache
1799+ result = self.obj.is_installed(name)
1800+
1801+ self.assertFalse(result, 'must not be installed')
1802+
1803+ def test_progress_bar(self):
1804+ """The progress bar class is correct."""
1805+ self.assertIsInstance(package_manager.PackageManagerProgressBar(),
1806+ package_manager.AptProgressBar)
1807+
1808+ @package_manager.inline_callbacks
1809+ def test_install(self):
1810+ """Install is correct."""
1811+ name = 'test'
1812+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
1813+
1814+ result = yield self.obj.install(name)
1815+
1816+ self.assertIsInstance(result, FakedTransaction)
1817+ self.assertFalse(result.was_run, 'transaction must not be run')
1818+
1819+ @package_manager.inline_callbacks
1820+ def test_transaction_install(self):
1821+ """Install is correct."""
1822+ name = 'test'
1823+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
1824+
1825+ trans = yield self.obj.install(name)
1826+
1827+ trans.connect('finished', self._set_called)
1828+ trans.run()
1829+
1830+ self.assertEqual(self._called, ((trans, SUCCESS), {}))
1831+ self.assertTrue(trans.was_run, 'transaction must be run')
1832+ self.assertTrue(self.obj.is_installed(name))
1833+
1834+ @package_manager.inline_callbacks
1835+ def test_install_if_installed(self):
1836+ """Install does nothing is package is already installed."""
1837+ name = 'test'
1838+ FAKED_CACHE[name] = FakedPackage(name, is_installed=True)
1839+
1840+ result = yield self.obj.install(name)
1841+
1842+ self.assertEqual(result, SUCCESS)
1843
1844=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
1845--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-16 21:09:46 +0000
1846+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-01-03 14:24:42 +0000
1847@@ -150,6 +150,14 @@
1848 """Return the status of the file sync service."""
1849 return self._process(self.sample_status)
1850
1851+ def enable_files(self):
1852+ """Enable files service."""
1853+ return self._process(None)
1854+
1855+ def disable_files(self):
1856+ """Disable files service."""
1857+ return self._process(None)
1858+
1859 def volumes_info(self):
1860 """Get the user volumes info."""
1861 return self._process(SAMPLE_VOLUMES_INFO)
1862@@ -383,6 +391,28 @@
1863 self.backend.remove_device, sample_token)
1864 return self.assert_correct_method_call(*args)
1865
1866+ def test_enable_files(self):
1867+ """Enable files service."""
1868+
1869+ def got_signal(*args):
1870+ """The correct signal was received."""
1871+ self.deferred.callback("success")
1872+
1873+ args = ("FilesEnabled", "FilesEnableError", got_signal,
1874+ self.backend.enable_files)
1875+ return self.assert_correct_method_call(*args)
1876+
1877+ def test_disable_files(self):
1878+ """Disable files service."""
1879+
1880+ def got_signal():
1881+ """The correct signal was received."""
1882+ self.deferred.callback("success")
1883+
1884+ args = ("FilesDisabled", "FilesDisableError", got_signal,
1885+ self.backend.disable_files)
1886+ return self.assert_correct_method_call(*args)
1887+
1888 def test_volumes_info(self):
1889 """The volumes info is reported."""
1890
1891@@ -527,6 +557,20 @@
1892 args = (dbus_service.FILE_SYNC_IDLE, "FileSyncStatusIdle")
1893 return self.assert_correct_status_signal(*args)
1894
1895+ def test_file_sync_status_changed(self):
1896+ """The file sync status is reported every time status changed."""
1897+ status = (
1898+ dbus_service.FILE_SYNC_DISABLED,
1899+ dbus_service.FILE_SYNC_DISCONNECTED,
1900+ dbus_service.FILE_SYNC_ERROR,
1901+ dbus_service.FILE_SYNC_IDLE,
1902+ dbus_service.FILE_SYNC_STARTING,
1903+ dbus_service.FILE_SYNC_SYNCING,
1904+ )
1905+ for arg in status:
1906+ args = (arg, "FileSyncStatusChanged")
1907+ return self.assert_correct_status_signal(*args, expected_msg=arg)
1908+
1909 def test_status_changed_handler(self):
1910 """The status changed handler is properly set."""
1911 be = MockBackend()
1912
1913=== modified file 'ubuntuone/controlpanel/logger.py'
1914--- ubuntuone/controlpanel/logger.py 2010-12-08 22:25:24 +0000
1915+++ ubuntuone/controlpanel/logger.py 2011-01-03 14:24:42 +0000
1916@@ -33,7 +33,7 @@
1917 LOG_LEVEL = logging.DEBUG
1918 else:
1919 # Only log this level and above
1920- LOG_LEVEL = logging.INFO
1921+ LOG_LEVEL = logging.DEBUG # before final release, switch to INFO
1922
1923 MAIN_HANDLER = RotatingFileHandler(os.path.join(LOGFOLDER, 'controlpanel.log'),
1924 maxBytes=1048576,
1925
1926=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
1927--- ubuntuone/controlpanel/tests/__init__.py 2010-12-02 16:23:03 +0000
1928+++ ubuntuone/controlpanel/tests/__init__.py 2011-01-03 14:24:42 +0000
1929@@ -18,7 +18,7 @@
1930
1931 """The test suite for the control panel for Ubuntu One."""
1932
1933-from twisted.trial import unittest
1934+from ubuntuone.devtools.testcase import TestCase as BaseTestCase
1935
1936
1937 TOKEN = {u'consumer_key': u'xQ7xDAz',
1938@@ -28,7 +28,7 @@
1939 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
1940
1941
1942-class TestCase(unittest.TestCase):
1943+class TestCase(BaseTestCase):
1944 """Basics for testing."""
1945
1946 def setUp(self):
1947
1948=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
1949--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-18 20:16:18 +0000
1950+++ ubuntuone/controlpanel/tests/test_backend.py 2011-01-03 14:24:42 +0000
1951@@ -257,11 +257,11 @@
1952
1953 def files_sync_enabled(self):
1954 """Get if file sync service is enabled."""
1955- return self.file_sync
1956+ return MockDBusClient.file_sync
1957
1958 def set_files_sync_enabled(self, enabled):
1959 """Set the file sync service to be 'enabled'."""
1960- self.file_sync = enabled
1961+ MockDBusClient.file_sync = enabled
1962
1963 def get_folders(self):
1964 """Grab list of folders."""
1965@@ -653,3 +653,21 @@
1966 # Access to a protected member _process_file_sync_status
1967 expected_status = self.be._process_file_sync_status(status)
1968 self.assertEqual(self._called, ((expected_status,), {}))
1969+
1970+
1971+class BackendSyncEnabledTestCase(BackendBasicTestCase):
1972+ """Syncdaemon enable/disable for the backend."""
1973+
1974+ def test_enable_files(self):
1975+ """Files service is enabled."""
1976+ self.be.disable_files()
1977+
1978+ self.be.enable_files()
1979+ self.assertTrue(MockDBusClient.file_sync)
1980+
1981+ def test_disable_files(self):
1982+ """Files service is disabled."""
1983+ self.be.enable_files()
1984+
1985+ self.be.disable_files()
1986+ self.assertFalse(MockDBusClient.file_sync)

Subscribers

People subscribed via source and target branches