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

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 5
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.1.0
Merge into: lp:ubuntu/natty/ubuntuone-control-panel
Diff against target: 4864 lines (+2941/-512)
29 files modified
PKG-INFO (+1/-1)
bin/ubuntuone-control-panel-backend (+2/-0)
bin/ubuntuone-control-panel-gtk (+4/-3)
data/account.ui (+92/-51)
data/controlpanel.ui (+0/-17)
data/device.ui (+203/-0)
data/devices.ui (+31/-1)
data/folders.ui (+26/-1)
data/management.ui (+16/-1)
debian/changelog (+60/-0)
debian/control (+4/-4)
po/POTFILES.in (+0/-1)
setup.py (+1/-1)
ubuntuone/controlpanel/__init__.py (+1/-1)
ubuntuone/controlpanel/backend.py (+176/-22)
ubuntuone/controlpanel/dbus_client.py (+144/-18)
ubuntuone/controlpanel/dbus_service.py (+117/-33)
ubuntuone/controlpanel/gtk/gui.py (+437/-77)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+771/-65)
ubuntuone/controlpanel/gtk/tests/test_widgets.py (+6/-13)
ubuntuone/controlpanel/gtk/widgets.py (+15/-8)
ubuntuone/controlpanel/integrationtests/__init__.py (+5/-0)
ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py (+210/-41)
ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py (+128/-80)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+102/-28)
ubuntuone/controlpanel/logger.py (+24/-1)
ubuntuone/controlpanel/tests/test_backend.py (+358/-19)
ubuntuone/controlpanel/utils.py (+0/-20)
ubuntuone/controlpanel/webclient.py (+7/-5)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.1.0
Reviewer Review Type Date Requested Status
Michael Terry Approve
Review via email: mp+44468@code.launchpad.net

