Merge lp:~nataliabidart/ubuntuone-control-panel/applications-woohoo into lp:ubuntuone-control-panel
- applications-woohoo
- Merge into trunk
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 |
Related bugs: |
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_
* added a new dbus signal FileSyncStatusC
* 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
* on terminal 2: DEBUG=True PYTHONPATH=. ./bin/ubuntuone
Got to the Services tab, and install desktopcouch using the same UI, and then install (if you want) the bindwood and evocouch packages.
Natalia Bidart (nataliabidart) wrote : | # |
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.
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.
John Lenton (chipaca) : | # |
Preview Diff
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) |
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 ralsina/ canonical/ ubuntuone/ woo/ubuntuone/ controlpanel/ gtk/gui. py", line 992, in load_replications replication_ service = self.replicatio n_exclusion_ class() pymodules/ python2. 7/desktopcouch/ application/ replication_ services/ ubuntuone. py", line 140, in __init__
res = f(*args, **kwargs)
File "/home/
self.
File "/usr/lib/
raise ValueError("No pairing record for ubuntuone.")
ValueError: No pairing record for ubuntuone.