Merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.5.1 into lp:ubuntu/natty/ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 6
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.5.1
Merge into: lp:ubuntu/natty/ubuntuone-control-panel
Diff against target: 3757 lines (+2468/-591)
27 files modified
PKG-INFO (+1/-1)
data/account.ui (+0/-197)
data/applications.ui (+0/-13)
data/dashboard.ui (+197/-0)
data/install.ui (+51/-0)
data/management.ui (+7/-7)
data/services.ui (+69/-0)
debian/changelog (+36/-0)
debian/control (+4/-2)
debian/ubuntuone-control-panel-gtk.install (+1/-0)
po/POTFILES.in (+5/-2)
pylintrc (+1/-1)
setup.py (+1/-1)
ubuntuone-control-panel-gtk.desktop.in (+14/-0)
ubuntuone/controlpanel/backend.py (+53/-1)
ubuntuone/controlpanel/dbus_service.py (+88/-0)
ubuntuone/controlpanel/gtk/gui.py (+315/-27)
ubuntuone/controlpanel/gtk/package_manager.py (+60/-0)
ubuntuone/controlpanel/gtk/tests/__init__.py (+151/-0)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+573/-158)
ubuntuone/controlpanel/gtk/tests/test_package_manager.py (+177/-0)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+99/-7)
ubuntuone/controlpanel/logger.py (+2/-5)
ubuntuone/controlpanel/replication_client.py (+115/-0)
ubuntuone/controlpanel/tests/__init__.py (+152/-4)
ubuntuone/controlpanel/tests/test_backend.py (+136/-165)
ubuntuone/controlpanel/tests/test_replication_client.py (+160/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.5.1
Reviewer Review Type Date Requested Status
Daniel Holbach (community) Approve
Review via email: mp+45658@code.launchpad.net

Description of the change

    * New upstream release (0.5.1):
      [ Natalia B. Bidart <email address hidden>]
      - Desktopcouch replication service is accessed in the GTK+ UI through the
      backend using its DBus service (LP: #696782).
      - Desktopcouch replication service is managed in the backend and exposed in
      the DBus service (LP: #696782).

    * New upstream release (0.5.0):
      [ Natalia B. Bidart <email address hidden> ]
      - 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).

    * debian/control (LP: #693798):
      - ubuntuone-control-panel now Recommneds ubuntuone-control-panel-gui
      - ubuntuone-control-panel-gtk now Provides ubuntuone-control-panel-gui
      - bumped required version of ubuntuone-client to >= 1.5.2

    * debian/ubuntuone-control-panel-gtk.install (LP: #693879):
      - provide the .desktop file for the GTK UI

To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote :

Good work.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2010-12-22 13:33:25 +0000
3+++ PKG-INFO 2011-01-10 02:57:12 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntuone-control-panel
7-Version: 0.1.0
8+Version: 0.5.1
9 Summary: Ubuntu One Control Panel
10 Home-page: https://launchpad.net/ubuntuone-control-panel
11 Author: Natalia Bidart
12
13=== removed file 'data/account.ui'
14--- data/account.ui 2010-12-22 13:33:25 +0000
15+++ data/account.ui 1970-01-01 00:00:00 +0000
16@@ -1,197 +0,0 @@
17-<?xml version="1.0" encoding="UTF-8"?>
18-<interface>
19- <requires lib="gtk+" version="2.16"/>
20- <!-- interface-naming-policy project-wide -->
21- <object class="GtkVBox" id="itself">
22- <property name="visible">True</property>
23- <property name="border_width">10</property>
24- <property name="spacing">10</property>
25- <child>
26- <object class="GtkHBox" id="hbox1">
27- <property name="visible">True</property>
28- <property name="spacing">10</property>
29- <child>
30- <object class="GtkVBox" id="account">
31- <property name="visible">True</property>
32- <property name="spacing">10</property>
33- <child>
34- <object class="GtkVBox" id="data">
35- <property name="visible">True</property>
36- <property name="spacing">10</property>
37- <child>
38- <object class="GtkVBox" id="vbox1">
39- <property name="visible">True</property>
40- <child>
41- <object class="GtkLabel" id="label1">
42- <property name="visible">True</property>
43- <property name="xalign">0</property>
44- <property name="label" translatable="yes">&lt;b&gt;Name:&lt;/b&gt;</property>
45- <property name="use_markup">True</property>
46- </object>
47- <packing>
48- <property name="position">0</property>
49- </packing>
50- </child>
51- <child>
52- <object class="GtkLabel" id="name_label">
53- <property name="visible">True</property>
54- <property name="xalign">0</property>
55- <property name="xpad">12</property>
56- <property name="label">tester name</property>
57- <property name="use_markup">True</property>
58- </object>
59- <packing>
60- <property name="expand">False</property>
61- <property name="position">1</property>
62- </packing>
63- </child>
64- </object>
65- <packing>
66- <property name="position">0</property>
67- </packing>
68- </child>
69- <child>
70- <object class="GtkVBox" id="vbox2">
71- <property name="visible">True</property>
72- <child>
73- <object class="GtkLabel" id="label3">
74- <property name="visible">True</property>
75- <property name="xalign">0</property>
76- <property name="label" translatable="yes">&lt;b&gt;Account type:&lt;/b&gt;</property>
77- <property name="use_markup">True</property>
78- </object>
79- <packing>
80- <property name="position">0</property>
81- </packing>
82- </child>
83- <child>
84- <object class="GtkLabel" id="type_label">
85- <property name="visible">True</property>
86- <property name="xalign">0</property>
87- <property name="xpad">12</property>
88- <property name="label">22GB awesomeness</property>
89- </object>
90- <packing>
91- <property name="position">1</property>
92- </packing>
93- </child>
94- </object>
95- <packing>
96- <property name="position">1</property>
97- </packing>
98- </child>
99- <child>
100- <object class="GtkVBox" id="vbox3">
101- <property name="visible">True</property>
102- <child>
103- <object class="GtkLabel" id="label2">
104- <property name="visible">True</property>
105- <property name="xalign">0</property>
106- <property name="label" translatable="yes">&lt;b&gt;Email address:&lt;/b&gt;</property>
107- <property name="use_markup">True</property>
108- </object>
109- <packing>
110- <property name="position">0</property>
111- </packing>
112- </child>
113- <child>
114- <object class="GtkLabel" id="email_label">
115- <property name="visible">True</property>
116- <property name="xalign">0</property>
117- <property name="xpad">12</property>
118- <property name="label">a@example.com</property>
119- </object>
120- <packing>
121- <property name="position">1</property>
122- </packing>
123- </child>
124- </object>
125- <packing>
126- <property name="position">2</property>
127- </packing>
128- </child>
129- </object>
130- <packing>
131- <property name="expand">False</property>
132- <property name="position">0</property>
133- </packing>
134- </child>
135- <child>
136- <object class="GtkHButtonBox" id="hbuttonbox1">
137- <property name="layout_style">center</property>
138- <child>
139- <object class="GtkButton" id="change_password_button">
140- <property name="label" translatable="yes">Change Password</property>
141- <property name="visible">True</property>
142- <property name="can_focus">True</property>
143- <property name="receives_default">True</property>
144- </object>
145- <packing>
146- <property name="expand">False</property>
147- <property name="fill">False</property>
148- <property name="position">0</property>
149- </packing>
150- </child>
151- </object>
152- <packing>
153- <property name="expand">False</property>
154- <property name="pack_type">end</property>
155- <property name="position">1</property>
156- </packing>
157- </child>
158- </object>
159- <packing>
160- <property name="position">0</property>
161- </packing>
162- </child>
163- <child>
164- <object class="GtkVButtonBox" id="vbuttonbox1">
165- <property name="visible">True</property>
166- <property name="spacing">10</property>
167- <property name="layout_style">center</property>
168- <child>
169- <object class="GtkLinkButton" id="linkbutton1">
170- <property name="label" translatable="yes">Upgrade subscription</property>
171- <property name="visible">True</property>
172- <property name="can_focus">True</property>
173- <property name="receives_default">True</property>
174- <property name="relief">none</property>
175- <property name="xalign">0</property>
176- <property name="uri">https://one.ubuntu.com/plans/</property>
177- </object>
178- <packing>
179- <property name="expand">False</property>
180- <property name="fill">False</property>
181- <property name="position">0</property>
182- </packing>
183- </child>
184- <child>
185- <object class="GtkLinkButton" id="linkbutton2">
186- <property name="label" translatable="yes">Support options</property>
187- <property name="visible">True</property>
188- <property name="can_focus">True</property>
189- <property name="receives_default">True</property>
190- <property name="relief">none</property>
191- <property name="xalign">0</property>
192- <property name="uri">https://one.ubuntu.com/support/</property>
193- </object>
194- <packing>
195- <property name="expand">False</property>
196- <property name="fill">False</property>
197- <property name="position">1</property>
198- </packing>
199- </child>
200- </object>
201- <packing>
202- <property name="expand">False</property>
203- <property name="pack_type">end</property>
204- <property name="position">1</property>
205- </packing>
206- </child>
207- </object>
208- <packing>
209- <property name="position">1</property>
210- </packing>
211- </child>
212- </object>
213-</interface>
214
215=== removed file 'data/applications.ui'
216--- data/applications.ui 2010-12-06 12:27:11 +0000
217+++ data/applications.ui 1970-01-01 00:00:00 +0000
218@@ -1,13 +0,0 @@
219-<?xml version="1.0" encoding="UTF-8"?>
220-<interface>
221- <requires lib="gtk+" version="2.16"/>
222- <!-- interface-naming-policy project-wide -->
223- <object class="GtkVBox" id="itself">
224- <property name="visible">True</property>
225- <property name="border_width">10</property>
226- <property name="spacing">10</property>
227- <child>
228- <placeholder/>
229- </child>
230- </object>
231-</interface>
232
233=== added file 'data/dashboard.ui'
234--- data/dashboard.ui 1970-01-01 00:00:00 +0000
235+++ data/dashboard.ui 2011-01-10 02:57:12 +0000
236@@ -0,0 +1,197 @@
237+<?xml version="1.0" encoding="UTF-8"?>
238+<interface>
239+ <requires lib="gtk+" version="2.16"/>
240+ <!-- interface-naming-policy project-wide -->
241+ <object class="GtkVBox" id="itself">
242+ <property name="visible">True</property>
243+ <property name="border_width">10</property>
244+ <property name="spacing">10</property>
245+ <child>
246+ <object class="GtkHBox" id="hbox1">
247+ <property name="visible">True</property>
248+ <property name="spacing">10</property>
249+ <child>
250+ <object class="GtkVBox" id="account">
251+ <property name="visible">True</property>
252+ <property name="spacing">10</property>
253+ <child>
254+ <object class="GtkVBox" id="data">
255+ <property name="visible">True</property>
256+ <property name="spacing">10</property>
257+ <child>
258+ <object class="GtkVBox" id="vbox1">
259+ <property name="visible">True</property>
260+ <child>
261+ <object class="GtkLabel" id="label1">
262+ <property name="visible">True</property>
263+ <property name="xalign">0</property>
264+ <property name="label" translatable="yes">&lt;b&gt;Name:&lt;/b&gt;</property>
265+ <property name="use_markup">True</property>
266+ </object>
267+ <packing>
268+ <property name="position">0</property>
269+ </packing>
270+ </child>
271+ <child>
272+ <object class="GtkLabel" id="name_label">
273+ <property name="visible">True</property>
274+ <property name="xalign">0</property>
275+ <property name="xpad">12</property>
276+ <property name="label">tester name</property>
277+ <property name="use_markup">True</property>
278+ </object>
279+ <packing>
280+ <property name="expand">False</property>
281+ <property name="position">1</property>
282+ </packing>
283+ </child>
284+ </object>
285+ <packing>
286+ <property name="position">0</property>
287+ </packing>
288+ </child>
289+ <child>
290+ <object class="GtkVBox" id="vbox2">
291+ <property name="visible">True</property>
292+ <child>
293+ <object class="GtkLabel" id="label3">
294+ <property name="visible">True</property>
295+ <property name="xalign">0</property>
296+ <property name="label" translatable="yes">&lt;b&gt;Account type:&lt;/b&gt;</property>
297+ <property name="use_markup">True</property>
298+ </object>
299+ <packing>
300+ <property name="position">0</property>
301+ </packing>
302+ </child>
303+ <child>
304+ <object class="GtkLabel" id="type_label">
305+ <property name="visible">True</property>
306+ <property name="xalign">0</property>
307+ <property name="xpad">12</property>
308+ <property name="label">22GB awesomeness</property>
309+ </object>
310+ <packing>
311+ <property name="position">1</property>
312+ </packing>
313+ </child>
314+ </object>
315+ <packing>
316+ <property name="position">1</property>
317+ </packing>
318+ </child>
319+ <child>
320+ <object class="GtkVBox" id="vbox3">
321+ <property name="visible">True</property>
322+ <child>
323+ <object class="GtkLabel" id="label2">
324+ <property name="visible">True</property>
325+ <property name="xalign">0</property>
326+ <property name="label" translatable="yes">&lt;b&gt;Email address:&lt;/b&gt;</property>
327+ <property name="use_markup">True</property>
328+ </object>
329+ <packing>
330+ <property name="position">0</property>
331+ </packing>
332+ </child>
333+ <child>
334+ <object class="GtkLabel" id="email_label">
335+ <property name="visible">True</property>
336+ <property name="xalign">0</property>
337+ <property name="xpad">12</property>
338+ <property name="label">a@example.com</property>
339+ </object>
340+ <packing>
341+ <property name="position">1</property>
342+ </packing>
343+ </child>
344+ </object>
345+ <packing>
346+ <property name="position">2</property>
347+ </packing>
348+ </child>
349+ </object>
350+ <packing>
351+ <property name="expand">False</property>
352+ <property name="position">0</property>
353+ </packing>
354+ </child>
355+ <child>
356+ <object class="GtkHButtonBox" id="hbuttonbox1">
357+ <property name="layout_style">center</property>
358+ <child>
359+ <object class="GtkButton" id="change_password_button">
360+ <property name="label" translatable="yes">Change Password</property>
361+ <property name="visible">True</property>
362+ <property name="can_focus">True</property>
363+ <property name="receives_default">True</property>
364+ </object>
365+ <packing>
366+ <property name="expand">False</property>
367+ <property name="fill">False</property>
368+ <property name="position">0</property>
369+ </packing>
370+ </child>
371+ </object>
372+ <packing>
373+ <property name="expand">False</property>
374+ <property name="pack_type">end</property>
375+ <property name="position">1</property>
376+ </packing>
377+ </child>
378+ </object>
379+ <packing>
380+ <property name="position">0</property>
381+ </packing>
382+ </child>
383+ <child>
384+ <object class="GtkVButtonBox" id="vbuttonbox1">
385+ <property name="visible">True</property>
386+ <property name="spacing">10</property>
387+ <property name="layout_style">center</property>
388+ <child>
389+ <object class="GtkLinkButton" id="linkbutton1">
390+ <property name="label" translatable="yes">Upgrade subscription</property>
391+ <property name="visible">True</property>
392+ <property name="can_focus">True</property>
393+ <property name="receives_default">True</property>
394+ <property name="relief">none</property>
395+ <property name="xalign">0</property>
396+ <property name="uri">https://one.ubuntu.com/plans/</property>
397+ </object>
398+ <packing>
399+ <property name="expand">False</property>
400+ <property name="fill">False</property>
401+ <property name="position">0</property>
402+ </packing>
403+ </child>
404+ <child>
405+ <object class="GtkLinkButton" id="linkbutton2">
406+ <property name="label" translatable="yes">Support options</property>
407+ <property name="visible">True</property>
408+ <property name="can_focus">True</property>
409+ <property name="receives_default">True</property>
410+ <property name="relief">none</property>
411+ <property name="xalign">0</property>
412+ <property name="uri">https://one.ubuntu.com/support/</property>
413+ </object>
414+ <packing>
415+ <property name="expand">False</property>
416+ <property name="fill">False</property>
417+ <property name="position">1</property>
418+ </packing>
419+ </child>
420+ </object>
421+ <packing>
422+ <property name="expand">False</property>
423+ <property name="pack_type">end</property>
424+ <property name="position">1</property>
425+ </packing>
426+ </child>
427+ </object>
428+ <packing>
429+ <property name="position">1</property>
430+ </packing>
431+ </child>
432+ </object>
433+</interface>
434
435=== added file 'data/install.ui'
436--- data/install.ui 1970-01-01 00:00:00 +0000
437+++ data/install.ui 2011-01-10 02:57:12 +0000
438@@ -0,0 +1,51 @@
439+<?xml version="1.0" encoding="UTF-8"?>
440+<interface>
441+ <requires lib="gtk+" version="2.16"/>
442+ <!-- interface-naming-policy project-wide -->
443+ <object class="GtkVBox" id="itself">
444+ <property name="visible">True</property>
445+ <property name="border_width">10</property>
446+ <property name="spacing">10</property>
447+ <child>
448+ <object class="GtkLabel" id="install_label">
449+ <property name="visible">True</property>
450+ <property name="xalign">0</property>
451+ <property name="label">label</property>
452+ <property name="wrap">True</property>
453+ </object>
454+ <packing>
455+ <property name="expand">False</property>
456+ <property name="position">0</property>
457+ </packing>
458+ </child>
459+ <child>
460+ <object class="GtkHButtonBox" id="install_button_box">
461+ <property name="visible">True</property>
462+ <child>
463+ <object class="GtkButton" id="install_button">
464+ <property name="label" translatable="yes">_Install now</property>
465+ <property name="visible">True</property>
466+ <property name="can_focus">True</property>
467+ <property name="receives_default">True</property>
468+ <property name="image">image1</property>
469+ <property name="use_underline">True</property>
470+ <signal name="clicked" handler="on_install_button_clicked"/>
471+ </object>
472+ <packing>
473+ <property name="expand">False</property>
474+ <property name="fill">False</property>
475+ <property name="position">0</property>
476+ </packing>
477+ </child>
478+ </object>
479+ <packing>
480+ <property name="expand">False</property>
481+ <property name="position">1</property>
482+ </packing>
483+ </child>
484+ </object>
485+ <object class="GtkImage" id="image1">
486+ <property name="visible">True</property>
487+ <property name="stock">gtk-ok</property>
488+ </object>
489+</interface>
490
491=== modified file 'data/management.ui'
492--- data/management.ui 2010-12-22 13:33:25 +0000
493+++ data/management.ui 2011-01-10 02:57:12 +0000
494@@ -64,8 +64,8 @@
495 <property name="visible">True</property>
496 <property name="layout_style">center</property>
497 <child>
498- <object class="GtkRadioButton" id="account_button">
499- <property name="label" translatable="yes">Account</property>
500+ <object class="GtkRadioButton" id="dashboard_button">
501+ <property name="label" translatable="yes">Dashboard</property>
502 <property name="visible">True</property>
503 <property name="can_focus">True</property>
504 <property name="receives_default">False</property>
505@@ -85,7 +85,7 @@
506 <property name="can_focus">True</property>
507 <property name="receives_default">False</property>
508 <property name="draw_indicator">False</property>
509- <property name="group">account_button</property>
510+ <property name="group">dashboard_button</property>
511 </object>
512 <packing>
513 <property name="expand">False</property>
514@@ -100,7 +100,7 @@
515 <property name="can_focus">True</property>
516 <property name="receives_default">False</property>
517 <property name="draw_indicator">False</property>
518- <property name="group">account_button</property>
519+ <property name="group">dashboard_button</property>
520 </object>
521 <packing>
522 <property name="expand">False</property>
523@@ -109,13 +109,13 @@
524 </packing>
525 </child>
526 <child>
527- <object class="GtkRadioButton" id="applications_button">
528- <property name="label" translatable="yes">Applications</property>
529+ <object class="GtkRadioButton" id="services_button">
530+ <property name="label" translatable="yes">Services</property>
531 <property name="visible">True</property>
532 <property name="can_focus">True</property>
533 <property name="receives_default">False</property>
534 <property name="draw_indicator">False</property>
535- <property name="group">account_button</property>
536+ <property name="group">dashboard_button</property>
537 </object>
538 <packing>
539 <property name="expand">False</property>
540
541=== added file 'data/services.ui'
542--- data/services.ui 1970-01-01 00:00:00 +0000
543+++ data/services.ui 2011-01-10 02:57:12 +0000
544@@ -0,0 +1,69 @@
545+<?xml version="1.0" encoding="UTF-8"?>
546+<interface>
547+ <requires lib="gtk+" version="2.16"/>
548+ <!-- interface-naming-policy project-wide -->
549+ <object class="GtkVBox" id="itself">
550+ <property name="visible">True</property>
551+ <property name="border_width">10</property>
552+ <property name="spacing">10</property>
553+ <child>
554+ <object class="GtkScrolledWindow" id="scrolledwindow1">
555+ <property name="visible">True</property>
556+ <property name="can_focus">True</property>
557+ <property name="hscrollbar_policy">automatic</property>
558+ <property name="vscrollbar_policy">automatic</property>
559+ <child>
560+ <object class="GtkViewport" id="viewport1">
561+ <property name="visible">True</property>
562+ <property name="resize_mode">queue</property>
563+ <property name="shadow_type">none</property>
564+ <child>
565+ <object class="GtkVBox" id="vbox1">
566+ <property name="visible">True</property>
567+ <child>
568+ <object class="GtkAlignment" id="alignment1">
569+ <property name="visible">True</property>
570+ <child>
571+ <object class="GtkVBox" id="replications">
572+ <property name="visible">True</property>
573+ <property name="spacing">5</property>
574+ <child>
575+ <placeholder/>
576+ </child>
577+ </object>
578+ </child>
579+ </object>
580+ <packing>
581+ <property name="expand">False</property>
582+ <property name="position">0</property>
583+ </packing>
584+ </child>
585+ <child>
586+ <object class="GtkAlignment" id="alignment2">
587+ <property name="visible">True</property>
588+ <child>
589+ <object class="GtkVBox" id="files">
590+ <property name="visible">True</property>
591+ <property name="spacing">5</property>
592+ <child>
593+ <placeholder/>
594+ </child>
595+ </object>
596+ </child>
597+ </object>
598+ <packing>
599+ <property name="expand">False</property>
600+ <property name="position">1</property>
601+ </packing>
602+ </child>
603+ </object>
604+ </child>
605+ </object>
606+ </child>
607+ </object>
608+ <packing>
609+ <property name="position">0</property>
610+ </packing>
611+ </child>
612+ </object>
613+</interface>
614
615=== modified file 'debian/changelog'
616--- debian/changelog 2010-12-22 17:03:15 +0000
617+++ debian/changelog 2011-01-10 02:57:12 +0000
618@@ -1,3 +1,39 @@
619+ubuntuone-control-panel (0.5.1-0ubuntu1) UNRELEASED; urgency=low
620+
621+ * New upstream release (0.5.1):
622+ [ Natalia B. Bidart <natalia.bidart@canonical.com>]
623+ - Desktopcouch replication service is accessed in the GTK+ UI through the
624+ backend using its DBus service (LP: #696782).
625+ - Desktopcouch replication service is managed in the backend and exposed in
626+ the DBus service (LP: #696782).
627+
628+ * New upstream release (0.5.0):
629+ [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
630+ - renamed Account to Dashboard.
631+ - renamed Applications to Services.
632+ - added enable_files/disable_files to backend + dbus service.
633+ - added a new dbus signal FileSyncStatusChanged.
634+ - desktopcouch replication exclusion layer is acceded directly on the GUI.
635+ We should later move that to the backend.
636+ - a new module package_manager was added to the gtk package, since we need
637+ to do some deeper work to abstract that manager from the toolkit (since it
638+ uses a gtk progress bar) (LP: #673673).
639+ - a new widget InstallPackage that provides package installation.
640+ - widgets FilesService and DesktopcouchService that can be re used as
641+ needed.
642+ - ServicesPanel checks for depenencies and creates widget as necessary (LP:
643+ #673672).
644+
645+ * debian/control (LP: #693798):
646+ - ubuntuone-control-panel now Recommneds ubuntuone-control-panel-gui
647+ - ubuntuone-control-panel-gtk now Provides ubuntuone-control-panel-gui
648+ - bumped required version of ubuntuone-client to >= 1.5.2
649+
650+ * debian/ubuntuone-control-panel-gtk.install (LP: #693879):
651+ - provide the .desktop file for the GTK UI
652+
653+ -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Fri, 07 Jan 2011 17:09:35 -0300
654+
655 ubuntuone-control-panel (0.1.0-0ubuntu1) natty; urgency=low
656
657 * debian/control
658
659=== modified file 'debian/control'
660--- debian/control 2010-12-22 17:03:15 +0000
661+++ debian/control 2011-01-10 02:57:12 +0000
662@@ -17,6 +17,7 @@
663 ${python:Depends},
664 python,
665 python-ubuntuone-control-panel (= ${binary:Version}),
666+Recommends: ubuntuone-control-panel-gui
667 Description: Ubuntu One Control Panel
668 Desktop application to manage a Ubuntu One account.
669 Ubuntu One Control Panel provides a DBus service to manage an Ubuntu One
670@@ -35,7 +36,7 @@
671 python-simplejson,
672 python-twisted-core,
673 python-twisted-web,
674- python-ubuntuone-client (>= 1.5.1),
675+ python-ubuntuone-client (>= 1.5.2),
676 ubuntu-sso-client (>= 1.1.7),
677 Description: Ubuntu One Control Panel Python Libraries
678 Ubuntu One Control Panel provides a Python library to manage an Ubuntu One
679@@ -50,8 +51,9 @@
680 python-dbus,
681 python-gobject,
682 python-gtk2,
683- python-ubuntuone-client (>= 1.5.1),
684+ python-ubuntuone-client (>= 1.5.2),
685 ubuntu-sso-client (>= 1.1.7),
686 ubuntuone-control-panel (= ${binary:Version}),
687+Provides: ubuntuone-control-panel-gui
688 Description: Ubuntu One Control Panel
689 GTK+ desktop application to manage a Ubuntu One account.
690
691=== modified file 'debian/ubuntuone-control-panel-gtk.install'
692--- debian/ubuntuone-control-panel-gtk.install 2010-12-06 12:27:11 +0000
693+++ debian/ubuntuone-control-panel-gtk.install 2011-01-10 02:57:12 +0000
694@@ -1,4 +1,5 @@
695 debian/tmp/usr/bin/ubuntuone-control-panel-gtk
696+debian/tmp/usr/share/applications/ubuntuone-control-panel-gtk.desktop
697 debian/tmp/usr/share/ubuntuone-control-panel/*.ui
698 debian/tmp/usr/share/ubuntuone-control-panel/*.png
699 debian/tmp/usr/share/man/man1/ubuntuone-control-panel-gtk.*
700
701=== modified file 'po/POTFILES.in'
702--- po/POTFILES.in 2010-12-22 13:33:25 +0000
703+++ po/POTFILES.in 2011-01-10 02:57:12 +0000
704@@ -1,6 +1,9 @@
705+ubuntuone-control-panel-gtk.desktop.in
706 ubuntuone/controlpanel/gtk/gui.py
707-[type: gettext/glade] data/account.ui
708-[type: gettext/glade] data/applications.ui
709+[type: gettext/glade] data/dashboard.ui
710+[type: gettext/glade] data/device.ui
711 [type: gettext/glade] data/devices.ui
712+[type: gettext/glade] data/install.ui
713 [type: gettext/glade] data/management.ui
714 [type: gettext/glade] data/overview.ui
715+[type: gettext/glade] data/services.ui
716
717=== modified file 'pylintrc'
718--- pylintrc 2010-12-06 12:27:11 +0000
719+++ pylintrc 2011-01-10 02:57:12 +0000
720@@ -272,7 +272,7 @@
721 max-line-length=79
722
723 # Maximum number of lines in a module
724-max-module-lines=2000
725+max-module-lines=2500
726
727 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
728 # tab).
729
730=== modified file 'setup.py'
731--- setup.py 2010-12-22 13:33:25 +0000
732+++ setup.py 2011-01-10 02:57:12 +0000
733@@ -75,7 +75,7 @@
734
735 DistUtilsExtra.auto.setup(
736 name='ubuntuone-control-panel',
737- version='0.1.0',
738+ version='0.5.1',
739 license='GPL v3',
740 author='Natalia Bidart',
741 author_email='natalia.bidart@canonical.com',
742
743=== added file 'ubuntuone-control-panel-gtk.desktop.in'
744--- ubuntuone-control-panel-gtk.desktop.in 1970-01-01 00:00:00 +0000
745+++ ubuntuone-control-panel-gtk.desktop.in 2011-01-10 02:57:12 +0000
746@@ -0,0 +1,14 @@
747+[Desktop Entry]
748+Name=Ubuntu One
749+_Comment=Configure and manage your Ubuntu One account
750+Exec=ubuntuone-control-panel-gtk
751+Icon=ubuntuone
752+Terminal=false
753+Type=Application
754+StartupNotify=true
755+Categories=GNOME;GTK;Settings;
756+X-Ayatana-Desktop-Shortcuts=U1
757+
758+[U1 Shortcut Group]
759+Name=Ubuntu One
760+Exec=ubuntuone-control-panel-gtk
761
762=== modified file 'ubuntuone/controlpanel/backend.py'
763--- ubuntuone/controlpanel/backend.py 2010-12-22 13:33:25 +0000
764+++ ubuntuone/controlpanel/backend.py 2011-01-10 02:57:12 +0000
765@@ -22,6 +22,7 @@
766 from twisted.internet.defer import inlineCallbacks, returnValue
767
768 from ubuntuone.controlpanel import dbus_client
769+from ubuntuone.controlpanel import replication_client
770 from ubuntuone.controlpanel.logger import setup_logging, log_call
771 from ubuntuone.controlpanel.webclient import WebClient
772
773@@ -48,6 +49,9 @@
774 MSG_KEY = 'message'
775 STATUS_KEY = 'status'
776
777+BOOKMARKS_PKG = 'xul-ext-bindwood'
778+CONTACTS_PKG = 'evolution-couchdb'
779+
780
781 def bool_str(value):
782 """Return the string representation of a bool (dbus-compatible)."""
783@@ -273,6 +277,18 @@
784
785 @log_call(logger.debug)
786 @inlineCallbacks
787+ def enable_files(self):
788+ """Enable the files service."""
789+ yield dbus_client.set_files_sync_enabled(True)
790+
791+ @log_call(logger.debug)
792+ @inlineCallbacks
793+ def disable_files(self):
794+ """Enable the files service."""
795+ yield dbus_client.set_files_sync_enabled(False)
796+
797+ @log_call(logger.debug)
798+ @inlineCallbacks
799 def volumes_info(self):
800 """Get the volumes info."""
801 result = yield dbus_client.get_folders()
802@@ -288,7 +304,7 @@
803
804 """
805 if 'subscribed' in settings:
806- subscribed = settings['subscribed']
807+ subscribed = bool(settings['subscribed'])
808 if subscribed:
809 yield self.subscribe_volume(volume_id)
810 else:
811@@ -309,6 +325,42 @@
812 yield dbus_client.unsubscribe_folder(volume_id)
813
814 @log_call(logger.debug)
815+ @inlineCallbacks
816+ def replications_info(self):
817+ """Get the user replications info."""
818+ replications = yield replication_client.get_replications()
819+ exclusions = yield replication_client.get_exclusions()
820+
821+ result = []
822+ for rep in replications:
823+ dependency = ''
824+ if rep == replication_client.BOOKMARKS:
825+ dependency = BOOKMARKS_PKG
826+ elif rep == replication_client.CONTACTS:
827+ dependency = CONTACTS_PKG
828+
829+ repd = {
830+ "replication_id": rep,
831+ "name": rep, # this may change to be more user friendly
832+ "enabled": bool_str(rep not in exclusions),
833+ "dependency": dependency,
834+ }
835+ result.append(repd)
836+
837+ returnValue(result)
838+
839+ @log_call(logger.info)
840+ @inlineCallbacks
841+ def change_replication_settings(self, replication_id, settings):
842+ """Change the settings for the given replication."""
843+ if 'enabled' in settings:
844+ if bool(settings['enabled']):
845+ yield replication_client.replicate(replication_id)
846+ else:
847+ yield replication_client.exclude(replication_id)
848+ returnValue(replication_id)
849+
850+ @log_call(logger.debug)
851 def query_bookmark_extension(self):
852 """True if the bookmark extension has been installed."""
853 # still pending (LP: #673672)
854
855=== modified file 'ubuntuone/controlpanel/dbus_service.py'
856--- ubuntuone/controlpanel/dbus_service.py 2010-12-22 13:33:25 +0000
857+++ ubuntuone/controlpanel/dbus_service.py 2011-01-10 02:57:12 +0000
858@@ -209,6 +209,8 @@
859 else:
860 self.FileSyncStatusError(error_handler(status_dict))
861
862+ self.FileSyncStatusChanged(status)
863+
864 @log_call(logger.debug)
865 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
866 def file_sync_status(self):
867@@ -242,6 +244,11 @@
868 def FileSyncStatusIdle(self, msg):
869 """The file sync service is idle."""
870
871+ @log_call(logger.debug)
872+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
873+ def FileSyncStatusChanged(self, msg):
874+ """The file sync service status changed."""
875+
876 @log_call(logger.error)
877 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
878 def FileSyncStatusError(self, error):
879@@ -251,6 +258,46 @@
880
881 @log_call(logger.debug)
882 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
883+ def enable_files(self):
884+ """Enable the files service."""
885+ d = self.backend.enable_files()
886+ d.addCallback(lambda _: self.FilesEnabled())
887+ d.addErrback(transform_failure(self.FilesEnableError))
888+
889+ @log_call(logger.debug)
890+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
891+ def FilesEnabled(self):
892+ """The files service is enabled."""
893+
894+ @log_call(logger.error)
895+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
896+ def FilesEnableError(self, error):
897+ """Problem enabling the files service."""
898+
899+ #---
900+
901+ @log_call(logger.debug)
902+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
903+ def disable_files(self):
904+ """Disable the files service."""
905+ d = self.backend.disable_files()
906+ d.addCallback(lambda _: self.FilesDisabled())
907+ d.addErrback(transform_failure(self.FilesDisableError))
908+
909+ @log_call(logger.debug)
910+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE)
911+ def FilesDisabled(self):
912+ """The files service is disabled."""
913+
914+ @log_call(logger.error)
915+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
916+ def FilesDisableError(self, error):
917+ """Problem disabling the files service."""
918+
919+ #---
920+
921+ @log_call(logger.debug)
922+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
923 def volumes_info(self):
924 """Find out the volumes info for the logged in user."""
925 d = self.backend.volumes_info()
926@@ -292,6 +339,47 @@
927
928 @log_call(logger.debug)
929 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
930+ def replications_info(self):
931+ """Return the replications info."""
932+ d = self.backend.replications_info()
933+ d.addCallback(self.ReplicationsInfoReady)
934+ d.addErrback(transform_failure(self.ReplicationsInfoError))
935+
936+ @log_call(logger.debug)
937+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
938+ def ReplicationsInfoReady(self, info):
939+ """The replications info is ready."""
940+
941+ @log_call(logger.error)
942+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
943+ def ReplicationsInfoError(self, error):
944+ """Problem getting the replications info."""
945+
946+ #---
947+
948+ @log_call(logger.info)
949+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
950+ def change_replication_settings(self, replication_id, settings):
951+ """Configure a given replication."""
952+ d = self.backend.change_replication_settings(replication_id, settings)
953+ d.addCallback(self.ReplicationSettingsChanged)
954+ d.addErrback(transform_failure(self.ReplicationSettingsChangeError),
955+ replication_id)
956+
957+ @log_call(logger.info)
958+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
959+ def ReplicationSettingsChanged(self, replication_id):
960+ """The settings for the replication were changed."""
961+
962+ @log_call(logger.error)
963+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
964+ def ReplicationSettingsChangeError(self, replication_id, error):
965+ """Problem changing settings for the replication."""
966+
967+ #---
968+
969+ @log_call(logger.debug)
970+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
971 def query_bookmark_extension(self):
972 """Check if the extension to sync bookmarks is installed."""
973 d = self.backend.query_bookmark_extension()
974
975=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
976--- ubuntuone/controlpanel/gtk/gui.py 2010-12-22 13:33:25 +0000
977+++ ubuntuone/controlpanel/gtk/gui.py 2011-01-10 02:57:12 +0000
978@@ -46,12 +46,13 @@
979 from ubuntuone.controlpanel.gtk.widgets import GreyableBin
980
981 from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
982- DBUS_PREFERENCES_IFACE)
983+ DBUS_PREFERENCES_IFACE, backend)
984 from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
985 DEVICE_TYPE_COMPUTER, bool_str)
986 from ubuntuone.controlpanel.logger import setup_logging, log_call
987 from ubuntuone.controlpanel.utils import get_data_file
988
989+from ubuntuone.controlpanel.gtk import package_manager
990
991 logger = setup_logging('gtk.gui')
992 _ = gettext.gettext
993@@ -77,6 +78,12 @@
994 VALUE_ERROR = _('Value could not be retrieved.')
995 WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
996 KILOBYTES = 1024
997+NO_OP = lambda *a, **kw: None
998+
999+
1000+def error_handler(*args, **kwargs):
1001+ """Log errors when calling D-Bus methods in a async way."""
1002+ logger.error('Error handler received: %r, %r', args, kwargs)
1003
1004
1005 def filter_by_app_name(f):
1006@@ -149,7 +156,7 @@
1007 class ControlPanelWindow(gtk.Window):
1008 """The main window for the Ubuntu One control panel."""
1009
1010- TITLE = _('My %(app_name)s Account')
1011+ TITLE = _('My %(app_name)s Dashboard')
1012
1013 def __init__(self):
1014 super(ControlPanelWindow, self).__init__()
1015@@ -351,7 +358,8 @@
1016 settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
1017 WINDOW_ID_KEY: str(self._window_id),
1018 PING_URL_KEY: U1_PING_URL}
1019- self.sso_backend.register(U1_APP_NAME, settings)
1020+ self.sso_backend.register(U1_APP_NAME, settings,
1021+ reply_handler=NO_OP, error_handler=error_handler)
1022 self.set_property('greyed', True)
1023 self.warning_label.set_text('')
1024
1025@@ -360,7 +368,8 @@
1026 settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
1027 WINDOW_ID_KEY: str(self._window_id),
1028 PING_URL_KEY: U1_PING_URL}
1029- self.sso_backend.login(U1_APP_NAME, settings)
1030+ self.sso_backend.login(U1_APP_NAME, settings,
1031+ reply_handler=NO_OP, error_handler=error_handler)
1032 self.set_property('greyed', True)
1033 self.warning_label.set_text('')
1034
1035@@ -407,11 +416,12 @@
1036 else:
1037 self.set_sensitive(True)
1038 self.warning_label.set_text('')
1039- self.sso_backend.find_credentials(U1_APP_NAME, {})
1040-
1041-
1042-class AccountPanel(UbuntuOneBin, ControlPanelMixin):
1043- """The account panel. The user can manage the subscription."""
1044+ self.sso_backend.find_credentials(U1_APP_NAME, {},
1045+ reply_handler=NO_OP, error_handler=error_handler)
1046+
1047+
1048+class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
1049+ """The dashboard panel. The user can manage the subscription."""
1050
1051 TITLE = _('Welcome to Ubuntu One!')
1052 NAME = _('Name')
1053@@ -420,7 +430,7 @@
1054
1055 def __init__(self):
1056 UbuntuOneBin.__init__(self)
1057- ControlPanelMixin.__init__(self, filename='account.ui')
1058+ ControlPanelMixin.__init__(self, filename='dashboard.ui')
1059 self.add(self.itself)
1060 self.show()
1061
1062@@ -515,11 +525,13 @@
1063 volume_id = checkbutton.get_label()
1064 subscribed = bool_str(checkbutton.get_active())
1065 self.backend.change_volume_settings(volume_id,
1066- {'subscribed': subscribed})
1067+ {'subscribed': subscribed},
1068+ reply_handler=NO_OP, error_handler=error_handler)
1069
1070 def load(self):
1071 """Load the volume list."""
1072- self.backend.volumes_info()
1073+ self.backend.volumes_info(reply_handler=NO_OP,
1074+ error_handler=error_handler)
1075 self.message.start()
1076
1077
1078@@ -564,7 +576,8 @@
1079 # Not disabling the GUI to avoid annyong twitchings
1080 #self.set_sensitive(False)
1081 self.warning_label.set_text('')
1082- self.backend.change_device_settings(self.id, self.__dict__)
1083+ self.backend.change_device_settings(self.id, self.__dict__,
1084+ reply_handler=NO_OP, error_handler=error_handler)
1085
1086 def _block_signals(f):
1087 """Execute 'f' while having the _updating flag set."""
1088@@ -590,7 +603,8 @@
1089
1090 def on_remove_clicked(self, widget):
1091 """Remove button was clicked or activated."""
1092- self.backend.remove_device(self.id)
1093+ self.backend.remove_device(self.id,
1094+ reply_handler=NO_OP, error_handler=error_handler)
1095 self.set_sensitive(False)
1096
1097 @_block_signals
1098@@ -746,30 +760,298 @@
1099
1100 def load(self):
1101 """Load the device list."""
1102- self.backend.devices_info()
1103+ self.backend.devices_info(reply_handler=NO_OP,
1104+ error_handler=error_handler)
1105 self.message.start()
1106
1107
1108-class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
1109- """The applications panel."""
1110+class InstallPackage(gtk.VBox, ControlPanelMixin):
1111+ """A widget to process the install of a package."""
1112+
1113+ INSTALL_PACKAGE = _('You need to install the package <i>%(package_name)s'
1114+ '</i> in order to enable replication.')
1115+ INSTALLING = _('The package <i>%(package_name)s</i> is being installed, '
1116+ 'please wait...')
1117+ FAILED_INSTALL = _('The installation of <i>%(package_name)s</i> failed.')
1118+ SUCCESS_INSTALL = _('The installation of <i>%(package_name)s</i> '
1119+ 'was successful.')
1120+
1121+ def __init__(self, package_name):
1122+ gtk.VBox.__init__(self)
1123+ ControlPanelMixin.__init__(self, filename='install.ui')
1124+ self.add(self.itself)
1125+
1126+ self.package_name = package_name
1127+ self.package_manager = package_manager.PackageManager()
1128+ self.args = {'package_name': self.package_name}
1129+ self.transaction = None
1130+
1131+ self.progress_bar = None
1132+ self.install_label.set_markup(self.INSTALL_PACKAGE % self.args)
1133+
1134+ self.show()
1135+
1136+ @package_manager.inline_callbacks
1137+ def on_install_button_clicked(self, button):
1138+ """The install button was clicked."""
1139+ try:
1140+ # create the install transaction
1141+ self.transaction = yield self.package_manager.install(
1142+ self.package_name)
1143+
1144+ # create the progress bar and pack it to the box
1145+ self.progress_bar = package_manager.PackageManagerProgressBar(
1146+ self.transaction)
1147+ self.progress_bar.show()
1148+
1149+ self.itself.remove(self.install_button_box)
1150+ self.itself.pack_start(self.progress_bar)
1151+
1152+ self.transaction.connect('finished', self.on_install_finished)
1153+ self.install_label.set_markup(self.INSTALLING % self.args)
1154+ yield self.transaction.run()
1155+ except: # pylint: disable=W0702
1156+ self._set_warning(self.FAILED_INSTALL % self.args,
1157+ self.install_label)
1158+
1159+ @log_call(logger.info)
1160+ def on_install_finished(self, transaction, exit_code):
1161+ """The installation finished."""
1162+ self.progress_bar.set_sensitive(False)
1163+
1164+ if exit_code != package_manager.aptdaemon.enums.EXIT_SUCCESS:
1165+ if hasattr(transaction, 'error'):
1166+ logger.error('transaction failed: %r', transaction.error)
1167+ self._set_warning(self.FAILED_INSTALL % self.args,
1168+ self.install_label)
1169+ else:
1170+ self.install_label.set_markup(self.SUCCESS_INSTALL % self.args)
1171+ self.emit('finished')
1172+
1173+
1174+class Service(gtk.VBox, ControlPanelMixin):
1175+ """A service."""
1176+
1177+ CHANGE_ERROR = _('The settings could not be changed,\n'
1178+ 'previous values were restored.')
1179+
1180+ def __init__(self, service_id, name, *args, **kwargs):
1181+ gtk.VBox.__init__(self)
1182+ ControlPanelMixin.__init__(self)
1183+ self.id = service_id
1184+
1185+ self.warning_label = gtk.Label()
1186+ self.pack_start(self.warning_label, expand=False)
1187+
1188+ self.button = gtk.CheckButton(label=name)
1189+ self.pack_start(self.button, expand=False)
1190+
1191+ self.show_all()
1192+
1193+
1194+class FilesService(Service):
1195+ """The file sync service."""
1196+
1197+ FILES_SERVICE_NAME = _('Files')
1198+
1199+ def __init__(self):
1200+ Service.__init__(self, service_id='files',
1201+ name=self.FILES_SERVICE_NAME)
1202+
1203+ self.set_sensitive(False)
1204+
1205+ self.backend.connect_to_signal('FileSyncStatusChanged',
1206+ self.on_file_sync_status_changed)
1207+ self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
1208+ self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
1209+
1210+ self.backend.file_sync_status(reply_handler=NO_OP,
1211+ error_handler=error_handler)
1212+
1213+ @log_call(logger.debug)
1214+ def on_file_sync_status_changed(self, status):
1215+ """File sync status changed."""
1216+ enabled = status != backend.FILE_SYNC_DISABLED
1217+ self.button.set_active(enabled)
1218+
1219+ if not self.is_sensitive():
1220+ # first time we're getting this event
1221+ self.button.connect('toggled', self.on_button_toggled)
1222+ self.set_sensitive(True)
1223+
1224+ def on_files_enabled(self):
1225+ """Files service was enabled."""
1226+ self.on_file_sync_status_changed('enabled!')
1227+
1228+ def on_files_disabled(self):
1229+ """Files service was disabled."""
1230+ self.on_file_sync_status_changed(backend.FILE_SYNC_DISABLED)
1231+
1232+ @log_call(logger.debug)
1233+ def on_button_toggled(self, button):
1234+ """Button was toggled, exclude/replicate the service properly."""
1235+ logger.info('File sync enabled? %r', self.button.get_active())
1236+ if self.button.get_active():
1237+ self.backend.enable_files(reply_handler=NO_OP,
1238+ error_handler=error_handler)
1239+ else:
1240+ self.backend.disable_files(reply_handler=NO_OP,
1241+ error_handler=error_handler)
1242+
1243+
1244+class DesktopcouchService(Service):
1245+ """A desktopcouch service."""
1246+
1247+ def __init__(self, service_id, name, enabled, dependency=None):
1248+ Service.__init__(self, service_id, name)
1249+
1250+ self.backend.connect_to_signal('ReplicationSettingsChanged',
1251+ self.on_replication_settings_changed)
1252+ self.backend.connect_to_signal('ReplicationSettingsChangeError',
1253+ self.on_replication_settings_change_error)
1254+
1255+ self.button.set_active(enabled)
1256+
1257+ self.dependency = None
1258+ if dependency is not None:
1259+ self.dependency = InstallPackage(dependency)
1260+ self.dependency.connect('finished', self.on_depedency_finished)
1261+ self.pack_start(self.dependency, expand=False)
1262+ self.button.set_sensitive(False)
1263+
1264+ self.button.connect('toggled', self.on_button_toggled)
1265+
1266+ def on_depedency_finished(self, widget):
1267+ """The dependency was installed."""
1268+ self.button.set_sensitive(True)
1269+ self.remove(self.dependency)
1270+ self.dependency = None
1271+
1272+ @log_call(logger.debug)
1273+ def on_button_toggled(self, button):
1274+ """Button was toggled, exclude/replicate the service properly."""
1275+ logger.info('Starting replication for %r? %r',
1276+ self.id, self.button.get_active())
1277+
1278+ args = {'enabled': bool_str(self.button.get_active())}
1279+ self.backend.change_replication_settings(self.id, args,
1280+ reply_handler=NO_OP, error_handler=error_handler)
1281+
1282+ @log_call(logger.info)
1283+ def on_replication_settings_changed(self, replication_id):
1284+ """The change of settings for this replication succeded."""
1285+ if replication_id != self.id:
1286+ return
1287+ self.warning_label.set_text('')
1288+
1289+ @log_call(logger.error)
1290+ def on_replication_settings_change_error(self, replication_id,
1291+ error_dict=None):
1292+ """The change of settings for this replication failed."""
1293+ if replication_id != self.id:
1294+ return
1295+ self.button.set_active(not self.button.get_active())
1296+ self._set_warning(self.CHANGE_ERROR, self.warning_label)
1297+
1298+
1299+class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
1300+ """The services panel."""
1301
1302 TITLE = _('Ubuntu One services including data sync are enabled for the '
1303- 'data types and applications listed below:')
1304+ 'data types and services listed below.')
1305+ CHOOSE_SERVICES = _('Choose services to synchronize with this computer:')
1306+ DESKTOPCOUCH_PKG = 'desktopcouch-ubuntuone'
1307+ BOOKMARKS = _('Bookmarks (Firefox)')
1308+ CONTACTS = _('Contacts (Evolution)')
1309+ NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
1310
1311 def __init__(self):
1312 UbuntuOneBin.__init__(self)
1313- ControlPanelMixin.__init__(self, filename='applications.ui')
1314+ ControlPanelMixin.__init__(self, filename='services.ui')
1315 self.add(self.itself)
1316+
1317+ self.package_manager = package_manager.PackageManager()
1318+ self.install_box = None
1319+
1320+ self.backend.connect_to_signal('ReplicationsInfoReady',
1321+ self.on_replications_info_ready)
1322+ self.backend.connect_to_signal('ReplicationsInfoError',
1323+ self.on_replications_info_error)
1324+
1325+ self.files.pack_start(FilesService(), expand=False)
1326+
1327 self.show()
1328
1329+ @property
1330+ def has_desktopcouch(self):
1331+ """Is desktopcouch installed?"""
1332+ return self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
1333+
1334+ @log_call(logger.debug)
1335+ def load(self):
1336+ """Load info."""
1337+ if self.install_box is not None:
1338+ self.itself.remove(self.install_box)
1339+ self.install_box = None
1340+
1341+ logger.info('load: has_desktopcouch? %r', self.has_desktopcouch)
1342+ if not self.has_desktopcouch:
1343+ self.message.set_text('')
1344+ self.replications.hide()
1345+
1346+ self.install_box = InstallPackage(self.DESKTOPCOUCH_PKG)
1347+ self.install_box.connect('finished', self.load_replications)
1348+ self.itself.pack_start(self.install_box, expand=False)
1349+ self.itself.reorder_child(self.install_box, 0)
1350+ else:
1351+ self.load_replications()
1352+
1353 self.message.stop()
1354- self.message.set_text('Under construction')
1355+
1356+ @log_call(logger.debug)
1357+ def load_replications(self, *args):
1358+ """Load replications info."""
1359+ # ask replications to the backend
1360+ self.message.start()
1361+ self.backend.replications_info(reply_handler=NO_OP,
1362+ error_handler=error_handler)
1363+
1364+ @log_call(logger.debug)
1365+ def on_replications_info_ready(self, info):
1366+ """The replication info is ready."""
1367+ self.on_success(self.CHOOSE_SERVICES)
1368+
1369+ self.replications.show()
1370+
1371+ if self.install_box is not None:
1372+ self.itself.remove(self.install_box)
1373+ self.install_box = None
1374+
1375+ for child in self.replications.get_children():
1376+ self.replications.remove(child)
1377+
1378+ for item in info:
1379+ pkg = item['dependency']
1380+ child = DesktopcouchService(service_id=item['replication_id'],
1381+ name=item['name'], # self.BOOKMARKS,
1382+ enabled=bool(item['enabled']),
1383+ dependency=pkg if pkg else None)
1384+ self.replications.pack_start(child, expand=False)
1385+
1386+ @log_call(logger.error)
1387+ def on_replications_info_error(self, error_dict=None):
1388+ """The replication info can not be retrieved."""
1389+ if error_dict is not None and \
1390+ error_dict.get('error_type', None) == 'NoPairingRecord':
1391+ self.on_error(self.NO_PAIRING_RECORD)
1392+ else:
1393+ self.on_error()
1394
1395
1396 class ManagementPanel(gtk.VBox, ControlPanelMixin):
1397 """The management panel.
1398
1399- The user can manage account, folders, devices and applications.
1400+ The user can manage dashboard, folders, devices and services.
1401
1402 """
1403
1404@@ -817,13 +1099,13 @@
1405 self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
1406 self.status_box.pack_end(self.status_label, expand=False)
1407
1408- self.account = AccountPanel()
1409+ self.dashboard = DashboardPanel()
1410 self.folders = FoldersPanel()
1411 self.devices = DevicesPanel()
1412- self.applications = ApplicationsPanel()
1413+ self.services = ServicesPanel()
1414
1415 cb = lambda button, page_num: self.notebook.set_current_page(page_num)
1416- self.tabs = (u'account', u'folders', u'devices', u'applications')
1417+ self.tabs = (u'dashboard', u'folders', u'devices', u'services')
1418 for page_num, tab in enumerate(self.tabs):
1419 setattr(self, ('%s_page' % tab).upper(), page_num)
1420 button = getattr(self, '%s_button' % tab)
1421@@ -835,6 +1117,7 @@
1422
1423 self.folders_button.connect('clicked', lambda b: self.folders.load())
1424 self.devices_button.connect('clicked', lambda b: self.devices.load())
1425+ self.services_button.connect('clicked', lambda b: self.services.load())
1426 self.devices.connect('local-device-removed',
1427 lambda widget: self.emit('local-device-removed'))
1428
1429@@ -855,9 +1138,11 @@
1430
1431 def load(self):
1432 """Load the account info and file sync status list."""
1433- self.backend.account_info()
1434- self.backend.file_sync_status()
1435- self.account_button.clicked()
1436+ self.backend.account_info(reply_handler=NO_OP,
1437+ error_handler=error_handler)
1438+ self.backend.file_sync_status(reply_handler=NO_OP,
1439+ error_handler=error_handler)
1440+ self.dashboard_button.clicked()
1441
1442 @log_call(logger.debug)
1443 def on_account_info_ready(self, info):
1444@@ -910,3 +1195,6 @@
1445
1446 gobject.signal_new('local-device-removed', DevicesPanel,
1447 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
1448+
1449+gobject.signal_new('finished', InstallPackage,
1450+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
1451
1452=== added file 'ubuntuone/controlpanel/gtk/package_manager.py'
1453--- ubuntuone/controlpanel/gtk/package_manager.py 1970-01-01 00:00:00 +0000
1454+++ ubuntuone/controlpanel/gtk/package_manager.py 2011-01-10 02:57:12 +0000
1455@@ -0,0 +1,60 @@
1456+# -*- coding: utf-8 -*-
1457+
1458+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1459+#
1460+# Copyright 2010 Canonical Ltd.
1461+#
1462+# This program is free software: you can redistribute it and/or modify it
1463+# under the terms of the GNU General Public License version 3, as published
1464+# by the Free Software Foundation.
1465+#
1466+# This program is distributed in the hope that it will be useful, but
1467+# WITHOUT ANY WARRANTY; without even the implied warranties of
1468+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1469+# PURPOSE. See the GNU General Public License for more details.
1470+#
1471+# You should have received a copy of the GNU General Public License along
1472+# with this program. If not, see <http://www.gnu.org/licenses/>.
1473+
1474+"""Client to manage packages."""
1475+
1476+import apt
1477+import aptdaemon.client
1478+import aptdaemon.enums
1479+
1480+try:
1481+ # Unable to import 'defer', pylint: disable=F0401,E0611
1482+ from aptdaemon.defer import inline_callbacks, return_value
1483+except ImportError:
1484+ # Unable to import 'defer', pylint: disable=F0401,E0611
1485+ from defer import inline_callbacks, return_value
1486+from aptdaemon.gtkwidgets import AptProgressBar
1487+
1488+from ubuntuone.controlpanel.logger import setup_logging
1489+
1490+
1491+logger = setup_logging('package_manager')
1492+
1493+
1494+class PackageManagerProgressBar(AptProgressBar):
1495+ """A progress bar for a transaction."""
1496+
1497+
1498+class PackageManager(object):
1499+ """Manage packages (check if is installed, install)."""
1500+
1501+ def is_installed(self, package_name):
1502+ """Return whether 'package_name' is installed in this system."""
1503+ cache = apt.Cache()
1504+ result = package_name in cache and cache[package_name].is_installed
1505+ return result
1506+
1507+ @inline_callbacks
1508+ def install(self, package_name):
1509+ """Install 'package_name' if is not installed in this system."""
1510+ if self.is_installed(package_name):
1511+ return_value(aptdaemon.enums.EXIT_SUCCESS)
1512+
1513+ client = aptdaemon.client.AptClient()
1514+ transaction = yield client.install_packages([package_name])
1515+ return_value(transaction)
1516
1517=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
1518--- ubuntuone/controlpanel/gtk/tests/__init__.py 2010-12-06 12:27:11 +0000
1519+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-10 02:57:12 +0000
1520@@ -17,3 +17,154 @@
1521 # with this program. If not, see <http://www.gnu.org/licenses/>.
1522
1523 """The test suite for the GTK UI for the control panel for Ubuntu One."""
1524+
1525+from collections import defaultdict
1526+
1527+from ubuntuone.controlpanel.gtk import gui
1528+from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
1529+ FakedTransaction)
1530+
1531+
1532+FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
1533+ 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
1534+
1535+FAKE_VOLUMES_INFO = [
1536+ {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},
1537+ {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},
1538+ {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
1539+]
1540+
1541+FAKE_DEVICE_INFO = {
1542+ 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
1543+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
1544+ 'max_upload_speed': '1000', 'max_download_speed': '72548',
1545+}
1546+
1547+FAKE_DEVICES_INFO = [
1548+ {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
1549+ 'is_local': '', 'configurable': ''},
1550+ {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
1551+ 'is_local': '', 'configurable': ''},
1552+ {'device_id': '2', 'name': 'Z', 'type': 'Computer',
1553+ 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
1554+ 'max_upload_speed': '0', 'max_download_speed': '0'},
1555+ {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
1556+ 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
1557+ 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
1558+]
1559+
1560+FAKE_REPLICATIONS_INFO = [
1561+ {'replication_id': 'foo', 'name': 'Bar',
1562+ 'enabled': 'True', 'dependency': ''},
1563+ {'replication_id': 'yadda', 'name': 'Foo',
1564+ 'enabled': '', 'dependency': 'a very weird one'},
1565+ {'replication_id': 'yoda', 'name': 'Figthers',
1566+ 'enabled': 'True', 'dependency': 'other dep'},
1567+]
1568+
1569+
1570+class FakedObject(object):
1571+ """Fake an object, record every call."""
1572+
1573+ exposed_methods = []
1574+
1575+ def __init__(self, *args, **kwargs):
1576+ self._args = args
1577+ self._kwargs = kwargs
1578+ self._called = {}
1579+ for i in self.exposed_methods:
1580+ setattr(self, i, self._record_call(i))
1581+
1582+ def _record_call(self, func_name):
1583+ """Store values when calling 'func_name'."""
1584+
1585+ def inner(*args, **kwargs):
1586+ """Fake 'func_name'."""
1587+ self._called[func_name] = (args, kwargs)
1588+
1589+ return inner
1590+
1591+
1592+class FakedNMState(FakedObject):
1593+ """Fake a NetworkManagerState."""
1594+
1595+ exposed_methods = ['find_online_state']
1596+
1597+
1598+class FakedDBusBackend(FakedObject):
1599+ """Fake a DBus Backend."""
1600+
1601+ bus_name = None
1602+ object_path = None
1603+ iface = None
1604+
1605+ def __init__(self, obj, dbus_interface, *args, **kwargs):
1606+ if dbus_interface != self.iface:
1607+ raise TypeError()
1608+ self._signals = defaultdict(list)
1609+ super(FakedDBusBackend, self).__init__(*args, **kwargs)
1610+
1611+ def connect_to_signal(self, signal, handler):
1612+ """Bind 'handler' to be callback'd when 'signal' is fired."""
1613+ self._signals[signal].append(handler)
1614+
1615+
1616+class FakedSSOBackend(FakedDBusBackend):
1617+ """Fake a SSO Backend, act as a dbus.Interface."""
1618+
1619+ bus_name = gui.ubuntu_sso.DBUS_BUS_NAME
1620+ object_path = gui.ubuntu_sso.DBUS_CREDENTIALS_PATH
1621+ iface = gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE
1622+ exposed_methods = ['find_credentials', 'clear_credentials',
1623+ 'login', 'register']
1624+
1625+
1626+class FakedControlPanelBackend(FakedDBusBackend):
1627+ """Fake a Control Panel Backend, act as a dbus.Interface."""
1628+
1629+ bus_name = gui.DBUS_BUS_NAME
1630+ object_path = gui.DBUS_PREFERENCES_PATH
1631+ iface = gui.DBUS_PREFERENCES_IFACE
1632+ exposed_methods = [
1633+ 'account_info', # account
1634+ 'devices_info', 'change_device_settings', 'remove_device', # devices
1635+ 'volumes_info', 'change_volume_settings', # volumes
1636+ 'replications_info', 'change_replication_settings', # replications
1637+ 'file_sync_status', 'enable_files', 'disable_files', # files
1638+ ]
1639+
1640+
1641+class FakedSessionBus(object):
1642+ """Fake a session bus."""
1643+
1644+ def get_object(self, bus_name, object_path, introspect=True,
1645+ follow_name_owner_changes=False, **kwargs):
1646+ """Return a faked proxy for the given remote object."""
1647+ return None
1648+
1649+
1650+class FakedInterface(object):
1651+ """Fake a dbus interface."""
1652+
1653+ def __new__(cls, obj, dbus_interface, *args, **kwargs):
1654+ if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
1655+ return FakedControlPanelBackend(obj, dbus_interface,
1656+ *args, **kwargs)
1657+ if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE:
1658+ return FakedSSOBackend(obj, dbus_interface, *args, **kwargs)
1659+
1660+
1661+class FakedPackageManager(object):
1662+ """Faked a package manager."""
1663+
1664+ def __init__(self):
1665+ self._installed = {}
1666+ self.is_installed = lambda package_name: \
1667+ self._installed.setdefault(package_name, False)
1668+
1669+ @gui.package_manager.inline_callbacks
1670+ def install(self, package_name):
1671+ """Install 'package_name' if is not installed in this system."""
1672+ yield
1673+ self._installed[package_name] = True
1674+ gui.package_manager.return_value(FakedTransaction([package_name]))
1675
1676=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
1677--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-22 13:33:25 +0000
1678+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-10 02:57:12 +0000
1679@@ -22,136 +22,24 @@
1680
1681 import logging
1682
1683-from collections import defaultdict
1684-
1685 from ubuntuone.devtools.handlers import MementoHandler
1686
1687 from ubuntuone.controlpanel.gtk import gui
1688+from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
1689+ FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
1690+ FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO,
1691+ FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
1692+ FakedPackageManager,
1693+)
1694 from ubuntuone.controlpanel.tests import TOKEN, TestCase
1695-
1696-# Attribute 'yyy' defined outside __init__
1697-# pylint: disable=W0201
1698-
1699-# Access to a protected member 'yyy' of a client class
1700-# pylint: disable=W0212
1701-
1702-
1703-FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
1704- 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
1705-
1706-FAKE_VOLUMES_INFO = [
1707- {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},
1708- {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},
1709- {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
1710-]
1711-
1712-FAKE_DEVICE_INFO = {
1713- 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
1714- 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
1715- 'max_upload_speed': '1000', 'max_download_speed': '72548',
1716-}
1717-
1718-FAKE_DEVICES_INFO = [
1719- {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
1720- 'is_local': '', 'configurable': ''},
1721- {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
1722- 'is_local': '', 'configurable': ''},
1723- {'device_id': '2', 'name': 'Z', 'type': 'Computer',
1724- 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
1725- 'max_upload_speed': '0', 'max_download_speed': '0'},
1726- {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
1727- 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
1728- 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
1729-]
1730-
1731-
1732-class FakedObject(object):
1733- """Fake an object, record every call."""
1734-
1735- exposed_methods = []
1736-
1737- def __init__(self, *args, **kwargs):
1738- self._args = args
1739- self._kwargs = kwargs
1740- self._called = {}
1741- for i in self.exposed_methods:
1742- setattr(self, i, self._record_call(i))
1743-
1744- def _record_call(self, func_name):
1745- """Store values when calling 'func_name'."""
1746-
1747- def inner(*args, **kwargs):
1748- """Fake 'func_name'."""
1749- self._called[func_name] = (args, kwargs)
1750-
1751- return inner
1752-
1753-
1754-class FakedNMState(FakedObject):
1755- """Fake a NetworkManagerState."""
1756-
1757- exposed_methods = ['find_online_state']
1758-
1759-
1760-class FakedDBusBackend(FakedObject):
1761- """Fake a DBus Backend."""
1762-
1763- bus_name = None
1764- object_path = None
1765- iface = None
1766-
1767- def __init__(self, obj, dbus_interface, *args, **kwargs):
1768- if dbus_interface != self.iface:
1769- raise TypeError()
1770- self._signals = defaultdict(list)
1771- super(FakedDBusBackend, self).__init__(*args, **kwargs)
1772-
1773- def connect_to_signal(self, signal, handler):
1774- """Bind 'handler' to be callback'd when 'signal' is fired."""
1775- self._signals[signal].append(handler)
1776-
1777-
1778-class FakedSSOBackend(FakedDBusBackend):
1779- """Fake a SSO Backend, act as a dbus.Interface."""
1780-
1781- bus_name = gui.ubuntu_sso.DBUS_BUS_NAME
1782- object_path = gui.ubuntu_sso.DBUS_CREDENTIALS_PATH
1783- iface = gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE
1784- exposed_methods = ['find_credentials', 'clear_credentials',
1785- 'login', 'register']
1786-
1787-
1788-class FakedControlPanelBackend(FakedDBusBackend):
1789- """Fake a Control Panel Backend, act as a dbus.Interface."""
1790-
1791- bus_name = gui.DBUS_BUS_NAME
1792- object_path = gui.DBUS_PREFERENCES_PATH
1793- iface = gui.DBUS_PREFERENCES_IFACE
1794- exposed_methods = [
1795- 'account_info', 'devices_info', 'change_device_settings',
1796- 'volumes_info', 'change_volume_settings', 'file_sync_status',
1797- 'remove_device',
1798- ]
1799-
1800-
1801-class FakedSessionBus(object):
1802- """Fake a session bus."""
1803-
1804- def get_object(self, bus_name, object_path, introspect=True,
1805- follow_name_owner_changes=False, **kwargs):
1806- """Return a faked proxy for the given remote object."""
1807- return None
1808-
1809-
1810-class FakedInterface(object):
1811- """Fake a dbus interface."""
1812-
1813- def __new__(cls, obj, dbus_interface, *args, **kwargs):
1814- if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
1815- return FakedControlPanelBackend(obj, dbus_interface,
1816- *args, **kwargs)
1817- if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE:
1818- return FakedSSOBackend(obj, dbus_interface, *args, **kwargs)
1819+from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
1820+ SUCCESS, FAILURE)
1821+
1822+
1823+# Attribute 'yyy' defined outside __init__, access to a protected member
1824+# pylint: disable=W0201, W0212
1825+# Too many lines in module
1826+# pylint: disable=C0302
1827
1828
1829 class BaseTestCase(TestCase):
1830@@ -168,6 +56,7 @@
1831 self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
1832 self.patch(gui.dbus, 'Interface', FakedInterface)
1833 self.patch(gui.networkstate, 'NetworkManagerState', FakedNMState)
1834+ self.patch(gui.package_manager, 'PackageManager', FakedPackageManager)
1835
1836 if self.klass is not None:
1837 self.ui = self.klass(**self.kwargs)
1838@@ -210,7 +99,9 @@
1839 if backend is None:
1840 backend = self.ui.backend
1841 self.assertIn(method_name, backend._called)
1842- self.assertEqual(backend._called[method_name], (args, {}))
1843+ kwargs = {'reply_handler': gui.NO_OP,
1844+ 'error_handler': gui.error_handler}
1845+ self.assertEqual(backend._called[method_name], (args, kwargs))
1846
1847 def assert_warning_correct(self, warning, text):
1848 """Check that 'warning' is visible, showing 'text'."""
1849@@ -339,10 +230,10 @@
1850
1851 self.assert_current_tab_correct(self.ui.management)
1852
1853- def test_credentials_found_shows_account_management_panel(self):
1854+ def test_credentials_found_shows_dashboard_management_panel(self):
1855 """On 'credentials-found' signal, the management panel is shown.
1856
1857- If first signal parameter is False, visible tab should be account.
1858+ If first signal parameter is False, visible tab should be dashboard.
1859
1860 """
1861 self.patch(self.ui.management, 'load', self._set_called)
1862@@ -350,7 +241,7 @@
1863
1864 self.assert_current_tab_correct(self.ui.management)
1865 self.assertEqual(self.ui.management.notebook.get_current_page(),
1866- self.ui.management.ACCOUNT_PAGE)
1867+ self.ui.management.DASHBOARD_PAGE)
1868 self.assertEqual(self._called, ((), {}))
1869
1870 def test_credentials_found_shows_folders_management_panel(self):
1871@@ -736,11 +627,11 @@
1872 messages = ['<small>Test me</small>', 'A <b>little</b> bit more']
1873
1874
1875-class AccountTestCase(ControlPanelMixinTestCase):
1876- """The test suite for the account panel."""
1877+class DashboardTestCase(ControlPanelMixinTestCase):
1878+ """The test suite for the dashboard panel."""
1879
1880- klass = gui.AccountPanel
1881- ui_filename = 'account.ui'
1882+ klass = gui.DashboardPanel
1883+ ui_filename = 'dashboard.ui'
1884
1885 def assert_account_info_correct(self, info):
1886 """Check that the displayed account info matches 'info'."""
1887@@ -1087,7 +978,8 @@
1888
1889 def test_on_limit_bandwidth_toggled(self):
1890 """When toggling limit_bandwidth, backend is updated."""
1891- self.ui.limit_bandwidth.toggled()
1892+ value = not self.ui.limit_bandwidth.get_active()
1893+ self.ui.limit_bandwidth.set_active(value)
1894 self.assert_device_settings_changed()
1895
1896 def test_on_max_upload_speed_value_changed(self):
1897@@ -1365,11 +1257,367 @@
1898 self.assertEqual(new_devices, old_devices)
1899
1900
1901-class ApplicationsTestCase(ControlPanelMixinTestCase):
1902- """The test suite for the applications panel."""
1903-
1904- klass = gui.ApplicationsPanel
1905- ui_filename = 'applications.ui'
1906+class InstallPackageTestCase(ControlPanelMixinTestCase):
1907+ """The test suite for the install widget."""
1908+
1909+ klass = gui.InstallPackage
1910+ ui_filename = 'install.ui'
1911+ kwargs = {'package_name': 'a test package'}
1912+
1913+ def test_is_an_box(self):
1914+ """Inherits from gtk.VBox."""
1915+ self.assertIsInstance(self.ui, gui.gtk.VBox)
1916+
1917+ def test_inner_widget_is_packed(self):
1918+ """The 'itself' vbox is packed into the widget."""
1919+ self.assertIn(self.ui.itself, self.ui.get_children())
1920+
1921+ def test_is_visible(self):
1922+ """Is visible."""
1923+ self.assertTrue(self.ui.get_visible())
1924+
1925+ def test_package_name(self):
1926+ """The package_name is stored."""
1927+ self.assertEqual(self.ui.package_name, self.kwargs['package_name'])
1928+
1929+ def test_children(self):
1930+ """The children is correct."""
1931+ children = self.ui.itself.get_children()
1932+ self.assertEqual(len(children), 2)
1933+ self.assertEqual(self.ui.install_label, children[0])
1934+ self.assertIn(self.ui.install_button, children[1].get_children())
1935+
1936+ def test_install_label(self):
1937+ """The install label is correct."""
1938+ msg = self.ui.INSTALL_PACKAGE % self.kwargs
1939+ self.assertEqual(self.ui.install_label.get_label(), msg)
1940+
1941+ @gui.package_manager.inline_callbacks
1942+ def test_install_button_clicked_shows_progress(self):
1943+ """The install button is correct."""
1944+ yield self.ui.install_button.clicked()
1945+
1946+ children = self.ui.itself.get_children()
1947+ self.assertEqual(len(children), 2)
1948+ self.assertEqual(self.ui.progress_bar, children[1])
1949+ self.assertTrue(self.ui.progress_bar.get_visible())
1950+ self.assertIsInstance(self.ui.progress_bar,
1951+ gui.package_manager.PackageManagerProgressBar)
1952+
1953+ def test_install_button_clicked_install_label(self):
1954+ """The install label is correct."""
1955+ yield self.ui.install_button.clicked()
1956+
1957+ children = self.ui.itself.get_children()
1958+ self.assertEqual(len(children), 2)
1959+ self.assertEqual(self.ui.install_label, children[0])
1960+ msg = self.ui.INSTALLING % self.kwargs
1961+ self.assertEqual(self.ui.install_label.get_label(), msg)
1962+
1963+ @gui.package_manager.inline_callbacks
1964+ def test_install_button_clicked_transaction(self):
1965+ """The install button transaction is correct."""
1966+ yield self.ui.install_button.clicked()
1967+
1968+ transaction = self.ui.transaction
1969+ self.assertTrue(transaction.packages, [self.ui.package_name])
1970+ self.assertIn(self.ui.on_install_finished,
1971+ transaction._signals['finished'])
1972+ self.assertTrue(transaction.was_run)
1973+
1974+ @gui.package_manager.inline_callbacks
1975+ def test_install_button_clicked_fails(self):
1976+ """The install button transaction is correct."""
1977+
1978+ def fail(*args):
1979+ """Simulate an error."""
1980+ raise Exception(args)
1981+
1982+ self.patch(self.ui.package_manager, 'install', fail)
1983+ yield self.ui.install_button.clicked()
1984+
1985+ msg = self.ui.FAILED_INSTALL % self.kwargs
1986+ self.assert_warning_correct(self.ui.install_label, msg)
1987+
1988+ @gui.package_manager.inline_callbacks
1989+ def test_on_install_finished_success(self):
1990+ """The install finished."""
1991+ self.ui.connect('finished', self._set_called)
1992+ yield self.ui.install_button.clicked()
1993+ self.ui.on_install_finished(object(), SUCCESS)
1994+
1995+ self.assertFalse(self.ui.progress_bar.get_sensitive())
1996+ msg = self.ui.SUCCESS_INSTALL % self.kwargs
1997+ self.assertEqual(self.ui.install_label.get_label(), msg)
1998+ self.assertEqual(self._called, ((self.ui,), {}))
1999+
2000+ @gui.package_manager.inline_callbacks
2001+ def test_on_install_finished_failed(self):
2002+ """The install finished."""
2003+ yield self.ui.install_button.clicked()
2004+ self.ui.on_install_finished(object(), FAILURE)
2005+
2006+ self.assertFalse(self.ui.progress_bar.get_sensitive())
2007+ msg = self.ui.FAILED_INSTALL % self.kwargs
2008+ self.assert_warning_correct(self.ui.install_label, msg)
2009+
2010+
2011+class ServiceTestCase(ControlPanelMixinTestCase):
2012+ """The test suite for a service."""
2013+
2014+ klass = gui.Service
2015+ service_id = 'dc_test'
2016+ name = u'Qué lindo test!'
2017+ kwargs = {'service_id': service_id, 'name': name}
2018+
2019+ def test_is_an_box(self):
2020+ """Inherits from gtk.VBox."""
2021+ self.assertIsInstance(self.ui, gui.gtk.VBox)
2022+
2023+ def test_is_visible(self):
2024+ """Is visible."""
2025+ self.assertTrue(self.ui.get_visible())
2026+
2027+ def test_warning_label_is_cleared(self):
2028+ """The warning label is cleared."""
2029+ self.assertEqual(self.ui.warning_label.get_text(), '')
2030+
2031+ def test_warning_label_packed(self):
2032+ """The warning label is packed as child."""
2033+ self.assertIn(self.ui.warning_label, self.ui.get_children())
2034+
2035+ def test_check_button_packed(self):
2036+ """A check button is packed as child."""
2037+ self.assertIn(self.ui.button, self.ui.get_children())
2038+
2039+ def test_label(self):
2040+ """The label is set."""
2041+ self.assertEqual(self.name, self.ui.button.get_label())
2042+
2043+ def test_service_id(self):
2044+ """The service id is set."""
2045+ self.assertEqual(self.service_id, self.ui.id)
2046+
2047+
2048+class FilesServiceTestCase(ServiceTestCase):
2049+ """The test suite for the file sync service."""
2050+
2051+ klass = gui.FilesService
2052+ service_id = 'files'
2053+ name = gui.FilesService.FILES_SERVICE_NAME
2054+ kwargs = {}
2055+
2056+ def test_backend_account_signals(self):
2057+ """The proper signals are connected to the backend."""
2058+ self.assertEqual(self.ui.backend._signals['FileSyncStatusChanged'],
2059+ [self.ui.on_file_sync_status_changed])
2060+ self.assertEqual(self.ui.backend._signals['FilesEnabled'],
2061+ [self.ui.on_files_enabled])
2062+ self.assertEqual(self.ui.backend._signals['FilesDisabled'],
2063+ [self.ui.on_files_disabled])
2064+
2065+ def test_file_sync_status_is_requested(self):
2066+ """The file sync status is requested to the backend."""
2067+ self.assert_backend_called('file_sync_status', ())
2068+
2069+ def test_is_disabled(self):
2070+ """Until file sync status is given, the widget is disabled."""
2071+ self.assertFalse(self.ui.get_sensitive())
2072+
2073+ def test_is_enabled_on_file_sync_status_changed(self):
2074+ """When the file sync status is given, the widget is enabled."""
2075+ self.ui.on_file_sync_status_changed('something')
2076+ self.assertTrue(self.ui.get_sensitive())
2077+
2078+ def test_active(self):
2079+ """Is active when file status is anything but 'file-sync-disabled'."""
2080+ self.ui.on_file_sync_status_changed('something not disabled')
2081+ self.assertTrue(self.ui.button.get_active())
2082+
2083+ def test_not_active(self):
2084+ """Is not active when status is exactly but 'file-sync-disabled'."""
2085+ self.ui.on_file_sync_status_changed(gui.backend.FILE_SYNC_DISABLED)
2086+ self.assertFalse(self.ui.button.get_active())
2087+
2088+ def test_on_button_toggled(self):
2089+ """When toggling the button, the file sync service is updated."""
2090+ self.ui.on_file_sync_status_changed('something not disabled')
2091+ assert self.ui.button.get_active()
2092+
2093+ self.ui.button.set_active(not self.ui.button.get_active())
2094+ self.assert_backend_called('disable_files', ())
2095+
2096+ self.ui.button.set_active(not self.ui.button.get_active())
2097+ self.assert_backend_called('enable_files', ())
2098+
2099+ def test_on_file_sync_enabled(self):
2100+ """When file sync is enabled, the button is active."""
2101+ self.ui.on_files_disabled()
2102+ assert not self.ui.button.get_active()
2103+
2104+ self.ui.on_files_enabled()
2105+ self.assertTrue(self.ui.button.get_active())
2106+
2107+ def test_on_file_sync_disabled(self):
2108+ """When file sync is disabled, the button is not active."""
2109+ self.ui.on_files_enabled()
2110+ assert self.ui.button.get_active()
2111+
2112+ self.ui.on_files_disabled()
2113+ self.assertFalse(self.ui.button.get_active())
2114+
2115+
2116+class DesktopcouchServiceTestCase(ServiceTestCase):
2117+ """The test suite for a desktopcouch service."""
2118+
2119+ klass = gui.DesktopcouchService
2120+ enabled = True
2121+
2122+ def setUp(self):
2123+ self.kwargs['enabled'] = self.enabled
2124+ super(DesktopcouchServiceTestCase, self).setUp()
2125+
2126+ def modify_settings(self):
2127+ """Modify settings so values actually change."""
2128+ self.ui.button.set_active(not self.ui.button.get_active())
2129+
2130+ def test_backend_account_signals(self):
2131+ """The proper signals are connected to the backend."""
2132+ self.assertEqual(
2133+ self.ui.backend._signals['ReplicationSettingsChanged'],
2134+ [self.ui.on_replication_settings_changed])
2135+ self.assertEqual(
2136+ self.ui.backend._signals['ReplicationSettingsChangeError'],
2137+ [self.ui.on_replication_settings_change_error])
2138+
2139+ def test_active(self):
2140+ """Is active if enabled."""
2141+ self.assertEqual(self.enabled, self.ui.button.get_active())
2142+
2143+ def test_on_button_toggled(self):
2144+ """When toggling the button, the DC exclude list is updated."""
2145+ self.ui.button.set_active(not self.ui.button.get_active())
2146+
2147+ args = (self.service_id,
2148+ {'enabled': gui.bool_str(self.ui.button.get_active())})
2149+ self.assert_backend_called('change_replication_settings', args)
2150+
2151+ def test_dependency(self):
2152+ """The dependency box is None."""
2153+ self.assertTrue(self.ui.dependency is None)
2154+
2155+ def test_button_sensitiveness(self):
2156+ """The check button is sensitive."""
2157+ self.assertTrue(self.ui.button.get_sensitive())
2158+
2159+ def test_on_replication_settings_changed(self):
2160+ """When settings were changed for this replication, enable it."""
2161+ new_val = not self.ui.button.get_active()
2162+ self.ui.button.set_active(new_val)
2163+
2164+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
2165+
2166+ self.assertEqual(self.ui.warning_label.get_text(), '')
2167+ self.assertEqual(new_val, self.ui.button.get_active())
2168+
2169+ def test_on_replication_settings_changed_after_error(self):
2170+ """Change success after error."""
2171+ self.ui.button.set_active(not self.ui.button.get_active())
2172+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
2173+
2174+ self.test_on_replication_settings_changed()
2175+
2176+ def test_on_replication_settings_changed_different_id(self):
2177+ """When settings were changed for other rep, nothing changes."""
2178+ self.ui.button.set_active(not self.ui.button.get_active())
2179+ self.ui.on_replication_settings_changed(replication_id='yadda')
2180+
2181+ self.assertEqual(self.ui.warning_label.get_text(), '')
2182+
2183+ def test_on_replication_settings_changed_different_id_after_error(self):
2184+ """When settings were changed for other + error, nothing changes."""
2185+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
2186+ self.ui.on_replication_settings_changed(replication_id='yadda')
2187+
2188+ self.assert_warning_correct(self.ui.warning_label,
2189+ self.ui.CHANGE_ERROR)
2190+
2191+ def test_on_replication_settings_change_error(self):
2192+ """When settings were not changed, notify the user.
2193+
2194+ Also, confirm that old value was restored.
2195+
2196+ """
2197+ old_val = self.ui.button.get_active()
2198+ self.ui.button.set_active(not old_val)
2199+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
2200+
2201+ self.assert_warning_correct(self.ui.warning_label,
2202+ self.ui.CHANGE_ERROR)
2203+ self.assertEqual(old_val, self.ui.button.get_active())
2204+
2205+ def test_on_replication_settings_change_error_after_success(self):
2206+ """Change error after success."""
2207+ self.ui.button.set_active(not self.ui.button.get_active())
2208+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
2209+
2210+ self.test_on_replication_settings_change_error()
2211+
2212+ def test_on_replication_settings_change_error_different_id(self):
2213+ """When settings were not changed for other replication, do nothing."""
2214+ self.ui.button.set_active(not self.ui.button.get_active())
2215+ self.ui.on_replication_settings_change_error(replication_id='yudo')
2216+
2217+ self.assertEqual(self.ui.warning_label.get_text(), '')
2218+
2219+
2220+class DesktopcouchServiceDisabledAtStartupTestCase(ServiceTestCase):
2221+ """The test suite for a desktopcouch service when enabled=False."""
2222+
2223+ enabled = False
2224+
2225+
2226+class DesktopcouchServiceWithDependencyTestCase(DesktopcouchServiceTestCase):
2227+ """The test suite for a desktopcouch service when it needs a dependency."""
2228+
2229+ def setUp(self):
2230+ self.kwargs['dependency'] = 'a package'
2231+ super(DesktopcouchServiceWithDependencyTestCase, self).setUp()
2232+
2233+ def test_dependency(self):
2234+ """The dependency bos is not hidden."""
2235+ self.assertIsInstance(self.ui.dependency, gui.InstallPackage)
2236+ self.assertEqual(self.ui.dependency.package_name,
2237+ self.kwargs['dependency'])
2238+
2239+ def test_dependency_is_packed(self):
2240+ """The dependency is packed in the ui."""
2241+ self.assertIn(self.ui.dependency, self.ui.get_children())
2242+
2243+ def test_button_sensitiveness(self):
2244+ """The check button is not sensitive until depedency installed."""
2245+ self.assertFalse(self.ui.button.get_sensitive())
2246+
2247+ def test_button_is_enabled_on_dependency_installed(self):
2248+ """The check button is sensitive when depedency is installed."""
2249+ self.ui.dependency.emit('finished')
2250+
2251+ self.assertTrue(self.ui.button.get_sensitive())
2252+
2253+ def test_install_widget_is_removed_on_dependency_installed(self):
2254+ """The install button is removed when depedency is installed."""
2255+ self.ui.dependency.emit('finished')
2256+
2257+ self.assertTrue(self.ui.dependency is None)
2258+ self.assertEqual(sorted(self.ui.get_children()),
2259+ sorted([self.ui.button, self.ui.warning_label]))
2260+
2261+
2262+class ServicesTestCase(ControlPanelMixinTestCase):
2263+ """The test suite for the services panel."""
2264+
2265+ klass = gui.ServicesPanel
2266+ ui_filename = 'services.ui'
2267
2268 def test_is_an_ubuntuone_bin(self):
2269 """Inherits from UbuntuOneBin."""
2270@@ -1383,6 +1631,169 @@
2271 """Is visible."""
2272 self.assertTrue(self.ui.get_visible())
2273
2274+ def test_package_manager(self):
2275+ """Has a package manager."""
2276+ self.assertIsInstance(self.ui.package_manager,
2277+ gui.package_manager.PackageManager)
2278+
2279+ def test_install_box(self):
2280+ """The install box is None."""
2281+ self.assertTrue(self.ui.install_box is None)
2282+
2283+ def test_backend_signals(self):
2284+ """The proper signals are connected to the backend."""
2285+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoReady'],
2286+ [self.ui.on_replications_info_ready])
2287+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoError'],
2288+ [self.ui.on_replications_info_error])
2289+
2290+
2291+class ServicesFilesTestCase(ServicesTestCase):
2292+ """The test suite for the services panel (files section)."""
2293+
2294+ def test_files_is_visible(self):
2295+ """Files section is visible."""
2296+ self.assertTrue(self.ui.files.get_visible())
2297+
2298+ def test_files_is_a_file_sync_service(self):
2299+ """Files contains a FilesService."""
2300+ child, = self.ui.files.get_children()
2301+ self.assertIsInstance(child, gui.FilesService)
2302+
2303+
2304+class ServicesWithoutDesktopcouchTestCase(ServicesTestCase):
2305+ """The test suite for the services panel when DC is not installed."""
2306+
2307+ def setUp(self):
2308+ super(ServicesWithoutDesktopcouchTestCase, self).setUp()
2309+ self.patch(self.ui.package_manager, 'is_installed', lambda *a: False)
2310+ self.ui.load()
2311+
2312+ def test_message(self):
2313+ """Global load message is stopped and cleared."""
2314+ self.assertFalse(self.ui.message.active)
2315+ self.assertEqual(self.ui.message.get_text(), '')
2316+
2317+ def test_has_desktopcouch(self):
2318+ """Has desktopcouch installed?"""
2319+ self.assertFalse(self.ui.has_desktopcouch)
2320+
2321+ def test_install_box_is_hidden(self):
2322+ """The install box is not hidden."""
2323+ self.assertTrue(self.ui.install_box.get_visible())
2324+
2325+ def test_replications_is_hidden(self):
2326+ """The replications section is disabled."""
2327+ self.assertFalse(self.ui.replications.get_visible())
2328+
2329+ def test_install_box(self):
2330+ """The install box is enabled."""
2331+ self.assertTrue(self.ui.install_box.get_visible())
2332+ self.assertIn(self.ui.install_box, self.ui.itself.get_children())
2333+ self.assertEqual(self.ui.install_box.package_name,
2334+ self.ui.DESKTOPCOUCH_PKG)
2335+
2336+ def test_install_box_finished_connected(self):
2337+ """The install box 'finished' signal is connected."""
2338+ self.patch(self.ui, 'load_replications', self._set_called)
2339+ self.ui.load() # ensure signal connection uses the new method
2340+
2341+ self.ui.install_box.emit('finished')
2342+
2343+ self.assertEqual(self._called, ((self.ui.install_box,), {}))
2344+
2345+ def test_load_replications(self):
2346+ """The load_replications starts the spinner and calls the backend."""
2347+ self.ui.load_replications()
2348+
2349+ self.assertTrue(self.ui.message.active)
2350+ self.assert_backend_called('replications_info', ())
2351+
2352+
2353+class ServicesWithDesktopcouchTestCase(ServicesTestCase):
2354+ """The test suite for the services panel."""
2355+
2356+ def setUp(self):
2357+ super(ServicesWithDesktopcouchTestCase, self).setUp()
2358+ self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
2359+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
2360+
2361+ def test_message(self):
2362+ """Global load message is stopped and proper test is shown."""
2363+ self.assertFalse(self.ui.message.active)
2364+ self.assertEqual(self.ui.message.get_text(), self.ui.CHOOSE_SERVICES)
2365+
2366+ def test_has_desktopcouch(self):
2367+ """Has desktopcouch installed?"""
2368+ self.assertTrue(self.ui.has_desktopcouch)
2369+
2370+ def test_replications(self):
2371+ """Has proper child for each desktopcouch replication available."""
2372+ self.assertTrue(self.ui.replications.get_visible())
2373+
2374+ children = self.ui.replications.get_children()
2375+ self.assertEqual(len(children), len(FAKE_REPLICATIONS_INFO))
2376+ for expected, child in zip(FAKE_REPLICATIONS_INFO, children):
2377+ self.assertIsInstance(child, gui.DesktopcouchService)
2378+ self.assertEqual(expected['replication_id'], child.id)
2379+ self.assertEqual(expected['name'], child.button.get_label())
2380+ self.assertEqual(bool(expected['enabled']),
2381+ child.button.get_active())
2382+
2383+ if expected['dependency']:
2384+ self.assertTrue(child.dependency is not None)
2385+ self.assertEqual(expected['dependency'],
2386+ child.dependency.package_name)
2387+ else:
2388+ self.assertTrue(child.dependency is None)
2389+
2390+ def test_replications_after_getting_info_twice(self):
2391+ """Has proper child after getting backend info twice."""
2392+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
2393+ self.test_replications()
2394+
2395+
2396+class ServicesWithDesktopcouchErrorTestCase(ServicesTestCase):
2397+ """The test suite for the services panel."""
2398+
2399+ def setUp(self):
2400+ super(ServicesWithDesktopcouchErrorTestCase, self).setUp()
2401+ self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
2402+
2403+ def test_no_pairing_record(self):
2404+ """The pairing record is not in place."""
2405+ error_dict = {'error_type': 'NoPairingRecord'}
2406+ self.ui.on_replications_info_error(error_dict)
2407+
2408+ self.assertEqual(self.ui.replications.get_children(), [])
2409+ self.assertFalse(self.ui.message.active)
2410+ self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
2411+
2412+ def test_other_error(self):
2413+ """There was an error other than no pairing record."""
2414+ error_dict = {'error_type': 'OtherError'}
2415+ self.ui.on_replications_info_error(error_dict)
2416+
2417+ self.assertEqual(self.ui.replications.get_children(), [])
2418+ self.assertFalse(self.ui.message.active)
2419+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
2420+
2421+ def test_empty_dict(self):
2422+ """Handle empty dicts errors."""
2423+ self.ui.on_replications_info_error(error_dict={})
2424+
2425+ self.assertEqual(self.ui.replications.get_children(), [])
2426+ self.assertFalse(self.ui.message.active)
2427+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
2428+
2429+ def test_error_dict_none(self):
2430+ """HGandle empty dicts errors."""
2431+ self.ui.on_replications_info_error(error_dict=None)
2432+
2433+ self.assertEqual(self.ui.replications.get_children(), [])
2434+ self.assertFalse(self.ui.message.active)
2435+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
2436+
2437
2438 class ManagementPanelTestCase(ControlPanelMixinTestCase):
2439 """The test suite for the management panel."""
2440@@ -1420,11 +1831,11 @@
2441 """Tabs are not shown."""
2442 self.assertFalse(self.ui.notebook.get_show_tabs())
2443
2444- def test_default_page_is_account(self):
2445- """The default page is Account."""
2446+ def test_default_page_is_dashboard(self):
2447+ """The default page is Dashboard."""
2448 self.assertEqual(self.ui.notebook.get_current_page(),
2449- self.ui.ACCOUNT_PAGE)
2450- self.assertTrue(self.ui.account_button.get_active())
2451+ self.ui.DASHBOARD_PAGE)
2452+ self.assertTrue(self.ui.dashboard_button.get_active())
2453
2454 def test_buttons_set_notebook_pages(self):
2455 """The notebook pages are set when clicking buttons."""
2456@@ -1449,10 +1860,6 @@
2457 active = getattr(self.ui, '%s_button' % other).get_active()
2458 self.assertFalse(active, msg % (button, other))
2459
2460-
2461-class ManagementPanelAccountTestCase(ManagementPanelTestCase):
2462- """The test suite for the management panel (account tab)."""
2463-
2464 def test_backend_account_signals(self):
2465 """The proper signals are connected to the backend."""
2466 self.assertEqual(self.ui.backend._signals['AccountInfoReady'],
2467@@ -1465,11 +1872,11 @@
2468 self.ui.load()
2469 self.assert_backend_called('account_info', ())
2470
2471- def test_account_panel_is_packed(self):
2472- """The account panel is packed."""
2473- self.assertIsInstance(self.ui.account, gui.AccountPanel)
2474- actual = self.ui.notebook.get_nth_page(self.ui.ACCOUNT_PAGE)
2475- self.assertTrue(self.ui.account is actual)
2476+ def test_dashboard_panel_is_packed(self):
2477+ """The dashboard panel is packed."""
2478+ self.assertIsInstance(self.ui.dashboard, gui.DashboardPanel)
2479+ actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE)
2480+ self.assertTrue(self.ui.dashboard is actual)
2481
2482 def test_folders_panel_is_packed(self):
2483 """The folders panel is packed."""
2484@@ -1483,11 +1890,11 @@
2485 actual = self.ui.notebook.get_nth_page(self.ui.DEVICES_PAGE)
2486 self.assertTrue(self.ui.devices is actual)
2487
2488- def test_applications_panel_is_packed(self):
2489- """The applications panel is packed."""
2490- self.assertIsInstance(self.ui.applications, gui.ApplicationsPanel)
2491- actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE)
2492- self.assertTrue(self.ui.applications is actual)
2493+ def test_services_panel_is_packed(self):
2494+ """The services panel is packed."""
2495+ self.assertIsInstance(self.ui.services, gui.ServicesPanel)
2496+ actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE)
2497+ self.assertTrue(self.ui.services is actual)
2498
2499 def test_entering_folders_tab_loads_content(self):
2500 """The volumes info is loaded when entering the Folders tab."""
2501@@ -1505,6 +1912,14 @@
2502
2503 self.assertEqual(self._called, ((), {}))
2504
2505+ def test_entering_services_tab_loads_content(self):
2506+ """The services info is loaded when entering the Devices tab."""
2507+ self.patch(self.ui.services, 'load', self._set_called)
2508+ # clean backend calls
2509+ self.ui.services_button.clicked()
2510+
2511+ self.assertEqual(self._called, ((), {}))
2512+
2513 def test_quota_placeholder_is_loading(self):
2514 """Placeholders for quota label is a Loading widget."""
2515 self.assertIsInstance(self.ui.quota_label, gui.LabelLoading)
2516
2517=== added file 'ubuntuone/controlpanel/gtk/tests/test_package_manager.py'
2518--- ubuntuone/controlpanel/gtk/tests/test_package_manager.py 1970-01-01 00:00:00 +0000
2519+++ ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-01-10 02:57:12 +0000
2520@@ -0,0 +1,177 @@
2521+# -*- coding: utf-8 -*-
2522+
2523+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
2524+#
2525+# Copyright 2010 Canonical Ltd.
2526+#
2527+# This program is free software: you can redistribute it and/or modify it
2528+# under the terms of the GNU General Public License version 3, as published
2529+# by the Free Software Foundation.
2530+#
2531+# This program is distributed in the hope that it will be useful, but
2532+# WITHOUT ANY WARRANTY; without even the implied warranties of
2533+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2534+# PURPOSE. See the GNU General Public License for more details.
2535+#
2536+# You should have received a copy of the GNU General Public License along
2537+# with this program. If not, see <http://www.gnu.org/licenses/>.
2538+
2539+"""Tests for the package manager service."""
2540+
2541+import collections
2542+
2543+try:
2544+ # Unable to import 'defer', pylint: disable=F0401,E0611
2545+ from aptdaemon import defer
2546+except ImportError:
2547+ # Unable to import 'defer', pylint: disable=F0401,E0611
2548+ import defer
2549+
2550+from ubuntuone.controlpanel.gtk import package_manager
2551+from ubuntuone.controlpanel.tests import TestCase
2552+
2553+
2554+FAKED_CACHE = {}
2555+SUCCESS = package_manager.aptdaemon.enums.EXIT_SUCCESS
2556+FAILURE = package_manager.aptdaemon.enums.EXIT_FAILED
2557+
2558+
2559+class FakedPackage(object):
2560+ """Fake a package."""
2561+
2562+ def __init__(self, name=None, is_installed=False):
2563+ self.name = name
2564+ self.is_installed = is_installed
2565+
2566+
2567+class FakedTransaction(object):
2568+ """Fake a transaction."""
2569+
2570+ failure = None
2571+
2572+ def __init__(self, packages, cache=None):
2573+ self._signals = collections.defaultdict(list)
2574+ self._cache = cache
2575+ self.was_run = False
2576+ self.packages = packages
2577+ self.connect = lambda sig, f: self._signals[sig].append(f)
2578+
2579+ def run(self):
2580+ """Run!"""
2581+ self.was_run = True
2582+
2583+ if self._cache is not None:
2584+ for package in self.packages:
2585+ FAKED_CACHE[package].is_installed = True
2586+
2587+ if self.failure is None:
2588+ code = SUCCESS
2589+ else:
2590+ code = FAILURE
2591+
2592+ for listener in self._signals['finished']:
2593+ listener(self, code)
2594+
2595+ d = defer.Deferred()
2596+ d.callback(code)
2597+ return d
2598+
2599+
2600+class FakedClient(object):
2601+ """Fake an apt client."""
2602+
2603+ def install_packages(self, packages_names, **kwargs):
2604+ """Install packages listed in 'package_names'.
2605+
2606+ package_names - a list of package names
2607+ wait - if True run the transaction immediately and return its exit
2608+ state instead of the transaction itself.
2609+ reply_handler - callback function. If specified in combination with
2610+ error_handler the method will be called asynchrounsouly.
2611+ error_handler - in case of an error the given callback gets the
2612+ corresponding DBus exception instance
2613+
2614+ """
2615+ if kwargs.get('wait', False):
2616+ return package_manager.aptdaemon.enums.EXIT_FAILED
2617+ d = defer.Deferred()
2618+ d.callback(FakedTransaction(packages_names, cache=FAKED_CACHE))
2619+ return d
2620+
2621+
2622+class PackageManagerTestCase(TestCase):
2623+ """Test for the package manager."""
2624+
2625+ timeout = 2
2626+
2627+ def setUp(self):
2628+ FAKED_CACHE.clear() # clean cache
2629+ self.patch(package_manager.apt, 'Cache', lambda: FAKED_CACHE)
2630+ self.patch(package_manager.aptdaemon.client, 'AptClient', FakedClient)
2631+ self.obj = package_manager.PackageManager()
2632+
2633+ def test_is_installed(self):
2634+ """Check that a package is installed."""
2635+ name = 'test'
2636+ FAKED_CACHE[name] = FakedPackage(name, is_installed=True)
2637+
2638+ result = self.obj.is_installed(name)
2639+
2640+ self.assertTrue(result, 'must be installed')
2641+
2642+ def test_is_not_installed(self):
2643+ """Check if a package is installed."""
2644+ name = 'test'
2645+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
2646+
2647+ result = self.obj.is_installed(name)
2648+
2649+ self.assertFalse(result, 'must not be installed')
2650+
2651+ def test_is_not_installed_if_key_error(self):
2652+ """Check if a package is installed when cache raises KeyError."""
2653+ name = 'test' # is not in the cache
2654+ result = self.obj.is_installed(name)
2655+
2656+ self.assertFalse(result, 'must not be installed')
2657+
2658+ def test_progress_bar(self):
2659+ """The progress bar class is correct."""
2660+ self.assertIsInstance(package_manager.PackageManagerProgressBar(),
2661+ package_manager.AptProgressBar)
2662+
2663+ @package_manager.inline_callbacks
2664+ def test_install(self):
2665+ """Install is correct."""
2666+ name = 'test'
2667+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
2668+
2669+ result = yield self.obj.install(name)
2670+
2671+ self.assertIsInstance(result, FakedTransaction)
2672+ self.assertFalse(result.was_run, 'transaction must not be run')
2673+
2674+ @package_manager.inline_callbacks
2675+ def test_transaction_install(self):
2676+ """Install is correct."""
2677+ name = 'test'
2678+ FAKED_CACHE[name] = FakedPackage(name) # not installed by default
2679+
2680+ trans = yield self.obj.install(name)
2681+
2682+ trans.connect('finished', self._set_called)
2683+ trans.run()
2684+
2685+ self.assertEqual(self._called, ((trans, SUCCESS), {}))
2686+ self.assertTrue(trans.was_run, 'transaction must be run')
2687+ self.assertTrue(self.obj.is_installed(name))
2688+
2689+ @package_manager.inline_callbacks
2690+ def test_install_if_installed(self):
2691+ """Install does nothing is package is already installed."""
2692+ name = 'test'
2693+ FAKED_CACHE[name] = FakedPackage(name, is_installed=True)
2694+
2695+ result = yield self.obj.install(name)
2696+
2697+ self.assertEqual(result, SUCCESS)
2698
2699=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
2700--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-22 13:33:25 +0000
2701+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-01-10 02:57:12 +0000
2702@@ -84,6 +84,11 @@
2703 },
2704 ]
2705
2706+SAMPLE_REPLICATIONS_INFO = [
2707+ {'replication_id': 'yadda', 'wait for it': 'awesome'},
2708+ {'replication_id': 'yoda', 'something else': 'awesome'},
2709+]
2710+
2711
2712 class DBusServiceMainTestCase(mocker.MockerTestCase):
2713 """Tests for the main function."""
2714@@ -150,6 +155,14 @@
2715 """Return the status of the file sync service."""
2716 return self._process(self.sample_status)
2717
2718+ def enable_files(self):
2719+ """Enable files service."""
2720+ return self._process(None)
2721+
2722+ def disable_files(self):
2723+ """Disable files service."""
2724+ return self._process(None)
2725+
2726 def volumes_info(self):
2727 """Get the user volumes info."""
2728 return self._process(SAMPLE_VOLUMES_INFO)
2729@@ -158,6 +171,19 @@
2730 """Configure a given volume."""
2731 return self._process(volume_id)
2732
2733+ def replications_info(self):
2734+ """Start the replication exclusion service if needed.
2735+
2736+ Return the replication info, which is a dictionary of (replication
2737+ name, enabled).
2738+
2739+ """
2740+ return self._process(SAMPLE_REPLICATIONS_INFO)
2741+
2742+ def change_replication_settings(self, replication_id, settings):
2743+ """Configure a given replication."""
2744+ return self._process(replication_id)
2745+
2746 def query_bookmark_extension(self):
2747 """True if the bookmark extension has been installed."""
2748 return self._process(False)
2749@@ -250,13 +276,13 @@
2750 self.assertEqual(expected, result)
2751
2752
2753-class OperationsTestCase(TestCase):
2754- """Test for the DBus service operations."""
2755+class BaseTestCase(TestCase):
2756+ """Base test case for the DBus service."""
2757
2758 timeout = 3
2759
2760 def setUp(self):
2761- super(OperationsTestCase, self).setUp()
2762+ super(BaseTestCase, self).setUp()
2763 dbus_service.init_mainloop()
2764 be = dbus_service.publish_backend(MockBackend())
2765 self.addCleanup(be.remove_from_connection)
2766@@ -271,7 +297,7 @@
2767 def tearDown(self):
2768 self.backend = None
2769 self.deferred = None
2770- super(OperationsTestCase, self).tearDown()
2771+ super(BaseTestCase, self).tearDown()
2772
2773 def got_error(self, *a):
2774 """Some error happened in the DBus call."""
2775@@ -314,6 +340,10 @@
2776
2777 return self.deferred
2778
2779+
2780+class OperationsTestCase(BaseTestCase):
2781+ """Test for the DBus service operations."""
2782+
2783 def test_account_info_returned(self):
2784 """The account info is successfully returned."""
2785
2786@@ -383,12 +413,34 @@
2787 self.backend.remove_device, sample_token)
2788 return self.assert_correct_method_call(*args)
2789
2790+ def test_enable_files(self):
2791+ """Enable files service."""
2792+
2793+ def got_signal(*args):
2794+ """The correct signal was received."""
2795+ self.deferred.callback("success")
2796+
2797+ args = ("FilesEnabled", "FilesEnableError", got_signal,
2798+ self.backend.enable_files)
2799+ return self.assert_correct_method_call(*args)
2800+
2801+ def test_disable_files(self):
2802+ """Disable files service."""
2803+
2804+ def got_signal():
2805+ """The correct signal was received."""
2806+ self.deferred.callback("success")
2807+
2808+ args = ("FilesDisabled", "FilesDisableError", got_signal,
2809+ self.backend.disable_files)
2810+ return self.assert_correct_method_call(*args)
2811+
2812 def test_volumes_info(self):
2813 """The volumes info is reported."""
2814
2815- def got_signal(volumes_dict):
2816+ def got_signal(volumes):
2817 """The correct info was received."""
2818- self.assertEqual(volumes_dict, SAMPLE_VOLUMES_INFO)
2819+ self.assertEqual(volumes, SAMPLE_VOLUMES_INFO)
2820 self.deferred.callback("success")
2821
2822 args = ("VolumesInfoReady", "VolumesInfoError", got_signal,
2823@@ -409,6 +461,32 @@
2824 expected_volume_id, {'subscribed': ''})
2825 return self.assert_correct_method_call(*args)
2826
2827+ def test_replications_info(self):
2828+ """The replications info is reported."""
2829+
2830+ def got_signal(replications):
2831+ """The correct info was received."""
2832+ self.assertEqual(replications, SAMPLE_REPLICATIONS_INFO)
2833+ self.deferred.callback("success")
2834+
2835+ args = ("ReplicationsInfoReady", "ReplicationsInfoError", got_signal,
2836+ self.backend.replications_info)
2837+ return self.assert_correct_method_call(*args)
2838+
2839+ def test_change_replication_settings(self):
2840+ """The replication settings are successfully changed."""
2841+ expected_replication_id = SAMPLE_REPLICATIONS_INFO[0]['replication_id']
2842+
2843+ def got_signal(replication_id):
2844+ """The correct replication was changed."""
2845+ self.assertEqual(replication_id, expected_replication_id)
2846+ self.deferred.callback("success")
2847+
2848+ args = ("ReplicationSettingsChanged", "ReplicationSettingsChangeError",
2849+ got_signal, self.backend.change_replication_settings,
2850+ expected_replication_id, {'enabled': ''})
2851+ return self.assert_correct_method_call(*args)
2852+
2853 def test_query_bookmarks_extension(self):
2854 """The bookmarks extension is queried."""
2855
2856@@ -465,7 +543,7 @@
2857 error_sig, success_sig, got_error_signal, method, *args)
2858
2859
2860-class FileSyncTestCase(OperationsTestCase):
2861+class FileSyncTestCase(BaseTestCase):
2862 """Test for the DBus service when requesting file sync status."""
2863
2864 def assert_correct_status_signal(self, status, sync_signal,
2865@@ -527,6 +605,20 @@
2866 args = (dbus_service.FILE_SYNC_IDLE, "FileSyncStatusIdle")
2867 return self.assert_correct_status_signal(*args)
2868
2869+ def test_file_sync_status_changed(self):
2870+ """The file sync status is reported every time status changed."""
2871+ status = (
2872+ dbus_service.FILE_SYNC_DISABLED,
2873+ dbus_service.FILE_SYNC_DISCONNECTED,
2874+ dbus_service.FILE_SYNC_ERROR,
2875+ dbus_service.FILE_SYNC_IDLE,
2876+ dbus_service.FILE_SYNC_STARTING,
2877+ dbus_service.FILE_SYNC_SYNCING,
2878+ )
2879+ for arg in status:
2880+ args = (arg, "FileSyncStatusChanged")
2881+ return self.assert_correct_status_signal(*args, expected_msg=arg)
2882+
2883 def test_status_changed_handler(self):
2884 """The status changed handler is properly set."""
2885 be = MockBackend()
2886
2887=== modified file 'ubuntuone/controlpanel/logger.py'
2888--- ubuntuone/controlpanel/logger.py 2010-12-22 13:33:25 +0000
2889+++ ubuntuone/controlpanel/logger.py 2011-01-10 02:57:12 +0000
2890@@ -33,7 +33,7 @@
2891 LOG_LEVEL = logging.DEBUG
2892 else:
2893 # Only log this level and above
2894- LOG_LEVEL = logging.INFO
2895+ LOG_LEVEL = logging.DEBUG # before final release, switch to INFO
2896
2897 MAIN_HANDLER = RotatingFileHandler(os.path.join(LOGFOLDER, 'controlpanel.log'),
2898 maxBytes=1048576,
2899@@ -53,10 +53,7 @@
2900 logger.addHandler(MAIN_HANDLER)
2901 if os.environ.get('DEBUG'):
2902 debug_handler = logging.StreamHandler(sys.stderr)
2903- if prefix is not None:
2904- fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n"
2905- formatter = logging.Formatter(fmt)
2906- debug_handler.setFormatter(formatter)
2907+ debug_handler.setFormatter(basic_formatter)
2908 logger.addHandler(debug_handler)
2909
2910 return logger
2911
2912=== added file 'ubuntuone/controlpanel/replication_client.py'
2913--- ubuntuone/controlpanel/replication_client.py 1970-01-01 00:00:00 +0000
2914+++ ubuntuone/controlpanel/replication_client.py 2011-01-10 02:57:12 +0000
2915@@ -0,0 +1,115 @@
2916+# -*- coding: utf-8 -*-
2917+
2918+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
2919+#
2920+# Copyright 2010 Canonical Ltd.
2921+#
2922+# This program is free software: you can redistribute it and/or modify it
2923+# under the terms of the GNU General Public License version 3, as published
2924+# by the Free Software Foundation.
2925+#
2926+# This program is distributed in the hope that it will be useful, but
2927+# WITHOUT ANY WARRANTY; without even the implied warranties of
2928+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2929+# PURPOSE. See the GNU General Public License for more details.
2930+#
2931+# You should have received a copy of the GNU General Public License along
2932+# with this program. If not, see <http://www.gnu.org/licenses/>.
2933+
2934+"""Client to use replication services."""
2935+
2936+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
2937+
2938+from ubuntuone.controlpanel.logger import setup_logging
2939+
2940+
2941+logger = setup_logging('replication_client')
2942+
2943+BOOKMARKS = 'bookmarks'
2944+CONTACTS = 'contacts'
2945+# we should get this list from somewhere else
2946+REPLICATIONS = set([BOOKMARKS, CONTACTS])
2947+
2948+
2949+class ReplicationError(Exception):
2950+ """A replication error."""
2951+
2952+
2953+class NoPairingRecord(ReplicationError):
2954+ """There is no pairing record."""
2955+
2956+
2957+class InvalidIdError(ReplicationError):
2958+ """The replication id is not valid."""
2959+
2960+
2961+class NotExcludedError(ReplicationError):
2962+ """The replication can not be replicated since is not excluded."""
2963+
2964+
2965+class AlreadyExcludedError(ReplicationError):
2966+ """The replication can not be excluded since is already excluded."""
2967+
2968+
2969+def get_replication_proxy(replication_module=None):
2970+ """Return a proxy to the replication client."""
2971+ d = Deferred()
2972+ if replication_module is None:
2973+ # delay import in case DC is not installed at module import time
2974+ # Unable to import 'desktopcouch.application.replication_services'
2975+ # pylint: disable=F0401
2976+ from desktopcouch.application.replication_services \
2977+ import ubuntuone as replication_module
2978+ try:
2979+ result = replication_module.ReplicationExclusion()
2980+ except ValueError:
2981+ d.errback(NoPairingRecord())
2982+ else:
2983+ d.callback(result)
2984+
2985+ return d
2986+
2987+
2988+@inlineCallbacks
2989+def get_replications():
2990+ """Retrieve the list of replications."""
2991+ yield get_replication_proxy()
2992+ returnValue(REPLICATIONS)
2993+
2994+
2995+@inlineCallbacks
2996+def get_exclusions():
2997+ """Retrieve the list of exclusions."""
2998+ proxy = yield get_replication_proxy()
2999+ result = proxy.all_exclusions()
3000+ returnValue(result)
3001+
3002+
3003+@inlineCallbacks
3004+def replicate(replication_id):
3005+ """Remove replication_id from the exclusions list."""
3006+ replications = yield get_replications()
3007+ if replication_id not in replications:
3008+ raise InvalidIdError(replication_id)
3009+
3010+ exclusions = yield get_exclusions()
3011+ if replication_id not in exclusions:
3012+ raise NotExcludedError(replication_id)
3013+
3014+ proxy = yield get_replication_proxy()
3015+ yield proxy.replicate(replication_id)
3016+
3017+
3018+@inlineCallbacks
3019+def exclude(replication_id):
3020+ """Add replication_id to the exclusions list."""
3021+ replications = yield get_replications()
3022+ if replication_id not in replications:
3023+ raise InvalidIdError(replication_id)
3024+
3025+ exclusions = yield get_exclusions()
3026+ if replication_id in exclusions:
3027+ raise AlreadyExcludedError(replication_id)
3028+
3029+ proxy = yield get_replication_proxy()
3030+ yield proxy.exclude(replication_id)
3031
3032=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
3033--- ubuntuone/controlpanel/tests/__init__.py 2010-12-06 12:27:11 +0000
3034+++ ubuntuone/controlpanel/tests/__init__.py 2011-01-10 02:57:12 +0000
3035@@ -18,17 +18,165 @@
3036
3037 """The test suite for the control panel for Ubuntu One."""
3038
3039-from twisted.trial import unittest
3040+from ubuntuone.devtools.testcase import TestCase as BaseTestCase
3041
3042
3043 TOKEN = {u'consumer_key': u'xQ7xDAz',
3044 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
3045 u'token_name': u'test',
3046- u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
3047+ u'token': u'ABCDEF01234-localtoken',
3048 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
3049
3050-
3051-class TestCase(unittest.TestCase):
3052+SAMPLE_ACCOUNT_JSON = """
3053+{
3054+ "username": "andrewpz",
3055+ "openid": "https://login.launchpad.net/+id/abcdefg",
3056+ "first_name": "Andrew P.",
3057+ "last_name": "Zoilo",
3058+ "couchdb": {
3059+ "host": "https://couchdb.one.ubuntu.com",
3060+ "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
3061+ "dbpath": "u/abc/def/12345"
3062+ },
3063+ "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
3064+ "email": "andrewpz@protocultura.net",%s
3065+ "nickname": "Andrew P. Zoilo",
3066+ "id": 12345,
3067+ "subscription": {
3068+ "upgrade_available": false,
3069+ "description": "Paid Plan, 50 GB of storage",
3070+ "trial": false,
3071+ "started": "2010-03-24T18:38:38Z",
3072+ "is_paid": true,
3073+ "expires": null,
3074+ "qty": 1,
3075+ "price": 0.0,
3076+ "currency": null,
3077+ "id": 654321,
3078+ "name": "50 GB"
3079+ }
3080+}
3081+"""
3082+
3083+CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
3084+SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
3085+
3086+SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
3087+SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
3088+
3089+
3090+SAMPLE_QUOTA_JSON = """
3091+{
3092+ "total": 53687091200,
3093+ "used": 2350345156
3094+}
3095+"""
3096+
3097+EXPECTED_ACCOUNT_INFO = {
3098+ "quota_used": "2350345156",
3099+ "quota_total": "53687091200",
3100+ "type": "Paid Plan, 50 GB of storage",
3101+ "name": "Andrew P. Zoilo",
3102+ "email": "andrewpz@protocultura.net",
3103+}
3104+
3105+EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
3106+ "quota_used": "2350345156",
3107+ "quota_total": "53687091200",
3108+ "type": CURRENT_PLAN,
3109+ "name": "Andrew P. Zoilo",
3110+ "email": "andrewpz@protocultura.net",
3111+}
3112+
3113+SAMPLE_DEVICES_JSON = """
3114+[
3115+ {
3116+ "token": "ABCDEF01234token",
3117+ "description": "Ubuntu One @ darkstar",
3118+ "kind": "Computer"
3119+ },
3120+ {
3121+ "token": "ABCDEF01234-localtoken",
3122+ "description": "Ubuntu One @ localhost",
3123+ "kind": "Computer"
3124+ },
3125+ {
3126+ "kind": "Phone",
3127+ "description": "Nokia E65",
3128+ "id": 1000
3129+ }
3130+]
3131+"""
3132+
3133+EXPECTED_DEVICES_INFO = [
3134+ {
3135+ "device_id": "ComputerABCDEF01234token",
3136+ "name": "Ubuntu One @ darkstar",
3137+ "type": "Computer",
3138+ "is_local": '',
3139+ "configurable": '',
3140+ },
3141+ {
3142+ 'is_local': 'True',
3143+ 'configurable': 'True',
3144+ 'device_id': 'ComputerABCDEF01234-localtoken',
3145+ 'limit_bandwidth': '',
3146+ 'max_download_speed': '-1',
3147+ 'max_upload_speed': '-1',
3148+ 'name': 'Ubuntu One @ localhost',
3149+ 'type': 'Computer'
3150+ },
3151+ {
3152+ "device_id": "Phone1000",
3153+ "name": "Nokia E65",
3154+ "type": "Phone",
3155+ "configurable": '',
3156+ "is_local": '',
3157+ },
3158+]
3159+
3160+SAMPLE_FOLDERS = [
3161+ {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
3162+ u'path': u'/home/tester/Public', u'subscribed': u'True',
3163+ u'suggested_path': u'~/Public',
3164+ u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
3165+ {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
3166+ u'path': u'/home/tester/Documents', u'subscribed': u'',
3167+ u'suggested_path': u'~/Documents',
3168+ u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
3169+ {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
3170+ u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
3171+ u'suggested_path': u'~/Pictures/Photos',
3172+ u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
3173+]
3174+
3175+SAMPLE_SHARES = [
3176+ {u'accepted': u'True', u'access_level': u'View',
3177+ u'free_bytes': u'39892622746', u'generation': u'2704',
3178+ u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
3179+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3180+ u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
3181+ u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
3182+ {u'accepted': u'True', u'access_level': u'Modify',
3183+ u'free_bytes': u'39892622746', u'generation': u'2704',
3184+ u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
3185+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3186+ u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
3187+ u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
3188+]
3189+
3190+SAMPLE_SHARED = [
3191+ {u'accepted': u'True', u'access_level': u'View',
3192+ u'free_bytes': u'', u'generation': u'',
3193+ u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
3194+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3195+ u'path': u'/home/tester/Ubuntu One/bar',
3196+ u'type': u'Shared',
3197+ u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
3198+]
3199+
3200+
3201+class TestCase(BaseTestCase):
3202 """Basics for testing."""
3203
3204 def setUp(self):
3205
3206=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
3207--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-22 13:33:25 +0000
3208+++ ubuntuone/controlpanel/tests/test_backend.py 2011-01-10 02:57:12 +0000
3209@@ -25,9 +25,9 @@
3210 from twisted.internet.defer import inlineCallbacks
3211 from ubuntuone.devtools.handlers import MementoHandler
3212
3213-from ubuntuone.controlpanel import backend
3214-from ubuntuone.controlpanel.backend import (ACCOUNT_API,
3215- DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
3216+from ubuntuone.controlpanel import backend, replication_client
3217+from ubuntuone.controlpanel.backend import (bool_str,
3218+ ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
3219 FILE_SYNC_DISABLED,
3220 FILE_SYNC_DISCONNECTED,
3221 FILE_SYNC_ERROR,
3222@@ -37,160 +37,21 @@
3223 FILE_SYNC_UNKNOWN,
3224 MSG_KEY, STATUS_KEY,
3225 )
3226-
3227-from ubuntuone.controlpanel.tests import TestCase
3228+from ubuntuone.controlpanel.tests import (TestCase,
3229+ EXPECTED_ACCOUNT_INFO,
3230+ EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
3231+ EXPECTED_DEVICES_INFO,
3232+ SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
3233+ SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
3234+ SAMPLE_DEVICES_JSON,
3235+ SAMPLE_FOLDERS,
3236+ SAMPLE_QUOTA_JSON,
3237+ SAMPLE_SHARED,
3238+ SAMPLE_SHARES,
3239+ TOKEN,
3240+)
3241 from ubuntuone.controlpanel.webclient import WebClientError
3242
3243-SAMPLE_CREDENTIALS = {"token": "ABC1234DEF"}
3244-
3245-SAMPLE_ACCOUNT_JSON = """
3246-{
3247- "username": "andrewpz",
3248- "openid": "https://login.launchpad.net/+id/abcdefg",
3249- "first_name": "Andrew P.",
3250- "last_name": "Zoilo",
3251- "couchdb": {
3252- "host": "https://couchdb.one.ubuntu.com",
3253- "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
3254- "dbpath": "u/abc/def/12345"
3255- },
3256- "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
3257- "email": "andrewpz@protocultura.net",%s
3258- "nickname": "Andrew P. Zoilo",
3259- "id": 12345,
3260- "subscription": {
3261- "upgrade_available": false,
3262- "description": "Paid Plan, 50 GB of storage",
3263- "trial": false,
3264- "started": "2010-03-24T18:38:38Z",
3265- "is_paid": true,
3266- "expires": null,
3267- "qty": 1,
3268- "price": 0.0,
3269- "currency": null,
3270- "id": 654321,
3271- "name": "50 GB"
3272- }
3273-}
3274-"""
3275-
3276-CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
3277-SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
3278-
3279-SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
3280-SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
3281-
3282-
3283-SAMPLE_QUOTA_JSON = """
3284-{
3285- "total": 53687091200,
3286- "used": 2350345156
3287-}
3288-"""
3289-
3290-EXPECTED_ACCOUNT_INFO = {
3291- "quota_used": "2350345156",
3292- "quota_total": "53687091200",
3293- "type": "Paid Plan, 50 GB of storage",
3294- "name": "Andrew P. Zoilo",
3295- "email": "andrewpz@protocultura.net",
3296-}
3297-
3298-EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
3299- "quota_used": "2350345156",
3300- "quota_total": "53687091200",
3301- "type": CURRENT_PLAN,
3302- "name": "Andrew P. Zoilo",
3303- "email": "andrewpz@protocultura.net",
3304-}
3305-
3306-SAMPLE_DEVICES_JSON = """
3307-[
3308- {
3309- "token": "ABCDEF01234token",
3310- "description": "Ubuntu One @ darkstar",
3311- "kind": "Computer"
3312- },
3313- {
3314- "token": "ABC1234DEF",
3315- "description": "Ubuntu One @ localhost",
3316- "kind": "Computer"
3317- },
3318- {
3319- "kind": "Phone",
3320- "description": "Nokia E65",
3321- "id": 1000
3322- }
3323-]
3324-"""
3325-
3326-EXPECTED_DEVICES_INFO = [
3327- {
3328- "device_id": "ComputerABCDEF01234token",
3329- "name": "Ubuntu One @ darkstar",
3330- "type": "Computer",
3331- "is_local": '',
3332- "configurable": '',
3333- },
3334- {
3335- 'is_local': 'True',
3336- 'configurable': 'True',
3337- 'device_id': 'ComputerABC1234DEF',
3338- 'limit_bandwidth': '',
3339- 'max_download_speed': '-1',
3340- 'max_upload_speed': '-1',
3341- 'name': 'Ubuntu One @ localhost',
3342- 'type': 'Computer'
3343- },
3344- {
3345- "device_id": "Phone1000",
3346- "name": "Nokia E65",
3347- "type": "Phone",
3348- "configurable": '',
3349- "is_local": '',
3350- },
3351-]
3352-
3353-SAMPLE_FOLDERS = [
3354- {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
3355- u'path': u'/home/tester/Public', u'subscribed': u'True',
3356- u'suggested_path': u'~/Public',
3357- u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
3358- {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
3359- u'path': u'/home/tester/Documents', u'subscribed': u'',
3360- u'suggested_path': u'~/Documents',
3361- u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
3362- {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
3363- u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
3364- u'suggested_path': u'~/Pictures/Photos',
3365- u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
3366-]
3367-
3368-SAMPLE_SHARES = [
3369- {u'accepted': u'True', u'access_level': u'View',
3370- u'free_bytes': u'39892622746', u'generation': u'2704',
3371- u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
3372- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3373- u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
3374- u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
3375- {u'accepted': u'True', u'access_level': u'Modify',
3376- u'free_bytes': u'39892622746', u'generation': u'2704',
3377- u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
3378- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3379- u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
3380- u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
3381-]
3382-
3383-SAMPLE_SHARED = [
3384- {u'accepted': u'True', u'access_level': u'View',
3385- u'free_bytes': u'', u'generation': u'',
3386- u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
3387- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
3388- u'path': u'/home/tester/Ubuntu One/bar',
3389- u'type': u'Shared',
3390- u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
3391-]
3392-
3393
3394 class MockWebClient(object):
3395 """A mock webclient."""
3396@@ -213,7 +74,7 @@
3397 class MockDBusClient(object):
3398 """A mock dbus_client module."""
3399
3400- creds = SAMPLE_CREDENTIALS
3401+ creds = TOKEN
3402 throttling = False
3403 limits = {"download": -1, "upload": -1}
3404 file_sync = True
3405@@ -257,11 +118,11 @@
3406
3407 def files_sync_enabled(self):
3408 """Get if file sync service is enabled."""
3409- return self.file_sync
3410+ return MockDBusClient.file_sync
3411
3412 def set_files_sync_enabled(self, enabled):
3413 """Set the file sync service to be 'enabled'."""
3414- self.file_sync = enabled
3415+ MockDBusClient.file_sync = enabled
3416
3417 def get_folders(self):
3418 """Grab list of folders."""
3419@@ -292,6 +153,36 @@
3420 return SAMPLE_SHARED
3421
3422
3423+class MockReplicationClient(object):
3424+ """A mock replication_client module."""
3425+
3426+ BOOKMARKS = 'awesome'
3427+ CONTACTS = 'legendary'
3428+
3429+ replications = set([BOOKMARKS, CONTACTS, 'other'])
3430+ exclusions = set([CONTACTS])
3431+
3432+ def get_replications(self):
3433+ """Grab the list of replications in this machine."""
3434+ return MockReplicationClient.replications
3435+
3436+ def get_exclusions(self):
3437+ """Grab the list of exclusions in this machine."""
3438+ return MockReplicationClient.exclusions
3439+
3440+ def replicate(self, replication_id):
3441+ """Remove replication_id from the exclusions list."""
3442+ if replication_id not in MockReplicationClient.replications:
3443+ raise replication_client.ReplicationError(replication_id)
3444+ MockReplicationClient.exclusions.remove(replication_id)
3445+
3446+ def exclude(self, replication_id):
3447+ """Add replication_id to the exclusions list."""
3448+ if replication_id not in MockReplicationClient.replications:
3449+ raise replication_client.ReplicationError(replication_id)
3450+ MockReplicationClient.exclusions.add(replication_id)
3451+
3452+
3453 class BackendBasicTestCase(TestCase):
3454 """Simple tests for the backend."""
3455
3456@@ -301,13 +192,14 @@
3457 super(BackendBasicTestCase, self).setUp()
3458 self.patch(backend, "WebClient", MockWebClient)
3459 self.patch(backend, "dbus_client", MockDBusClient())
3460- self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]
3461+ self.patch(backend, "replication_client", MockReplicationClient())
3462+ self.local_token = "Computer" + TOKEN["token"]
3463 self.be = backend.ControlBackend()
3464
3465 self.memento = MementoHandler()
3466 backend.logger.addHandler(self.memento)
3467
3468- MockDBusClient.creds = SAMPLE_CREDENTIALS
3469+ MockDBusClient.creds = TOKEN
3470
3471 def test_backend_creation(self):
3472 """The backend instance is successfully created."""
3473@@ -317,7 +209,7 @@
3474 def test_get_token(self):
3475 """The get_token method returns the right token."""
3476 token = yield self.be.get_token()
3477- self.assertEqual(token, SAMPLE_CREDENTIALS["token"])
3478+ self.assertEqual(token, TOKEN["token"])
3479
3480 @inlineCallbacks
3481 def test_device_is_local(self):
3482@@ -388,12 +280,12 @@
3483 result = yield self.be.remove_device(device_id)
3484 self.assertEqual(result, device_id)
3485 # credentials were not cleared
3486- self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)
3487+ self.assertEqual(MockDBusClient.creds, TOKEN)
3488
3489 @inlineCallbacks
3490 def test_remove_device_clear_credentials_if_local_device(self):
3491 """The remove_device method clears the credentials if is local."""
3492- apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])
3493+ apiurl = DEVICE_REMOVE_API % ('computer', TOKEN['token'])
3494 # pylint: disable=E1101
3495 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
3496 yield self.be.remove_device(self.local_token)
3497@@ -487,10 +379,10 @@
3498 """The volume settings can be changed."""
3499 fid = '0123-4567'
3500
3501- yield self.be.change_volume_settings(fid, {'subscribed': True})
3502+ yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
3503 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
3504
3505- yield self.be.change_volume_settings(fid, {'subscribed': False})
3506+ yield self.be.change_volume_settings(fid, {'subscribed': ''})
3507 self.assertEqual(MockDBusClient.subscribed_folders, [])
3508
3509 @inlineCallbacks
3510@@ -653,3 +545,82 @@
3511 # Access to a protected member _process_file_sync_status
3512 expected_status = self.be._process_file_sync_status(status)
3513 self.assertEqual(self._called, ((expected_status,), {}))
3514+
3515+
3516+class BackendSyncEnabledTestCase(BackendBasicTestCase):
3517+ """Syncdaemon enable/disable for the backend."""
3518+
3519+ def test_enable_files(self):
3520+ """Files service is enabled."""
3521+ self.be.disable_files()
3522+
3523+ self.be.enable_files()
3524+ self.assertTrue(MockDBusClient.file_sync)
3525+
3526+ def test_disable_files(self):
3527+ """Files service is disabled."""
3528+ self.be.enable_files()
3529+
3530+ self.be.disable_files()
3531+ self.assertFalse(MockDBusClient.file_sync)
3532+
3533+
3534+class BackendReplicationsTestCase(BackendBasicTestCase):
3535+ """Replications tests for the backend."""
3536+
3537+ @inlineCallbacks
3538+ def test_replications_info(self):
3539+ """The replications_info method exercises its callback."""
3540+ result = yield self.be.replications_info()
3541+
3542+ # replications_info will use exclusions information
3543+ expected = []
3544+ for name in MockReplicationClient.replications:
3545+ enabled = bool_str(name not in MockReplicationClient.exclusions)
3546+ dependency = ''
3547+ if name == MockReplicationClient.BOOKMARKS:
3548+ dependency = backend.BOOKMARKS_PKG
3549+ elif name == MockReplicationClient.CONTACTS:
3550+ dependency = backend.CONTACTS_PKG
3551+
3552+ item = {'replication_id': name, 'name': name,
3553+ 'enabled': enabled, 'dependency': dependency}
3554+ expected.append(item)
3555+ self.assertEqual(sorted(expected), sorted(result))
3556+
3557+ @inlineCallbacks
3558+ def test_change_replication_settings(self):
3559+ """The replication settings can be changed."""
3560+ rid = '0123-4567'
3561+ MockReplicationClient.replications.add(rid)
3562+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
3563+
3564+ yield self.be.change_replication_settings(rid, {'enabled': ''})
3565+ self.assertIn(rid, MockReplicationClient.exclusions)
3566+
3567+ yield self.be.change_replication_settings(rid, {'enabled': 'True'})
3568+ self.assertNotIn(rid, MockReplicationClient.exclusions)
3569+
3570+ @inlineCallbacks
3571+ def test_change_replication_settings_not_in_replications(self):
3572+ """The settings can not be changed for an item not in replications."""
3573+ rid = '0123-4567'
3574+ assert rid not in MockReplicationClient.replications
3575+
3576+ d = self.be.change_replication_settings(rid, {'enabled': 'True'})
3577+ yield self.assertFailure(d, replication_client.ReplicationError)
3578+
3579+ d = self.be.change_replication_settings(rid, {'enabled': ''})
3580+ yield self.assertFailure(d, replication_client.ReplicationError)
3581+
3582+ @inlineCallbacks
3583+ def test_change_replication_settings_no_setting(self):
3584+ """The change replication settings does not fail on empty settings."""
3585+ rid = '0123-4567'
3586+ MockReplicationClient.replications.add(rid)
3587+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
3588+
3589+ prior = MockReplicationClient.exclusions.copy()
3590+ yield self.be.change_replication_settings(rid, {})
3591+
3592+ self.assertEqual(MockReplicationClient.exclusions, prior)
3593
3594=== added file 'ubuntuone/controlpanel/tests/test_replication_client.py'
3595--- ubuntuone/controlpanel/tests/test_replication_client.py 1970-01-01 00:00:00 +0000
3596+++ ubuntuone/controlpanel/tests/test_replication_client.py 2011-01-10 02:57:12 +0000
3597@@ -0,0 +1,160 @@
3598+# -*- coding: utf-8 -*-
3599+
3600+# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
3601+#
3602+# Copyright 2010 Canonical Ltd.
3603+#
3604+# This program is free software: you can redistribute it and/or modify it
3605+# under the terms of the GNU General Public License version 3, as published
3606+# by the Free Software Foundation.
3607+#
3608+# This program is distributed in the hope that it will be useful, but
3609+# WITHOUT ANY WARRANTY; without even the implied warranties of
3610+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3611+# PURPOSE. See the GNU General Public License for more details.
3612+#
3613+# You should have received a copy of the GNU General Public License along
3614+# with this program. If not, see <http://www.gnu.org/licenses/>.
3615+
3616+"""Tests for the DBus service when accessing desktopcouch replications."""
3617+
3618+from twisted.internet.defer import inlineCallbacks
3619+
3620+from ubuntuone.controlpanel import replication_client
3621+from ubuntuone.controlpanel.tests import TestCase
3622+
3623+EXCLUSIONS = set()
3624+
3625+
3626+class FakedReplication(object):
3627+ """Faked a DC replication exclusion."""
3628+
3629+ def __init__(self):
3630+ self.all_exclusions = lambda: EXCLUSIONS
3631+ self.replicate = EXCLUSIONS.remove
3632+ self.exclude = EXCLUSIONS.add
3633+
3634+
3635+class FakedReplicationModule(object):
3636+ """Faked a DC replication module."""
3637+
3638+ ReplicationExclusion = FakedReplication
3639+
3640+
3641+class ReplicationsTestCase(TestCase):
3642+ """Test for the replications client methods."""
3643+
3644+ def setUp(self):
3645+ super(ReplicationsTestCase, self).setUp()
3646+
3647+ orig_get_proxy = replication_client.get_replication_proxy
3648+
3649+ def get_proxy():
3650+ """Fake the proxy getter."""
3651+ return orig_get_proxy(replication_module=FakedReplicationModule())
3652+
3653+ self.patch(replication_client, 'get_replication_proxy', get_proxy)
3654+
3655+ def tearDown(self):
3656+ EXCLUSIONS.clear()
3657+ super(ReplicationsTestCase, self).tearDown()
3658+
3659+ @inlineCallbacks
3660+ def test_no_pairing_record(self):
3661+ """Handle ValueError from replication layer."""
3662+
3663+ def no_pairing_record(*args, **kwargs):
3664+ """Fail with ValueError."""
3665+ raise ValueError('No pairing record.')
3666+
3667+ self.patch(FakedReplicationModule, 'ReplicationExclusion',
3668+ no_pairing_record)
3669+
3670+ yield self.assertFailure(replication_client.get_replications(),
3671+ replication_client.NoPairingRecord)
3672+
3673+ @inlineCallbacks
3674+ def test_get_replications(self):
3675+ """Replications are correctly retrieved."""
3676+ result = yield replication_client.get_replications()
3677+ self.assertEqual(result, replication_client.REPLICATIONS)
3678+
3679+ @inlineCallbacks
3680+ def test_get_exclusions(self):
3681+ """Exclusions are correctly retrieved."""
3682+ replications = yield replication_client.get_replications()
3683+ for rep in replications:
3684+ yield replication_client.exclude(rep)
3685+
3686+ result = yield replication_client.get_exclusions()
3687+ self.assertEqual(result, replications)
3688+
3689+ @inlineCallbacks
3690+ def test_replicate(self):
3691+ """Replicate a service is correct."""
3692+ replications = yield replication_client.get_replications()
3693+ rid = list(replications)[0]
3694+ yield replication_client.exclude(rid)
3695+
3696+ yield replication_client.replicate(rid)
3697+ exclusions = yield replication_client.get_exclusions()
3698+ self.assertNotIn(rid, exclusions)
3699+
3700+ @inlineCallbacks
3701+ def test_replicate_name_not_in_replications(self):
3702+ """Replicate a service fails if not in replications."""
3703+ replications = yield replication_client.get_replications()
3704+ rid = 'not in replications'
3705+ assert rid not in replications
3706+
3707+ yield self.assertFailure(replication_client.replicate(rid),
3708+ replication_client.InvalidIdError)
3709+
3710+ @inlineCallbacks
3711+ def test_replicate_name_not_in_exclusions(self):
3712+ """Replicate a service fails if not in exclusions."""
3713+ replications = yield replication_client.get_replications()
3714+ rid = list(replications)[0]
3715+ assert rid in replications
3716+
3717+ exclusions = yield replication_client.get_exclusions()
3718+ assert rid not in exclusions
3719+
3720+ yield self.assertFailure(replication_client.replicate(rid),
3721+ replication_client.NotExcludedError)
3722+
3723+ @inlineCallbacks
3724+ def test_exclude(self):
3725+ """Excluding a service is correct."""
3726+ replications = yield replication_client.get_replications()
3727+ rid = list(replications)[0]
3728+ yield replication_client.exclude(rid)
3729+ yield replication_client.replicate(rid)
3730+
3731+ yield replication_client.exclude(rid)
3732+ exclusions = yield replication_client.get_exclusions()
3733+ self.assertIn(rid, exclusions)
3734+
3735+ @inlineCallbacks
3736+ def test_exclude_name_not_in_replications(self):
3737+ """Excluding a service fails if not in replications."""
3738+ replications = yield replication_client.get_replications()
3739+ rid = 'not in replications'
3740+ assert rid not in replications
3741+
3742+ yield self.assertFailure(replication_client.exclude(rid),
3743+ replication_client.InvalidIdError)
3744+
3745+ @inlineCallbacks
3746+ def test_exclude_name_in_exclusions(self):
3747+ """Excluding a service fails if already on exclusions."""
3748+ replications = yield replication_client.get_replications()
3749+ rid = list(replications)[0]
3750+ assert rid in replications
3751+
3752+ yield replication_client.exclude(rid)
3753+ exclusions = yield replication_client.get_exclusions()
3754+ assert rid in exclusions
3755+
3756+ yield self.assertFailure(replication_client.exclude(rid),
3757+ replication_client.AlreadyExcludedError)

Subscribers

People subscribed via source and target branches