Description of the change

 * debian/control
    - depends on ubutuone-client >= 1.5.1
    - depends on ubuntu-sso-client >= 1.1.7

  * New upstream release.
    [ Natalia B. Bidart <email address hidden> ]
      * Updated list of messages to be shown on the overview panel (LP:
      #690379).
      * Implemented callback for failure when loading devices.
      * Added a spinner on every UbuntuOneBin to be shown while loading the
      panel content.
      * Devices can now be removed (LP: #691295).
      * Migrated dbus_client to use non-deprectaed SSO D-Bus API.
      * Added clear_credentials to dbus_client module.
      * Implemented devices tab (LP: #690649).
      * Maximun size is set using geometry hints (LP: #683164).
      * Added logging to dbus_service and backend.
      * Error signals now sent the object id when available (LP: #691292).
      * After machine was added, Folders page is shown (LP: #674459).*
      VolumesInfoError signal is now handled (LP: #690292).
      * VolumesInfoError signal is now handled (LP: #690292).
      * When FileSyncStatusError is received, no more DbusException messages
      are leaked to the end user (LP: #690305).
      * User can now subcribe and unsubscribe from folders (LP: #689646).
      * Dbus booleans are now those strings whose bool() defines its value. So,
      '' for False and any other non empty string for True (LP: #683619).
      * File sync status is retrieved and displayed in the right top corner
      (LP: #673670).
      * Adding handling for file sync disabled (only backend for now).
      * Management panel is not twined itself if CredentialsFound signal is
     received several times (LP: #683649).
    [ Rodney Dawes <email address hidden> ]
      * Default to None and initialize if None in code, instead of mutable
      defaults.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

Pushed, with slight debian/changelog typo corrections.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'PKG-INFO'
--- PKG-INFO 2010-12-06 12:27:11 +0000
+++ PKG-INFO 2010-12-22 14:37:52 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.11Metadata-Version: 1.1
2Name: ubuntuone-control-panel2Name: ubuntuone-control-panel
3Version: 0.0.93Version: 0.1.0
4Summary: Ubuntu One Control Panel4Summary: Ubuntu One Control Panel
5Home-page: https://launchpad.net/ubuntuone-control-panel5Home-page: https://launchpad.net/ubuntuone-control-panel
6Author: Natalia Bidart6Author: Natalia Bidart
77
=== modified file 'bin/ubuntuone-control-panel-backend'
--- bin/ubuntuone-control-panel-backend 2010-12-06 12:27:11 +0000
+++ bin/ubuntuone-control-panel-backend 2010-12-22 14:37:52 +0000
@@ -18,6 +18,8 @@
18# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
19"""Execute the DBus backend for the Ubuntu One control panel."""19"""Execute the DBus backend for the Ubuntu One control panel."""
2020
21# Invalid name "ubuntuone-control-panel-backend", pylint: disable=C0103
22
2123
22from ubuntuone.controlpanel import dbus_service24from ubuntuone.controlpanel import dbus_service
2325
2426
=== modified file 'bin/ubuntuone-control-panel-gtk'
--- bin/ubuntuone-control-panel-gtk 2010-12-06 12:27:11 +0000
+++ bin/ubuntuone-control-panel-gtk 2010-12-22 14:37:52 +0000
@@ -18,7 +18,7 @@
18# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
19"""Execute the graphical interface for the Ubuntu One control panel."""19"""Execute the graphical interface for the Ubuntu One control panel."""
2020
21import gtk21# Invalid name "ubuntuone-control-panel-gtk", pylint: disable=C0103
2222
23import dbus.mainloop.glib23import dbus.mainloop.glib
2424
@@ -26,6 +26,7 @@
2626
27dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)27dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
2828
29
29if __name__ == "__main__":30if __name__ == "__main__":
30 ui = ubuntuone.controlpanel.gtk.gui.ControlPanelWindow()31 gui = ubuntuone.controlpanel.gtk.gui.ControlPanelWindow()
31 ui.main()32 gui.main()
3233
=== modified file 'data/account.ui'
--- data/account.ui 2010-12-06 12:27:11 +0000
+++ data/account.ui 2010-12-22 14:37:52 +0000
@@ -9,26 +9,107 @@
9 <child>9 <child>
10 <object class="GtkHBox" id="hbox1">10 <object class="GtkHBox" id="hbox1">
11 <property name="visible">True</property>11 <property name="visible">True</property>
12 <property name="spacing">15</property>12 <property name="spacing">10</property>
13 <child>13 <child>
14 <object class="GtkVBox" id="vbox1">14 <object class="GtkVBox" id="account">
15 <property name="visible">True</property>15 <property name="visible">True</property>
16 <property name="spacing">10</property>16 <property name="spacing">10</property>
17 <child>17 <child>
18 <object class="GtkHBox" id="name_box">18 <object class="GtkVBox" id="data">
19 <property name="visible">True</property>19 <property name="visible">True</property>
20 <property name="spacing">5</property>20 <property name="spacing">10</property>
21 <child>21 <child>
22 <object class="GtkLabel" id="name_label">22 <object class="GtkVBox" id="vbox1">
23 <property name="visible">True</property>23 <property name="visible">True</property>
24 <property name="xalign">0</property>24 <child>
25 <property name="label">Name:</property>25 <object class="GtkLabel" id="label1">
26 <property name="visible">True</property>
27 <property name="xalign">0</property>
28 <property name="label" translatable="yes">&lt;b&gt;Name:&lt;/b&gt;</property>
29 <property name="use_markup">True</property>
30 </object>
31 <packing>
32 <property name="position">0</property>
33 </packing>
34 </child>
35 <child>
36 <object class="GtkLabel" id="name_label">
37 <property name="visible">True</property>
38 <property name="xalign">0</property>
39 <property name="xpad">12</property>
40 <property name="label">tester name</property>
41 <property name="use_markup">True</property>
42 </object>
43 <packing>
44 <property name="expand">False</property>
45 <property name="position">1</property>
46 </packing>
47 </child>
26 </object>48 </object>
27 <packing>49 <packing>
28 <property name="expand">False</property>
29 <property name="position">0</property>50 <property name="position">0</property>
30 </packing>51 </packing>
31 </child>52 </child>
53 <child>
54 <object class="GtkVBox" id="vbox2">
55 <property name="visible">True</property>
56 <child>
57 <object class="GtkLabel" id="label3">
58 <property name="visible">True</property>
59 <property name="xalign">0</property>
60 <property name="label" translatable="yes">&lt;b&gt;Account type:&lt;/b&gt;</property>
61 <property name="use_markup">True</property>
62 </object>
63 <packing>
64 <property name="position">0</property>
65 </packing>
66 </child>
67 <child>
68 <object class="GtkLabel" id="type_label">
69 <property name="visible">True</property>
70 <property name="xalign">0</property>
71 <property name="xpad">12</property>
72 <property name="label">22GB awesomeness</property>
73 </object>
74 <packing>
75 <property name="position">1</property>
76 </packing>
77 </child>
78 </object>
79 <packing>
80 <property name="position">1</property>
81 </packing>
82 </child>
83 <child>
84 <object class="GtkVBox" id="vbox3">
85 <property name="visible">True</property>
86 <child>
87 <object class="GtkLabel" id="label2">
88 <property name="visible">True</property>
89 <property name="xalign">0</property>
90 <property name="label" translatable="yes">&lt;b&gt;Email address:&lt;/b&gt;</property>
91 <property name="use_markup">True</property>
92 </object>
93 <packing>
94 <property name="position">0</property>
95 </packing>
96 </child>
97 <child>
98 <object class="GtkLabel" id="email_label">
99 <property name="visible">True</property>
100 <property name="xalign">0</property>
101 <property name="xpad">12</property>
102 <property name="label">a@example.com</property>
103 </object>
104 <packing>
105 <property name="position">1</property>
106 </packing>
107 </child>
108 </object>
109 <packing>
110 <property name="position">2</property>
111 </packing>
112 </child>
32 </object>113 </object>
33 <packing>114 <packing>
34 <property name="expand">False</property>115 <property name="expand">False</property>
@@ -36,48 +117,6 @@
36 </packing>117 </packing>
37 </child>118 </child>
38 <child>119 <child>
39 <object class="GtkHBox" id="type_box">
40 <property name="visible">True</property>
41 <property name="spacing">5</property>
42 <child>
43 <object class="GtkLabel" id="label2">
44 <property name="visible">True</property>
45 <property name="xalign">0</property>
46 <property name="label" translatable="yes">Account type:</property>
47 </object>
48 <packing>
49 <property name="expand">False</property>
50 <property name="position">0</property>
51 </packing>
52 </child>
53 </object>
54 <packing>
55 <property name="expand">False</property>
56 <property name="position">1</property>
57 </packing>
58 </child>
59 <child>
60 <object class="GtkHBox" id="email_box">
61 <property name="visible">True</property>
62 <property name="spacing">5</property>
63 <child>
64 <object class="GtkLabel" id="label3">
65 <property name="visible">True</property>
66 <property name="xalign">0</property>
67 <property name="label" translatable="yes">Email address:</property>
68 </object>
69 <packing>
70 <property name="expand">False</property>
71 <property name="position">0</property>
72 </packing>
73 </child>
74 </object>
75 <packing>
76 <property name="expand">False</property>
77 <property name="position">2</property>
78 </packing>
79 </child>
80 <child>
81 <object class="GtkHButtonBox" id="hbuttonbox1">120 <object class="GtkHButtonBox" id="hbuttonbox1">
82 <property name="layout_style">center</property>121 <property name="layout_style">center</property>
83 <child>122 <child>
@@ -97,7 +136,7 @@
97 <packing>136 <packing>
98 <property name="expand">False</property>137 <property name="expand">False</property>
99 <property name="pack_type">end</property>138 <property name="pack_type">end</property>
100 <property name="position">3</property>139 <property name="position">1</property>
101 </packing>140 </packing>
102 </child>141 </child>
103 </object>142 </object>
@@ -108,6 +147,7 @@
108 <child>147 <child>
109 <object class="GtkVButtonBox" id="vbuttonbox1">148 <object class="GtkVButtonBox" id="vbuttonbox1">
110 <property name="visible">True</property>149 <property name="visible">True</property>
150 <property name="spacing">10</property>
111 <property name="layout_style">center</property>151 <property name="layout_style">center</property>
112 <child>152 <child>
113 <object class="GtkLinkButton" id="linkbutton1">153 <object class="GtkLinkButton" id="linkbutton1">
@@ -144,6 +184,7 @@
144 </object>184 </object>
145 <packing>185 <packing>
146 <property name="expand">False</property>186 <property name="expand">False</property>
187 <property name="pack_type">end</property>
147 <property name="position">1</property>188 <property name="position">1</property>
148 </packing>189 </packing>
149 </child>190 </child>
150191
=== removed file 'data/controlpanel.ui'
--- data/controlpanel.ui 2010-12-06 12:27:11 +0000
+++ data/controlpanel.ui 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.16"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>
7 <child>
8 <placeholder/>
9 </child>
10 <child>
11 <placeholder/>
12 </child>
13 <child>
14 <placeholder/>
15 </child>
16 </object>
17</interface>
180
=== added file 'data/device.ui'
--- data/device.ui 1970-01-01 00:00:00 +0000
+++ data/device.ui 2010-12-22 14:37:52 +0000
@@ -0,0 +1,203 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.16"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkAdjustment" id="adjustment2">
6 <property name="upper">10000</property>
7 <property name="step_increment">1</property>
8 </object>
9 <object class="GtkAdjustment" id="adjustment1">
10 <property name="upper">10000</property>
11 <property name="step_increment">1</property>
12 </object>
13 <object class="GtkVBox" id="itself">
14 <property name="visible">True</property>
15 <property name="spacing">5</property>
16 <child>
17 <object class="GtkHBox" id="hbox1">
18 <property name="visible">True</property>
19 <property name="spacing">15</property>
20 <child>
21 <object class="GtkVBox" id="vbox1">
22 <property name="visible">True</property>
23 <property name="spacing">5</property>
24 <child>
25 <object class="GtkHBox" id="hbox2">
26 <property name="visible">True</property>
27 <child>
28 <object class="GtkImage" id="device_type">
29 <property name="visible">True</property>
30 <property name="icon_name">computer</property>
31 </object>
32 <packing>
33 <property name="expand">False</property>
34 <property name="position">0</property>
35 </packing>
36 </child>
37 <child>
38 <object class="GtkLabel" id="device_name">
39 <property name="visible">True</property>
40 <property name="xalign">0</property>
41 <property name="xpad">5</property>
42 <property name="label">My Laptop</property>
43 </object>
44 <packing>
45 <property name="position">1</property>
46 </packing>
47 </child>
48 <child>
49 <object class="GtkLabel" id="device_id">
50 <property name="label" translatable="yes">device id (hidden)</property>
51 </object>
52 <packing>
53 <property name="expand">False</property>
54 <property name="position">2</property>
55 </packing>
56 </child>
57 <child>
58 <object class="GtkLabel" id="date_added">
59 <property name="label">30.02.09</property>
60 </object>
61 <packing>
62 <property name="expand">False</property>
63 <property name="position">3</property>
64 </packing>
65 </child>
66 </object>
67 <packing>
68 <property name="position">0</property>
69 </packing>
70 </child>
71 <child>
72 <object class="GtkTable" id="throttling">
73 <property name="visible">True</property>
74 <property name="n_rows">3</property>
75 <property name="n_columns">2</property>
76 <property name="column_spacing">3</property>
77 <property name="row_spacing">3</property>
78 <child>
79 <object class="GtkCheckButton" id="limit_bandwidth">
80 <property name="label" translatable="yes">Limit bandwidth usage</property>
81 <property name="visible">True</property>
82 <property name="can_focus">True</property>
83 <property name="receives_default">False</property>
84 <property name="draw_indicator">True</property>
85 <signal name="toggled" handler="on_limit_bandwidth_toggled" swapped="no"/>
86 </object>
87 </child>
88 <child>
89 <object class="GtkLabel" id="max_upload_speed_label">
90 <property name="visible">True</property>
91 <property name="xalign">1</property>
92 <property name="xpad">5</property>
93 <property name="label" translatable="yes">Max upload speed (KiB/s)</property>
94 </object>
95 <packing>
96 <property name="top_attach">1</property>
97 <property name="bottom_attach">2</property>
98 </packing>
99 </child>
100 <child>
101 <object class="GtkLabel" id="max_download_speed_label">
102 <property name="visible">True</property>
103 <property name="xalign">1</property>
104 <property name="xpad">5</property>
105 <property name="label" translatable="yes">Max download speed (KiB/s)</property>
106 </object>
107 <packing>
108 <property name="top_attach">2</property>
109 <property name="bottom_attach">3</property>
110 </packing>
111 </child>
112 <child>
113 <object class="GtkSpinButton" id="max_upload_speed">
114 <property name="visible">True</property>
115 <property name="can_focus">True</property>
116 <property name="invisible_char">•</property>
117 <property name="activates_default">True</property>
118 <property name="adjustment">adjustment1</property>
119 <signal name="value-changed" handler="on_max_upload_speed_value_changed" swapped="no"/>
120 </object>
121 <packing>
122 <property name="left_attach">1</property>
123 <property name="right_attach">2</property>
124 <property name="top_attach">1</property>
125 <property name="bottom_attach">2</property>
126 <property name="x_options">GTK_FILL</property>
127 <property name="y_options">GTK_FILL</property>
128 </packing>
129 </child>
130 <child>
131 <object class="GtkSpinButton" id="max_download_speed">
132 <property name="visible">True</property>
133 <property name="can_focus">True</property>
134 <property name="invisible_char">•</property>
135 <property name="activates_default">True</property>
136 <property name="adjustment">adjustment2</property>
137 <signal name="value-changed" handler="on_max_download_speed_value_changed" swapped="no"/>
138 </object>
139 <packing>
140 <property name="left_attach">1</property>
141 <property name="right_attach">2</property>
142 <property name="top_attach">2</property>
143 <property name="bottom_attach">3</property>
144 </packing>
145 </child>
146 <child>
147 <placeholder/>
148 </child>
149 </object>
150 <packing>
151 <property name="expand">False</property>
152 <property name="position">1</property>
153 </packing>
154 </child>
155 </object>
156 <packing>
157 <property name="expand">False</property>
158 <property name="position">0</property>
159 </packing>
160 </child>
161 <child>
162 <object class="GtkVButtonBox" id="vbuttonbox1">
163 <property name="visible">True</property>
164 <property name="layout_style">start</property>
165 <child>
166 <object class="GtkButton" id="remove">
167 <property name="label">gtk-remove</property>
168 <property name="visible">True</property>
169 <property name="can_focus">True</property>
170 <property name="receives_default">True</property>
171 <property name="use_stock">True</property>
172 <signal name="activate" handler="on_remove_clicked" swapped="no"/>
173 <signal name="clicked" handler="on_remove_clicked" swapped="no"/>
174 </object>
175 <packing>
176 <property name="expand">False</property>
177 <property name="fill">False</property>
178 <property name="position">0</property>
179 </packing>
180 </child>
181 </object>
182 <packing>
183 <property name="expand">False</property>
184 <property name="pack_type">end</property>
185 <property name="position">1</property>
186 </packing>
187 </child>
188 </object>
189 <packing>
190 <property name="expand">False</property>
191 <property name="position">0</property>
192 </packing>
193 </child>
194 <child>
195 <object class="GtkLabel" id="warning_label">
196 <property name="visible">True</property>
197 </object>
198 <packing>
199 <property name="position">1</property>
200 </packing>
201 </child>
202 </object>
203</interface>
0204
=== modified file 'data/devices.ui'
--- data/devices.ui 2010-12-06 12:27:11 +0000
+++ data/devices.ui 2010-12-22 14:37:52 +0000
@@ -7,7 +7,37 @@
7 <property name="border_width">10</property>7 <property name="border_width">10</property>
8 <property name="spacing">10</property>8 <property name="spacing">10</property>
9 <child>9 <child>
10 <placeholder/>10 <object class="GtkScrolledWindow" id="scrolledwindow1">
11 <property name="visible">True</property>
12 <property name="can_focus">True</property>
13 <property name="hscrollbar_policy">automatic</property>
14 <property name="vscrollbar_policy">automatic</property>
15 <child>
16 <object class="GtkViewport" id="viewport1">
17 <property name="visible">True</property>
18 <property name="resize_mode">queue</property>
19 <property name="shadow_type">none</property>
20 <child>
21 <object class="GtkAlignment" id="alignment1">
22 <property name="visible">True</property>
23 <property name="xscale">0</property>
24 <property name="yscale">0</property>
25 <child>
26 <object class="GtkVBox" id="devices">
27 <property name="visible">True</property>
28 <child>
29 <placeholder/>
30 </child>
31 </object>
32 </child>
33 </object>
34 </child>
35 </object>
36 </child>
37 </object>
38 <packing>
39 <property name="position">0</property>
40 </packing>
11 </child>41 </child>
12 </object>42 </object>
13</interface>43</interface>
1444
=== modified file 'data/folders.ui'
--- data/folders.ui 2010-12-06 12:27:11 +0000
+++ data/folders.ui 2010-12-22 14:37:52 +0000
@@ -7,7 +7,32 @@
7 <property name="border_width">10</property>7 <property name="border_width">10</property>
8 <property name="spacing">10</property>8 <property name="spacing">10</property>
9 <child>9 <child>
10 <placeholder/>10 <object class="GtkScrolledWindow" id="scrolledwindow1">
11 <property name="visible">True</property>
12 <property name="can_focus">True</property>
13 <property name="hscrollbar_policy">automatic</property>
14 <property name="vscrollbar_policy">automatic</property>
15 <child>
16 <object class="GtkViewport" id="viewport1">
17 <property name="visible">True</property>
18 <property name="resize_mode">queue</property>
19 <property name="shadow_type">none</property>
20 <child>
21 <object class="GtkAlignment" id="folders">
22 <property name="visible">True</property>
23 <property name="xscale">0</property>
24 <property name="yscale">0</property>
25 <child>
26 <placeholder/>
27 </child>
28 </object>
29 </child>
30 </object>
31 </child>
32 </object>
33 <packing>
34 <property name="position">0</property>
35 </packing>
11 </child>36 </child>
12 </object>37 </object>
13</interface>38</interface>
1439
=== modified file 'data/management.ui'
--- data/management.ui 2010-12-06 12:27:11 +0000
+++ data/management.ui 2010-12-22 14:37:52 +0000
@@ -16,8 +16,23 @@
16 <property name="border_width">10</property>16 <property name="border_width">10</property>
17 <property name="spacing">10</property>17 <property name="spacing">10</property>
18 <child>18 <child>
19 <object class="GtkProgressBar" id="quota_progressbar">19 <object class="GtkHBox" id="quota_box">
20 <property name="visible">True</property>20 <property name="visible">True</property>
21 <child>
22 <object class="GtkAlignment" id="alignment1">
23 <property name="visible">True</property>
24 <property name="xscale">0</property>
25 <property name="yscale">0</property>
26 <child>
27 <object class="GtkProgressBar" id="quota_progressbar">
28 <property name="visible">True</property>
29 </object>
30 </child>
31 </object>
32 <packing>
33 <property name="position">0</property>
34 </packing>
35 </child>
21 </object>36 </object>
22 <packing>37 <packing>
23 <property name="expand">False</property>38 <property name="expand">False</property>
2439
=== modified file 'debian/changelog'
--- debian/changelog 2010-12-21 14:09:31 +0000
+++ debian/changelog 2010-12-22 14:37:52 +0000
@@ -1,3 +1,63 @@
1ubuntuone-control-panel (0.1.0-0ubuntu1) UNRELEASED; urgency=low
2
3 * debian/control
4 - depends on ubutuone-client >= 1.5.1
5 - depends on ubuntu-sso-client >= 1.1.7
6
7 * New upstream release.
8
9 [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
10
11 * Updated list of messages to be shown on the overview panel (LP:
12 #690379).
13
14 * Implemented callback for failure when loading devices.
15
16 * Added a spinner on every UbuntuOneBin to be shown while loading the
17 panel content.
18
19 * Devices can now be removed (LP: #691295).
20
21 * Migrated dbus_client to use non-deprectaed SSO D-Bus API.
22
23 * Added clear_credentials to dbus_client module.
24
25 * Implemented devices tab (LP: #690649).
26
27 * Maximun size is set using geometry hints (LP: #683164).
28
29 * Added logging to dbus_service and backend.
30
31 * Error signals now sent the object id when available (LP: #691292).
32
33 * After machine was added, Folders page is shown (LP: #674459).*
34 VolumesInfoError signal is now handled (LP: #690292).
35
36 * VolumesInfoError signal is now handled (LP: #690292).
37
38 * When FileSyncStatusError is received, no more DbusException messages
39 are leaked to the end user (LP: #690305).
40
41 * User can now subcribe and unsubscribe from folders (LP: #689646).
42
43 * Dbus booleans are now those strings whose bool() defines its value. So,
44 '' for False and any other non empty string for True (LP: #683619).
45
46 * File sync status is retrieved and displayed in the right top corner
47 (LP: #673670).
48
49  * Adding handling for file sync disabled (only backend for now).
50
51  * Management panel is not twined itself if CredentialsFound signal is
52 received several times (LP: #683649).
53
54 [ Rodney Dawes <rodney.dawes@canonical.com> ]
55
56 * Default to None and initialize if None in code, instead of mutable
57 defaults.
58
59 -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Wed, 22 Dec 2010 11:15:04 -0300
60
1ubuntuone-control-panel (0.0.9-0ubuntu3) natty; urgency=low61ubuntuone-control-panel (0.0.9-0ubuntu3) natty; urgency=low
262
3 * debian/control63 * debian/control
464
=== modified file 'debian/control'
--- debian/control 2010-12-21 14:09:31 +0000
+++ debian/control 2010-12-22 14:37:52 +0000
@@ -35,8 +35,8 @@
35 python-simplejson,35 python-simplejson,
36 python-twisted-core,36 python-twisted-core,
37 python-twisted-web,37 python-twisted-web,
38 python-ubuntuone-client (>= 1.5.0),38 python-ubuntuone-client (>= 1.5.1),
39 ubuntu-sso-client (>= 1.1.2),39 ubuntu-sso-client (>= 1.1.7),
40Description: Ubuntu One Control Panel Python Libraries40Description: Ubuntu One Control Panel Python Libraries
41 Ubuntu One Control Panel provides a Python library to manage an Ubuntu One41 Ubuntu One Control Panel provides a Python library to manage an Ubuntu One
42 account.42 account.
@@ -50,8 +50,8 @@
50 python-dbus,50 python-dbus,
51 python-gobject,51 python-gobject,
52 python-gtk2,52 python-gtk2,
53 python-ubuntuone-client (>= 1.5.0),53 python-ubuntuone-client (>= 1.5.1),
54 ubuntu-sso-client (>= 1.1.2),54 ubuntu-sso-client (>= 1.1.7),
55 ubuntuone-control-panel (= ${binary:Version}),55 ubuntuone-control-panel (= ${binary:Version}),
56Description: Ubuntu One Control Panel56Description: Ubuntu One Control Panel
57 GTK+ desktop application to manage a Ubuntu One account.57 GTK+ desktop application to manage a Ubuntu One account.
5858
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2010-12-06 12:27:11 +0000
+++ po/POTFILES.in 2010-12-22 14:37:52 +0000
@@ -1,7 +1,6 @@
1ubuntuone/controlpanel/gtk/gui.py1ubuntuone/controlpanel/gtk/gui.py
2[type: gettext/glade] data/account.ui2[type: gettext/glade] data/account.ui
3[type: gettext/glade] data/applications.ui3[type: gettext/glade] data/applications.ui
4[type: gettext/glade] data/controlpanel.ui
5[type: gettext/glade] data/devices.ui4[type: gettext/glade] data/devices.ui
6[type: gettext/glade] data/management.ui5[type: gettext/glade] data/management.ui
7[type: gettext/glade] data/overview.ui6[type: gettext/glade] data/overview.ui
87
=== modified file 'setup.py'
--- setup.py 2010-12-06 12:27:11 +0000
+++ setup.py 2010-12-22 14:37:52 +0000
@@ -75,7 +75,7 @@
7575
76DistUtilsExtra.auto.setup(76DistUtilsExtra.auto.setup(
77 name='ubuntuone-control-panel',77 name='ubuntuone-control-panel',
78 version='0.0.9',78 version='0.1.0',
79 license='GPL v3',79 license='GPL v3',
80 author='Natalia Bidart',80 author='Natalia Bidart',
81 author_email='natalia.bidart@canonical.com',81 author_email='natalia.bidart@canonical.com',
8282
=== modified file 'ubuntuone/controlpanel/__init__.py'
--- ubuntuone/controlpanel/__init__.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/__init__.py 2010-12-22 14:37:52 +0000
@@ -29,4 +29,4 @@
29DBUS_PREFERENCES_PATH = "/preferences"29DBUS_PREFERENCES_PATH = "/preferences"
30DBUS_PREFERENCES_IFACE = "com.ubuntuone.controlpanel.Preferences"30DBUS_PREFERENCES_IFACE = "com.ubuntuone.controlpanel.Preferences"
3131
32WEBSERVICE_BASE_URL = "https://one.ubuntu.com/api/"32WEBSERVICE_BASE_URL = u"https://one.ubuntu.com/api/"
3333
=== modified file 'ubuntuone/controlpanel/backend.py'
--- ubuntuone/controlpanel/backend.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/backend.py 2010-12-22 14:37:52 +0000
@@ -22,8 +22,12 @@
22from twisted.internet.defer import inlineCallbacks, returnValue22from twisted.internet.defer import inlineCallbacks, returnValue
2323
24from ubuntuone.controlpanel import dbus_client24from ubuntuone.controlpanel import dbus_client
25from ubuntuone.controlpanel.logger import setup_logging, log_call
25from ubuntuone.controlpanel.webclient import WebClient26from ubuntuone.controlpanel.webclient import WebClient
2627
28
29logger = setup_logging('backend')
30
27ACCOUNT_API = "account/"31ACCOUNT_API = "account/"
28QUOTA_API = "quota/"32QUOTA_API = "quota/"
29DEVICES_API = "1.0/devices/"33DEVICES_API = "1.0/devices/"
@@ -33,6 +37,22 @@
33UPLOAD_KEY = "max_upload_speed"37UPLOAD_KEY = "max_upload_speed"
34DOWNLOAD_KEY = "max_download_speed"38DOWNLOAD_KEY = "max_download_speed"
3539
40FILE_SYNC_DISABLED = 'file-sync-disabled'
41FILE_SYNC_DISCONNECTED = 'file-sync-disconnected'
42FILE_SYNC_ERROR = 'file-sync-error'
43FILE_SYNC_IDLE = 'file-sync-idle'
44FILE_SYNC_STARTING = 'file-sync-starting'
45FILE_SYNC_SYNCING = 'file-sync-syncing'
46FILE_SYNC_UNKNOWN = 'file-sync-unknown'
47
48MSG_KEY = 'message'
49STATUS_KEY = 'status'
50
51
52def bool_str(value):
53 """Return the string representation of a bool (dbus-compatible)."""
54 return 'True' if value else ''
55
3656
37class ControlBackend(object):57class ControlBackend(object):
38 """The control panel backend."""58 """The control panel backend."""
@@ -40,20 +60,101 @@
40 def __init__(self):60 def __init__(self):
41 """Initialize the webclient."""61 """Initialize the webclient."""
42 self.wc = WebClient(dbus_client.get_credentials)62 self.wc = WebClient(dbus_client.get_credentials)
63 self._status_changed_handler = None
64 self.status_changed_handler = lambda *a: None
65
66 def _process_file_sync_status(self, status):
67 """Process raw file sync status into custom format.
68
69 Return a dictionary with two members:
70 * STATUS_KEY: the current status of syncdaemon, can be one of:
71 FILE_SYNC_DISABLED, FILE_SYNC_STARTING, FILE_SYNC_DISCONNECTED,
72 FILE_SYNC_SYNCING, FILE_SYNC_IDLE, FILE_SYNC_ERROR,
73 FILE_SYNC_UNKNOWN
74 * MSG_KEY: a non translatable but human readable string of the status.
75
76 """
77 if not status:
78 return {MSG_KEY: '', STATUS_KEY: FILE_SYNC_DISABLED}
79
80 msg = '%s (%s)' % (status['description'], status['name'])
81 result = {MSG_KEY: msg}
82
83 # file synch is enabled
84 is_error = bool(status['is_error'])
85 is_synching = bool(status['is_connected'])
86 is_idle = bool(status['is_online']) and status['queues'] == 'IDLE'
87 is_disconnected = status['name'] == 'READY' and \
88 'Not User' in status['connection']
89 is_starting = status['name'] in ('INIT', 'LOCAL_RESCAN', 'READY')
90
91 if is_error:
92 result[STATUS_KEY] = FILE_SYNC_ERROR
93 elif is_idle:
94 result[STATUS_KEY] = FILE_SYNC_IDLE
95 elif is_synching:
96 result[STATUS_KEY] = FILE_SYNC_SYNCING
97 elif is_disconnected:
98 result[STATUS_KEY] = FILE_SYNC_DISCONNECTED
99 elif is_starting:
100 result[STATUS_KEY] = FILE_SYNC_STARTING
101 else:
102 logger.warning('file_sync_status: unknown (got %r)', status)
103 result[STATUS_KEY] = FILE_SYNC_UNKNOWN
104
105 return result
106
107 def _set_status_changed_handler(self, handler):
108 """Set 'handler' to be called when file sync status changes."""
109 logger.debug('setting status_changed_handler to %r', handler)
110
111 def process_and_callback(status):
112 """Process syncdaemon's status and callback 'handler'."""
113 result = self._process_file_sync_status(status)
114 handler(result)
115
116 self._status_changed_handler = process_and_callback
117 dbus_client.set_status_changed_handler(process_and_callback)
118
119 def _get_status_changed_handler(self, handler):
120 """Return the handler to be called when file sync status changes."""
121 return self._status_changed_handler
122
123 status_changed_handler = property(_get_status_changed_handler,
124 _set_status_changed_handler)
43125
44 @inlineCallbacks126 @inlineCallbacks
45 def get_token(self):127 def get_token(self):
46 """Return the token from the credentials"""128 """Return the token from the credentials."""
47 credentials = yield dbus_client.get_credentials()129 credentials = yield dbus_client.get_credentials()
48 returnValue(credentials["token"])130 returnValue(credentials["token"])
49131
50 @inlineCallbacks132 @inlineCallbacks
133 def device_is_local(self, device_id):
134 """Return whether 'device_id' is the local devicew or not."""
135 dtype, did = self.type_n_id(device_id)
136 local_token = yield self.get_token()
137 is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)
138 logger.info('device_is_local: result %r, ', is_local)
139 returnValue(is_local)
140
141 @log_call(logger.debug)
142 @inlineCallbacks
51 def account_info(self):143 def account_info(self):
52 """Get the user account info."""144 """Get the user account info."""
53 result = {}145 result = {}
54146
55 account_info = yield self.wc.call_api(ACCOUNT_API)147 account_info = yield self.wc.call_api(ACCOUNT_API)
56 result["type"] = account_info["subscription"]["description"]148
149 if "current_plan" in account_info:
150 desc = account_info["current_plan"]
151 elif "subscription" in account_info \
152 and "description" in account_info["subscription"]:
153 desc = account_info["subscription"]["description"]
154 else:
155 desc = ''
156
157 result["type"] = desc
57 result["name"] = account_info["nickname"]158 result["name"] = account_info["nickname"]
58 result["email"] = account_info["email"]159 result["email"] = account_info["email"]
59160
@@ -63,11 +164,11 @@
63164
64 returnValue(result)165 returnValue(result)
65166
167 @log_call(logger.debug)
66 @inlineCallbacks168 @inlineCallbacks
67 def devices_info(self):169 def devices_info(self):
68 """Get the user devices info."""170 """Get the user devices info."""
69 result = []171 result = []
70 this_token = yield self.get_token()
71 limit_bw = yield dbus_client.bandwidth_throttling_enabled()172 limit_bw = yield dbus_client.bandwidth_throttling_enabled()
72 limits = yield dbus_client.get_throttling_limits()173 limits = yield dbus_client.get_throttling_limits()
73174
@@ -77,19 +178,25 @@
77 result.append(di)178 result.append(di)
78 di["type"] = d["kind"]179 di["type"] = d["kind"]
79 di["name"] = d["description"]180 di["name"] = d["description"]
80 di["configurable"] = "0"181 di["configurable"] = ''
81 if di["type"] == DEVICE_TYPE_COMPUTER:182 if di["type"] == DEVICE_TYPE_COMPUTER:
82 di["device_id"] = di["type"] + d["token"]183 di["device_id"] = di["type"] + d["token"]
83 if d["token"] == this_token:
84 di["configurable"] = "1"
85 di["limit_bandwidth"] = "1" if limit_bw else "0"
86 upload = limits["upload"]
87 download = limits["download"]
88 di[UPLOAD_KEY] = str(upload)
89 di[DOWNLOAD_KEY] = str(download)
90 if di["type"] == DEVICE_TYPE_PHONE:184 if di["type"] == DEVICE_TYPE_PHONE:
91 di["device_id"] = di["type"] + str(d["id"])185 di["device_id"] = di["type"] + str(d["id"])
92186
187 is_local = yield self.device_is_local(di["device_id"])
188 di["is_local"] = bool_str(is_local)
189 # currently, only local devices are configurable.
190 # eventually, more the devices will be configurable.
191 di["configurable"] = bool_str(is_local)
192
193 if bool(di["configurable"]):
194 di["limit_bandwidth"] = bool_str(limit_bw)
195 upload = limits["upload"]
196 download = limits["download"]
197 di[UPLOAD_KEY] = str(upload)
198 di[DOWNLOAD_KEY] = str(download)
199
93 # date_added is not in the webservice yet (LP: #673668)200 # date_added is not in the webservice yet (LP: #673668)
94 # di["date_added"] = ""201 # di["date_added"] = ""
95202
@@ -107,18 +214,17 @@
107 return DEVICE_TYPE_PHONE, device_id[5:]214 return DEVICE_TYPE_PHONE, device_id[5:]
108 return "No device", device_id215 return "No device", device_id
109216
217 @log_call(logger.info)
110 @inlineCallbacks218 @inlineCallbacks
111 def change_device_settings(self, device_id, settings):219 def change_device_settings(self, device_id, settings):
112 """Change the settings for the given device."""220 """Change the settings for the given device."""
113 dtype, did = self.type_n_id(device_id)221 is_local = yield self.device_is_local(device_id)
114 local_token = yield self.get_token()
115 is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)
116222
117 if is_local and "limit_bandwidth" in settings:223 if is_local and "limit_bandwidth" in settings:
118 limit_bw = settings["limit_bandwidth"]224 limit_bw = bool(settings["limit_bandwidth"])
119 if limit_bw == "0":225 if not limit_bw:
120 yield dbus_client.disable_bandwidth_throttling()226 yield dbus_client.disable_bandwidth_throttling()
121 if limit_bw == "1":227 else:
122 yield dbus_client.enable_bandwidth_throttling()228 yield dbus_client.enable_bandwidth_throttling()
123229
124 if is_local and (UPLOAD_KEY in settings or230 if is_local and (UPLOAD_KEY in settings or
@@ -137,30 +243,78 @@
137 # still pending: more work on the settings dict (LP: #673674)243 # still pending: more work on the settings dict (LP: #673674)
138 returnValue(device_id)244 returnValue(device_id)
139245
246 @log_call(logger.warning)
140 @inlineCallbacks247 @inlineCallbacks
141 def remove_device(self, device_id):248 def remove_device(self, device_id):
142 """Remove a device's tokens from the sso server."""249 """Remove a device's tokens from the sso server."""
143 dtype, did = self.type_n_id(device_id)250 dtype, did = self.type_n_id(device_id)
144 api = DEVICE_REMOVE_API % (dtype, did)251 is_local = yield self.device_is_local(device_id)
252
253 api = DEVICE_REMOVE_API % (dtype.lower(), did)
145 yield self.wc.call_api(api)254 yield self.wc.call_api(api)
255
256 if is_local:
257 logger.warning('remove_device: device is local, id %r, '
258 'clearing credentials.', device_id)
259 yield dbus_client.clear_credentials()
260
146 returnValue(device_id)261 returnValue(device_id)
147262
263 @log_call(logger.debug)
264 @inlineCallbacks
148 def file_sync_status(self):265 def file_sync_status(self):
149 """Return the status of the file sync service."""266 """Return the status of the file sync service."""
150 # still pending (LP: #673670)267 enabled = yield dbus_client.files_sync_enabled()
151 returnValue((True, "Synchronizing"))268 if enabled:
269 status = yield dbus_client.get_current_status()
270 else:
271 status = {}
272 returnValue(self._process_file_sync_status(status))
152273
274 @log_call(logger.debug)
153 @inlineCallbacks275 @inlineCallbacks
154 def volumes_info(self):276 def volumes_info(self):
155 """Get the user defined volumes info."""277 """Get the volumes info."""
156 result = yield dbus_client.get_volumes()278 result = yield dbus_client.get_folders()
279 # later, we may request shares info as well
157 returnValue(result)280 returnValue(result)
158281
282 @log_call(logger.debug)
283 @inlineCallbacks
284 def change_volume_settings(self, volume_id, settings):
285 """Change settings for 'volume_id'.
286
287 Currently, only supported setting is boolean 'subscribed'.
288
289 """
290 if 'subscribed' in settings:
291 subscribed = settings['subscribed']
292 if subscribed:
293 yield self.subscribe_volume(volume_id)
294 else:
295 yield self.unsubscribe_volume(volume_id)
296
297 @inlineCallbacks
298 def subscribe_volume(self, volume_id):
299 """Subscribe to 'volume_id'."""
300 # when dealing with shares, we need to check if 'volume_id'
301 # is a folder or a share
302 yield dbus_client.subscribe_folder(volume_id)
303
304 @inlineCallbacks
305 def unsubscribe_volume(self, volume_id):
306 """Unsubscribe from 'volume_id'."""
307 # when dealing with shares, we need to check if 'volume_id'
308 # is a folder or a share
309 yield dbus_client.unsubscribe_folder(volume_id)
310
311 @log_call(logger.debug)
159 def query_bookmark_extension(self):312 def query_bookmark_extension(self):
160 """True if the bookmark extension has been installed."""313 """True if the bookmark extension has been installed."""
161 # still pending (LP: #673672)314 # still pending (LP: #673672)
162 returnValue(False)315 returnValue(False)
163316
317 @log_call(logger.debug)
164 def install_bookmarks_extension(self):318 def install_bookmarks_extension(self):
165 """Install the extension to sync bookmarks."""319 """Install the extension to sync bookmarks."""
166 # still pending (LP: #673673)320 # still pending (LP: #673673)
167321
=== modified file 'ubuntuone/controlpanel/dbus_client.py'
--- ubuntuone/controlpanel/dbus_client.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/dbus_client.py 2010-12-22 14:37:52 +0000
@@ -25,7 +25,8 @@
25from twisted.internet import defer25from twisted.internet import defer
2626
27from ubuntuone.clientdefs import APP_NAME27from ubuntuone.clientdefs import APP_NAME
28from ubuntuone.syncdaemon import dbus_interface as sd_dbus_iface28from ubuntuone.platform.linux import dbus_interface as sd_dbus_iface
29from ubuntuone.platform.linux.tools import SyncDaemonTool
2930
30from ubuntuone.controlpanel.logger import setup_logging31from ubuntuone.controlpanel.logger import setup_logging
3132
@@ -45,36 +46,88 @@
45 """Do nothing"""46 """Do nothing"""
4647
4748
49def get_sso_proxy():
50 """Get a DBus proxy for credentials management."""
51 bus = dbus.SessionBus()
52 obj = bus.get_object(bus_name=ubuntu_sso.DBUS_BUS_NAME,
53 object_path=ubuntu_sso.DBUS_CREDENTIALS_PATH,
54 follow_name_owner_changes=True)
55 proxy = dbus.Interface(object=obj,
56 dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE)
57 return proxy
58
59
48def get_credentials():60def get_credentials():
49 """Get the credentials from the ubuntu-sso-client."""61 """Get the credentials from the ubuntu-sso-client."""
50 d = defer.Deferred()62 d = defer.Deferred()
51 bus = dbus.SessionBus()63 proxy = get_sso_proxy()
52 obj = bus.get_object(bus_name=ubuntu_sso.DBUS_BUS_NAME,
53 object_path=ubuntu_sso.DBUS_CRED_PATH,
54 follow_name_owner_changes=True)
55 proxy = dbus.Interface(object=obj,
56 dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME)
5764
58 def found_credentials(app_name, creds):65 def found_credentials(app_name, creds):
59 """Credentials have been found."""66 """Credentials have been found."""
67 logger.debug('credentials were found for app_name %r.', app_name)
60 if app_name == APP_NAME:68 if app_name == APP_NAME:
69 logger.info('credentials were found! (%r).', APP_NAME)
61 d.callback(creds)70 d.callback(creds)
6271
63 def credentials_error(app_name, error_message, detailed_error):72 def credentials_error(app_name, error_dict=None):
64 """No credentials could be retrieved."""73 """No credentials could be retrieved."""
65 if app_name == APP_NAME:74 if app_name == APP_NAME:
66 error = CredentialsError(app_name, error_message, detailed_error)75 if error_dict is None:
76 error_dict = {'error_message': 'Credentials were not found.',
77 'detailed_error': ''}
78
79 logger.error('credentials error (%r, %r).', app_name, error_dict)
80 error = CredentialsError(app_name, error_dict)
67 d.errback(error)81 d.errback(error)
6882
69 foundsig = proxy.connect_to_signal("CredentialsFound", found_credentials)83 foundsig = proxy.connect_to_signal("CredentialsFound", found_credentials)
84 notfoundsig = proxy.connect_to_signal("CredentialsNotFound",
85 credentials_error)
70 errorsig = proxy.connect_to_signal("CredentialsError", credentials_error)86 errorsig = proxy.connect_to_signal("CredentialsError", credentials_error)
71 proxy.login_or_register_to_get_credentials(APP_NAME, "tcurl", "help", 0,87 logger.debug('calling get_credentials.')
72 reply_handler=no_op,88 proxy.find_credentials(APP_NAME, {'': ''},
73 error_handler=d.errback)89 reply_handler=no_op, error_handler=d.errback)
7490
75 def cleanup_signals(result):91 def cleanup_signals(result):
76 """Remove signals after use."""92 """Remove signals after use."""
77 foundsig.remove()93 foundsig.remove()
94 notfoundsig.remove()
95 errorsig.remove()
96 return result
97
98 d.addBoth(cleanup_signals)
99 return d
100
101
102def clear_credentials():
103 """Clear the credentials using the ubuntu-sso-client."""
104 d = defer.Deferred()
105 proxy = get_sso_proxy()
106
107 def credentials_cleared(app_name):
108 """Credentials have been cleared."""
109 logger.debug('credentials were cleared for app_name %r.', app_name)
110 if app_name == APP_NAME:
111 logger.info('credentials were cleared! (%r).', APP_NAME)
112 d.callback(app_name)
113
114 def credentials_error(app_name, error_dict=None):
115 """No credentials could be retrieved."""
116 if app_name == APP_NAME:
117 logger.error('credentials error (%r, %r).', app_name, error_dict)
118 error = CredentialsError(app_name, error_dict)
119 d.errback(error)
120
121 clearedsig = proxy.connect_to_signal("CredentialsCleared",
122 credentials_cleared)
123 errorsig = proxy.connect_to_signal("CredentialsError", credentials_error)
124 logger.warning('calling clear_credentials.')
125 proxy.clear_credentials(APP_NAME, {'': ''},
126 reply_handler=no_op, error_handler=d.errback)
127
128 def cleanup_signals(result):
129 """Remove signals after use."""
130 clearedsig.remove()
78 errorsig.remove()131 errorsig.remove()
79 return result132 return result
80133
@@ -106,6 +159,12 @@
106 sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME)159 sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME)
107160
108161
162def get_status_syncdaemon_proxy():
163 """Get a DBus proxy for syncdaemon status calls."""
164 return get_syncdaemon_proxy("/status",
165 sd_dbus_iface.DBUS_IFACE_STATUS_NAME)
166
167
109def get_throttling_limits():168def get_throttling_limits():
110 """Get the speed limits from the syncdaemon."""169 """Get the speed limits from the syncdaemon."""
111 d = defer.Deferred()170 d = defer.Deferred()
@@ -154,18 +213,16 @@
154 return d213 return d
155214
156215
157def get_volumes():216def get_folders():
158 """Retrieve the volumes information from syncdaemon."""217 """Retrieve the folders information from syncdaemon."""
159 # grab folders
160 d = defer.Deferred()218 d = defer.Deferred()
161 proxy = get_folders_syncdaemon_proxy()219 proxy = get_folders_syncdaemon_proxy()
162 proxy.get_folders(reply_handler=d.callback, error_handler=d.errback)220 proxy.get_folders(reply_handler=d.callback, error_handler=d.errback)
163 # grab shares?
164 return d221 return d
165222
166223
167def create_volume(path):224def create_folder(path):
168 """Create a new volume through syncdaemon."""225 """Create a new folder through syncdaemon."""
169 d = defer.Deferred()226 d = defer.Deferred()
170 proxy = get_folders_syncdaemon_proxy()227 proxy = get_folders_syncdaemon_proxy()
171228
@@ -183,3 +240,72 @@
183240
184 d.addBoth(cleanup_signals)241 d.addBoth(cleanup_signals)
185 return d242 return d
243
244
245def subscribe_folder(folder_id):
246 """Subscribe to 'folder_id'."""
247 d = defer.Deferred()
248 proxy = get_folders_syncdaemon_proxy()
249
250 sig = proxy.connect_to_signal('FolderSubscribed', d.callback)
251 cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))
252 esig = proxy.connect_to_signal('FolderSubscribeError', cb)
253
254 proxy.subscribe(folder_id, reply_handler=no_op, error_handler=no_op)
255
256 def cleanup_signals(result):
257 """Remove signals after use."""
258 sig.remove()
259 esig.remove()
260 return result
261
262 d.addBoth(cleanup_signals)
263 return d
264
265
266def unsubscribe_folder(folder_id):
267 """Unsubscribe 'folder_id'."""
268 d = defer.Deferred()
269 proxy = get_folders_syncdaemon_proxy()
270
271 sig = proxy.connect_to_signal('FolderUnSubscribed', d.callback)
272 cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))
273 esig = proxy.connect_to_signal('FolderUnSubscribeError', cb)
274
275 proxy.unsubscribe(folder_id, reply_handler=no_op, error_handler=no_op)
276
277 def cleanup_signals(result):
278 """Remove signals after use."""
279 sig.remove()
280 esig.remove()
281 return result
282
283 d.addBoth(cleanup_signals)
284 return d
285
286
287def get_current_status():
288 """Retrieve the current status from syncdaemon."""
289 d = defer.Deferred()
290 proxy = get_status_syncdaemon_proxy()
291 proxy.current_status(reply_handler=d.callback, error_handler=d.errback)
292 return d
293
294
295def set_status_changed_handler(handler):
296 """Connect 'handler' with syncdaemon's StatusChanged signal."""
297 proxy = get_status_syncdaemon_proxy()
298 sig = proxy.connect_to_signal('StatusChanged', handler)
299 return proxy, sig
300
301
302def files_sync_enabled():
303 """Get if file sync service is enabled."""
304 enabled = SyncDaemonTool(bus=None).is_files_sync_enabled()
305 return defer.succeed(enabled)
306
307
308@defer.inlineCallbacks
309def set_files_sync_enabled(enabled):
310 """Set the file sync service to be 'enabled'."""
311 yield SyncDaemonTool(bus=dbus.SessionBus()).enable_files_sync(enabled)
186312
=== modified file 'ubuntuone/controlpanel/dbus_service.py'
--- ubuntuone/controlpanel/dbus_service.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/dbus_service.py 2010-12-22 14:37:52 +0000
@@ -28,9 +28,15 @@
28from twisted.python.failure import Failure28from twisted.python.failure import Failure
2929
30from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,30from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
31 DBUS_PREFERENCES_IFACE, utils)31 DBUS_PREFERENCES_IFACE)
32from ubuntuone.controlpanel.backend import ControlBackend32from ubuntuone.controlpanel.backend import (
33from ubuntuone.controlpanel.logger import setup_logging33 ControlBackend, FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED,
34 FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_SYNCING,
35 MSG_KEY, STATUS_KEY,
36)
37from ubuntuone.controlpanel.logger import setup_logging, log_call
38from ubuntuone.controlpanel.utils import (ERROR_TYPE, ERROR_MESSAGE,
39 failure_to_error_dict, exception_to_error_dict)
3440
3541
36logger = setup_logging('dbus_service')42logger = setup_logging('dbus_service')
@@ -55,15 +61,15 @@
55 """61 """
56 result = {}62 result = {}
57 if isinstance(error, Failure):63 if isinstance(error, Failure):
58 result = utils.failure_to_error_dict(error)64 result = failure_to_error_dict(error)
59 elif isinstance(error, Exception):65 elif isinstance(error, Exception):
60 result = utils.exception_to_error_dict(error)66 result = exception_to_error_dict(error)
61 elif isinstance(error, dict):67 elif isinstance(error, dict):
62 # ensure that both keys and values are unicodes68 # ensure that both keys and values are unicodes
63 result = dict(map(make_unicode, i) for i in error.iteritems())69 result = dict(map(make_unicode, i) for i in error.iteritems())
64 else:70 else:
65 msg = 'Got unexpected error argument %r' % error71 msg = 'Got unexpected error argument %r' % error
66 result = {utils.ERROR_TYPE: 'UnknownError', utils.ERROR_MESSAGE: msg}72 result = {ERROR_TYPE: 'UnknownError', ERROR_MESSAGE: msg}
6773
68 return result74 return result
6975
@@ -74,10 +80,14 @@
74 With this call, a Failure is transformed into a string-string dict.80 With this call, a Failure is transformed into a string-string dict.
7581
76 """82 """
77 def inner(error, *a):83 def inner(error, _=None):
78 """Do the Failure transformation."""84 """Do the Failure transformation."""
79 error_dict = error_handler(error)85 error_dict = error_handler(error)
80 return f(error_dict)86 if _ is not None:
87 result = f(_, error_dict)
88 else:
89 result = f(error_dict)
90 return result
8191
82 return inner92 return inner
8393
@@ -89,10 +99,14 @@
89 """Create this instance of the backend."""99 """Create this instance of the backend."""
90 super(ControlPanelBackend, self).__init__(*args, **kwargs)100 super(ControlPanelBackend, self).__init__(*args, **kwargs)
91 self.backend = backend101 self.backend = backend
92 logger.debug('ControlPanelBackend created with %r, %r', args, kwargs)102 self.backend.status_changed_handler = self.process_status
103 logger.debug('ControlPanelBackend: created with %r, %r.\n'
104 'status_changed_handler is %r.',
105 args, kwargs, self.process_status)
93106
94 # pylint: disable=C0103107 # pylint: disable=C0103
95108
109 @log_call(logger.debug)
96 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")110 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
97 def account_info(self):111 def account_info(self):
98 """Find out the account info for the current logged in user."""112 """Find out the account info for the current logged in user."""
@@ -100,16 +114,19 @@
100 d.addCallback(self.AccountInfoReady)114 d.addCallback(self.AccountInfoReady)
101 d.addErrback(transform_failure(self.AccountInfoError))115 d.addErrback(transform_failure(self.AccountInfoError))
102116
117 @log_call(logger.debug)
103 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")118 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
104 def AccountInfoReady(self, info):119 def AccountInfoReady(self, info):
105 """The info for the current user is available right now."""120 """The info for the current user is available right now."""
106121
122 @log_call(logger.error)
107 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")123 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
108 def AccountInfoError(self, error):124 def AccountInfoError(self, error):
109 """The info for the current user is currently unavailable."""125 """The info for the current user is currently unavailable."""
110126
111 #---127 #---
112128
129 @log_call(logger.debug)
113 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")130 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
114 def devices_info(self):131 def devices_info(self):
115 """Find out the devices info for the logged in user."""132 """Find out the devices info for the logged in user."""
@@ -117,67 +134,122 @@
117 d.addCallback(self.DevicesInfoReady)134 d.addCallback(self.DevicesInfoReady)
118 d.addErrback(transform_failure(self.DevicesInfoError))135 d.addErrback(transform_failure(self.DevicesInfoError))
119136
137 @log_call(logger.debug)
120 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")138 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
121 def DevicesInfoReady(self, info):139 def DevicesInfoReady(self, info):
122 """The info for the devices is available right now."""140 """The info for the devices is available right now."""
123141
142 @log_call(logger.error)
124 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")143 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
125 def DevicesInfoError(self, error):144 def DevicesInfoError(self, error):
126 """The info for the devices is currently unavailable."""145 """The info for the devices is currently unavailable."""
127146
128 #---147 #---
129148
149 @log_call(logger.info)
130 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")150 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
131 def change_device_settings(self, token, settings):151 def change_device_settings(self, device_id, settings):
132 """Configure a given device."""152 """Configure a given device."""
133 d = self.backend.change_device_settings(token, settings)153 d = self.backend.change_device_settings(device_id, settings)
134 d.addCallback(self.DeviceSettingsChanged)154 d.addCallback(self.DeviceSettingsChanged)
135 d.addErrback(transform_failure(self.DeviceSettingsChangeError))155 d.addErrback(transform_failure(self.DeviceSettingsChangeError),
156 device_id)
136157
158 @log_call(logger.info)
137 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")159 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
138 def DeviceSettingsChanged(self, token):160 def DeviceSettingsChanged(self, device_id):
139 """The settings for the device were changed."""161 """The settings for the device were changed."""
140162
141 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")163 @log_call(logger.error)
142 def DeviceSettingsChangeError(self, error):164 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
165 def DeviceSettingsChangeError(self, device_id, error):
143 """Problem changing settings for the device."""166 """Problem changing settings for the device."""
144167
145 #---168 #---
146169
170 @log_call(logger.warning)
147 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")171 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")
148 def remove_device(self, token):172 def remove_device(self, device_id):
149 """Remove a given device."""173 """Remove a given device."""
150 d = self.backend.remove_device(token)174 d = self.backend.remove_device(device_id)
151 d.addCallback(self.DeviceRemoved)175 d.addCallback(self.DeviceRemoved)
152 d.addErrback(transform_failure(self.DeviceRemovalError))176 d.addErrback(transform_failure(self.DeviceRemovalError), device_id)
153177
178 @log_call(logger.warning)
154 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")179 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
155 def DeviceRemoved(self, token):180 def DeviceRemoved(self, device_id):
156 """The removal for the device was completed."""181 """The removal for the device was completed."""
157182
158 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")183 @log_call(logger.error)
159 def DeviceRemovalError(self, error):184 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
185 def DeviceRemovalError(self, device_id, error):
160 """Problem removing the device."""186 """Problem removing the device."""
161187
162 #---188 #---
163189
190 def process_status(self, status_dict):
191 """Match status with signals."""
192 logger.info('process_status: new status received %r', status_dict)
193 status = status_dict[STATUS_KEY]
194 msg = status_dict[MSG_KEY]
195 if status == FILE_SYNC_DISABLED:
196 self.FileSyncStatusDisabled(msg)
197 elif status == FILE_SYNC_STARTING:
198 self.FileSyncStatusStarting(msg)
199 elif status == FILE_SYNC_DISCONNECTED:
200 self.FileSyncStatusDisconnected(msg)
201 elif status == FILE_SYNC_SYNCING:
202 self.FileSyncStatusSyncing(msg)
203 elif status == FILE_SYNC_IDLE:
204 self.FileSyncStatusIdle(msg)
205 elif status == FILE_SYNC_ERROR:
206 error_dict = {ERROR_TYPE: 'FileSyncStatusError',
207 ERROR_MESSAGE: msg}
208 self.FileSyncStatusError(error_dict)
209 else:
210 self.FileSyncStatusError(error_handler(status_dict))
211
212 @log_call(logger.debug)
164 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")213 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
165 def file_sync_status(self):214 def file_sync_status(self):
166 """Get the status of the file sync service."""215 """Get the status of the file sync service."""
167 d = self.backend.file_sync_status()216 d = self.backend.file_sync_status()
168 d.addCallback(lambda args: self.FileSyncStatusReady(*args))217 d.addCallback(self.process_status)
169 d.addErrback(transform_failure(self.FileSyncStatusError))218 d.addErrback(transform_failure(self.FileSyncStatusError))
170219
171 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="bs")220 @log_call(logger.debug)
172 def FileSyncStatusReady(self, enabled, status):221 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
173 """The new file sync status is available."""222 def FileSyncStatusDisabled(self, msg):
174223 """The file sync status is disabled."""
224
225 @log_call(logger.debug)
226 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
227 def FileSyncStatusStarting(self, msg):
228 """The file sync service is starting."""
229
230 @log_call(logger.debug)
231 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
232 def FileSyncStatusDisconnected(self, msg):
233 """The file sync service is waiting for user to request connection."""
234
235 @log_call(logger.debug)
236 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
237 def FileSyncStatusSyncing(self, msg):
238 """The file sync service is currently syncing."""
239
240 @log_call(logger.debug)
241 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
242 def FileSyncStatusIdle(self, msg):
243 """The file sync service is idle."""
244
245 @log_call(logger.error)
175 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")246 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
176 def FileSyncStatusError(self, error):247 def FileSyncStatusError(self, error):
177 """Problem getting the file sync status."""248 """Problem getting the file sync status."""
178249
179 #---250 #---
180251
252 @log_call(logger.debug)
181 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")253 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
182 def volumes_info(self):254 def volumes_info(self):
183 """Find out the volumes info for the logged in user."""255 """Find out the volumes info for the logged in user."""
@@ -185,35 +257,40 @@
185 d.addCallback(self.VolumesInfoReady)257 d.addCallback(self.VolumesInfoReady)
186 d.addErrback(transform_failure(self.VolumesInfoError))258 d.addErrback(transform_failure(self.VolumesInfoError))
187259
188 @utils.log_call(logger.info)260 @log_call(logger.debug)
189 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")261 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
190 def VolumesInfoReady(self, info):262 def VolumesInfoReady(self, info):
191 """The info for the volumes is available right now."""263 """The info for the volumes is available right now."""
192264
193 @utils.log_call(logger.error)265 @log_call(logger.error)
194 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")266 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
195 def VolumesInfoError(self, error):267 def VolumesInfoError(self, error):
196 """The info for the volumes is currently unavailable."""268 """The info for the volumes is currently unavailable."""
197269
198 #---270 #---
199271
272 @log_call(logger.info)
200 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")273 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
201 def change_volume_settings(self, volume_id, settings):274 def change_volume_settings(self, volume_id, settings):
202 """Configure a given volume."""275 """Configure a given volume."""
203 d = self.backend.change_volume_settings(volume_id, settings)276 d = self.backend.change_volume_settings(volume_id, settings)
204 d.addCallback(self.VolumeSettingsChanged)277 d.addCallback(self.VolumeSettingsChanged)
205 d.addErrback(transform_failure(self.VolumeSettingsChangeError))278 d.addErrback(transform_failure(self.VolumeSettingsChangeError),
279 volume_id)
206280
281 @log_call(logger.info)
207 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")282 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
208 def VolumeSettingsChanged(self, token):283 def VolumeSettingsChanged(self, volume_id):
209 """The settings for the volume were changed."""284 """The settings for the volume were changed."""
210285
211 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")286 @log_call(logger.error)
212 def VolumeSettingsChangeError(self, error):287 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
288 def VolumeSettingsChangeError(self, volume_id, error):
213 """Problem changing settings for the volume."""289 """Problem changing settings for the volume."""
214290
215 #---291 #---
216292
293 @log_call(logger.debug)
217 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")294 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
218 def query_bookmark_extension(self):295 def query_bookmark_extension(self):
219 """Check if the extension to sync bookmarks is installed."""296 """Check if the extension to sync bookmarks is installed."""
@@ -221,16 +298,19 @@
221 d.addCallback(self.QueryBookmarksResult)298 d.addCallback(self.QueryBookmarksResult)
222 d.addErrback(transform_failure(self.QueryBookmarksError))299 d.addErrback(transform_failure(self.QueryBookmarksError))
223300
301 @log_call(logger.debug)
224 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b")302 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b")
225 def QueryBookmarksResult(self, enabled):303 def QueryBookmarksResult(self, enabled):
226 """The bookmark extension is or is not installed."""304 """The bookmark extension is or is not installed."""
227305
306 @log_call(logger.error)
228 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")307 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
229 def QueryBookmarksError(self, error):308 def QueryBookmarksError(self, error):
230 """Problem getting the status of the extension."""309 """Problem getting the status of the extension."""
231310
232 #---311 #---
233312
313 @log_call(logger.info)
234 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")314 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
235 def install_bookmarks_extension(self):315 def install_bookmarks_extension(self):
236 """Install the extension to sync bookmarks."""316 """Install the extension to sync bookmarks."""
@@ -238,10 +318,12 @@
238 d.addCallback(lambda _: self.InstallBookmarksSuccess())318 d.addCallback(lambda _: self.InstallBookmarksSuccess())
239 d.addErrback(transform_failure(self.InstallBookmarksError))319 d.addErrback(transform_failure(self.InstallBookmarksError))
240320
321 @log_call(logger.info)
241 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="")322 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="")
242 def InstallBookmarksSuccess(self):323 def InstallBookmarksSuccess(self):
243 """The extension to sync bookmarks has been installed."""324 """The extension to sync bookmarks has been installed."""
244325
326 @log_call(logger.error)
245 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")327 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
246 def InstallBookmarksError(self, error):328 def InstallBookmarksError(self, error):
247 """Problem installing the extension to sync bookmarks."""329 """Problem installing the extension to sync bookmarks."""
@@ -274,8 +356,10 @@
274 return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())356 return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())
275357
276358
277def publish_backend(backend=ControlBackend()):359def publish_backend(backend=None):
278 """Publish the backend on the DBus."""360 """Publish the backend on the DBus."""
361 if backend is None:
362 backend = ControlBackend()
279 return ControlPanelBackend(backend=backend,363 return ControlPanelBackend(backend=backend,
280 object_path=DBUS_PREFERENCES_PATH,364 object_path=DBUS_PREFERENCES_PATH,
281 bus_name=get_busname())365 bus_name=get_busname())
282366
=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
--- ubuntuone/controlpanel/gtk/gui.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/gtk/gui.py 2010-12-22 14:37:52 +0000
@@ -21,6 +21,7 @@
21from __future__ import division21from __future__ import division
2222
23import gettext23import gettext
24import operator
2425
25from functools import wraps26from functools import wraps
2627
@@ -46,8 +47,10 @@
4647
47from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,48from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
48 DBUS_PREFERENCES_IFACE)49 DBUS_PREFERENCES_IFACE)
49from ubuntuone.controlpanel.logger import setup_logging50from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
50from ubuntuone.controlpanel.utils import get_data_file, log_call51 DEVICE_TYPE_COMPUTER, bool_str)
52from ubuntuone.controlpanel.logger import setup_logging, log_call
53from ubuntuone.controlpanel.utils import get_data_file
5154
5255
53logger = setup_logging('gtk.gui')56logger = setup_logging('gtk.gui')
@@ -58,16 +61,22 @@
58ORANGE = '#c95724'61ORANGE = '#c95724'
59LOADING = _('Loading...')62LOADING = _('Loading...')
60OVERVIEW_MSGS = [63OVERVIEW_MSGS = [
61 _('<b>Ubuntu One</b> Music Store. '64 _('Backup and access your files from <b>any</b> computer (Ubuntu &amp; '
62 'A cloud enabled mobile shopping experience.\n'65 'Windows), mobile device (Android) or the web.'),
63 '<small>«this is custom text #1»</small>'),66 _('A <b>portable address book</b> that unifies your most important '
64 _('<i>Mobile</i> contacts sync. '67 'contact sources: mobile phones, social networks, email clients and '
65 '<span foreground="red">Your portable address book.</span>\n'68 'services.'),
66 '<small>«this is custom text #3»</small>'),69 _('A <b>personal music library</b> that can be streamed to Android, '
67 _('...'),70 'iPhone and AirPlay-enabled devices.'),
68 _('Sync data between computers.\n'71 _('Add to that music library with a <b>Music Store</b> that automatically '
69 '<small>«this is custom text #<i>N</i>»</small>')]72 'delivers to your personal cloud and connected devices.'),
73 _('Keep your <b>Firefox bookmarks</b> and <b>Tomboy notes</b> in sync '
74 'across computers (Ubuntu &amp; Windows).'),
75 _('<b>2GB</b> of free storage.'),
76]
77VALUE_ERROR = _('Value could not be retrieved.')
70WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE78WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
79KILOBYTES = 1024
7180
7281
73def filter_by_app_name(f):82def filter_by_app_name(f):
@@ -85,16 +94,9 @@
85 return filter_by_app_name_inner94 return filter_by_app_name_inner
8695
8796
88def dbus_str_to_bool(boolstr):
89 """Transform a dbus string representation of a boolean to True/False."""
90 return False if (not boolstr or boolstr == '0') else True
91
92
93class ControlPanelMixin(object):97class ControlPanelMixin(object):
94 """The main interface for the Ubuntu One control panel."""98 """The main interface for the Ubuntu One control panel."""
9599
96 VALUE_ERROR = _('Value could not be retrieved.')
97
98 def __init__(self, filename=None):100 def __init__(self, filename=None):
99 bus = dbus.SessionBus()101 bus = dbus.SessionBus()
100 try:102 try:
@@ -155,6 +157,8 @@
155 self.set_title(self.TITLE % {'app_name': U1_APP_NAME})157 self.set_title(self.TITLE % {'app_name': U1_APP_NAME})
156 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)158 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
157 self.set_icon_name('ubuntuone')159 self.set_icon_name('ubuntuone')
160 self.set_size_request(-1, 525) # bug #683164
161
158 self.connect('delete-event', lambda w, e: gtk.main_quit())162 self.connect('delete-event', lambda w, e: gtk.main_quit())
159 self.show()163 self.show()
160164
@@ -170,25 +174,49 @@
170 gtk.main()174 gtk.main()
171175
172176
173class ControlPanel(gtk.VBox, ControlPanelMixin):177class ControlPanel(gtk.Notebook):
174 """The control panel per se, can be added into any other widget."""178 """The control panel per se, can be added into any other widget."""
175179
176 # should not be any larger than 736x525180 # should not be any larger than 736x525
177181
178 def __init__(self, window_id=0):182 def __init__(self, window_id=0):
179 gtk.VBox.__init__(self)183 gtk.Notebook.__init__(self)
180 ControlPanelMixin.__init__(self, filename='controlpanel.ui')
181 self._window_id = window_id184 self._window_id = window_id
182185
183 self.overview = OverviewPanel(window_id=window_id)186 self.set_show_tabs(False)
187 self.set_show_border(False)
188
189 self.overview = OverviewPanel(window_id=self._window_id)
190 self.insert_page(self.overview, position=0)
191
192 self.management = ManagementPanel()
193 self.insert_page(self.management, position=1)
194
184 self.overview.connect('credentials-found',195 self.overview.connect('credentials-found',
185 self.on_show_management_panel)196 self.on_show_management_panel)
186 self.add(self.overview)197 self.management.connect('local-device-removed',
198 self.on_show_overview_panel)
199
187 self.show()200 self.show()
188201 self.on_show_overview_panel()
189 def on_show_management_panel(self, *args, **kwargs):202
190 """Show the netbook (main panel)."""203 logger.debug('%s: started (window size %r).',
191 self.add(ManagementPanel())204 self.__class__.__name__, self.get_size_request())
205
206 def on_show_overview_panel(self, widget=None):
207 """Show the overview panel."""
208 self.set_current_page(0)
209
210 def on_show_management_panel(self, widget=None,
211 credentials_are_new=False, token=None):
212 """Show the notebook (main panel)."""
213 if self.get_current_page() == 0:
214 self.management.load()
215 if credentials_are_new:
216 # redirect user to Folders page to review folders subscription
217 self.management.folders_button.clicked()
218
219 self.next_page()
192220
193221
194class UbuntuOneBin(gtk.VBox):222class UbuntuOneBin(gtk.VBox):
@@ -204,8 +232,26 @@
204 self.title = PanelTitle(markup='<b>' + title + '</b>')232 self.title = PanelTitle(markup='<b>' + title + '</b>')
205 self.pack_start(self.title, expand=False)233 self.pack_start(self.title, expand=False)
206234
235 self.message = LabelLoading(LOADING)
236 self.pack_start(self.message, expand=False)
237
207 self.show_all()238 self.show_all()
208239
240 @log_call(logger.debug)
241 def on_success(self, message=''):
242 """Use this callback to stop the Loading and show 'message'."""
243 self.message.stop()
244 self.message.set_markup(message)
245
246 @log_call(logger.error)
247 def on_error(self, message=None):
248 """Use this callback to stop the Loading and set a warning message."""
249 if message == None:
250 message = VALUE_ERROR
251
252 self.message.stop()
253 self.message.set_markup(WARNING_MARKUP % message)
254
209255
210class OverviewPanel(GreyableBin, ControlPanelMixin):256class OverviewPanel(GreyableBin, ControlPanelMixin):
211 """The overview panel. Introduces Ubuntu One to the not logged user."""257 """The overview panel. Introduces Ubuntu One to the not logged user."""
@@ -222,7 +268,6 @@
222 BULLET = '<span foreground="%s">✔</span>' % ORANGE268 BULLET = '<span foreground="%s">✔</span>' % ORANGE
223269
224 def __init__(self, messages=None, window_id=0):270 def __init__(self, messages=None, window_id=0):
225 gtk.link_button_set_uri_hook(lambda *a: None)
226 GreyableBin.__init__(self)271 GreyableBin.__init__(self)
227 ControlPanelMixin.__init__(self, filename='overview.ui')272 ControlPanelMixin.__init__(self, filename='overview.ui')
228 self.add(self.itself)273 self.add(self.itself)
@@ -230,6 +275,9 @@
230 self.warning_label.set_property('xalign', 0.5)275 self.warning_label.set_property('xalign', 0.5)
231 self.warning_label.connect('size-allocate', self.on_size_allocate)276 self.warning_label.connect('size-allocate', self.on_size_allocate)
232277
278 self.connect_button.set_uri('')
279
280 self._credentials_are_new = False
233 self._messages = messages281 self._messages = messages
234 self._window_id = window_id282 self._window_id = window_id
235 self._build_messages()283 self._build_messages()
@@ -320,13 +368,14 @@
320 @log_call(logger.info)368 @log_call(logger.info)
321 def on_credentials_found(self, app_name, credentials):369 def on_credentials_found(self, app_name, credentials):
322 """SSO backend notifies of credentials found."""370 """SSO backend notifies of credentials found."""
323 self.emit('credentials-found', app_name, credentials)371 self.set_property('greyed', False)
324 self.hide()372 self.emit('credentials-found', self._credentials_are_new, credentials)
325373
326 @filter_by_app_name374 @filter_by_app_name
327 @log_call(logger.info)375 @log_call(logger.info)
328 def on_credentials_not_found(self, app_name):376 def on_credentials_not_found(self, app_name):
329 """SSO backend notifies of credentials not found."""377 """SSO backend notifies of credentials not found."""
378 self._credentials_are_new = True
330 self.set_property('greyed', False)379 self.set_property('greyed', False)
331380
332 @filter_by_app_name381 @filter_by_app_name
@@ -365,42 +414,36 @@
365 """The account panel. The user can manage the subscription."""414 """The account panel. The user can manage the subscription."""
366415
367 TITLE = _('Welcome to Ubuntu One!')416 TITLE = _('Welcome to Ubuntu One!')
417 NAME = _('Name')
418 TYPE = _('Account type')
419 EMAIL = _('Email address')
368420
369 def __init__(self):421 def __init__(self):
370 UbuntuOneBin.__init__(self)422 UbuntuOneBin.__init__(self)
371 ControlPanelMixin.__init__(self, filename='account.ui')423 ControlPanelMixin.__init__(self, filename='account.ui')
372 self.pack_start(self.itself)424 self.add(self.itself)
373 self.show()425 self.show()
374426
375 self.backend.connect_to_signal('AccountInfoReady',427 self.backend.connect_to_signal('AccountInfoReady',
376 self.on_account_info_ready)428 self.on_account_info_ready)
377 self.backend.connect_to_signal('AccountInfoError',429 self.backend.connect_to_signal('AccountInfoError',
378 self.on_account_info_error)430 self.on_account_info_error)
379431 self.account.hide()
380 self.name_label = LabelLoading(LOADING)
381 self.name_box.pack_start(self.name_label)
382
383 self.type_label = LabelLoading(LOADING)
384 self.type_box.pack_start(self.type_label)
385
386 self.email_label = LabelLoading(LOADING)
387 self.email_box.pack_start(self.email_label)
388432
389 @log_call(logger.debug)433 @log_call(logger.debug)
390 def on_account_info_ready(self, info):434 def on_account_info_ready(self, info):
391 """Backend notifies of account info."""435 """Backend notifies of account info."""
392 for i in ('name', 'type', 'email'):436 self.on_success()
437
438 for i in (u'name', u'type', u'email'):
393 label = getattr(self, '%s_label' % i)439 label = getattr(self, '%s_label' % i)
394 label.set_markup('<b>%s</b>' % info[i])440 label.set_markup('%s' % (info[i]))
395 label.stop()441 self.account.show()
396442
397 @log_call(logger.error)443 @log_call(logger.error)
398 def on_account_info_error(self, error_dict=None):444 def on_account_info_error(self, error_dict=None):
399 """Backend notifies of an error when fetching account info."""445 """Backend notifies of an error when fetching account info."""
400 for i in ('name', 'type', 'email'):446 self.on_error()
401 label = getattr(self, '%s_label' % i)
402 label.set_markup(WARNING_MARKUP % self.VALUE_ERROR)
403 label.stop()
404447
405448
406class FoldersPanel(UbuntuOneBin, ControlPanelMixin):449class FoldersPanel(UbuntuOneBin, ControlPanelMixin):
@@ -408,48 +451,241 @@
408451
409 TITLE = _('Listed below are the folders available on this machine. '452 TITLE = _('Listed below are the folders available on this machine. '
410 'Subscribed means the folder will receive and send updates.')453 'Subscribed means the folder will receive and send updates.')
454 NO_VOLUMES = _('No folders to show.')
411455
412 def __init__(self):456 def __init__(self):
413 UbuntuOneBin.__init__(self)457 UbuntuOneBin.__init__(self)
414 ControlPanelMixin.__init__(self, filename='folders.ui')458 ControlPanelMixin.__init__(self, filename='folders.ui')
415 self.pack_start(self.itself)459 self.add(self.itself)
416 self.show_all()460 self.show_all()
417461
418 self.header = (gtk.Label(), gtk.Label())
419 self.header[0].set_markup('<b>' + _('Local path') + '</b>')
420 self.header[1].set_markup('<b>' + _('Subscribed') + '</b>')
421
422 self.backend.connect_to_signal('VolumesInfoReady',462 self.backend.connect_to_signal('VolumesInfoReady',
423 self.on_volumes_info_ready)463 self.on_volumes_info_ready)
424 self.backend.connect_to_signal('VolumesInfoError',464 self.backend.connect_to_signal('VolumesInfoError',
425 self.on_volumes_info_error)465 self.on_volumes_info_error)
426 self.backend.volumes_info()466 self.volumes = None
467 self._subscribed = []
427468
428 @log_call(logger.debug)
429 def on_volumes_info_ready(self, info):469 def on_volumes_info_ready(self, info):
430 """Backend notifies of volumes info."""470 """Backend notifies of volumes info."""
431 folders = gtk.Table(rows=len(info) + 1, columns=2)471
432472 if self.volumes is not None:
433 folders.attach(self.header[0], 0, 1, 0, 1)473 self.folders.remove(self.volumes)
434 folders.attach(self.header[1], 1, 2, 0, 1, xoptions=0)474 self.volumes = None
435475
476 if not info:
477 self.on_success(self.NO_VOLUMES)
478 return
479 else:
480 self.on_success()
481
482 self.volumes = gtk.Table(rows=len(info) + 1, columns=2)
483
484 header = (gtk.Label(), gtk.Label())
485 header[0].set_markup('<b>' + _('Local path') + '</b>')
486 header[1].set_markup('<b>' + _('Subscribed') + '</b>')
487 self.volumes.attach(header[0], 0, 1, 0, 1)
488 self.volumes.attach(header[1], 1, 2, 0, 1, xoptions=0)
489 self.volumes.show_all()
490
491 info.sort(key=operator.itemgetter('suggested_path'))
436 for i, volume in enumerate(info):492 for i, volume in enumerate(info):
437 path = gtk.Label(volume['path'])493 path = gtk.Label(volume['suggested_path'])
438 path.set_property('xalign', 0)494 path.set_property('xalign', 0)
439 folders.attach(path, 0, 1, i + 1, i + 2)495 path.show()
440496 self.volumes.attach(path, 0, 1, i + 1, i + 2)
441 subscribed = gtk.CheckButton()497
442 subscribed.set_active(dbus_str_to_bool(volume['subscribed']))498 subscribed = gtk.CheckButton(volume['volume_id'])
443 folders.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0)499 subscribed.set_active(bool(volume['subscribed']))
444500 subscribed.show()
445 alig = gtk.Alignment(xalign=0.5)501 subscribed.get_child().hide()
446 alig.add(folders)502 subscribed.connect('clicked', self.on_subscribed_clicked)
447 alig.show_all()503 self._subscribed.append(subscribed)
448 self.itself.pack_start(alig, expand=False)504 self.volumes.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0)
505
506 self.folders.add(self.volumes)
449507
450 @log_call(logger.error)508 @log_call(logger.error)
451 def on_volumes_info_error(self, error_dict=None):509 def on_volumes_info_error(self, error_dict=None):
452 """Backend notifies of an error when fetching volumes info."""510 """Backend notifies of an error when fetching volumes info."""
511 self.on_error()
512
513 def on_subscribed_clicked(self, checkbutton):
514 """The user toggled 'checkbutton'."""
515 volume_id = checkbutton.get_label()
516 subscribed = bool_str(checkbutton.get_active())
517 self.backend.change_volume_settings(volume_id,
518 {'subscribed': subscribed})
519
520 def load(self):
521 """Load the volume list."""
522 self.backend.volumes_info()
523 self.message.start()
524
525
526class Device(gtk.VBox, ControlPanelMixin):
527 """The device widget."""
528
529 DEVICE_CHANGE_ERROR = _('The settings could not be changed,\n'
530 'previous values were restored.')
531 DEVICE_REMOVAL_ERROR = _('The device could not be removed.')
532
533 def __init__(self):
534 gtk.VBox.__init__(self)
535 ControlPanelMixin.__init__(self, filename='device.ui')
536
537 self._updating = False
538 self._last_settings = {}
539 self.id = None
540 self.is_local = False
541 self.configurable = False
542
543 self.update(device_id=None, device_name='',
544 is_local=False, configurable=False, limit_bandwidth=False,
545 max_upload_speed=0, max_download_speed=0)
546
547 self.add(self.itself)
548 self.show()
549
550 self.backend.connect_to_signal('DeviceSettingsChanged',
551 self.on_device_settings_changed)
552 self.backend.connect_to_signal('DeviceSettingsChangeError',
553 self.on_device_settings_change_error)
554 self.backend.connect_to_signal('DeviceRemoved',
555 self.on_device_removed)
556 self.backend.connect_to_signal('DeviceRemovalError',
557 self.on_device_removal_error)
558
559 def _change_device_settings(self, *args):
560 """Update backend settings for this device."""
561 if self._updating:
562 return
563
564 # Not disabling the GUI to avoid annyong twitchings
565 #self.set_sensitive(False)
566 self.warning_label.set_text('')
567 self.backend.change_device_settings(self.id, self.__dict__)
568
569 def _block_signals(f):
570 """Execute 'f' while having the _updating flag set."""
571
572 # pylint: disable=E0213,W0212,E1102
573
574 @wraps(f)
575 def inner(self, *args, **kwargs):
576 """Execute 'f' while having the _updating flag set."""
577 old = self._updating
578 self._updating = True
579
580 result = f(self, *args, **kwargs)
581
582 self._updating = old
583 return result
584
585 return inner
586
587 on_limit_bandwidth_toggled = _change_device_settings
588 on_max_upload_speed_value_changed = _change_device_settings
589 on_max_download_speed_value_changed = _change_device_settings
590
591 def on_remove_clicked(self, widget):
592 """Remove button was clicked or activated."""
593 self.backend.remove_device(self.id)
594 self.set_sensitive(False)
595
596 @_block_signals
597 def update(self, **kwargs):
598 """Update according to named parameters.
599
600 Possible settings are:
601 * device_id (string, not shown to the user)
602 * device_name (string)
603 * type (either DEVICE_TYPE_PHONE or DEVICE_TYPE_COMPUTER)
604 * is_local (True/False)
605 * configurable (True/False)
606 * if configurable, the following can be set:
607 * limit_bandwidth (True/False)
608 * max_upload_speed (bytes)
609 * max_download_speed (bytes)
610
611 """
612 if 'device_id' in kwargs:
613 self.id = kwargs['device_id']
614
615 if 'device_name' in kwargs:
616 self.device_name.set_markup('<b>%s</b>' % kwargs['device_name'])
617
618 if 'device_type' in kwargs:
619 dtype = kwargs['device_type']
620 if dtype in (DEVICE_TYPE_COMPUTER, DEVICE_TYPE_PHONE):
621 self.device_type.set_from_icon_name(dtype.lower(),
622 gtk.ICON_SIZE_BUTTON)
623
624 if 'is_local' in kwargs:
625 self.is_local = bool(kwargs['is_local'])
626
627 if 'configurable' in kwargs:
628 self.configurable = bool(kwargs['configurable'])
629 self.throttling.set_visible(self.configurable)
630
631 if 'limit_bandwidth' in kwargs:
632 self.limit_bandwidth.set_active(bool(kwargs['limit_bandwidth']))
633
634 for speed in ('max_upload_speed', 'max_download_speed'):
635 if speed in kwargs:
636 value = int(kwargs[speed]) // KILOBYTES
637 getattr(self, speed).set_value(value)
638
639 self._last_settings = self.__dict__
640
641 @property
642 def __dict__(self):
643 result = {
644 'device_id': self.id,
645 'device_name': self.device_name.get_text(),
646 'device_type': self.device_type.get_icon_name()[0].capitalize(),
647 'is_local': bool_str(self.is_local),
648 'configurable': bool_str(self.configurable),
649 'limit_bandwidth': bool_str(self.limit_bandwidth.get_active()),
650 'max_upload_speed': \
651 str(self.max_upload_speed.get_value_as_int() * KILOBYTES),
652 'max_download_speed': \
653 str(self.max_download_speed.get_value_as_int() * KILOBYTES),
654 }
655 return result
656
657 @log_call(logger.info)
658 def on_device_settings_changed(self, device_id):
659 """The change of this device settings succeded."""
660 if device_id != self.id:
661 return
662 self.set_sensitive(True)
663 self.warning_label.set_text('')
664 self._last_settings = self.__dict__
665
666 @log_call(logger.error)
667 def on_device_settings_change_error(self, device_id, error_dict=None):
668 """The change of this device settings failed."""
669 if device_id != self.id:
670 return
671 self.update(**self._last_settings)
672 self._set_warning(self.DEVICE_CHANGE_ERROR, self.warning_label)
673 self.set_sensitive(True)
674
675 @log_call(logger.warning)
676 def on_device_removed(self, device_id):
677 """The removal of this device succeded."""
678 if device_id != self.id:
679 return
680 self.hide()
681
682 @log_call(logger.error)
683 def on_device_removal_error(self, device_id, error_dict=None):
684 """The removal of this device failed."""
685 if device_id != self.id:
686 return
687 self._set_warning(self.DEVICE_REMOVAL_ERROR, self.warning_label)
688 self.set_sensitive(True)
453689
454690
455class DevicesPanel(UbuntuOneBin, ControlPanelMixin):691class DevicesPanel(UbuntuOneBin, ControlPanelMixin):
@@ -457,13 +693,62 @@
457693
458 TITLE = _('The devices connected with your personal cloud network are '694 TITLE = _('The devices connected with your personal cloud network are '
459 'listed below:')695 'listed below:')
696 NO_DEVICES = _('No devices to show.')
460697
461 def __init__(self):698 def __init__(self):
462 UbuntuOneBin.__init__(self)699 UbuntuOneBin.__init__(self)
463 ControlPanelMixin.__init__(self, filename='devices.ui')700 ControlPanelMixin.__init__(self, filename='devices.ui')
464 self.pack_start(self.itself)701 self.add(self.itself)
465 self.show()702 self.show()
466703
704 self._devices = {}
705
706 self.backend.connect_to_signal('DevicesInfoReady',
707 self.on_devices_info_ready)
708 self.backend.connect_to_signal('DevicesInfoError',
709 self.on_devices_info_error)
710 self.backend.connect_to_signal('DeviceRemoved',
711 self.on_device_removed)
712
713 def on_devices_info_ready(self, info):
714 """Backend notifies of devices info."""
715 for child in self.devices.get_children():
716 self.devices.remove(child)
717
718 if not info:
719 self.on_success(self.NO_DEVICES)
720 else:
721 self.on_success()
722
723 for device_info in info:
724 device = Device()
725 device_info['device_name'] = device_info.pop('name', '')
726 device_info['device_type'] = device_info.pop('type',
727 DEVICE_TYPE_COMPUTER)
728 device.update(**device_info)
729 self.devices.pack_start(device)
730 self._devices[device.id] = device
731
732 @log_call(logger.error)
733 def on_devices_info_error(self, error_dict=None):
734 """Backend notifies of an error when fetching volumes info."""
735 self.on_error()
736
737 @log_call(logger.warning)
738 def on_device_removed(self, device_id):
739 """The removal of a device succeded."""
740 if device_id in self._devices:
741 child = self._devices.pop(device_id)
742 self.devices.remove(child)
743
744 if child.is_local:
745 self.emit('local-device-removed')
746
747 def load(self):
748 """Load the device list."""
749 self.backend.devices_info()
750 self.message.start()
751
467752
468class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):753class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
469 """The applications panel."""754 """The applications panel."""
@@ -474,9 +759,12 @@
474 def __init__(self):759 def __init__(self):
475 UbuntuOneBin.__init__(self)760 UbuntuOneBin.__init__(self)
476 ControlPanelMixin.__init__(self, filename='applications.ui')761 ControlPanelMixin.__init__(self, filename='applications.ui')
477 self.pack_start(self.itself)762 self.add(self.itself)
478 self.show()763 self.show()
479764
765 self.message.stop()
766 self.message.set_text('Under construction')
767
480768
481class ManagementPanel(gtk.VBox, ControlPanelMixin):769class ManagementPanel(gtk.VBox, ControlPanelMixin):
482 """The management panel.770 """The management panel.
@@ -486,11 +774,21 @@
486 """774 """
487775
488 QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)')776 QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)')
777 FILE_SYNC_DISABLED = _('File synchronization service is not enabled.')
778 FILE_SYNC_STARTING = _('File synchronization service is starting,\n'
779 'please wait...')
780 FILE_SYNC_DISCONNECTED = _('File synchronization service is ready,\nplease'
781 ' connect it to access your personal cloud. ')
782 FILE_SYNC_SYNCING = _('File synchronization service is fully functional,\n'
783 'performing synchronization now...')
784 FILE_SYNC_IDLE = _('File synchronization service is idle,\n'
785 'all the files are synchronized.')
786 FILE_SYNC_ERROR = _('File synchronization status can not be retrieved.')
489787
490 def __init__(self):788 def __init__(self):
491 gtk.VBox.__init__(self)789 gtk.VBox.__init__(self)
492 ControlPanelMixin.__init__(self, filename='management.ui')790 ControlPanelMixin.__init__(self, filename='management.ui')
493 self.pack_start(self.itself)791 self.add(self.itself)
494 self.show()792 self.show()
495793
496 self.backend.connect_to_signal('AccountInfoReady',794 self.backend.connect_to_signal('AccountInfoReady',
@@ -498,10 +796,25 @@
498 self.backend.connect_to_signal('AccountInfoError',796 self.backend.connect_to_signal('AccountInfoError',
499 self.on_account_info_error)797 self.on_account_info_error)
500798
799 self.backend.connect_to_signal('FileSyncStatusDisabled',
800 self.on_file_sync_status_disabled)
801 self.backend.connect_to_signal('FileSyncStatusStarting',
802 self.on_file_sync_status_starting)
803 self.backend.connect_to_signal('FileSyncStatusDisconnected',
804 self.on_file_sync_status_disconnected)
805 self.backend.connect_to_signal('FileSyncStatusSyncing',
806 self.on_file_sync_status_syncing)
807 self.backend.connect_to_signal('FileSyncStatusIdle',
808 self.on_file_sync_status_idle)
809 self.backend.connect_to_signal('FileSyncStatusError',
810 self.on_file_sync_status_error)
811
501 self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))812 self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))
813
502 self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)814 self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
815 self.quota_box.pack_start(self.quota_label, expand=False)
816
503 self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)817 self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
504 self.status_box.pack_start(self.quota_label, expand=False)
505 self.status_box.pack_end(self.status_label, expand=False)818 self.status_box.pack_end(self.status_label, expand=False)
506819
507 self.account = AccountPanel()820 self.account = AccountPanel()
@@ -520,7 +833,10 @@
520 gtk.gdk.Color(DEFAULT_FG))833 gtk.gdk.Color(DEFAULT_FG))
521 self.notebook.insert_page(getattr(self, tab), position=page_num)834 self.notebook.insert_page(getattr(self, tab), position=page_num)
522835
523 self.backend.account_info()836 self.folders_button.connect('clicked', lambda b: self.folders.load())
837 self.devices_button.connect('clicked', lambda b: self.devices.load())
838 self.devices.connect('local-device-removed',
839 lambda widget: self.emit('local-device-removed'))
524840
525 def _update_quota(self, msg, data=None):841 def _update_quota(self, msg, data=None):
526 """Update the quota info."""842 """Update the quota info."""
@@ -532,6 +848,17 @@
532 fraction = data.get('percentage', 0.0) / 100848 fraction = data.get('percentage', 0.0) / 100
533 self.quota_progressbar.set_fraction(fraction)849 self.quota_progressbar.set_fraction(fraction)
534850
851 def _update_status(self, msg):
852 """Update the status info."""
853 self.status_label.set_markup(msg)
854 self.status_label.stop()
855
856 def load(self):
857 """Load the account info and file sync status list."""
858 self.backend.account_info()
859 self.backend.file_sync_status()
860 self.account_button.clicked()
861
535 @log_call(logger.debug)862 @log_call(logger.debug)
536 def on_account_info_ready(self, info):863 def on_account_info_ready(self, info):
537 """Backend notifies of account info."""864 """Backend notifies of account info."""
@@ -544,9 +871,42 @@
544 @log_call(logger.error)871 @log_call(logger.error)
545 def on_account_info_error(self, error_dict=None):872 def on_account_info_error(self, error_dict=None):
546 """Backend notifies of an error when fetching account info."""873 """Backend notifies of an error when fetching account info."""
547 self._update_quota(WARNING_MARKUP % self.VALUE_ERROR)874 self._update_quota(WARNING_MARKUP % VALUE_ERROR)
875
876 @log_call(logger.info)
877 def on_file_sync_status_disabled(self, msg):
878 """Backend notifies of file sync status being disabled."""
879 self._update_status(self.FILE_SYNC_DISABLED)
880
881 @log_call(logger.info)
882 def on_file_sync_status_starting(self, msg):
883 """Backend notifies of file sync status being starting."""
884 self._update_status(self.FILE_SYNC_STARTING)
885
886 @log_call(logger.info)
887 def on_file_sync_status_disconnected(self, msg):
888 """Backend notifies of file sync status being ready."""
889 self._update_status(self.FILE_SYNC_DISCONNECTED)
890
891 @log_call(logger.info)
892 def on_file_sync_status_syncing(self, msg):
893 """Backend notifies of file sync status being syncing."""
894 self._update_status(self.FILE_SYNC_SYNCING)
895
896 @log_call(logger.info)
897 def on_file_sync_status_idle(self, msg):
898 """Backend notifies of file sync status being idle."""
899 self._update_status(self.FILE_SYNC_IDLE)
900
901 @log_call(logger.error)
902 def on_file_sync_status_error(self, error_dict=None):
903 """Backend notifies of an error when fetching file sync status."""
904 self._update_status(WARNING_MARKUP % self.FILE_SYNC_ERROR)
548905
549906
550gobject.signal_new('credentials-found', OverviewPanel,907gobject.signal_new('credentials-found', OverviewPanel,
551 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,908 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
552 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT))909 (gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT))
910
911gobject.signal_new('local-device-removed', DevicesPanel,
912 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
553913
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-22 14:37:52 +0000
@@ -40,9 +40,28 @@
40 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}40 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
4141
42FAKE_VOLUMES_INFO = [42FAKE_VOLUMES_INFO = [
43 {'volume_id': '0', 'path': '~/foo', 'subscribed': '0'},43 {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},
44 {'volume_id': '1', 'path': '~/bar', 'subscribed': '1'},44 {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},
45 {'volume_id': '2', 'path': '~/baz', 'subscribed': '1'},45 {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
46]
47
48FAKE_DEVICE_INFO = {
49 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer',
50 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
51 'max_upload_speed': '1000', 'max_download_speed': '72548',
52}
53
54FAKE_DEVICES_INFO = [
55 {'device_id': '0', 'name': 'Foo', 'type': 'Computer',
56 'is_local': '', 'configurable': ''},
57 {'device_id': '1', 'name': 'Bar', 'type': 'Phone',
58 'is_local': '', 'configurable': ''},
59 {'device_id': '2', 'name': 'Z', 'type': 'Computer',
60 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '',
61 'max_upload_speed': '0', 'max_download_speed': '0'},
62 {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer',
63 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True',
64 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
46]65]
4766
4867
@@ -108,8 +127,11 @@
108 bus_name = gui.DBUS_BUS_NAME127 bus_name = gui.DBUS_BUS_NAME
109 object_path = gui.DBUS_PREFERENCES_PATH128 object_path = gui.DBUS_PREFERENCES_PATH
110 iface = gui.DBUS_PREFERENCES_IFACE129 iface = gui.DBUS_PREFERENCES_IFACE
111 exposed_methods = ['account_info', 'devices_info', 'volumes_info',130 exposed_methods = [
112 'file_sync_status']131 'account_info', 'devices_info', 'change_device_settings',
132 'volumes_info', 'change_volume_settings', 'file_sync_status',
133 'remove_device',
134 ]
113135
114136
115class FakedSessionBus(object):137class FakedSessionBus(object):
@@ -193,7 +215,6 @@
193 def assert_warning_correct(self, warning, text):215 def assert_warning_correct(self, warning, text):
194 """Check that 'warning' is visible, showing 'text'."""216 """Check that 'warning' is visible, showing 'text'."""
195 self.assertTrue(warning.get_visible(), 'Must be visible.')217 self.assertTrue(warning.get_visible(), 'Must be visible.')
196 self.assertEqual(warning.get_text(), text)
197 self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text)218 self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text)
198219
199 def assert_function_decorated(self, decorator, func):220 def assert_function_decorated(self, decorator, func):
@@ -268,29 +289,39 @@
268 self.assertEqual(self.ui.get_icon_name(), 'ubuntuone')289 self.assertEqual(self.ui.get_icon_name(), 'ubuntuone')
269290
270 def test_max_size(self):291 def test_max_size(self):
271 """Max size is not bigger than 966x576 (LP: #645526)."""292 """Max size is not bigger than 736x525 (LP: #645526, LP: #683164)."""
272 self.assertTrue(self.ui.get_size_request() <= (966, 576))293 self.assertTrue(self.ui.get_size_request() <= (736, 525))
273294
274295
275class ControlPanelTestCase(ControlPanelMixinTestCase):296class ControlPanelTestCase(BaseTestCase):
276 """The test suite for the control panel itself."""297 """The test suite for the control panel itself."""
277298
278 klass = gui.ControlPanel299 klass = gui.ControlPanel
279 kwargs = {'window_id': 7}300 kwargs = {'window_id': 7}
280 ui_filename = 'controlpanel.ui'301
281302 def assert_current_tab_correct(self, expected_tab):
282 def test_is_a_vbox(self):303 """Check that the wiget 'expected_tab' is the current page."""
304 actual = self.ui.get_nth_page(self.ui.get_current_page())
305 self.assertTrue(expected_tab is actual)
306
307 def test_is_a_notebook(self):
283 """Inherits from gtk.VBox."""308 """Inherits from gtk.VBox."""
284 self.assertIsInstance(self.ui, gui.gtk.VBox)309 self.assertIsInstance(self.ui, gui.gtk.Notebook)
285310
286 def test_startup_visibility(self):311 def test_startup_visibility(self):
287 """The widget is visible at startup."""312 """The widget is visible at startup."""
288 self.assertTrue(self.ui.itself.get_visible(),313 self.assertTrue(self.ui.get_visible(),
289 'must be visible at startup.')314 'must be visible at startup.')
290315
316 def test_startup_props(self):
317 """The tabs and border are not shown."""
318 self.assertFalse(self.ui.get_show_border(), 'must not show border.')
319 self.assertFalse(self.ui.get_show_tabs(), 'must not show tabs.')
320
291 def test_overview_is_shown_at_startup(self):321 def test_overview_is_shown_at_startup(self):
292 """The overview is shown at startup."""322 """The overview is shown at startup."""
293 self.assertIsInstance(self.ui.overview, gui.OverviewPanel)323 self.assertIsInstance(self.ui.overview, gui.OverviewPanel)
324 self.assert_current_tab_correct(self.ui.overview)
294325
295 def test_window_id_is_passed_to_child(self):326 def test_window_id_is_passed_to_child(self):
296 """The child gets the window_id."""327 """The child gets the window_id."""
@@ -299,8 +330,48 @@
299 def test_on_show_management_panel(self):330 def test_on_show_management_panel(self):
300 """A ManagementPanel is shown when the callback is executed."""331 """A ManagementPanel is shown when the callback is executed."""
301 self.ui.on_show_management_panel()332 self.ui.on_show_management_panel()
302 children = self.ui.get_children()333 self.assert_current_tab_correct(self.ui.management)
303 self.assertIsInstance(children[-1], gui.ManagementPanel)334
335 def test_on_show_management_panel_is_idempotent(self):
336 """Only one ManagementPanel is shown."""
337 self.ui.on_show_management_panel()
338 self.ui.on_show_management_panel()
339
340 self.assert_current_tab_correct(self.ui.management)
341
342 def test_credentials_found_shows_account_management_panel(self):
343 """On 'credentials-found' signal, the management panel is shown.
344
345 If first signal parameter is False, visible tab should be account.
346
347 """
348 self.patch(self.ui.management, 'load', self._set_called)
349 self.ui.overview.emit('credentials-found', False, object())
350
351 self.assert_current_tab_correct(self.ui.management)
352 self.assertEqual(self.ui.management.notebook.get_current_page(),
353 self.ui.management.ACCOUNT_PAGE)
354 self.assertEqual(self._called, ((), {}))
355
356 def test_credentials_found_shows_folders_management_panel(self):
357 """On 'credentials-found' signal, the management panel is shown.
358
359 If first signal parameter is True, visible tab should be folders.
360
361 """
362 a_token = object()
363 self.ui.overview.emit('credentials-found', True, a_token)
364
365 self.assert_current_tab_correct(self.ui.management)
366 self.assertEqual(self.ui.management.notebook.get_current_page(),
367 self.ui.management.FOLDERS_PAGE)
368
369 def test_local_device_removed_shows_overview_panel(self):
370 """On 'local-device-removed' signal, the overview panel is shown."""
371 self.ui.overview.emit('credentials-found', True, object())
372 self.ui.management.emit('local-device-removed')
373
374 self.assert_current_tab_correct(self.ui.overview)
304375
305376
306class UbuntuOneBinTestCase(BaseTestCase):377class UbuntuOneBinTestCase(BaseTestCase):
@@ -334,6 +405,37 @@
334 ui = self.klass() # no title given405 ui = self.klass() # no title given
335 self.assertEqual(ui.title.label.get_text(), '')406 self.assertEqual(ui.title.label.get_text(), '')
336407
408 def test_message_is_a_label_loading(self):
409 """Message is the correct widget."""
410 self.assertIsInstance(self.ui.message, gui.LabelLoading)
411 self.assertIn(self.ui.message, self.ui.get_children())
412
413 def test_on_success(self):
414 """Callback to stop the Loading and clear messages."""
415 self.ui.on_success()
416 self.assertEqual(self.ui.message.get_label(), '')
417 self.assertFalse(self.ui.message.active)
418
419 def test_on_success_with_message(self):
420 """Callback to stop the Loading and show a info message."""
421 msg = 'WOW! <i>this rocks</i>'
422 self.ui.on_success(message=msg)
423 self.assertEqual(self.ui.message.get_label(), msg)
424 self.assertFalse(self.ui.message.active)
425
426 def test_on_error(self):
427 """Callback to stop the Loading and clear messages."""
428 self.ui.on_error()
429 self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
430 self.assertFalse(self.ui.message.active)
431
432 def test_on_error_with_message(self):
433 """Callback to stop the Loading and show a info message."""
434 msg = 'WOW! <i>this does not rock</i> :-/'
435 self.ui.on_error(message=msg)
436 self.assert_warning_correct(self.ui.message, msg)
437 self.assertFalse(self.ui.message.active)
438
337439
338class OverwiewPanelTestCase(ControlPanelMixinTestCase):440class OverwiewPanelTestCase(ControlPanelMixinTestCase):
339 """The test suite for the overview panel."""441 """The test suite for the overview panel."""
@@ -418,6 +520,7 @@
418520
419 def test_find_credentials_is_called(self):521 def test_find_credentials_is_called(self):
420 """Credentials are asked to SSO backend."""522 """Credentials are asked to SSO backend."""
523 self.assertFalse(self.ui._credentials_are_new)
421 self.assert_backend_called('find_credentials', (gui.U1_APP_NAME, {}),524 self.assert_backend_called('find_credentials', (gui.U1_APP_NAME, {}),
422 backend=self.ui.sso_backend)525 backend=self.ui.sso_backend)
423526
@@ -427,13 +530,28 @@
427530
428 self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN)531 self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN)
429532
430 self.assertFalse(self.ui.get_visible())533 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
431 self.assertEqual(self._called, ((self.ui, gui.U1_APP_NAME, TOKEN), {}))534 # assume credentials were in local keyring
535 self.assertEqual(self._called, ((self.ui, False, TOKEN), {}))
536
537 def test_on_credentials_found_when_creds_are_not_new(self):
538 """Callback 'on_credentials_found' distinguish if creds are new."""
539 self.ui.connect('credentials-found', self._set_called)
540
541 # credentials weren't in the system
542 self.ui.on_credentials_not_found(gui.U1_APP_NAME)
543 # now they are!
544 self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN)
545
546 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
547 # assume credentials were not in local keyring
548 self.assertEqual(self._called, ((self.ui, True, TOKEN), {}))
432549
433 def test_on_credentials_not_found(self):550 def test_on_credentials_not_found(self):
434 """Callback 'on_credentials_not_found' is correct."""551 """Callback 'on_credentials_not_found' is correct."""
435 self.ui.on_credentials_not_found(gui.U1_APP_NAME)552 self.ui.on_credentials_not_found(gui.U1_APP_NAME)
436 self.assertTrue(self.ui.get_visible())553 self.assertTrue(self.ui.get_visible())
554 self.assertTrue(self.ui._credentials_are_new)
437555
438 def test_on_credentials_error(self):556 def test_on_credentials_error(self):
439 """Callback 'on_credentials_error' is correct."""557 """Callback 'on_credentials_error' is correct."""
@@ -626,11 +744,11 @@
626744
627 def assert_account_info_correct(self, info):745 def assert_account_info_correct(self, info):
628 """Check that the displayed account info matches 'info'."""746 """Check that the displayed account info matches 'info'."""
629 self.assertEqual(self.ui.name_label.get_text(),747 self.assertEqual(self.ui.name_label.get_label(),
630 FAKE_ACCOUNT_INFO['name'])748 FAKE_ACCOUNT_INFO['name'])
631 self.assertEqual(self.ui.type_label.get_text(),749 self.assertEqual(self.ui.type_label.get_label(),
632 FAKE_ACCOUNT_INFO['type'])750 FAKE_ACCOUNT_INFO['type'])
633 self.assertEqual(self.ui.email_label.get_text(),751 self.assertEqual(self.ui.email_label.get_label(),
634 FAKE_ACCOUNT_INFO['email'])752 FAKE_ACCOUNT_INFO['email'])
635753
636 def test_is_an_ubuntuone_bin(self):754 def test_is_an_ubuntuone_bin(self):
@@ -645,15 +763,9 @@
645 """Is visible."""763 """Is visible."""
646 self.assertTrue(self.ui.get_visible())764 self.assertTrue(self.ui.get_visible())
647765
648 def test_type_label_is_loading(self):766 def test_account_info_is_not_visible(self):
649 """Placeholder for type label is a Loading widget."""767 """Account info is not visible."""
650 self.assertIsInstance(self.ui.type_label, gui.LabelLoading)768 self.assertFalse(self.ui.account.get_visible())
651 self.assertIn(self.ui.type_label, self.ui.type_box.get_children())
652
653 def test_email_label_is_loading(self):
654 """Placeholder for email label is a Loading widget."""
655 self.assertIsInstance(self.ui.email_label, gui.LabelLoading)
656 self.assertIn(self.ui.email_label, self.ui.email_box.get_children())
657769
658 def test_backend_signals(self):770 def test_backend_signals(self):
659 """The proper signals are connected to the backend."""771 """The proper signals are connected to the backend."""
@@ -666,18 +778,16 @@
666 """The account info is processed when ready."""778 """The account info is processed when ready."""
667 self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO)779 self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO)
668 self.assert_account_info_correct(FAKE_ACCOUNT_INFO)780 self.assert_account_info_correct(FAKE_ACCOUNT_INFO)
669781 self.assertTrue(self.ui.account.get_visible())
670 for widget in (self.ui.name_label, self.ui.type_label,782 self.assertFalse(self.ui.message.active)
671 self.ui.email_label):783 self.assertEqual(self.ui.message.get_label(), '')
672 self.assertFalse(widget.active)
673784
674 def test_on_account_info_error(self):785 def test_on_account_info_error(self):
675 """The account info couldn't be retrieved."""786 """The account info couldn't be retrieved."""
676 self.ui.on_account_info_error()787 self.ui.on_account_info_error()
677 for widget in (self.ui.name_label, self.ui.type_label,788 self.assertFalse(self.ui.account.get_visible())
678 self.ui.email_label):789 self.assertFalse(self.ui.message.active)
679 self.assert_warning_correct(widget, self.ui.VALUE_ERROR)790 self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
680 self.assertFalse(widget.active)
681791
682792
683class FoldersTestCase(ControlPanelMixinTestCase):793class FoldersTestCase(ControlPanelMixinTestCase):
@@ -686,6 +796,10 @@
686 klass = gui.FoldersPanel796 klass = gui.FoldersPanel
687 ui_filename = 'folders.ui'797 ui_filename = 'folders.ui'
688798
799 def setUp(self):
800 super(FoldersTestCase, self).setUp()
801 self.ui.load()
802
689 def test_is_an_ubuntuone_bin(self):803 def test_is_an_ubuntuone_bin(self):
690 """Inherits from UbuntuOneBin."""804 """Inherits from UbuntuOneBin."""
691 self.assertIsInstance(self.ui, gui.UbuntuOneBin)805 self.assertIsInstance(self.ui, gui.UbuntuOneBin)
@@ -705,34 +819,393 @@
705 self.assertEqual(self.ui.backend._signals['VolumesInfoError'],819 self.assertEqual(self.ui.backend._signals['VolumesInfoError'],
706 [self.ui.on_volumes_info_error])820 [self.ui.on_volumes_info_error])
707821
708 def test_volumes_info_is_requested(self):822 def test_volumes_info_is_requested_on_load(self):
709 """The volumes info is requested to the backend."""823 """The volumes info is requested to the backend."""
824 # clean backend calls
825 self.ui.backend._called.pop('volumes_info', None)
826 self.ui.load()
827
710 self.assert_backend_called('volumes_info', ())828 self.assert_backend_called('volumes_info', ())
711829
712 def _test_on_volumes_info_ready(self):830 def test_message_after_load(self):
831 """The volumes label is active when contents are load."""
832 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
833 self.ui.load()
834
835 self.assertTrue(self.ui.message.active)
836
837 def test_message_after_non_empty_volumes_info_ready(self):
838 """The volumes label is a LabelLoading."""
839 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
840
841 self.assertFalse(self.ui.message.active)
842
843 def test_message_after_empty_volumes_info_ready(self):
844 """When there are no volumes, a notification is shown."""
845 self.ui.on_volumes_info_ready([])
846
847 self.assertFalse(self.ui.message.active)
848 self.assertEqual(self.ui.message.get_text(), self.ui.NO_VOLUMES)
849
850 def test_on_volumes_info_ready(self):
713 """The volumes info is processed when ready."""851 """The volumes info is processed when ready."""
714 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)852 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
715853
716 volumes = self.ui.itself.get_children()854 self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes])
855
856 volumes = self.ui.volumes.get_children()
857 volumes.reverse()
858
859 header = volumes[:2] # grab header
860 self.assertEqual(header[0].get_text(), 'Local path')
861 self.assertEqual(header[1].get_text(), 'Subscribed')
862
863 volumes = volumes[2:] # drop header
864 labels = filter(lambda w: isinstance(w, gui.gtk.Label), volumes)
865 checks = filter(lambda w: isinstance(w, gui.gtk.CheckButton), volumes)
866
867 self.assertEqual(len(checks), len(FAKE_VOLUMES_INFO))
868
869 for label, check, volume in zip(labels, checks, FAKE_VOLUMES_INFO):
870 self.assertEqual(volume['suggested_path'], label.get_text())
871 self.assertEqual(bool(volume['subscribed']), check.get_active())
872 self.assertEqual(volume['volume_id'], check.get_label())
873 self.assertFalse(check.get_child().get_visible())
874
875 def test_on_volumes_info_ready_clears_the_list(self):
876 """The old volumes info is cleared before updated."""
877 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
878 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
879
880 self.assertEqual(len(self.ui.folders.get_children()), 1)
881 child = self.ui.folders.get_children()[0]
882 self.assertEqual(child, self.ui.volumes)
883
884 volumes = filter(lambda w: isinstance(w, gui.gtk.CheckButton),
885 self.ui.volumes.get_children())
717 self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO))886 self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO))
718887
719 for i, volume in enumerate(FAKE_VOLUMES_INFO):888 def test_on_volumes_info_ready_with_no_volumes(self):
720 vol_widget = volumes[i]889 """When there are no volumes, a notification is shown."""
721 self.assertEqual(vol_widget.id, volume['volume_id'])890 self.ui.on_volumes_info_ready([])
722 self.assertEqual(vol_widget.path.get_text(), volume['path'])891 # no volumes table
723 subscribed = gui.dbus_str_to_bool(volume['subscribed'])892 self.assertEqual(len(self.ui.folders.get_children()), 0)
724 self.assertEqual(vol_widget.subscribed.get_active(), subscribed)893 self.assertTrue(self.ui.volumes is None)
894
895 def test_on_subscribed_clicked(self):
896 """Clicking on 'subscribed' updates the folder subscription."""
897 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
898
899 method = 'change_volume_settings'
900 for checkbutton in self.ui._subscribed:
901 checkbutton.clicked()
902 fid = checkbutton.get_label()
903
904 subscribed = gui.bool_str(checkbutton.get_active())
905 self.assert_backend_called(method,
906 (fid, {'subscribed': subscribed}))
907 # clean backend calls
908 self.ui.backend._called.pop(method)
909
910 checkbutton.clicked()
911 subscribed = gui.bool_str(checkbutton.get_active())
912 self.assert_backend_called('change_volume_settings',
913 (fid, {'subscribed': subscribed}))
725914
726 def test_on_volumes_info_error(self):915 def test_on_volumes_info_error(self):
727 """The volumes info couldn't be retrieved."""916 """The volumes info couldn't be retrieved."""
728 self.ui.on_volumes_info_error()917 self.ui.on_volumes_info_error()
729918 self.assert_warning_correct(warning=self.ui.message,
730 def _test_on_volumes_info_ready_clears_the_list(self):919 text=gui.VALUE_ERROR)
731 """The old volumes info is cleared before updated."""920 self.assertFalse(self.ui.message.active)
732 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)921
733 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)922 def test_on_volumes_info_error_after_success(self):
734 volumes = self.ui.itself.get_children()923 """The volumes info couldn't be retrieved after a prior success."""
735 self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO))924 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
925
926 self.ui.on_volumes_info_error()
927
928 self.test_on_volumes_info_error()
929 self.test_on_volumes_info_ready_with_no_volumes()
930
931
932class DeviceTestCase(ControlPanelMixinTestCase):
933 """The test suite for the device widget."""
934
935 klass = gui.Device
936 ui_filename = 'device.ui'
937
938 def assert_device_equal(self, device, expected):
939 """Assert that the device has the values from expected."""
940 self.assertEqual(device.id,
941 expected['device_id'])
942 self.assertEqual(device.device_name.get_text(),
943 expected['device_name'])
944 self.assertEqual(device.device_type.get_icon_name()[0],
945 expected['device_type'].lower())
946 self.assertEqual(device.is_local,
947 bool(expected['is_local']))
948 self.assertEqual(device.configurable,
949 bool(expected['configurable']))
950 self.assertEqual(device.limit_bandwidth.get_active(),
951 bool(expected['limit_bandwidth']))
952
953 value = int(expected['max_upload_speed']) // gui.KILOBYTES
954 self.assertEqual(device.max_upload_speed.get_value_as_int(), value)
955 value = int(expected['max_download_speed']) // gui.KILOBYTES
956 self.assertEqual(device.max_download_speed.get_value_as_int(), value)
957
958 def assert_device_settings_changed(self):
959 """Changing throttling settings updates the backend properly."""
960 expected = self.ui.__dict__
961 self.assert_backend_called('change_device_settings',
962 (self.ui.id, expected))
963 self.assertEqual(self.ui.warning_label.get_text(), '')
964
965 def modify_settings(self):
966 """Modify settings so values actually change."""
967 new_val = not self.ui.limit_bandwidth.get_active()
968 self.ui.limit_bandwidth.set_active(new_val)
969
970 new_val = self.ui.max_upload_speed.get_value_as_int() + 1
971 self.ui.max_upload_speed.set_value(new_val)
972
973 new_val = self.ui.max_download_speed.get_value_as_int() + 1
974 self.ui.max_download_speed.set_value(new_val)
975
976 def test_is_a_vbox(self):
977 """Inherits from VBox."""
978 self.assertIsInstance(self.ui, gui.gtk.VBox)
979
980 def test_inner_widget_is_packed(self):
981 """The 'itself' vbox is packed into the widget."""
982 self.assertIn(self.ui.itself, self.ui.get_children())
983
984 def test_is_visible(self):
985 """Is visible."""
986 self.assertTrue(self.ui.get_visible())
987
988 def test_is_sensitive(self):
989 """Is sensitive."""
990 self.assertTrue(self.ui.get_sensitive())
991
992 def test_warning_label_is_cleared(self):
993 """The warning label is cleared."""
994 self.assertEqual(self.ui.warning_label.get_text(), '')
995
996 def test_default_values(self):
997 """Default values are correct."""
998 self.assertEqual(self.ui.id, None)
999 self.assertEqual(self.ui.device_name.get_text(), '')
1000 self.assertEqual(self.ui.device_type.get_icon_name()[0],
1001 gui.DEVICE_TYPE_COMPUTER.lower())
1002 self.assertEqual(self.ui.is_local, False)
1003 self.assertEqual(self.ui.configurable, False)
1004 self.assertEqual(self.ui.limit_bandwidth.get_active(), False)
1005 self.assertEqual(self.ui.max_upload_speed.get_value_as_int(), 0)
1006 self.assertEqual(self.ui.max_download_speed.get_value_as_int(), 0)
1007
1008 def test_init_does_not_call_backend(self):
1009 """When updating, the backend is not called."""
1010 self.assertEqual(self.ui.backend._called, {})
1011
1012 def test_update_device_name(self):
1013 """A device can be updated from a dict."""
1014 value = 'The death star'
1015 self.ui.update(device_name=value)
1016 self.assertEqual(value, self.ui.device_name.get_text())
1017
1018 def test_update_unicode_device_name(self):
1019 """A device can be updated from a dict."""
1020 value = u'Ñoño Ñandú'
1021 self.ui.update(device_name=value)
1022 self.assertEqual(value, self.ui.device_name.get_text())
1023
1024 def test_update_device_type_computer(self):
1025 """A device can be updated from a dict."""
1026 dtype = gui.DEVICE_TYPE_COMPUTER
1027 self.ui.update(device_type=dtype)
1028 self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON),
1029 self.ui.device_type.get_icon_name())
1030
1031 def test_update_device_type_phone(self):
1032 """A device can be updated from a dict."""
1033 dtype = gui.DEVICE_TYPE_PHONE
1034 self.ui.update(device_type=dtype)
1035 self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON),
1036 self.ui.device_type.get_icon_name())
1037
1038 def test_update_is_not_local(self):
1039 """A device can be updated from a dict."""
1040 self.ui.update(is_local='')
1041 self.assertFalse(self.ui.is_local)
1042
1043 def test_update_is_local(self):
1044 """A device can be updated from a dict."""
1045 self.ui.update(is_local='True')
1046 self.assertTrue(self.ui.is_local)
1047
1048 def test_update_non_configurable(self):
1049 """A device can be updated from a dict."""
1050 self.ui.update(configurable='')
1051 self.assertFalse(self.ui.configurable)
1052 self.assertFalse(self.ui.throttling.get_visible())
1053
1054 def test_update_configurable(self):
1055 """A device can be updated from a dict."""
1056 self.ui.update(configurable='True')
1057 self.assertTrue(self.ui.configurable)
1058 self.assertTrue(self.ui.throttling.get_visible())
1059
1060 def test_update_limit_bandwidth(self):
1061 """A device can be updated from a dict."""
1062 self.ui.update(limit_bandwidth='')
1063 self.assertFalse(self.ui.limit_bandwidth.get_active())
1064
1065 self.ui.update(limit_bandwidth='True')
1066 self.assertTrue(self.ui.limit_bandwidth.get_active())
1067
1068 def test_update_upload_speed(self):
1069 """A device can be updated from a dict."""
1070 value = '12345'
1071 self.ui.update(max_upload_speed=value)
1072 self.assertEqual(int(value) // gui.KILOBYTES,
1073 self.ui.max_upload_speed.get_value_as_int())
1074
1075 def test_update_download_speed(self):
1076 """A device can be updated from a dict."""
1077 value = '987654'
1078 self.ui.update(max_download_speed=value)
1079 self.assertEqual(int(value) // gui.KILOBYTES,
1080 self.ui.max_download_speed.get_value_as_int())
1081
1082 def test_update_does_not_call_backend(self):
1083 """When updating, the backend is not called."""
1084 self.ui.update(**FAKE_DEVICE_INFO)
1085 self.assertEqual(self.ui.backend._called, {})
1086 self.assert_device_equal(self.ui, FAKE_DEVICE_INFO)
1087
1088 def test_on_limit_bandwidth_toggled(self):
1089 """When toggling limit_bandwidth, backend is updated."""
1090 self.ui.limit_bandwidth.toggled()
1091 self.assert_device_settings_changed()
1092
1093 def test_on_max_upload_speed_value_changed(self):
1094 """When setting max_upload_speed, backend is updated."""
1095 self.ui.max_upload_speed.set_value(25)
1096 self.assert_device_settings_changed()
1097
1098 def test_on_max_download_speed_value_changed(self):
1099 """When setting max_download_speed, backend is updated."""
1100 self.ui.max_download_speed.set_value(52)
1101 self.assert_device_settings_changed()
1102
1103 def test_backend_signals(self):
1104 """The proper signals are connected to the backend."""
1105 self.assertEqual(self.ui.backend._signals['DeviceSettingsChanged'],
1106 [self.ui.on_device_settings_changed])
1107 self.assertEqual(self.ui.backend._signals['DeviceSettingsChangeError'],
1108 [self.ui.on_device_settings_change_error])
1109 self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
1110 [self.ui.on_device_removed])
1111 self.assertEqual(self.ui.backend._signals['DeviceRemovalError'],
1112 [self.ui.on_device_removal_error])
1113
1114 def test_on_device_settings_changed(self):
1115 """When settings were changed for this device, enable it."""
1116 self.modify_settings()
1117 self.ui.on_device_settings_changed(device_id=self.ui.id)
1118
1119 self.assertTrue(self.ui.get_sensitive())
1120 self.assertEqual(self.ui.warning_label.get_text(), '')
1121 self.assertEqual(self.ui.__dict__, self.ui._last_settings)
1122
1123 def test_on_device_settings_change_after_error(self):
1124 """Change success after error."""
1125 self.modify_settings()
1126 self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
1127
1128 self.test_on_device_settings_changed()
1129
1130 def test_on_device_settings_changed_different_id(self):
1131 """When settings were changed for other device, nothing changes."""
1132 self.modify_settings()
1133 self.ui.on_device_settings_changed(device_id='yadda')
1134
1135 self.assertEqual(self.ui.warning_label.get_text(), '')
1136
1137 def test_on_device_settings_change_error(self):
1138 """When settings were not changed for this device, notify the user.
1139
1140 Also, confirm that old values were restored.
1141
1142 """
1143 self.ui.update(**FAKE_DEVICE_INFO) # use known values
1144
1145 self.modify_settings()
1146
1147 self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
1148
1149 self.assertTrue(self.ui.get_sensitive())
1150 self.assert_warning_correct(self.ui.warning_label,
1151 self.ui.DEVICE_CHANGE_ERROR)
1152 self.assert_device_equal(self.ui, FAKE_DEVICE_INFO) # restored info
1153
1154 def test_on_device_settings_change_error_after_success(self):
1155 """Change error after success."""
1156 self.modify_settings()
1157 self.ui.on_device_settings_changed(device_id=self.ui.id)
1158
1159 self.test_on_device_settings_change_error()
1160
1161 def test_on_device_settings_change_error_different_id(self):
1162 """When settings were not changed for other device, do nothing."""
1163 self.modify_settings()
1164 self.ui.on_device_settings_change_error(device_id='yudo')
1165 self.assertEqual(self.ui.warning_label.get_text(), '')
1166
1167 def test_remove(self):
1168 """Clicking on remove calls the backend properly."""
1169 self.ui.is_local = False
1170 self.ui.remove.clicked()
1171
1172 self.assert_backend_called('remove_device', (self.ui.id,))
1173 self.assertFalse(self.ui.get_sensitive(),
1174 'Must be disabled while removing.')
1175
1176 def test_on_device_removed(self):
1177 """On this device removed, hide and destroy."""
1178 self.ui.remove.clicked()
1179 self.ui.on_device_removed(device_id=self.ui.id)
1180
1181 self.assertFalse(self.ui.get_visible(),
1182 'Must not be visible after removed.')
1183
1184 def test_on_device_removed_other_id(self):
1185 """On other device removed, do nothing."""
1186 self.ui.remove.clicked()
1187 self.ui.on_device_removed(device_id='foo')
1188
1189 self.assertTrue(self.ui.get_visible(),
1190 'Must be visible after other device was removed.')
1191
1192 def test_on_device_removal_error(self):
1193 """On this device removal error, re-enable and show error."""
1194 self.ui.remove.clicked()
1195 self.ui.on_device_removal_error(device_id=self.ui.id)
1196
1197 self.assertTrue(self.ui.get_sensitive(),
1198 'Must be enabled after removal error.')
1199 self.assert_warning_correct(self.ui.warning_label,
1200 self.ui.DEVICE_REMOVAL_ERROR)
1201
1202 def test_on_device_removal_error_other_id(self):
1203 """On other device removal error, do nothing."""
1204 self.ui.remove.clicked()
1205 self.ui.on_device_removal_error(device_id='foo')
1206
1207 self.assertFalse(self.ui.get_sensitive(),
1208 'Must be disabled after other device removal error.')
7361209
7371210
738class DevicesTestCase(ControlPanelMixinTestCase):1211class DevicesTestCase(ControlPanelMixinTestCase):
@@ -753,6 +1226,144 @@
753 """Is visible."""1226 """Is visible."""
754 self.assertTrue(self.ui.get_visible())1227 self.assertTrue(self.ui.get_visible())
7551228
1229 def test_backend_signals(self):
1230 """The proper signals are connected to the backend."""
1231 self.assertEqual(self.ui.backend._signals['DevicesInfoReady'],
1232 [self.ui.on_devices_info_ready])
1233 self.assertEqual(self.ui.backend._signals['DevicesInfoError'],
1234 [self.ui.on_devices_info_error])
1235 self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
1236 [self.ui.on_device_removed])
1237
1238 def test_devices_info_is_requested_on_load(self):
1239 """The devices info is requested to the backend."""
1240 # clean backend calls
1241 self.ui.backend._called.pop('devices_info', None)
1242 self.ui.load()
1243
1244 self.assert_backend_called('devices_info', ())
1245
1246 def test_message_after_load(self):
1247 """The devices label is active when contents are load."""
1248 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1249 self.ui.load()
1250
1251 self.assertTrue(self.ui.message.active)
1252
1253 def test_message_after_non_empty_devices_info_ready(self):
1254 """The devices label is a LabelLoading."""
1255 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1256
1257 self.assertFalse(self.ui.message.active)
1258
1259 def test_message_after_empty_devices_info_ready(self):
1260 """When there are no devices, a notification is shown."""
1261 self.ui.on_devices_info_ready([])
1262
1263 self.assertFalse(self.ui.message.active)
1264 self.assertEqual(self.ui.message.get_text(), self.ui.NO_DEVICES)
1265
1266 def test_on_devices_info_ready(self):
1267 """The devices info is processed when ready."""
1268 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1269
1270 children = self.ui.devices.get_children()
1271 self.assertEqual(len(children), len(FAKE_DEVICES_INFO))
1272
1273 for child, device in zip(children, FAKE_DEVICES_INFO):
1274 self.assertIsInstance(child, gui.Device)
1275
1276 self.assertEqual(device['device_id'], child.id)
1277 self.assertEqual(device['device_name'],
1278 child.device_name.get_text())
1279 self.assertEqual(device['device_type'].lower(),
1280 child.device_type.get_icon_name()[0])
1281 self.assertEqual(bool(device['is_local']),
1282 child.is_local)
1283 self.assertEqual(bool(device['configurable']),
1284 child.configurable)
1285
1286 if bool(device['configurable']):
1287 self.assertEqual(bool(device['limit_bandwidth']),
1288 child.limit_bandwidth.get_active())
1289 value = int(device['max_upload_speed']) // gui.KILOBYTES
1290 self.assertEqual(value,
1291 child.max_upload_speed.get_value_as_int())
1292 value = int(device['max_download_speed']) // gui.KILOBYTES
1293 self.assertEqual(value,
1294 child.max_download_speed.get_value_as_int())
1295
1296 def test_on_devices_info_ready_have_devices_cached(self):
1297 """The devices are cached for further removal."""
1298 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1299
1300 for child in self.ui.devices.get_children():
1301 self.assertTrue(self.ui._devices[child.id] is child)
1302
1303 def test_on_devices_info_ready_clears_the_list(self):
1304 """The old devices info is cleared before updated."""
1305 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1306 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1307
1308 devices = self.ui.devices.get_children()
1309 self.assertEqual(len(devices), len(FAKE_DEVICES_INFO))
1310
1311 def test_on_devices_info_ready_with_no_devices(self):
1312 """When there are no devices, a notification is shown."""
1313 self.ui.on_devices_info_ready([])
1314 self.assertEqual(len(self.ui.devices.get_children()), 0)
1315
1316 def test_on_devices_info_error(self):
1317 """The devices info couldn't be retrieved."""
1318 self.ui.on_devices_info_error()
1319
1320 self.assert_warning_correct(warning=self.ui.message,
1321 text=gui.VALUE_ERROR)
1322 self.assertFalse(self.ui.message.active)
1323
1324 def test_on_devices_info_error_after_success(self):
1325 """The devices info couldn't be retrieved after a prior success."""
1326 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1327
1328 self.ui.on_devices_info_error()
1329
1330 self.test_on_devices_info_error()
1331 self.test_on_devices_info_ready_with_no_devices()
1332
1333 def test_on_device_removed(self):
1334 """When a child device was removed, remove and destroy."""
1335 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1336 did = FAKE_DEVICES_INFO[0]['device_id']
1337 device = self.ui._devices[did]
1338 self.ui.on_device_removed(device_id=did)
1339
1340 self.assertTrue(device not in self.ui.devices.get_children())
1341 self.assertTrue(did not in self.ui._devices)
1342
1343 def test_on_local_device_removed(self):
1344 """Removing the local device emits local-device-removed."""
1345 self.ui.connect('local-device-removed', self._set_called)
1346
1347 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1348 local_device = FAKE_DEVICES_INFO[-1]
1349 assert bool(local_device['is_local'])
1350 local_device_id = local_device['device_id']
1351 assert self.ui._devices[local_device_id].is_local
1352
1353 self.ui.on_device_removed(device_id=local_device_id)
1354
1355 self.assertEqual(self._called, ((self.ui,), {}))
1356
1357 def test_on_device_removed_for_no_child_device(self):
1358 """On other device removed, do nothing."""
1359 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
1360 old_devices = self.ui.devices.get_children()
1361
1362 self.ui.on_device_removed(device_id='foo')
1363
1364 new_devices = self.ui.devices.get_children()
1365 self.assertEqual(new_devices, old_devices)
1366
7561367
757class ApplicationsTestCase(ControlPanelMixinTestCase):1368class ApplicationsTestCase(ControlPanelMixinTestCase):
758 """The test suite for the applications panel."""1369 """The test suite for the applications panel."""
@@ -842,15 +1453,16 @@
842class ManagementPanelAccountTestCase(ManagementPanelTestCase):1453class ManagementPanelAccountTestCase(ManagementPanelTestCase):
843 """The test suite for the management panel (account tab)."""1454 """The test suite for the management panel (account tab)."""
8441455
845 def test_backend_signals(self):1456 def test_backend_account_signals(self):
846 """The proper signals are connected to the backend."""1457 """The proper signals are connected to the backend."""
847 self.assertEqual(self.ui.backend._signals['AccountInfoReady'],1458 self.assertEqual(self.ui.backend._signals['AccountInfoReady'],
848 [self.ui.on_account_info_ready])1459 [self.ui.on_account_info_ready])
849 self.assertEqual(self.ui.backend._signals['AccountInfoError'],1460 self.assertEqual(self.ui.backend._signals['AccountInfoError'],
850 [self.ui.on_account_info_error])1461 [self.ui.on_account_info_error])
8511462
852 def test_account_info_is_requested(self):1463 def test_account_info_is_requested_on_load(self):
853 """The account info is requested to the backend."""1464 """The account info is requested to the backend."""
1465 self.ui.load()
854 self.assert_backend_called('account_info', ())1466 self.assert_backend_called('account_info', ())
8551467
856 def test_account_panel_is_packed(self):1468 def test_account_panel_is_packed(self):
@@ -877,12 +1489,31 @@
877 actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE)1489 actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE)
878 self.assertTrue(self.ui.applications is actual)1490 self.assertTrue(self.ui.applications is actual)
8791491
880 def test_placeholders_are_loading(self):1492 def test_entering_folders_tab_loads_content(self):
881 """Placeholders for labels are a Loading widget."""1493 """The volumes info is loaded when entering the Folders tab."""
882 widgets = (self.ui.quota_label, self.ui.status_label)1494 self.patch(self.ui.folders, 'load', self._set_called)
883 for widget in widgets:1495 # clean backend calls
884 self.assertIsInstance(widget, gui.LabelLoading)1496 self.ui.folders_button.clicked()
885 self.assertIn(widget, self.ui.status_box.get_children())1497
1498 self.assertEqual(self._called, ((), {}))
1499
1500 def test_entering_devices_tab_loads_content(self):
1501 """The devices info is loaded when entering the Devices tab."""
1502 self.patch(self.ui.devices, 'load', self._set_called)
1503 # clean backend calls
1504 self.ui.devices_button.clicked()
1505
1506 self.assertEqual(self._called, ((), {}))
1507
1508 def test_quota_placeholder_is_loading(self):
1509 """Placeholders for quota label is a Loading widget."""
1510 self.assertIsInstance(self.ui.quota_label, gui.LabelLoading)
1511 self.assertIn(self.ui.quota_label, self.ui.quota_box.get_children())
1512
1513 def test_file_sync_status_placeholder_is_loading(self):
1514 """Placeholders for file sync label is a Loading widget."""
1515 self.assertIsInstance(self.ui.status_label, gui.LabelLoading)
1516 self.assertIn(self.ui.status_label, self.ui.status_box.get_children())
8861517
887 def test_on_account_info_ready(self):1518 def test_on_account_info_ready(self):
888 """The account info is processed when ready."""1519 """The account info is processed when ready."""
@@ -896,5 +1527,80 @@
896 """The account info couldn't be retrieved."""1527 """The account info couldn't be retrieved."""
897 self.ui.on_account_info_error()1528 self.ui.on_account_info_error()
898 for widget in (self.ui.quota_label,):1529 for widget in (self.ui.quota_label,):
899 self.assert_warning_correct(widget, self.ui.VALUE_ERROR)1530 self.assert_warning_correct(widget, gui.VALUE_ERROR)
900 self.assertFalse(widget.active)1531 self.assertFalse(widget.active)
1532
1533 def test_backend_file_sync_signals(self):
1534 """The proper signals are connected to the backend."""
1535 matches = (
1536 ('FileSyncStatusDisabled', [self.ui.on_file_sync_status_disabled]),
1537 ('FileSyncStatusStarting', [self.ui.on_file_sync_status_starting]),
1538 ('FileSyncStatusDisconnected',
1539 [self.ui.on_file_sync_status_disconnected]),
1540 ('FileSyncStatusSyncing', [self.ui.on_file_sync_status_syncing]),
1541 ('FileSyncStatusIdle', [self.ui.on_file_sync_status_idle]),
1542 ('FileSyncStatusError', [self.ui.on_file_sync_status_error]),
1543 )
1544 for sig, handlers in matches:
1545 self.assertEqual(self.ui.backend._signals[sig], handlers)
1546
1547 def test_file_sync_status_is_requested_on_load(self):
1548 """The file sync status is requested to the backend."""
1549 self.ui.load()
1550 self.assert_backend_called('file_sync_status', ())
1551
1552 def test_on_file_sync_status_disabled(self):
1553 """The file sync is disabled."""
1554 self.ui.on_file_sync_status_disabled('msg')
1555
1556 self.assertFalse(self.ui.status_label.active)
1557 self.assertEqual(self.ui.status_label.get_text(),
1558 self.ui.FILE_SYNC_DISABLED)
1559
1560 def test_on_file_sync_status_starting(self):
1561 """The file sync status is starting."""
1562 self.ui.on_file_sync_status_starting('msg')
1563
1564 self.assertFalse(self.ui.status_label.active)
1565 self.assertEqual(self.ui.status_label.get_text(),
1566 self.ui.FILE_SYNC_STARTING)
1567
1568 def test_on_file_sync_status_disconnected(self):
1569 """The file sync status is disconnected."""
1570 self.ui.on_file_sync_status_disconnected('msg')
1571
1572 self.assertFalse(self.ui.status_label.active)
1573 self.assertEqual(self.ui.status_label.get_text(),
1574 self.ui.FILE_SYNC_DISCONNECTED)
1575
1576 def test_on_file_sync_status_syncing(self):
1577 """The file sync status is syncing."""
1578 self.ui.on_file_sync_status_syncing('msg')
1579
1580 self.assertFalse(self.ui.status_label.active)
1581 self.assertEqual(self.ui.status_label.get_text(),
1582 self.ui.FILE_SYNC_SYNCING)
1583
1584 def test_on_file_sync_status_idle(self):
1585 """The file sync status is idle."""
1586 self.ui.on_file_sync_status_idle('msg')
1587
1588 self.assertFalse(self.ui.status_label.active)
1589 self.assertEqual(self.ui.status_label.get_text(),
1590 self.ui.FILE_SYNC_IDLE)
1591
1592 def test_on_file_sync_status_error(self):
1593 """The file sync status couldn't be retrieved."""
1594 self.ui.on_file_sync_status_error({'error_msg': 'error msg'})
1595
1596 self.assert_warning_correct(self.ui.status_label,
1597 self.ui.FILE_SYNC_ERROR)
1598 self.assertFalse(self.ui.status_label.active)
1599
1600 def test_local_device_removed_is_emitted(self):
1601 """Signal local-device-removed is sent when DevicesPanel emits it."""
1602 self.ui.connect('local-device-removed', self._set_called)
1603
1604 self.ui.devices.emit('local-device-removed')
1605
1606 self.assertEqual(self._called, ((self.ui,), {}))
9011607
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_widgets.py'
--- ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-22 14:37:52 +0000
@@ -75,33 +75,26 @@
75 def test_creation(self):75 def test_creation(self):
76 """A LabelLoading can be created."""76 """A LabelLoading can be created."""
77 self.assertEqual(self.widget.label.get_text(), '')77 self.assertEqual(self.widget.label.get_text(), '')
78 self.assertFalse(self.widget.label.get_visible())78 self.assertTrue(self.widget.label.get_visible())
7979
80 self.assertIsInstance(self.widget.loading, widgets.Loading)80 self.assertIsInstance(self.widget.loading, widgets.Loading)
81 self.assertTrue(self.widget.loading.get_visible())81 self.assertTrue(self.widget.loading.get_visible())
82 self.assertTrue(self.widget.active)82 self.assertTrue(self.widget.active) # loading label is packed
8383
84 def test_stop(self):84 def test_stop(self):
85 """Stop hides the Loading widget."""85 """Stop hides the Loading widget."""
86 self.widget.stop()86 self.widget.stop()
87 self.assertTrue(self.widget.label.get_visible())87
88 self.assertFalse(self.widget.loading.get_visible())88 self.assertTrue(self.widget.get_child() is self.widget.label)
89 self.assertFalse(self.widget.active)89 self.assertFalse(self.widget.active)
9090
91 def test_start(self):91 def test_start(self):
92 """Start shows the Loading widget."""92 """Start shows the Loading widget."""
93 self.widget.start()93 self.widget.start()
94 self.assertFalse(self.widget.label.get_visible())94
95 self.assertTrue(self.widget.loading.get_visible())95 self.assertTrue(self.widget.get_child() is self.widget.loading)
96 self.assertTrue(self.widget.active)96 self.assertTrue(self.widget.active)
9797
98 def test_children(self):
99 """A LabelLoading have proper children."""
100 children = self.widget.get_children()
101 self.assertEqual(2, len(children))
102 self.assertTrue(self.widget.label is children[0])
103 self.assertTrue(self.widget.loading is children[-1])
104
105 def test_get_text(self):98 def test_get_text(self):
106 """Text can be queried."""99 """Text can be queried."""
107 text = 'Test me.'100 text = 'Test me.'
108101
=== modified file 'ubuntuone/controlpanel/gtk/widgets.py'
--- ubuntuone/controlpanel/gtk/widgets.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/gtk/widgets.py 2010-12-22 14:37:52 +0000
@@ -49,7 +49,7 @@
49 self.show_all()49 self.show_all()
5050
5151
52class LabelLoading(gtk.HBox):52class LabelLoading(gtk.Alignment):
53 """A spinner and a label."""53 """A spinner and a label."""
5454
55 def __init__(self, loading_label, fg_color=None, *args, **kwargs):55 def __init__(self, loading_label, fg_color=None, *args, **kwargs):
@@ -57,29 +57,36 @@
57 self.loading = Loading(loading_label, fg_color=fg_color)57 self.loading = Loading(loading_label, fg_color=fg_color)
5858
59 self.label = gtk.Label()59 self.label = gtk.Label()
60 self.label.show()
60 if fg_color is not None:61 if fg_color is not None:
61 self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(fg_color))62 self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(fg_color))
6263
63 self.pack_start(self.label, expand=False)64 self.add(self.loading)
64 self.pack_start(self.loading, expand=False)
6565
66 self.show()66 self.show()
67 self.set(xalign=0.5, yalign=0.5, xscale=0, yscale=0)
68 self.set_padding(padding_top=5, padding_bottom=0,
69 padding_left=5, padding_right=5)
67 self.start()70 self.start()
6871
69 @property72 @property
70 def active(self):73 def active(self):
71 """Whether the Loading widget is visible or not."""74 """Whether the Loading widget is visible or not."""
72 return not self.label.get_visible() and self.loading.get_visible()75 return self.get_child() is self.loading
7376
74 def start(self):77 def start(self):
75 """Show the Loading instead of the Label widget."""78 """Show the Loading instead of the Label widget."""
76 self.label.hide()79 for child in self.get_children():
77 self.loading.show()80 self.remove(child)
81
82 self.add(self.loading)
7883
79 def stop(self):84 def stop(self):
80 """Show the label instead of the Loading widget."""85 """Show the label instead of the Loading widget."""
81 self.label.show()86 for child in self.get_children():
82 self.loading.hide()87 self.remove(child)
88
89 self.add(self.label)
8390
84 def set_text(self, text):91 def set_text(self, text):
85 """Set 'text' to be the label's text."""92 """Set 'text' to be the label's text."""
8693
=== modified file 'ubuntuone/controlpanel/integrationtests/__init__.py'
--- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-22 14:37:52 +0000
@@ -43,8 +43,13 @@
43 def setUp(self):43 def setUp(self):
44 super(DBusClientTestCase, self).setUp()44 super(DBusClientTestCase, self).setUp()
45 self.mock = None45 self.mock = None
46 self._called = False
46 dbus_service.init_mainloop()47 dbus_service.init_mainloop()
4748
49 def _set_called(self, *args, **kwargs):
50 """Keep track of function calls, useful for monkeypatching."""
51 self._called = (args, kwargs)
52
48 def register_mockserver(self, bus_name, object_path, object_class,53 def register_mockserver(self, bus_name, object_path, object_class,
49 **kwargs):54 **kwargs):
50 """The mock service is registered on the DBus."""55 """The mock service is registered on the DBus."""
5156
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-22 14:37:52 +0000
@@ -204,13 +204,13 @@
204204
205205
206class FoldersMockDBusSyncDaemon(sd_dbus_iface.Folders):206class FoldersMockDBusSyncDaemon(sd_dbus_iface.Folders):
207 """A mock object that mimicks syncdaemon regarding the folders iface."""207 """A mock object that mimicks syncdaemon regarding the Folders iface."""
208208
209 # __init__ method from a non direct base class 'Object' is called209 # __init__ method from a non direct base class 'Object' is called
210 # __init__ method from base class 'Folders' is not called210 # __init__ method from base class 'Folders' is not called
211 # pylint: disable=W0231, W0233211 # pylint: disable=W0231, W0233
212212
213 def __init__(self, object_path, conn, vm=None):213 def __init__(self, object_path, conn):
214 self.udfs = {}214 self.udfs = {}
215 self.udf_id = 1215 self.udf_id = 1
216 dbus.service.Object.__init__(self,216 dbus.service.Object.__init__(self,
@@ -220,9 +220,15 @@
220 def _new_udf(cls, uid, path, subscribed=False):220 def _new_udf(cls, uid, path, subscribed=False):
221 """Create a new faked udf."""221 """Create a new faked udf."""
222 udf = {}222 udf = {}
223 udf['path'] = udf['suggested_path'] = path223 if isinstance(path, str):
224 udf['id'] = uid224 path = path.decode('utf-8')
225 udf['subscribed'] = subscribed225 udf[u'path'] = udf[u'suggested_path'] = path
226 udf[u'volume_id'] = unicode(uid)
227 udf[u'subscribed'] = sd_dbus_iface.bool_str(subscribed)
228 udf[u'node_id'] = u'a18f4cbd-a846-4405-aaa1-b28904817089'
229 udf[u'generation'] = u''
230 udf[u'type'] = u'UDF'
231
226 return udf232 return udf
227233
228 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,234 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,
@@ -231,9 +237,10 @@
231 """Create a user defined folder in the specified path."""237 """Create a user defined folder in the specified path."""
232 if path == '': # simulate an error238 if path == '': # simulate an error
233 self.emit_folder_create_error(path, 'create failed!')239 self.emit_folder_create_error(path, 'create failed!')
240 return
234241
235 udf = self._new_udf(self.udf_id, path)242 udf = self._new_udf(self.udf_id, path)
236 self.udfs[self.udf_id] = udf243 self.udfs[udf['volume_id']] = udf
237 self.udf_id += 1244 self.udf_id += 1
238 self.emit_folder_created(udf)245 self.emit_folder_created(udf)
239246
@@ -243,6 +250,7 @@
243 """Delete the folder specified by folder_id"""250 """Delete the folder specified by folder_id"""
244 if folder_id not in self.udfs:251 if folder_id not in self.udfs:
245 self.emit_folder_delete_error(folder_id, 'failed!')252 self.emit_folder_delete_error(folder_id, 'failed!')
253 return
246254
247 udf = self.udfs.pop(folder_id)255 udf = self.udfs.pop(folder_id)
248 self.emit_folder_deleted(udf)256 self.emit_folder_deleted(udf)
@@ -260,8 +268,9 @@
260 if folder_id not in self.udfs:268 if folder_id not in self.udfs:
261 # simulate error269 # simulate error
262 self.emit_folder_subscribe_error(folder_id, 'some error')270 self.emit_folder_subscribe_error(folder_id, 'some error')
271 return
263272
264 self.udfs[folder_id]['subscribed'] = True273 self.udfs[folder_id]['subscribed'] = sd_dbus_iface.bool_str(True)
265 self.emit_folder_subscribed(self.udfs[folder_id])274 self.emit_folder_subscribed(self.udfs[folder_id])
266275
267 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,276 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,
@@ -271,8 +280,9 @@
271 if folder_id not in self.udfs:280 if folder_id not in self.udfs:
272 # simulate error281 # simulate error
273 self.emit_folder_unsubscribe_error(folder_id, 'some error')282 self.emit_folder_unsubscribe_error(folder_id, 'some error')
283 return
274284
275 self.udfs[folder_id]['subscribed'] = False285 self.udfs[folder_id]['subscribed'] = sd_dbus_iface.bool_str(False)
276 self.emit_folder_unsubscribed(self.udfs[folder_id])286 self.emit_folder_unsubscribed(self.udfs[folder_id])
277287
278 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,288 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME,
@@ -296,49 +306,208 @@
296306
297 def setUp(self):307 def setUp(self):
298 super(FoldersTestCase, self).setUp()308 super(FoldersTestCase, self).setUp()
299309 self.patch(sd_dbus_iface, '_get_udf_dict', lambda udf: udf)
300 def _get_udf_dict(udf):
301 """Get a dict with all the attributes of: udf."""
302 udf_dict = udf.copy()
303 for k, val in udf_dict.items():
304 if val is None:
305 udf_dict[unicode(k)] = ''
306 elif k == 'subscribed':
307 udf_dict[unicode(k)] = sd_dbus_iface.bool_str(val)
308 elif k in ('path', 'suggested_path') and isinstance(val, str):
309 udf_dict[unicode(k)] = val.decode('utf-8')
310 else:
311 udf_dict[unicode(k)] = unicode(val)
312 return udf_dict
313
314 self.patch(sd_dbus_iface, '_get_udf_dict', _get_udf_dict)
315 self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME,310 self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME,
316 "/folders", FoldersMockDBusSyncDaemon)311 "/folders", FoldersMockDBusSyncDaemon)
317312
318 @inlineCallbacks313 @inlineCallbacks
319 def test_get_volumes(self):314 def test_get_folders(self):
320 """Retrieve volumes info list."""315 """Retrieve folders info list."""
321 path = '~/bar/baz'316 path = '~/bar/baz'
322 yield dbus_client.create_volume(path)317 yield dbus_client.create_folder(path)
323318
324 result = yield dbus_client.get_volumes()319 result = yield dbus_client.get_folders()
325320
326 expected = FoldersMockDBusSyncDaemon._new_udf(1, path)321 expected = FoldersMockDBusSyncDaemon._new_udf(1, path)
327 self.assertEqual(result, [sd_dbus_iface._get_udf_dict(expected)])322 self.assertEqual(result, [sd_dbus_iface._get_udf_dict(expected)])
328323
329 @inlineCallbacks324 @inlineCallbacks
330 def test_create_volume(self):325 def test_get_folders_error(self):
331 """Create a new volume."""326 """Handle error when retrieving current syncdaemon status."""
332 path = '~/bar/baz'327 path = '~/bar/baz'
333 volume_info = yield dbus_client.create_volume(path)328 yield dbus_client.create_folder(path)
329
330 def fail(value):
331 """Fake an error."""
332 raise TestDBusException(value)
333
334 self.patch(sd_dbus_iface, '_get_udf_dict', fail)
335
336 try:
337 yield dbus_client.get_folders()
338 except dbus.DBusException:
339 pass # test passes!
340 else:
341 self.fail('dbus_client.get_folders should be errbacking')
342
343 @inlineCallbacks
344 def test_create_folder(self):
345 """Create a new folder."""
346 path = '~/bar/baz'
347 folder_info = yield dbus_client.create_folder(path)
334348
335 expected = FoldersMockDBusSyncDaemon._new_udf(1, path)349 expected = FoldersMockDBusSyncDaemon._new_udf(1, path)
336 self.assertEqual(sd_dbus_iface._get_udf_dict(expected), volume_info)350 self.assertEqual(sd_dbus_iface._get_udf_dict(expected), folder_info)
337351
338 @inlineCallbacks352 @inlineCallbacks
339 def test_create_volume_error(self):353 def test_create_folder_error(self):
340 """Create a new volume fails."""354 """Create a new folder fails."""
341 try:355 path = ''
342 yield dbus_client.create_volume(path='')356 try:
343 except dbus_client.VolumesError, e:357 yield dbus_client.create_folder(path=path)
344 self.assertEqual(e[0], {'path': ''})358 except dbus_client.VolumesError, e:
359 self.assertEqual(e[0], {'path': path})
360 else:
361 self.fail('dbus_client.create_folder should be errbacking')
362
363 @inlineCallbacks
364 def test_subscribe_folder(self):
365 """Subscribe to a folder."""
366 path = '~/bar/baz'
367 folder_info = yield dbus_client.create_folder(path)
368 fid = folder_info['volume_id']
369 yield dbus_client.subscribe_folder(fid)
370
371 result = yield dbus_client.get_folders()
372 expected, = filter(lambda folder: folder['volume_id'] == fid, result)
373 self.assertEqual(expected['subscribed'], 'True')
374
375 @inlineCallbacks
376 def test_subscribe_folder_error(self):
377 """Subscribe to a folder."""
378 fid = u'does not exist'
379 try:
380 yield dbus_client.subscribe_folder(fid)
381 except dbus_client.VolumesError, e:
382 self.assertEqual(e[0], {'id': fid})
383 else:
384 self.fail('dbus_client.subscribe_folder should be errbacking')
385
386 @inlineCallbacks
387 def test_unsubscribe_folder(self):
388 """Unsubscribe to a folder."""
389 path = '~/bar/baz'
390 folder_info = yield dbus_client.create_folder(path)
391 fid = folder_info['volume_id']
392 yield dbus_client.subscribe_folder(fid)
393 # folder is subscribed
394
395 yield dbus_client.unsubscribe_folder(fid)
396
397 result = yield dbus_client.get_folders()
398 expected, = filter(lambda folder: folder['volume_id'] == fid, result)
399 self.assertEqual(expected['subscribed'], '')
400
401 @inlineCallbacks
402 def test_unsubscribe_folder_error(self):
403 """Unsubscribe to a folder."""
404 fid = u'does not exist'
405 try:
406 yield dbus_client.unsubscribe_folder(fid)
407 except dbus_client.VolumesError, e:
408 self.assertEqual(e[0], {'id': fid})
409 else:
410 self.fail('dbus_client.unsubscribe_folder should be errbacking')
411
412
413class StatusMockDBusSyncDaemon(dbus.service.Object):
414 """A mock object that mimicks syncdaemon regarding the Status iface."""
415
416 state_dict = {
417 'name': 'TEST',
418 'description': 'Some test state, nothing else.',
419 'is_error': '',
420 'is_connected': 'True',
421 'is_online': '',
422 'queues': 'GORGEOUS',
423 'connection': '',
424 }
425
426 def _get_current_state(self):
427 """Get the current status of the system."""
428 return self.state_dict
429
430 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_STATUS_NAME,
431 in_signature='', out_signature='a{ss}')
432 def current_status(self):
433 """Return the current faked status of the system."""
434 return self._get_current_state()
435
436 # pylint: disable=C0103
437 # Invalid name "StatusChanged"
438
439 @dbus.service.signal(sd_dbus_iface.DBUS_IFACE_STATUS_NAME)
440 def StatusChanged(self, status):
441 """Fire a signal to notify that the status of the system changed."""
442
443 def emit_status_changed(self, state=None):
444 """Emit StatusChanged."""
445 self.StatusChanged(self._get_current_state())
446
447
448class StatusTestCase(DBusClientTestCase):
449 """Test for the status dbus client methods."""
450
451 def setUp(self):
452 super(StatusTestCase, self).setUp()
453 self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME,
454 "/status", StatusMockDBusSyncDaemon)
455
456 @inlineCallbacks
457 def test_get_current_status(self):
458 """Retrieve current syncdaemon status."""
459 status = yield dbus_client.get_current_status()
460
461 self.assertEqual(StatusMockDBusSyncDaemon.state_dict, status)
462
463 @inlineCallbacks
464 def test_get_current_status_error(self):
465 """Handle error when retrieving current syncdaemon status."""
466
467 def fail(value):
468 """Fake an error."""
469 raise TestDBusException(value)
470
471 self.patch(StatusMockDBusSyncDaemon, '_get_current_state', fail)
472
473 try:
474 yield dbus_client.get_current_status()
475 except dbus.DBusException:
476 pass # test passes!
477 else:
478 self.fail('dbus_client.get_current_status should be errbacking')
479
480 def test_set_status_changed_handler(self):
481 """A proper callback can be connected to StatusChanged signal."""
482 _, sig = dbus_client.set_status_changed_handler(self._set_called)
483
484 self.assertEqual(sig._handler, self._set_called)
485 self.assertEqual(sig._member, 'StatusChanged')
486 self.assertEqual(sig._path, '/status')
487 self.assertEqual(sig._interface, sd_dbus_iface.DBUS_IFACE_STATUS_NAME)
488
489
490class FileSyncTestCase(DBusClientTestCase):
491 """Test for the files sync enabled dbus client methods."""
492
493 @inlineCallbacks
494 def test_files_sync_enabled(self):
495 """Retrieve whether file sync is enabled."""
496 expected = object()
497 self.patch(dbus_client.SyncDaemonTool, 'is_files_sync_enabled',
498 lambda _: expected)
499
500 enabled = yield dbus_client.files_sync_enabled()
501
502 self.assertEqual(expected, enabled)
503
504 @inlineCallbacks
505 def test_set_files_sync_enabled(self):
506 """Set if file sync is enabled or not."""
507 self.patch(dbus_client.SyncDaemonTool, 'enable_files_sync',
508 self._set_called)
509 expected = object()
510 # set the opposite value
511 yield dbus_client.set_files_sync_enabled(expected)
512
513 self.assertEqual(self._called, ((expected,), {}))
345514
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py 2010-12-22 14:37:52 +0000
@@ -23,8 +23,9 @@
23# DBus signals have CamelCased names23# DBus signals have CamelCased names
2424
25import dbus25import dbus
26import ubuntu_sso
2726
27from ubuntu_sso import (DBUS_BUS_NAME,
28 DBUS_CREDENTIALS_PATH, DBUS_CREDENTIALS_IFACE)
28from twisted.internet.defer import inlineCallbacks29from twisted.internet.defer import inlineCallbacks
2930
30from ubuntuone.controlpanel import dbus_client31from ubuntuone.controlpanel import dbus_client
@@ -36,6 +37,8 @@
36 "token": "ABCDEF12345678",37 "token": "ABCDEF12345678",
37 "access_token": "DEADCAFE2010",38 "access_token": "DEADCAFE2010",
38}39}
40OTHER_CREDS = {"token": "other!"}
41SAMPLE_ERROR = {"error message": "test", "detailed_error": "error details"}
3942
40# pylint: disable=C032243# pylint: disable=C0322
41# pylint, you have to go to decorator's school44# pylint, you have to go to decorator's school
@@ -44,116 +47,161 @@
44class MockDBusSSOService(dbus.service.Object):47class MockDBusSSOService(dbus.service.Object):
45 """A mock object that mimicks ussoc."""48 """A mock object that mimicks ussoc."""
4649
47 @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,50 found = True
48 in_signature="sssx")51 wrong_app = None
49 def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid):52 error = None
50 """Get creds from the keyring, login/register if needed."""53
51 self.CredentialsFound(app_name, SAMPLE_CREDS)54 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
5255 in_signature='sa{ss}', out_signature='')
53 @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,56 def find_credentials(self, app_name, args):
54 signature="sa{ss}")57 """Get creds from the keyring, login/register if needed."""
55 def CredentialsFound(self, app_name, credentials):58 if self.wrong_app is None and self.error is None:
56 """Credentials were finally found."""59 if self.found:
5760 self.CredentialsFound(app_name, SAMPLE_CREDS)
5861 else:
59class MockDBusSSOServiceOther(dbus.service.Object):62 self.CredentialsNotFound(app_name)
60 """A mock object that mimicks ussoc."""63 elif self.wrong_app is not None and self.error is None:
6164 self.CredentialsFound(self.wrong_app, OTHER_CREDS)
62 @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,65 elif self.wrong_app is None and self.error is not None:
63 in_signature="sssx")66 self.CredentialsError(app_name, self.error)
64 def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid):67 else:
65 """Get creds from the keyring, login/register if needed."""68 self.CredentialsError(self.wrong_app, self.error)
66 self.CredentialsFound("wrong app name", {})69
67 self.CredentialsFound(app_name, SAMPLE_CREDS)70 self.CredentialsFound(app_name, SAMPLE_CREDS)
6871
69 @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,72 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
70 signature="sa{ss}")73 in_signature='sa{ss}', out_signature='')
71 def CredentialsFound(self, app_name, credentials):74 def clear_credentials(self, app_name, args):
72 """Credentials were finally found."""75 """Clear the credentials for an application."""
7376 self.found = False
7477
75class MockDBusSSOClientOtherFailing(dbus.service.Object):78 if self.wrong_app is None and self.error is None:
76 """A mock object that mimicks ussoc."""79 self.CredentialsCleared(app_name)
7780 elif self.wrong_app is not None and self.error is None:
78 @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,81 self.CredentialsCleared(self.wrong_app)
79 in_signature="sssx")82 elif self.wrong_app is None and self.error is not None:
80 def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid):83 self.CredentialsError(app_name, self.error)
81 """Get creds from the keyring, login/register if needed."""84 else:
82 self.CredentialsError("wrong app", "error message", "error details")85 self.CredentialsError(self.wrong_app, self.error)
83 self.CredentialsFound(app_name, SAMPLE_CREDS)86
8487 self.CredentialsCleared(app_name)
85 @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,88
86 signature="sa{ss}")89 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
87 def CredentialsFound(self, app_name, credentials):90 def CredentialsFound(self, app_name, credentials):
88 """Credentials were finally found."""91 """Signal thrown when the credentials are found."""
8992
90 @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,93 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
91 signature="sss")94 def CredentialsNotFound(self, app_name):
92 def CredentialsError(self, app_name, error_message, detailed_error):95 """Signal thrown when the credentials are not found."""
93 """Some error happened and credentials were not found."""96
9497 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
9598 def CredentialsCleared(self, app_name):
96class MockDBusSSOClientFailing(dbus.service.Object):99 """Signal thrown when the credentials were cleared."""
97 """A mock object that mimicks ussoc but fails."""100
98101 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
99 @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,102 def CredentialsStored(self, app_name):
100 in_signature="sssx")103 """Signal thrown when the credentials were cleared."""
101 def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid):104
102 """Fail while trying to get creds from the keyring."""105 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
103 self.CredentialsError(app_name, "error message", "error details")106 def CredentialsError(self, app_name, error_dict):
104107 """Signal thrown when there is a problem getting the credentials."""
105 @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME,
106 signature="sss")
107 def CredentialsError(self, app_name, error_message, detailed_error):
108 """Some error happened and credentials were not found."""
109108
110109
111class SSOClientTestCase(DBusClientTestCase):110class SSOClientTestCase(DBusClientTestCase):
112 """Test for the SSO dbus client."""111 """Test for the SSO dbus client."""
113112
113 def setUp(self):
114 super(SSOClientTestCase, self).setUp()
115 self.register_mockserver(DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH,
116 MockDBusSSOService)
117 MockDBusSSOService.wrong_app = None
118 MockDBusSSOService.error = None
119 MockDBusSSOService.found = True
120
121 # get_credentials
122
114 @inlineCallbacks123 @inlineCallbacks
115 def test_get_credentials_ok(self):124 def test_get_credentials_ok(self):
116 """Test the success case for get_credentials."""125 """Test the success case for get_credentials."""
117 self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME,
118 ubuntu_sso.DBUS_CRED_PATH, MockDBusSSOService)
119 creds = yield dbus_client.get_credentials()126 creds = yield dbus_client.get_credentials()
120 self.assertEqual(creds, SAMPLE_CREDS)127 self.assertEqual(creds, SAMPLE_CREDS)
121128
122 @inlineCallbacks129 @inlineCallbacks
130 def test_get_credentials_not_found(self):
131 """Credentials were not found."""
132 yield dbus_client.clear_credentials() # not found will be sent
133 yield self.assertFailure(dbus_client.get_credentials(),
134 dbus_client.CredentialsError)
135
136 @inlineCallbacks
123 def test_get_credentials_other(self):137 def test_get_credentials_other(self):
124 """Creds for other apps are ignored."""138 """Creds for other apps are ignored."""
125 self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME,139 MockDBusSSOService.wrong_app = 'other app!'
126 ubuntu_sso.DBUS_CRED_PATH,
127 MockDBusSSOServiceOther)
128 creds = yield dbus_client.get_credentials()140 creds = yield dbus_client.get_credentials()
129 self.assertEqual(creds, SAMPLE_CREDS)141 self.assertEqual(creds, SAMPLE_CREDS)
130142
131 @inlineCallbacks143 @inlineCallbacks
132 def test_get_credentials_error(self):144 def test_get_credentials_error(self):
133 """Test what happens when the creds can't be retrieved."""145 """Test what happens when the creds can't be retrieved."""
134 self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME,146 MockDBusSSOService.error = SAMPLE_ERROR
135 ubuntu_sso.DBUS_CRED_PATH,
136 MockDBusSSOClientFailing)
137
138 yield self.assertFailure(dbus_client.get_credentials(),147 yield self.assertFailure(dbus_client.get_credentials(),
139 dbus_client.CredentialsError)148 dbus_client.CredentialsError)
140149
141 @inlineCallbacks150 @inlineCallbacks
142 def test_get_credentials_other_error(self):151 def test_get_credentials_other_error(self):
143 """Other creds err before ours are retrieved."""152 """Other creds err before ours are retrieved."""
144 self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME,153 MockDBusSSOService.wrong_app = 'other app!'
145 ubuntu_sso.DBUS_CRED_PATH,154 MockDBusSSOService.error = SAMPLE_ERROR
146 MockDBusSSOClientOtherFailing)
147
148 creds = yield dbus_client.get_credentials()155 creds = yield dbus_client.get_credentials()
149 self.assertEqual(creds, SAMPLE_CREDS)156 self.assertEqual(creds, SAMPLE_CREDS)
150157
158 # clear_credentials
159
160 @inlineCallbacks
161 def test_clear_credentials_ok(self):
162 """Test the success case for clear_credentials."""
163 result = yield dbus_client.clear_credentials()
164 self.assertEqual(result, dbus_client.APP_NAME)
165
166 @inlineCallbacks
167 def test_clear_credentials_other(self):
168 """Creds for other apps are ignored."""
169 MockDBusSSOService.wrong_app = 'other app!'
170 result = yield dbus_client.clear_credentials()
171 self.assertEqual(result, dbus_client.APP_NAME)
172
173 @inlineCallbacks
174 def test_clear_credentials_error(self):
175 """Test what happens when the creds can't be retrieved."""
176 MockDBusSSOService.error = SAMPLE_ERROR
177 yield self.assertFailure(dbus_client.clear_credentials(),
178 dbus_client.CredentialsError)
179
180 @inlineCallbacks
181 def test_clear_credentials_other_error(self):
182 """Other creds err before ours are retrieved."""
183 MockDBusSSOService.wrong_app = 'other app!'
184 MockDBusSSOService.error = SAMPLE_ERROR
185 result = yield dbus_client.clear_credentials()
186 self.assertEqual(result, dbus_client.APP_NAME)
187
188
189class NoMethodsSSOClientTestCase(DBusClientTestCase):
190 """Test for the SSO dbus client when the service provides no methods."""
191
192 def setUp(self):
193 super(NoMethodsSSOClientTestCase, self).setUp()
194 self.register_mockserver(DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH,
195 MockDBusNoMethods)
196
151 @inlineCallbacks197 @inlineCallbacks
152 def test_get_credentials_dbus_error(self):198 def test_get_credentials_dbus_error(self):
153 """Test what happens when there's a DBus error."""199 """Test what happens when there's a DBus error."""
154 self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME,200 yield self.assertFailure(dbus_client.get_credentials(),
155 ubuntu_sso.DBUS_CRED_PATH,201 dbus.DBusException)
156 MockDBusNoMethods)
157202
158 yield self.assertFailure(dbus_client.get_credentials(),203 @inlineCallbacks
204 def test_clear_credentials_dbus_error(self):
205 """Test what happens when there's a DBus error."""
206 yield self.assertFailure(dbus_client.clear_credentials(),
159 dbus.DBusException)207 dbus.DBusException)
160208
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-22 14:37:52 +0000
@@ -45,8 +45,8 @@
45 "name": "Ubuntu One @ darkstar",45 "name": "Ubuntu One @ darkstar",
46 "date_added": "2008-12-06T18:15:38.0",46 "date_added": "2008-12-06T18:15:38.0",
47 "type": "computer",47 "type": "computer",
48 "configurable": "1",48 "configurable": 'True',
49 "limit_bandwidth": "1",49 "limit_bandwidth": 'True',
50 "max_upload_speed": "12345",50 "max_upload_speed": "12345",
51 "max_download_speed": "54321",51 "max_download_speed": "54321",
52 "available_services": "files, contacts, music, bookmarks",52 "available_services": "files, contacts, music, bookmarks",
@@ -57,7 +57,7 @@
57 "name": "Ubuntu One @ brightmoon",57 "name": "Ubuntu One @ brightmoon",
58 "date_added": "2010-09-22T20:45:38.0",58 "date_added": "2010-09-22T20:45:38.0",
59 "type": "computer",59 "type": "computer",
60 "configurable": "0",60 "configurable": '',
61 "available_services": "files, contacts, bookmarks",61 "available_services": "files, contacts, bookmarks",
62 "enabled_services": "files, bookmarks",62 "enabled_services": "files, bookmarks",
63 },63 },
@@ -68,19 +68,19 @@
68 "volume_id": "volume-0",68 "volume_id": "volume-0",
69 "path": "/home/user/Documents",69 "path": "/home/user/Documents",
70 "suggested_path": "~/Documents",70 "suggested_path": "~/Documents",
71 "subscribed": "1",71 "subscribed": 'True',
72 },72 },
73 {73 {
74 "volume_id": "volume-1",74 "volume_id": "volume-1",
75 "path": "/home/user/Music",75 "path": "/home/user/Music",
76 "suggested_path": "~/Music",76 "suggested_path": "~/Music",
77 "subscribed": "0",77 "subscribed": '',
78 },78 },
79 {79 {
80 "volume_id": "volume-2",80 "volume_id": "volume-2",
81 "path": "/home/user/Pictures/Photos",81 "path": "/home/user/Pictures/Photos",
82 "suggested_path": "~/Pictures/Photos",82 "suggested_path": "~/Pictures/Photos",
83 "subscribed": "0",83 "subscribed": '',
84 },84 },
85]85]
8686
@@ -115,7 +115,13 @@
115115
116class MockBackend(object):116class MockBackend(object):
117 """A mock backend."""117 """A mock backend."""
118
118 exception = None119 exception = None
120 sample_status = {
121 dbus_service.MSG_KEY: 'test me please',
122 dbus_service.STATUS_KEY: dbus_service.FILE_SYNC_IDLE,
123 }
124 status_changed_handler = None
119125
120 def _process(self, result):126 def _process(self, result):
121 """Process the request with the given result."""127 """Process the request with the given result."""
@@ -142,7 +148,7 @@
142148
143 def file_sync_status(self):149 def file_sync_status(self):
144 """Return the status of the file sync service."""150 """Return the status of the file sync service."""
145 return self._process((True, "Synchronizing"))151 return self._process(self.sample_status)
146152
147 def volumes_info(self):153 def volumes_info(self):
148 """Get the user volumes info."""154 """Get the user volumes info."""
@@ -170,6 +176,11 @@
170 """Initialize each test run."""176 """Initialize each test run."""
171 super(DBusServiceTestCase, self).setUp()177 super(DBusServiceTestCase, self).setUp()
172 dbus_service.init_mainloop()178 dbus_service.init_mainloop()
179 self._called = False
180
181 def _set_called(self, *args, **kwargs):
182 """Keep track of function calls, useful for monkeypatching."""
183 self._called = (args, kwargs)
173184
174 def test_register_service(self):185 def test_register_service(self):
175 """The DBus service is successfully registered."""186 """The DBus service is successfully registered."""
@@ -193,7 +204,7 @@
193 def test_error_handler_with_failure(self):204 def test_error_handler_with_failure(self):
194 """Ensure to build a string-string dict to pass to error signals."""205 """Ensure to build a string-string dict to pass to error signals."""
195 error = dbus_service.Failure(TypeError('oh no!'))206 error = dbus_service.Failure(TypeError('oh no!'))
196 expected = dbus_service.utils.failure_to_error_dict(error)207 expected = dbus_service.failure_to_error_dict(error)
197208
198 result = dbus_service.error_handler(error)209 result = dbus_service.error_handler(error)
199210
@@ -202,7 +213,7 @@
202 def test_error_handler_with_exception(self):213 def test_error_handler_with_exception(self):
203 """Ensure to build a string-string dict to pass to error signals."""214 """Ensure to build a string-string dict to pass to error signals."""
204 error = TypeError('oh no, no again!')215 error = TypeError('oh no, no again!')
205 expected = dbus_service.utils.exception_to_error_dict(error)216 expected = dbus_service.exception_to_error_dict(error)
206217
207 result = dbus_service.error_handler(error)218 result = dbus_service.error_handler(error)
208219
@@ -231,8 +242,8 @@
231 def test_error_handler_default(self):242 def test_error_handler_default(self):
232 """Ensure to build a string-string dict to pass to error signals."""243 """Ensure to build a string-string dict to pass to error signals."""
233 msg = 'Got unexpected error argument %r' % None244 msg = 'Got unexpected error argument %r' % None
234 expected = {dbus_service.utils.ERROR_TYPE: 'UnknownError',245 expected = {dbus_service.ERROR_TYPE: 'UnknownError',
235 dbus_service.utils.ERROR_MESSAGE: msg}246 dbus_service.ERROR_MESSAGE: msg}
236247
237 result = dbus_service.error_handler(None)248 result = dbus_service.error_handler(None)
238249
@@ -330,7 +341,7 @@
330 self.assertIn("date_added", device_info)341 self.assertIn("date_added", device_info)
331 self.assertIn("type", device_info)342 self.assertIn("type", device_info)
332 self.assertIn("configurable", device_info)343 self.assertIn("configurable", device_info)
333 if int(device_info["configurable"]):344 if bool(device_info["configurable"]):
334 self.assertIn("limit_bandwidth", device_info)345 self.assertIn("limit_bandwidth", device_info)
335 self.assertIn("max_upload_speed", device_info)346 self.assertIn("max_upload_speed", device_info)
336 self.assertIn("max_download_speed", device_info)347 self.assertIn("max_download_speed", device_info)
@@ -372,19 +383,6 @@
372 self.backend.remove_device, sample_token)383 self.backend.remove_device, sample_token)
373 return self.assert_correct_method_call(*args)384 return self.assert_correct_method_call(*args)
374385
375 def test_file_sync_status(self):
376 """The file sync status is reported."""
377
378 def got_signal(enabled, status):
379 """The correct status was received."""
380 self.assertEqual(enabled, True)
381 self.assertEqual(status, "Synchronizing")
382 self.deferred.callback("success")
383
384 args = ("FileSyncStatusReady", "FileSyncStatusError", got_signal,
385 self.backend.file_sync_status)
386 return self.assert_correct_method_call(*args)
387
388 def test_volumes_info(self):386 def test_volumes_info(self):
389 """The volumes info is reported."""387 """The volumes info is reported."""
390388
@@ -408,7 +406,7 @@
408406
409 args = ("VolumeSettingsChanged", "VolumeSettingsChangeError",407 args = ("VolumeSettingsChanged", "VolumeSettingsChangeError",
410 got_signal, self.backend.change_volume_settings,408 got_signal, self.backend.change_volume_settings,
411 expected_volume_id, {'subscribed': '0'})409 expected_volume_id, {'subscribed': ''})
412 return self.assert_correct_method_call(*args)410 return self.assert_correct_method_call(*args)
413411
414 def test_query_bookmarks_extension(self):412 def test_query_bookmarks_extension(self):
@@ -451,11 +449,87 @@
451449
452 """450 """
453451
454 def got_error_signal(error_dict):452 def got_error_signal(*a):
455 """The error signal was received."""453 """The error signal was received."""
456 self.assertEqual(error_dict[dbus_service.utils.ERROR_TYPE],454 if len(a) == 1:
455 error_dict = a[0]
456 else:
457 an_id, error_dict = a
458 self.assertEqual(an_id, args[0])
459
460 self.assertEqual(error_dict[dbus_service.ERROR_TYPE],
457 'AssertionError')461 'AssertionError')
458 self.deferred.callback("success")462 self.deferred.callback("success")
459463
460 return super(OperationsErrorTestCase, self).assert_correct_method_call(464 return super(OperationsErrorTestCase, self).assert_correct_method_call(
461 error_sig, success_sig, got_error_signal, method, *args)465 error_sig, success_sig, got_error_signal, method, *args)
466
467
468class FileSyncTestCase(OperationsTestCase):
469 """Test for the DBus service when requesting file sync status."""
470
471 def assert_correct_status_signal(self, status, sync_signal,
472 error_signal="FileSyncStatusError",
473 expected_msg=None):
474 """The file sync status is reported properly."""
475 MockBackend.sample_status[dbus_service.STATUS_KEY] = status
476 if expected_msg is None:
477 expected_msg = MockBackend.sample_status[dbus_service.MSG_KEY]
478
479 def got_signal(msg):
480 """The correct status was received."""
481 self.assertEqual(msg, expected_msg)
482 self.deferred.callback("success")
483
484 args = (sync_signal, error_signal, got_signal,
485 self.backend.file_sync_status)
486 return self.assert_correct_method_call(*args)
487
488 def test_file_sync_status_unknown(self):
489 """The file sync status is reported properly."""
490 msg = MockBackend.sample_status
491 args = ('invalid-file-sync-status', "FileSyncStatusError",
492 "FileSyncStatusIdle", msg)
493 return self.assert_correct_status_signal(*args)
494
495 def test_file_sync_status_error(self):
496 """The file sync status is reported properly."""
497 msg = MockBackend.sample_status[dbus_service.MSG_KEY]
498 err_dict = {dbus_service.ERROR_TYPE: 'FileSyncStatusError',
499 dbus_service.ERROR_MESSAGE: msg}
500 args = (dbus_service.FILE_SYNC_ERROR, "FileSyncStatusError",
501 "FileSyncStatusIdle", err_dict)
502 return self.assert_correct_status_signal(*args)
503
504 def test_file_sync_status_disabled(self):
505 """The file sync status is reported properly."""
506 args = (dbus_service.FILE_SYNC_DISABLED, "FileSyncStatusDisabled")
507 return self.assert_correct_status_signal(*args)
508
509 def test_file_sync_status_starting(self):
510 """The file sync status is reported properly."""
511 args = (dbus_service.FILE_SYNC_STARTING, "FileSyncStatusStarting")
512 return self.assert_correct_status_signal(*args)
513
514 def test_file_sync_status_disconnected(self):
515 """The file sync status is reported properly."""
516 args = (dbus_service.FILE_SYNC_DISCONNECTED,
517 "FileSyncStatusDisconnected")
518 return self.assert_correct_status_signal(*args)
519
520 def test_file_sync_status_syncing(self):
521 """The file sync status is reported properly."""
522 args = (dbus_service.FILE_SYNC_SYNCING, "FileSyncStatusSyncing")
523 return self.assert_correct_status_signal(*args)
524
525 def test_file_sync_status_idle(self):
526 """The file sync status is reported properly."""
527 args = (dbus_service.FILE_SYNC_IDLE, "FileSyncStatusIdle")
528 return self.assert_correct_status_signal(*args)
529
530 def test_status_changed_handler(self):
531 """The status changed handler is properly set."""
532 be = MockBackend()
533 cpbe = dbus_service.ControlPanelBackend(backend=be)
534
535 self.assertEqual(be.status_changed_handler, cpbe.process_status)
462536
=== modified file 'ubuntuone/controlpanel/logger.py'
--- ubuntuone/controlpanel/logger.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/logger.py 2010-12-22 14:37:52 +0000
@@ -22,6 +22,7 @@
22import os22import os
23import sys23import sys
2424
25from functools import wraps
25from logging.handlers import RotatingFileHandler26from logging.handlers import RotatingFileHandler
2627
27# pylint: disable=F0401,E061128# pylint: disable=F0401,E0611
@@ -41,7 +42,7 @@
41MAIN_HANDLER.setLevel(LOG_LEVEL)42MAIN_HANDLER.setLevel(LOG_LEVEL)
4243
4344
44def setup_logging(log_domain):45def setup_logging(log_domain, prefix=None):
45 """Create a logger for 'log_domain'.46 """Create a logger for 'log_domain'.
4647
47 Final domain will be 'ubuntuone.controlpanel.<log_domain>.48 Final domain will be 'ubuntuone.controlpanel.<log_domain>.
@@ -52,6 +53,28 @@
52 logger.addHandler(MAIN_HANDLER)53 logger.addHandler(MAIN_HANDLER)
53 if os.environ.get('DEBUG'):54 if os.environ.get('DEBUG'):
54 debug_handler = logging.StreamHandler(sys.stderr)55 debug_handler = logging.StreamHandler(sys.stderr)
56 if prefix is not None:
57 fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n"
58 formatter = logging.Formatter(fmt)
59 debug_handler.setFormatter(formatter)
55 logger.addHandler(debug_handler)60 logger.addHandler(debug_handler)
5661
57 return logger62 return logger
63
64
65def log_call(log_func):
66 """Decorator to add log info using 'log_func'."""
67
68 def middle(f):
69 """Add logging when calling 'f'."""
70
71 @wraps(f)
72 def inner(*args, **kwargs):
73 """Call f(*args, **kwargs)."""
74 log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs)
75 res = f(*args, **kwargs)
76 return res
77
78 return inner
79
80 return middle
5881
=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/tests/test_backend.py 2010-12-22 14:37:52 +0000
@@ -23,10 +23,21 @@
2323
24from twisted.internet import defer24from twisted.internet import defer
25from twisted.internet.defer import inlineCallbacks25from twisted.internet.defer import inlineCallbacks
26from ubuntuone.devtools.handlers import MementoHandler
2627
27from ubuntuone.controlpanel import backend28from ubuntuone.controlpanel import backend
28from ubuntuone.controlpanel.backend import (ACCOUNT_API, QUOTA_API,29from ubuntuone.controlpanel.backend import (ACCOUNT_API,
29 DEVICES_API, DEVICE_REMOVE_API)30 DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
31 FILE_SYNC_DISABLED,
32 FILE_SYNC_DISCONNECTED,
33 FILE_SYNC_ERROR,
34 FILE_SYNC_IDLE,
35 FILE_SYNC_STARTING,
36 FILE_SYNC_SYNCING,
37 FILE_SYNC_UNKNOWN,
38 MSG_KEY, STATUS_KEY,
39)
40
30from ubuntuone.controlpanel.tests import TestCase41from ubuntuone.controlpanel.tests import TestCase
31from ubuntuone.controlpanel.webclient import WebClientError42from ubuntuone.controlpanel.webclient import WebClientError
3243
@@ -44,7 +55,7 @@
44 "dbpath": "u/abc/def/12345"55 "dbpath": "u/abc/def/12345"
45 },56 },
46 "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",57 "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
47 "email": "andrewpz@protocultura.net",58 "email": "andrewpz@protocultura.net",%s
48 "nickname": "Andrew P. Zoilo",59 "nickname": "Andrew P. Zoilo",
49 "id": 12345,60 "id": 12345,
50 "subscription": {61 "subscription": {
@@ -63,6 +74,13 @@
63}74}
64"""75"""
6576
77CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
78SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
79
80SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
81SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
82
83
66SAMPLE_QUOTA_JSON = """84SAMPLE_QUOTA_JSON = """
67{85{
68 "total": 53687091200,86 "total": 53687091200,
@@ -78,6 +96,14 @@
78 "email": "andrewpz@protocultura.net",96 "email": "andrewpz@protocultura.net",
79}97}
8098
99EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
100 "quota_used": "2350345156",
101 "quota_total": "53687091200",
102 "type": CURRENT_PLAN,
103 "name": "Andrew P. Zoilo",
104 "email": "andrewpz@protocultura.net",
105}
106
81SAMPLE_DEVICES_JSON = """107SAMPLE_DEVICES_JSON = """
82[108[
83 {109 {
@@ -103,12 +129,14 @@
103 "device_id": "ComputerABCDEF01234token",129 "device_id": "ComputerABCDEF01234token",
104 "name": "Ubuntu One @ darkstar",130 "name": "Ubuntu One @ darkstar",
105 "type": "Computer",131 "type": "Computer",
106 "configurable": "0",132 "is_local": '',
133 "configurable": '',
107 },134 },
108 {135 {
109 'configurable': '1',136 'is_local': 'True',
137 'configurable': 'True',
110 'device_id': 'ComputerABC1234DEF',138 'device_id': 'ComputerABC1234DEF',
111 'limit_bandwidth': "0",139 'limit_bandwidth': '',
112 'max_download_speed': '-1',140 'max_download_speed': '-1',
113 'max_upload_speed': '-1',141 'max_upload_speed': '-1',
114 'name': 'Ubuntu One @ localhost',142 'name': 'Ubuntu One @ localhost',
@@ -118,13 +146,49 @@
118 "device_id": "Phone1000",146 "device_id": "Phone1000",
119 "name": "Nokia E65",147 "name": "Nokia E65",
120 "type": "Phone",148 "type": "Phone",
121 "configurable": "0",149 "configurable": '',
150 "is_local": '',
122 },151 },
123]152]
124153
125SAMPLE_VOLUMES = [154SAMPLE_FOLDERS = [
126 {'volume_id': 'test1', 'path': '~/test1', 'subscribed': False},155 {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
127 {'volume_id': 'test2', 'path': '~/test2', 'subscribed': True},156 u'path': u'/home/tester/Public', u'subscribed': u'True',
157 u'suggested_path': u'~/Public',
158 u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
159 {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
160 u'path': u'/home/tester/Documents', u'subscribed': u'',
161 u'suggested_path': u'~/Documents',
162 u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
163 {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
164 u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
165 u'suggested_path': u'~/Pictures/Photos',
166 u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
167]
168
169SAMPLE_SHARES = [
170 {u'accepted': u'True', u'access_level': u'View',
171 u'free_bytes': u'39892622746', u'generation': u'2704',
172 u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
173 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
174 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
175 u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
176 {u'accepted': u'True', u'access_level': u'Modify',
177 u'free_bytes': u'39892622746', u'generation': u'2704',
178 u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
179 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
180 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
181 u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
182]
183
184SAMPLE_SHARED = [
185 {u'accepted': u'True', u'access_level': u'View',
186 u'free_bytes': u'', u'generation': u'',
187 u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
188 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
189 u'path': u'/home/tester/Ubuntu One/bar',
190 u'type': u'Shared',
191 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
128]192]
129193
130194
@@ -152,11 +216,24 @@
152 creds = SAMPLE_CREDENTIALS216 creds = SAMPLE_CREDENTIALS
153 throttling = False217 throttling = False
154 limits = {"download": -1, "upload": -1}218 limits = {"download": -1, "upload": -1}
219 file_sync = True
220 status = {
221 'name': 'TEST', 'queues': 'GORGEOUS', 'connection': '',
222 'description': 'Some test state, nothing else.',
223 'is_error': '', 'is_connected': 'True', 'is_online': '',
224 }
225 status_changed_handler = None
226 subscribed_folders = []
155227
156 def get_credentials(self):228 def get_credentials(self):
157 """Return the mock credentials."""229 """Return the mock credentials."""
158 return defer.succeed(self.creds)230 return defer.succeed(self.creds)
159231
232 def clear_credentials(self):
233 """Clear the mock credentials."""
234 MockDBusClient.creds = None
235 return defer.succeed(None)
236
160 def get_throttling_limits(self):237 def get_throttling_limits(self):
161 """Return the sample speed limits."""238 """Return the sample speed limits."""
162 return self.limits239 return self.limits
@@ -178,9 +255,41 @@
178 """Disable bw throttling."""255 """Disable bw throttling."""
179 self.throttling = False256 self.throttling = False
180257
181 def get_volumes(self):258 def files_sync_enabled(self):
182 """Grab list of folders and shares."""259 """Get if file sync service is enabled."""
183 return SAMPLE_VOLUMES260 return self.file_sync
261
262 def set_files_sync_enabled(self, enabled):
263 """Set the file sync service to be 'enabled'."""
264 self.file_sync = enabled
265
266 def get_folders(self):
267 """Grab list of folders."""
268 return SAMPLE_FOLDERS
269
270 def subscribe_folder(self, volume_id):
271 """Subcribe to 'volume_id'."""
272 self.subscribed_folders.append(volume_id)
273
274 def unsubscribe_folder(self, volume_id):
275 """Unsubcribe from 'volume_id'."""
276 self.subscribed_folders.remove(volume_id)
277
278 def get_current_status(self):
279 """Grab syncdaemon status."""
280 return self.status
281
282 def set_status_changed_handler(self, handler):
283 """Connect a handler for tracking syncdaemon status changes."""
284 self.status_changed_handler = handler
285
286 def get_shares(self):
287 """Grab list of shares (shares from others to the user)."""
288 return SAMPLE_SHARES
289
290 def get_shared(self):
291 """Grab list of shared (shares from the user to others)."""
292 return SAMPLE_SHARED
184293
185294
186class BackendBasicTestCase(TestCase):295class BackendBasicTestCase(TestCase):
@@ -189,11 +298,17 @@
189 timeout = 3298 timeout = 3
190299
191 def setUp(self):300 def setUp(self):
301 super(BackendBasicTestCase, self).setUp()
192 self.patch(backend, "WebClient", MockWebClient)302 self.patch(backend, "WebClient", MockWebClient)
193 self.patch(backend, "dbus_client", MockDBusClient())303 self.patch(backend, "dbus_client", MockDBusClient())
194 self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]304 self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]
195 self.be = backend.ControlBackend()305 self.be = backend.ControlBackend()
196306
307 self.memento = MementoHandler()
308 backend.logger.addHandler(self.memento)
309
310 MockDBusClient.creds = SAMPLE_CREDENTIALS
311
197 def test_backend_creation(self):312 def test_backend_creation(self):
198 """The backend instance is successfully created."""313 """The backend instance is successfully created."""
199 self.assertEqual(self.be.wc.__class__, MockWebClient)314 self.assertEqual(self.be.wc.__class__, MockWebClient)
@@ -204,6 +319,16 @@
204 token = yield self.be.get_token()319 token = yield self.be.get_token()
205 self.assertEqual(token, SAMPLE_CREDENTIALS["token"])320 self.assertEqual(token, SAMPLE_CREDENTIALS["token"])
206321
322 @inlineCallbacks
323 def test_device_is_local(self):
324 """The device_is_local returns the right result."""
325 result = yield self.be.device_is_local(self.local_token)
326 self.assertTrue(result)
327
328 did = EXPECTED_DEVICES_INFO[0]['device_id']
329 result = yield self.be.device_is_local(did)
330 self.assertFalse(result)
331
207332
208class BackendAccountTestCase(BackendBasicTestCase):333class BackendAccountTestCase(BackendBasicTestCase):
209 """Account tests for the backend."""334 """Account tests for the backend."""
@@ -212,12 +337,21 @@
212 def test_account_info(self):337 def test_account_info(self):
213 """The account_info method exercises its callback."""338 """The account_info method exercises its callback."""
214 # pylint: disable=E1101339 # pylint: disable=E1101
215 self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_JSON340 self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN
216 self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON341 self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
217 result = yield self.be.account_info()342 result = yield self.be.account_info()
218 self.assertEqual(result, EXPECTED_ACCOUNT_INFO)343 self.assertEqual(result, EXPECTED_ACCOUNT_INFO)
219344
220 @inlineCallbacks345 @inlineCallbacks
346 def test_account_info_with_current_plan(self):
347 """The account_info method exercises its callback."""
348 # pylint: disable=E1101
349 self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_WITH_CURRENT_PLAN
350 self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
351 result = yield self.be.account_info()
352 self.assertEqual(result, EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN)
353
354 @inlineCallbacks
221 def test_account_info_fails(self):355 def test_account_info_fails(self):
222 """The account_info method exercises its errback."""356 """The account_info method exercises its errback."""
223 # pylint: disable=E1101357 # pylint: disable=E1101
@@ -248,11 +382,23 @@
248 """The remove_device method calls the right api."""382 """The remove_device method calls the right api."""
249 dtype, did = "Computer", "SAMPLE-TOKEN"383 dtype, did = "Computer", "SAMPLE-TOKEN"
250 device_id = dtype + did384 device_id = dtype + did
251 apiurl = DEVICE_REMOVE_API % (dtype, did)385 apiurl = DEVICE_REMOVE_API % (dtype.lower(), did)
252 # pylint: disable=E1101386 # pylint: disable=E1101
253 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON387 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
254 result = yield self.be.remove_device(device_id)388 result = yield self.be.remove_device(device_id)
255 self.assertEqual(result, device_id)389 self.assertEqual(result, device_id)
390 # credentials were not cleared
391 self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)
392
393 @inlineCallbacks
394 def test_remove_device_clear_credentials_if_local_device(self):
395 """The remove_device method clears the credentials if is local."""
396 apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])
397 # pylint: disable=E1101
398 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
399 yield self.be.remove_device(self.local_token)
400 # credentials were cleared
401 self.assertEqual(MockDBusClient.creds, None)
256402
257 @inlineCallbacks403 @inlineCallbacks
258 def test_remove_device_fails(self):404 def test_remove_device_fails(self):
@@ -266,10 +412,10 @@
266 """The device settings are updated."""412 """The device settings are updated."""
267 backend.dbus_client.throttling = False413 backend.dbus_client.throttling = False
268 yield self.be.change_device_settings(self.local_token,414 yield self.be.change_device_settings(self.local_token,
269 {"limit_bandwidth": "1"})415 {"limit_bandwidth": 'True'})
270 self.assertEqual(backend.dbus_client.throttling, True)416 self.assertEqual(backend.dbus_client.throttling, True)
271 yield self.be.change_device_settings(self.local_token,417 yield self.be.change_device_settings(self.local_token,
272 {"limit_bandwidth": "0"})418 {"limit_bandwidth": ''})
273 self.assertEqual(backend.dbus_client.throttling, False)419 self.assertEqual(backend.dbus_client.throttling, False)
274420
275 @inlineCallbacks421 @inlineCallbacks
@@ -298,7 +444,7 @@
298 new_settings = {444 new_settings = {
299 "max_download_speed": "99",445 "max_download_speed": "99",
300 "max_upload_speed": "99",446 "max_upload_speed": "99",
301 "limit_bandwidth": "1",447 "limit_bandwidth": 'True',
302 }448 }
303 yield self.be.change_device_settings("wrong token!", new_settings)449 yield self.be.change_device_settings("wrong token!", new_settings)
304 self.assertEqual(backend.dbus_client.throttling, False)450 self.assertEqual(backend.dbus_client.throttling, False)
@@ -313,4 +459,197 @@
313 def test_volumes_info(self):459 def test_volumes_info(self):
314 """The volumes_info method exercises its callback."""460 """The volumes_info method exercises its callback."""
315 result = yield self.be.volumes_info()461 result = yield self.be.volumes_info()
316 self.assertEqual(result, SAMPLE_VOLUMES)462 # later, we should unify folders and shares info
463 self.assertEqual(result, SAMPLE_FOLDERS)
464
465 @inlineCallbacks
466 def test_subscribe_volume(self):
467 """The subscribe_volume method subscribes a folder."""
468 fid = '0123-4567'
469 yield self.be.subscribe_volume(volume_id=fid)
470 self.addCleanup(lambda: MockDBusClient.subscribed_folders.remove(fid))
471
472 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
473
474 @inlineCallbacks
475 def test_unsubscribe_volume(self):
476 """The unsubscribe_volume method unsubscribes a folder."""
477 fid = '0123-4567'
478 yield self.be.subscribe_volume(volume_id=fid)
479 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
480
481 yield self.be.unsubscribe_volume(volume_id=fid)
482
483 self.assertEqual(MockDBusClient.subscribed_folders, [])
484
485 @inlineCallbacks
486 def test_change_volume_settings(self):
487 """The volume settings can be changed."""
488 fid = '0123-4567'
489
490 yield self.be.change_volume_settings(fid, {'subscribed': True})
491 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
492
493 yield self.be.change_volume_settings(fid, {'subscribed': False})
494 self.assertEqual(MockDBusClient.subscribed_folders, [])
495
496 @inlineCallbacks
497 def test_change_volume_settings_no_setting(self):
498 """The change volume settings does not fail on empty settings."""
499 yield self.be.change_volume_settings('test', {})
500 self.assertEqual(MockDBusClient.subscribed_folders, [])
501
502
503class BackendSyncStatusTestCase(BackendBasicTestCase):
504 """Syncdaemon state for the backend."""
505
506 def _build_msg(self):
507 """Build expected message regarding file sync status."""
508 return '%s (%s)' % (MockDBusClient.status['description'],
509 MockDBusClient.status['name'])
510
511 @inlineCallbacks
512 def assert_correct_status(self, status, msg=None):
513 """Check that the resulting status is correct."""
514 expected = {MSG_KEY: self._build_msg() if msg is None else msg,
515 STATUS_KEY: status}
516 result = yield self.be.file_sync_status()
517 self.assertEqual(expected, result)
518
519 @inlineCallbacks
520 def test_disabled(self):
521 """The syncdaemon status is processed and emitted."""
522 self.patch(MockDBusClient, 'file_sync', False)
523 yield self.assert_correct_status(FILE_SYNC_DISABLED, msg='')
524
525 @inlineCallbacks
526 def test_error(self):
527 """The syncdaemon status is processed and emitted."""
528 MockDBusClient.status = {
529 'is_error': 'True', # nothing else matters
530 'is_online': '', 'is_connected': '',
531 'name': 'AUTH_FAILED', 'connection': '', 'queues': '',
532 'description': 'auth failed',
533 }
534 yield self.assert_correct_status(FILE_SYNC_ERROR)
535
536 @inlineCallbacks
537 def test_starting_when_init_not_user(self):
538 """The syncdaemon status is processed and emitted."""
539 MockDBusClient.status = {
540 'is_error': '', 'is_online': '', 'is_connected': '',
541 'connection': 'Not User With Network', 'queues': '',
542 'name': 'INIT', 'description': 'something new',
543 }
544 yield self.assert_correct_status(FILE_SYNC_STARTING)
545
546 @inlineCallbacks
547 def test_starting_when_init_with_user(self):
548 """The syncdaemon status is processed and emitted."""
549 MockDBusClient.status = {
550 'is_error': '', 'is_online': '', 'is_connected': '',
551 'connection': 'With User With Network', 'queues': '',
552 'name': 'INIT', 'description': 'something new',
553 }
554 yield self.assert_correct_status(FILE_SYNC_STARTING)
555
556 @inlineCallbacks
557 def test_starting_when_local_rescan_not_user(self):
558 """The syncdaemon status is processed and emitted."""
559 MockDBusClient.status = {
560 'is_error': '', 'is_online': '', 'is_connected': '',
561 'connection': 'Not User With Network', 'queues': '',
562 'name': 'LOCAL_RESCAN', 'description': 'something new',
563 }
564 yield self.assert_correct_status(FILE_SYNC_STARTING)
565
566 @inlineCallbacks
567 def test_starting_when_local_rescan_with_user(self):
568 """The syncdaemon status is processed and emitted."""
569 MockDBusClient.status = {
570 'is_error': '', 'is_online': '', 'is_connected': '',
571 'connection': 'With User With Network', 'queues': '',
572 'name': 'LOCAL_RESCAN', 'description': 'something new',
573 }
574 yield self.assert_correct_status(FILE_SYNC_STARTING)
575
576 @inlineCallbacks
577 def test_starting_when_ready_with_user(self):
578 """The syncdaemon status is processed and emitted."""
579 MockDBusClient.status = {
580 'is_error': '', 'is_online': '', 'is_connected': '',
581 'connection': 'With User With Network', 'queues': '',
582 'name': 'READY', 'description': 'something nicer',
583 }
584 yield self.assert_correct_status(FILE_SYNC_STARTING)
585
586 @inlineCallbacks
587 def test_disconnected(self):
588 """The syncdaemon status is processed and emitted."""
589 MockDBusClient.status = {
590 'is_error': '', 'is_online': '', 'is_connected': '',
591 'queues': '', 'description': 'something new',
592 'connection': 'Not User With Network', # user didn't connect
593 'name': 'READY', # must be READY, otherwise is STARTING
594 }
595 yield self.assert_correct_status(FILE_SYNC_DISCONNECTED)
596
597 @inlineCallbacks
598 def test_syncing_if_online(self):
599 """The syncdaemon status is processed and emitted."""
600 MockDBusClient.status = {
601 'is_error': '', 'is_online': 'True', 'is_connected': 'True',
602 'name': 'QUEUE_MANAGER', 'connection': '',
603 'queues': 'WORKING_ON_CONTENT', # anything but IDLE
604 'description': 'something nicer',
605 }
606 yield self.assert_correct_status(FILE_SYNC_SYNCING)
607
608 @inlineCallbacks
609 def test_syncing_even_if_not_online(self):
610 """The syncdaemon status is processed and emitted."""
611 MockDBusClient.status = {
612 'is_error': '', 'is_online': '', 'is_connected': 'True',
613 'name': 'CHECK_VERSION', 'connection': '',
614 'queues': 'WORKING_ON_CONTENT',
615 'description': 'something nicer',
616 }
617 yield self.assert_correct_status(FILE_SYNC_SYNCING)
618
619 @inlineCallbacks
620 def test_idle(self):
621 """The syncdaemon status is processed and emitted."""
622 MockDBusClient.status = {
623 'is_error': '', 'is_online': 'True', 'is_connected': 'True',
624 'name': 'QUEUE_MANAGER', 'connection': '',
625 'queues': 'IDLE',
626 'description': 'something nice',
627 }
628 yield self.assert_correct_status(FILE_SYNC_IDLE)
629
630 @inlineCallbacks
631 def test_unknown(self):
632 """The syncdaemon status is processed and emitted."""
633 MockDBusClient.status = {
634 'is_error': '', 'is_online': '', 'is_connected': '',
635 'name': '', 'connection': '', 'queues': '',
636 'description': '',
637 }
638 yield self.assert_correct_status(FILE_SYNC_UNKNOWN)
639
640 has_warning = self.memento.check_warning('file_sync_status: unknown',
641 repr(MockDBusClient.status))
642 self.assertTrue(has_warning)
643
644 def test_status_changed(self):
645 """The file_sync_status is the status changed handler."""
646 self.be.status_changed_handler = self._set_called
647 status = {'name': 'foo', 'description': 'bar', 'is_error': '',
648 'is_connected': '', 'is_online': '', 'queues': ''}
649 # pylint: disable=E1101
650 backend.dbus_client.status_changed_handler(status)
651
652 # pylint: disable=W0212
653 # Access to a protected member _process_file_sync_status
654 expected_status = self.be._process_file_sync_status(status)
655 self.assertEqual(self._called, ((expected_status,), {}))
317656
=== modified file 'ubuntuone/controlpanel/utils.py'
--- ubuntuone/controlpanel/utils.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/utils.py 2010-12-22 14:37:52 +0000
@@ -20,8 +20,6 @@
2020
21import os21import os
2222
23from functools import wraps
24
25import dbus23import dbus
2624
27from ubuntuone.controlpanel.logger import setup_logging25from ubuntuone.controlpanel.logger import setup_logging
@@ -68,24 +66,6 @@
68 return os.path.join(get_project_dir(), filename)66 return os.path.join(get_project_dir(), filename)
6967
7068
71def log_call(log_func):
72 """Decorator to add log info using 'log_func'."""
73
74 def middle(f):
75 """Add logging when calling 'f'."""
76
77 @wraps(f)
78 def inner(*args, **kwargs):
79 """Call f(*args, **kwargs)."""
80 log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs)
81 res = f(*args, **kwargs)
82 return res
83
84 return inner
85
86 return middle
87
88
89def is_dbus_no_reply(failure):69def is_dbus_no_reply(failure):
90 """Decide if 'failure' is a DBus NoReply Error."""70 """Decide if 'failure' is a DBus NoReply Error."""
91 exc = failure.value71 exc = failure.value
9272
=== modified file 'ubuntuone/controlpanel/webclient.py'
--- ubuntuone/controlpanel/webclient.py 2010-12-06 12:27:11 +0000
+++ ubuntuone/controlpanel/webclient.py 2010-12-22 14:37:52 +0000
@@ -48,22 +48,24 @@
4848
49 def _handler(self, session, msg, d):49 def _handler(self, session, msg, d):
50 """Handle the result of an http message."""50 """Handle the result of an http message."""
51 logger.debug("WebClient: got http response: %d", msg.status_code)51 logger.debug("WebClient: got http response %d for uri %r",
52 msg.status_code, msg.get_uri().to_string(False))
53 data = msg.response_body.data
52 if msg.status_code == 200:54 if msg.status_code == 200:
53 result = simplejson.loads(msg.response_body.data)55 result = simplejson.loads(data)
54 d.callback(result)56 d.callback(result)
55 else:57 else:
56 e = WebClientError(msg.status_code, msg)58 e = WebClientError(msg.status_code, data)
57 d.errback(e)59 d.errback(e)
5860
59 def _call_api_with_creds(self, credentials, api_name):61 def _call_api_with_creds(self, credentials, api_name):
60 """Get a given url from the webservice with credentials."""62 """Get a given url from the webservice with credentials."""
61 url = self.base_url + api_name63 url = (self.base_url + api_name).encode('utf-8')
62 method = "GET"64 method = "GET"
65 logger.debug("WebClient: getting url: %s, %s", method, url)
63 msg = Soup.Message.new(method, url)66 msg = Soup.Message.new(method, url)
64 add_oauth_headers(msg.request_headers.append, method, url, credentials)67 add_oauth_headers(msg.request_headers.append, method, url, credentials)
65 d = defer.Deferred()68 d = defer.Deferred()
66 logger.debug("WebClient: getting url: %s", url)
67 self.session.queue_message(msg, self._handler, d)69 self.session.queue_message(msg, self._handler, d)
68 return d70 return d
6971

Subscribers

People subscribed via source and target branches