Merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.1.0 into lp:ubuntu/natty/ubuntuone-control-panel
- Natty (11.04)
- ubuntuone-control-panel-0.1.0
- Merge into natty
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Terry | Approve | ||
Review via email: mp+44468@code.launchpad.net |
Commit message
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).*
VolumesIn
* 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.
Preview Diff
1 | === modified file 'PKG-INFO' | |||
2 | --- PKG-INFO 2010-12-06 12:27:11 +0000 | |||
3 | +++ PKG-INFO 2010-12-22 14:37:52 +0000 | |||
4 | @@ -1,6 +1,6 @@ | |||
5 | 1 | Metadata-Version: 1.1 | 1 | Metadata-Version: 1.1 |
6 | 2 | Name: ubuntuone-control-panel | 2 | Name: ubuntuone-control-panel |
8 | 3 | Version: 0.0.9 | 3 | Version: 0.1.0 |
9 | 4 | Summary: Ubuntu One Control Panel | 4 | Summary: Ubuntu One Control Panel |
10 | 5 | Home-page: https://launchpad.net/ubuntuone-control-panel | 5 | Home-page: https://launchpad.net/ubuntuone-control-panel |
11 | 6 | Author: Natalia Bidart | 6 | Author: Natalia Bidart |
12 | 7 | 7 | ||
13 | === modified file 'bin/ubuntuone-control-panel-backend' | |||
14 | --- bin/ubuntuone-control-panel-backend 2010-12-06 12:27:11 +0000 | |||
15 | +++ bin/ubuntuone-control-panel-backend 2010-12-22 14:37:52 +0000 | |||
16 | @@ -18,6 +18,8 @@ | |||
17 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 19 | """Execute the DBus backend for the Ubuntu One control panel.""" | 19 | """Execute the DBus backend for the Ubuntu One control panel.""" |
19 | 20 | 20 | ||
20 | 21 | # Invalid name "ubuntuone-control-panel-backend", pylint: disable=C0103 | ||
21 | 22 | |||
22 | 21 | 23 | ||
23 | 22 | from ubuntuone.controlpanel import dbus_service | 24 | from ubuntuone.controlpanel import dbus_service |
24 | 23 | 25 | ||
25 | 24 | 26 | ||
26 | === modified file 'bin/ubuntuone-control-panel-gtk' | |||
27 | --- bin/ubuntuone-control-panel-gtk 2010-12-06 12:27:11 +0000 | |||
28 | +++ bin/ubuntuone-control-panel-gtk 2010-12-22 14:37:52 +0000 | |||
29 | @@ -18,7 +18,7 @@ | |||
30 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
31 | 19 | """Execute the graphical interface for the Ubuntu One control panel.""" | 19 | """Execute the graphical interface for the Ubuntu One control panel.""" |
32 | 20 | 20 | ||
34 | 21 | import gtk | 21 | # Invalid name "ubuntuone-control-panel-gtk", pylint: disable=C0103 |
35 | 22 | 22 | ||
36 | 23 | import dbus.mainloop.glib | 23 | import dbus.mainloop.glib |
37 | 24 | 24 | ||
38 | @@ -26,6 +26,7 @@ | |||
39 | 26 | 26 | ||
40 | 27 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) | 27 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
41 | 28 | 28 | ||
42 | 29 | |||
43 | 29 | if __name__ == "__main__": | 30 | if __name__ == "__main__": |
46 | 30 | ui = ubuntuone.controlpanel.gtk.gui.ControlPanelWindow() | 31 | gui = ubuntuone.controlpanel.gtk.gui.ControlPanelWindow() |
47 | 31 | ui.main() | 32 | gui.main() |
48 | 32 | 33 | ||
49 | === modified file 'data/account.ui' | |||
50 | --- data/account.ui 2010-12-06 12:27:11 +0000 | |||
51 | +++ data/account.ui 2010-12-22 14:37:52 +0000 | |||
52 | @@ -9,26 +9,107 @@ | |||
53 | 9 | <child> | 9 | <child> |
54 | 10 | <object class="GtkHBox" id="hbox1"> | 10 | <object class="GtkHBox" id="hbox1"> |
55 | 11 | <property name="visible">True</property> | 11 | <property name="visible">True</property> |
57 | 12 | <property name="spacing">15</property> | 12 | <property name="spacing">10</property> |
58 | 13 | <child> | 13 | <child> |
60 | 14 | <object class="GtkVBox" id="vbox1"> | 14 | <object class="GtkVBox" id="account"> |
61 | 15 | <property name="visible">True</property> | 15 | <property name="visible">True</property> |
62 | 16 | <property name="spacing">10</property> | 16 | <property name="spacing">10</property> |
63 | 17 | <child> | 17 | <child> |
65 | 18 | <object class="GtkHBox" id="name_box"> | 18 | <object class="GtkVBox" id="data"> |
66 | 19 | <property name="visible">True</property> | 19 | <property name="visible">True</property> |
68 | 20 | <property name="spacing">5</property> | 20 | <property name="spacing">10</property> |
69 | 21 | <child> | 21 | <child> |
71 | 22 | <object class="GtkLabel" id="name_label"> | 22 | <object class="GtkVBox" id="vbox1"> |
72 | 23 | <property name="visible">True</property> | 23 | <property name="visible">True</property> |
75 | 24 | <property name="xalign">0</property> | 24 | <child> |
76 | 25 | <property name="label">Name:</property> | 25 | <object class="GtkLabel" id="label1"> |
77 | 26 | <property name="visible">True</property> | ||
78 | 27 | <property name="xalign">0</property> | ||
79 | 28 | <property name="label" translatable="yes"><b>Name:</b></property> | ||
80 | 29 | <property name="use_markup">True</property> | ||
81 | 30 | </object> | ||
82 | 31 | <packing> | ||
83 | 32 | <property name="position">0</property> | ||
84 | 33 | </packing> | ||
85 | 34 | </child> | ||
86 | 35 | <child> | ||
87 | 36 | <object class="GtkLabel" id="name_label"> | ||
88 | 37 | <property name="visible">True</property> | ||
89 | 38 | <property name="xalign">0</property> | ||
90 | 39 | <property name="xpad">12</property> | ||
91 | 40 | <property name="label">tester name</property> | ||
92 | 41 | <property name="use_markup">True</property> | ||
93 | 42 | </object> | ||
94 | 43 | <packing> | ||
95 | 44 | <property name="expand">False</property> | ||
96 | 45 | <property name="position">1</property> | ||
97 | 46 | </packing> | ||
98 | 47 | </child> | ||
99 | 26 | </object> | 48 | </object> |
100 | 27 | <packing> | 49 | <packing> |
101 | 28 | <property name="expand">False</property> | ||
102 | 29 | <property name="position">0</property> | 50 | <property name="position">0</property> |
103 | 30 | </packing> | 51 | </packing> |
104 | 31 | </child> | 52 | </child> |
105 | 53 | <child> | ||
106 | 54 | <object class="GtkVBox" id="vbox2"> | ||
107 | 55 | <property name="visible">True</property> | ||
108 | 56 | <child> | ||
109 | 57 | <object class="GtkLabel" id="label3"> | ||
110 | 58 | <property name="visible">True</property> | ||
111 | 59 | <property name="xalign">0</property> | ||
112 | 60 | <property name="label" translatable="yes"><b>Account type:</b></property> | ||
113 | 61 | <property name="use_markup">True</property> | ||
114 | 62 | </object> | ||
115 | 63 | <packing> | ||
116 | 64 | <property name="position">0</property> | ||
117 | 65 | </packing> | ||
118 | 66 | </child> | ||
119 | 67 | <child> | ||
120 | 68 | <object class="GtkLabel" id="type_label"> | ||
121 | 69 | <property name="visible">True</property> | ||
122 | 70 | <property name="xalign">0</property> | ||
123 | 71 | <property name="xpad">12</property> | ||
124 | 72 | <property name="label">22GB awesomeness</property> | ||
125 | 73 | </object> | ||
126 | 74 | <packing> | ||
127 | 75 | <property name="position">1</property> | ||
128 | 76 | </packing> | ||
129 | 77 | </child> | ||
130 | 78 | </object> | ||
131 | 79 | <packing> | ||
132 | 80 | <property name="position">1</property> | ||
133 | 81 | </packing> | ||
134 | 82 | </child> | ||
135 | 83 | <child> | ||
136 | 84 | <object class="GtkVBox" id="vbox3"> | ||
137 | 85 | <property name="visible">True</property> | ||
138 | 86 | <child> | ||
139 | 87 | <object class="GtkLabel" id="label2"> | ||
140 | 88 | <property name="visible">True</property> | ||
141 | 89 | <property name="xalign">0</property> | ||
142 | 90 | <property name="label" translatable="yes"><b>Email address:</b></property> | ||
143 | 91 | <property name="use_markup">True</property> | ||
144 | 92 | </object> | ||
145 | 93 | <packing> | ||
146 | 94 | <property name="position">0</property> | ||
147 | 95 | </packing> | ||
148 | 96 | </child> | ||
149 | 97 | <child> | ||
150 | 98 | <object class="GtkLabel" id="email_label"> | ||
151 | 99 | <property name="visible">True</property> | ||
152 | 100 | <property name="xalign">0</property> | ||
153 | 101 | <property name="xpad">12</property> | ||
154 | 102 | <property name="label">a@example.com</property> | ||
155 | 103 | </object> | ||
156 | 104 | <packing> | ||
157 | 105 | <property name="position">1</property> | ||
158 | 106 | </packing> | ||
159 | 107 | </child> | ||
160 | 108 | </object> | ||
161 | 109 | <packing> | ||
162 | 110 | <property name="position">2</property> | ||
163 | 111 | </packing> | ||
164 | 112 | </child> | ||
165 | 32 | </object> | 113 | </object> |
166 | 33 | <packing> | 114 | <packing> |
167 | 34 | <property name="expand">False</property> | 115 | <property name="expand">False</property> |
168 | @@ -36,48 +117,6 @@ | |||
169 | 36 | </packing> | 117 | </packing> |
170 | 37 | </child> | 118 | </child> |
171 | 38 | <child> | 119 | <child> |
172 | 39 | <object class="GtkHBox" id="type_box"> | ||
173 | 40 | <property name="visible">True</property> | ||
174 | 41 | <property name="spacing">5</property> | ||
175 | 42 | <child> | ||
176 | 43 | <object class="GtkLabel" id="label2"> | ||
177 | 44 | <property name="visible">True</property> | ||
178 | 45 | <property name="xalign">0</property> | ||
179 | 46 | <property name="label" translatable="yes">Account type:</property> | ||
180 | 47 | </object> | ||
181 | 48 | <packing> | ||
182 | 49 | <property name="expand">False</property> | ||
183 | 50 | <property name="position">0</property> | ||
184 | 51 | </packing> | ||
185 | 52 | </child> | ||
186 | 53 | </object> | ||
187 | 54 | <packing> | ||
188 | 55 | <property name="expand">False</property> | ||
189 | 56 | <property name="position">1</property> | ||
190 | 57 | </packing> | ||
191 | 58 | </child> | ||
192 | 59 | <child> | ||
193 | 60 | <object class="GtkHBox" id="email_box"> | ||
194 | 61 | <property name="visible">True</property> | ||
195 | 62 | <property name="spacing">5</property> | ||
196 | 63 | <child> | ||
197 | 64 | <object class="GtkLabel" id="label3"> | ||
198 | 65 | <property name="visible">True</property> | ||
199 | 66 | <property name="xalign">0</property> | ||
200 | 67 | <property name="label" translatable="yes">Email address:</property> | ||
201 | 68 | </object> | ||
202 | 69 | <packing> | ||
203 | 70 | <property name="expand">False</property> | ||
204 | 71 | <property name="position">0</property> | ||
205 | 72 | </packing> | ||
206 | 73 | </child> | ||
207 | 74 | </object> | ||
208 | 75 | <packing> | ||
209 | 76 | <property name="expand">False</property> | ||
210 | 77 | <property name="position">2</property> | ||
211 | 78 | </packing> | ||
212 | 79 | </child> | ||
213 | 80 | <child> | ||
214 | 81 | <object class="GtkHButtonBox" id="hbuttonbox1"> | 120 | <object class="GtkHButtonBox" id="hbuttonbox1"> |
215 | 82 | <property name="layout_style">center</property> | 121 | <property name="layout_style">center</property> |
216 | 83 | <child> | 122 | <child> |
217 | @@ -97,7 +136,7 @@ | |||
218 | 97 | <packing> | 136 | <packing> |
219 | 98 | <property name="expand">False</property> | 137 | <property name="expand">False</property> |
220 | 99 | <property name="pack_type">end</property> | 138 | <property name="pack_type">end</property> |
222 | 100 | <property name="position">3</property> | 139 | <property name="position">1</property> |
223 | 101 | </packing> | 140 | </packing> |
224 | 102 | </child> | 141 | </child> |
225 | 103 | </object> | 142 | </object> |
226 | @@ -108,6 +147,7 @@ | |||
227 | 108 | <child> | 147 | <child> |
228 | 109 | <object class="GtkVButtonBox" id="vbuttonbox1"> | 148 | <object class="GtkVButtonBox" id="vbuttonbox1"> |
229 | 110 | <property name="visible">True</property> | 149 | <property name="visible">True</property> |
230 | 150 | <property name="spacing">10</property> | ||
231 | 111 | <property name="layout_style">center</property> | 151 | <property name="layout_style">center</property> |
232 | 112 | <child> | 152 | <child> |
233 | 113 | <object class="GtkLinkButton" id="linkbutton1"> | 153 | <object class="GtkLinkButton" id="linkbutton1"> |
234 | @@ -144,6 +184,7 @@ | |||
235 | 144 | </object> | 184 | </object> |
236 | 145 | <packing> | 185 | <packing> |
237 | 146 | <property name="expand">False</property> | 186 | <property name="expand">False</property> |
238 | 187 | <property name="pack_type">end</property> | ||
239 | 147 | <property name="position">1</property> | 188 | <property name="position">1</property> |
240 | 148 | </packing> | 189 | </packing> |
241 | 149 | </child> | 190 | </child> |
242 | 150 | 191 | ||
243 | === removed file 'data/controlpanel.ui' | |||
244 | --- data/controlpanel.ui 2010-12-06 12:27:11 +0000 | |||
245 | +++ data/controlpanel.ui 1970-01-01 00:00:00 +0000 | |||
246 | @@ -1,17 +0,0 @@ | |||
247 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
248 | 2 | <interface> | ||
249 | 3 | <requires lib="gtk+" version="2.16"/> | ||
250 | 4 | <!-- interface-naming-policy project-wide --> | ||
251 | 5 | <object class="GtkVBox" id="itself"> | ||
252 | 6 | <property name="visible">True</property> | ||
253 | 7 | <child> | ||
254 | 8 | <placeholder/> | ||
255 | 9 | </child> | ||
256 | 10 | <child> | ||
257 | 11 | <placeholder/> | ||
258 | 12 | </child> | ||
259 | 13 | <child> | ||
260 | 14 | <placeholder/> | ||
261 | 15 | </child> | ||
262 | 16 | </object> | ||
263 | 17 | </interface> | ||
264 | 18 | 0 | ||
265 | === added file 'data/device.ui' | |||
266 | --- data/device.ui 1970-01-01 00:00:00 +0000 | |||
267 | +++ data/device.ui 2010-12-22 14:37:52 +0000 | |||
268 | @@ -0,0 +1,203 @@ | |||
269 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
270 | 2 | <interface> | ||
271 | 3 | <requires lib="gtk+" version="2.16"/> | ||
272 | 4 | <!-- interface-naming-policy project-wide --> | ||
273 | 5 | <object class="GtkAdjustment" id="adjustment2"> | ||
274 | 6 | <property name="upper">10000</property> | ||
275 | 7 | <property name="step_increment">1</property> | ||
276 | 8 | </object> | ||
277 | 9 | <object class="GtkAdjustment" id="adjustment1"> | ||
278 | 10 | <property name="upper">10000</property> | ||
279 | 11 | <property name="step_increment">1</property> | ||
280 | 12 | </object> | ||
281 | 13 | <object class="GtkVBox" id="itself"> | ||
282 | 14 | <property name="visible">True</property> | ||
283 | 15 | <property name="spacing">5</property> | ||
284 | 16 | <child> | ||
285 | 17 | <object class="GtkHBox" id="hbox1"> | ||
286 | 18 | <property name="visible">True</property> | ||
287 | 19 | <property name="spacing">15</property> | ||
288 | 20 | <child> | ||
289 | 21 | <object class="GtkVBox" id="vbox1"> | ||
290 | 22 | <property name="visible">True</property> | ||
291 | 23 | <property name="spacing">5</property> | ||
292 | 24 | <child> | ||
293 | 25 | <object class="GtkHBox" id="hbox2"> | ||
294 | 26 | <property name="visible">True</property> | ||
295 | 27 | <child> | ||
296 | 28 | <object class="GtkImage" id="device_type"> | ||
297 | 29 | <property name="visible">True</property> | ||
298 | 30 | <property name="icon_name">computer</property> | ||
299 | 31 | </object> | ||
300 | 32 | <packing> | ||
301 | 33 | <property name="expand">False</property> | ||
302 | 34 | <property name="position">0</property> | ||
303 | 35 | </packing> | ||
304 | 36 | </child> | ||
305 | 37 | <child> | ||
306 | 38 | <object class="GtkLabel" id="device_name"> | ||
307 | 39 | <property name="visible">True</property> | ||
308 | 40 | <property name="xalign">0</property> | ||
309 | 41 | <property name="xpad">5</property> | ||
310 | 42 | <property name="label">My Laptop</property> | ||
311 | 43 | </object> | ||
312 | 44 | <packing> | ||
313 | 45 | <property name="position">1</property> | ||
314 | 46 | </packing> | ||
315 | 47 | </child> | ||
316 | 48 | <child> | ||
317 | 49 | <object class="GtkLabel" id="device_id"> | ||
318 | 50 | <property name="label" translatable="yes">device id (hidden)</property> | ||
319 | 51 | </object> | ||
320 | 52 | <packing> | ||
321 | 53 | <property name="expand">False</property> | ||
322 | 54 | <property name="position">2</property> | ||
323 | 55 | </packing> | ||
324 | 56 | </child> | ||
325 | 57 | <child> | ||
326 | 58 | <object class="GtkLabel" id="date_added"> | ||
327 | 59 | <property name="label">30.02.09</property> | ||
328 | 60 | </object> | ||
329 | 61 | <packing> | ||
330 | 62 | <property name="expand">False</property> | ||
331 | 63 | <property name="position">3</property> | ||
332 | 64 | </packing> | ||
333 | 65 | </child> | ||
334 | 66 | </object> | ||
335 | 67 | <packing> | ||
336 | 68 | <property name="position">0</property> | ||
337 | 69 | </packing> | ||
338 | 70 | </child> | ||
339 | 71 | <child> | ||
340 | 72 | <object class="GtkTable" id="throttling"> | ||
341 | 73 | <property name="visible">True</property> | ||
342 | 74 | <property name="n_rows">3</property> | ||
343 | 75 | <property name="n_columns">2</property> | ||
344 | 76 | <property name="column_spacing">3</property> | ||
345 | 77 | <property name="row_spacing">3</property> | ||
346 | 78 | <child> | ||
347 | 79 | <object class="GtkCheckButton" id="limit_bandwidth"> | ||
348 | 80 | <property name="label" translatable="yes">Limit bandwidth usage</property> | ||
349 | 81 | <property name="visible">True</property> | ||
350 | 82 | <property name="can_focus">True</property> | ||
351 | 83 | <property name="receives_default">False</property> | ||
352 | 84 | <property name="draw_indicator">True</property> | ||
353 | 85 | <signal name="toggled" handler="on_limit_bandwidth_toggled" swapped="no"/> | ||
354 | 86 | </object> | ||
355 | 87 | </child> | ||
356 | 88 | <child> | ||
357 | 89 | <object class="GtkLabel" id="max_upload_speed_label"> | ||
358 | 90 | <property name="visible">True</property> | ||
359 | 91 | <property name="xalign">1</property> | ||
360 | 92 | <property name="xpad">5</property> | ||
361 | 93 | <property name="label" translatable="yes">Max upload speed (KiB/s)</property> | ||
362 | 94 | </object> | ||
363 | 95 | <packing> | ||
364 | 96 | <property name="top_attach">1</property> | ||
365 | 97 | <property name="bottom_attach">2</property> | ||
366 | 98 | </packing> | ||
367 | 99 | </child> | ||
368 | 100 | <child> | ||
369 | 101 | <object class="GtkLabel" id="max_download_speed_label"> | ||
370 | 102 | <property name="visible">True</property> | ||
371 | 103 | <property name="xalign">1</property> | ||
372 | 104 | <property name="xpad">5</property> | ||
373 | 105 | <property name="label" translatable="yes">Max download speed (KiB/s)</property> | ||
374 | 106 | </object> | ||
375 | 107 | <packing> | ||
376 | 108 | <property name="top_attach">2</property> | ||
377 | 109 | <property name="bottom_attach">3</property> | ||
378 | 110 | </packing> | ||
379 | 111 | </child> | ||
380 | 112 | <child> | ||
381 | 113 | <object class="GtkSpinButton" id="max_upload_speed"> | ||
382 | 114 | <property name="visible">True</property> | ||
383 | 115 | <property name="can_focus">True</property> | ||
384 | 116 | <property name="invisible_char">•</property> | ||
385 | 117 | <property name="activates_default">True</property> | ||
386 | 118 | <property name="adjustment">adjustment1</property> | ||
387 | 119 | <signal name="value-changed" handler="on_max_upload_speed_value_changed" swapped="no"/> | ||
388 | 120 | </object> | ||
389 | 121 | <packing> | ||
390 | 122 | <property name="left_attach">1</property> | ||
391 | 123 | <property name="right_attach">2</property> | ||
392 | 124 | <property name="top_attach">1</property> | ||
393 | 125 | <property name="bottom_attach">2</property> | ||
394 | 126 | <property name="x_options">GTK_FILL</property> | ||
395 | 127 | <property name="y_options">GTK_FILL</property> | ||
396 | 128 | </packing> | ||
397 | 129 | </child> | ||
398 | 130 | <child> | ||
399 | 131 | <object class="GtkSpinButton" id="max_download_speed"> | ||
400 | 132 | <property name="visible">True</property> | ||
401 | 133 | <property name="can_focus">True</property> | ||
402 | 134 | <property name="invisible_char">•</property> | ||
403 | 135 | <property name="activates_default">True</property> | ||
404 | 136 | <property name="adjustment">adjustment2</property> | ||
405 | 137 | <signal name="value-changed" handler="on_max_download_speed_value_changed" swapped="no"/> | ||
406 | 138 | </object> | ||
407 | 139 | <packing> | ||
408 | 140 | <property name="left_attach">1</property> | ||
409 | 141 | <property name="right_attach">2</property> | ||
410 | 142 | <property name="top_attach">2</property> | ||
411 | 143 | <property name="bottom_attach">3</property> | ||
412 | 144 | </packing> | ||
413 | 145 | </child> | ||
414 | 146 | <child> | ||
415 | 147 | <placeholder/> | ||
416 | 148 | </child> | ||
417 | 149 | </object> | ||
418 | 150 | <packing> | ||
419 | 151 | <property name="expand">False</property> | ||
420 | 152 | <property name="position">1</property> | ||
421 | 153 | </packing> | ||
422 | 154 | </child> | ||
423 | 155 | </object> | ||
424 | 156 | <packing> | ||
425 | 157 | <property name="expand">False</property> | ||
426 | 158 | <property name="position">0</property> | ||
427 | 159 | </packing> | ||
428 | 160 | </child> | ||
429 | 161 | <child> | ||
430 | 162 | <object class="GtkVButtonBox" id="vbuttonbox1"> | ||
431 | 163 | <property name="visible">True</property> | ||
432 | 164 | <property name="layout_style">start</property> | ||
433 | 165 | <child> | ||
434 | 166 | <object class="GtkButton" id="remove"> | ||
435 | 167 | <property name="label">gtk-remove</property> | ||
436 | 168 | <property name="visible">True</property> | ||
437 | 169 | <property name="can_focus">True</property> | ||
438 | 170 | <property name="receives_default">True</property> | ||
439 | 171 | <property name="use_stock">True</property> | ||
440 | 172 | <signal name="activate" handler="on_remove_clicked" swapped="no"/> | ||
441 | 173 | <signal name="clicked" handler="on_remove_clicked" swapped="no"/> | ||
442 | 174 | </object> | ||
443 | 175 | <packing> | ||
444 | 176 | <property name="expand">False</property> | ||
445 | 177 | <property name="fill">False</property> | ||
446 | 178 | <property name="position">0</property> | ||
447 | 179 | </packing> | ||
448 | 180 | </child> | ||
449 | 181 | </object> | ||
450 | 182 | <packing> | ||
451 | 183 | <property name="expand">False</property> | ||
452 | 184 | <property name="pack_type">end</property> | ||
453 | 185 | <property name="position">1</property> | ||
454 | 186 | </packing> | ||
455 | 187 | </child> | ||
456 | 188 | </object> | ||
457 | 189 | <packing> | ||
458 | 190 | <property name="expand">False</property> | ||
459 | 191 | <property name="position">0</property> | ||
460 | 192 | </packing> | ||
461 | 193 | </child> | ||
462 | 194 | <child> | ||
463 | 195 | <object class="GtkLabel" id="warning_label"> | ||
464 | 196 | <property name="visible">True</property> | ||
465 | 197 | </object> | ||
466 | 198 | <packing> | ||
467 | 199 | <property name="position">1</property> | ||
468 | 200 | </packing> | ||
469 | 201 | </child> | ||
470 | 202 | </object> | ||
471 | 203 | </interface> | ||
472 | 0 | 204 | ||
473 | === modified file 'data/devices.ui' | |||
474 | --- data/devices.ui 2010-12-06 12:27:11 +0000 | |||
475 | +++ data/devices.ui 2010-12-22 14:37:52 +0000 | |||
476 | @@ -7,7 +7,37 @@ | |||
477 | 7 | <property name="border_width">10</property> | 7 | <property name="border_width">10</property> |
478 | 8 | <property name="spacing">10</property> | 8 | <property name="spacing">10</property> |
479 | 9 | <child> | 9 | <child> |
481 | 10 | <placeholder/> | 10 | <object class="GtkScrolledWindow" id="scrolledwindow1"> |
482 | 11 | <property name="visible">True</property> | ||
483 | 12 | <property name="can_focus">True</property> | ||
484 | 13 | <property name="hscrollbar_policy">automatic</property> | ||
485 | 14 | <property name="vscrollbar_policy">automatic</property> | ||
486 | 15 | <child> | ||
487 | 16 | <object class="GtkViewport" id="viewport1"> | ||
488 | 17 | <property name="visible">True</property> | ||
489 | 18 | <property name="resize_mode">queue</property> | ||
490 | 19 | <property name="shadow_type">none</property> | ||
491 | 20 | <child> | ||
492 | 21 | <object class="GtkAlignment" id="alignment1"> | ||
493 | 22 | <property name="visible">True</property> | ||
494 | 23 | <property name="xscale">0</property> | ||
495 | 24 | <property name="yscale">0</property> | ||
496 | 25 | <child> | ||
497 | 26 | <object class="GtkVBox" id="devices"> | ||
498 | 27 | <property name="visible">True</property> | ||
499 | 28 | <child> | ||
500 | 29 | <placeholder/> | ||
501 | 30 | </child> | ||
502 | 31 | </object> | ||
503 | 32 | </child> | ||
504 | 33 | </object> | ||
505 | 34 | </child> | ||
506 | 35 | </object> | ||
507 | 36 | </child> | ||
508 | 37 | </object> | ||
509 | 38 | <packing> | ||
510 | 39 | <property name="position">0</property> | ||
511 | 40 | </packing> | ||
512 | 11 | </child> | 41 | </child> |
513 | 12 | </object> | 42 | </object> |
514 | 13 | </interface> | 43 | </interface> |
515 | 14 | 44 | ||
516 | === modified file 'data/folders.ui' | |||
517 | --- data/folders.ui 2010-12-06 12:27:11 +0000 | |||
518 | +++ data/folders.ui 2010-12-22 14:37:52 +0000 | |||
519 | @@ -7,7 +7,32 @@ | |||
520 | 7 | <property name="border_width">10</property> | 7 | <property name="border_width">10</property> |
521 | 8 | <property name="spacing">10</property> | 8 | <property name="spacing">10</property> |
522 | 9 | <child> | 9 | <child> |
524 | 10 | <placeholder/> | 10 | <object class="GtkScrolledWindow" id="scrolledwindow1"> |
525 | 11 | <property name="visible">True</property> | ||
526 | 12 | <property name="can_focus">True</property> | ||
527 | 13 | <property name="hscrollbar_policy">automatic</property> | ||
528 | 14 | <property name="vscrollbar_policy">automatic</property> | ||
529 | 15 | <child> | ||
530 | 16 | <object class="GtkViewport" id="viewport1"> | ||
531 | 17 | <property name="visible">True</property> | ||
532 | 18 | <property name="resize_mode">queue</property> | ||
533 | 19 | <property name="shadow_type">none</property> | ||
534 | 20 | <child> | ||
535 | 21 | <object class="GtkAlignment" id="folders"> | ||
536 | 22 | <property name="visible">True</property> | ||
537 | 23 | <property name="xscale">0</property> | ||
538 | 24 | <property name="yscale">0</property> | ||
539 | 25 | <child> | ||
540 | 26 | <placeholder/> | ||
541 | 27 | </child> | ||
542 | 28 | </object> | ||
543 | 29 | </child> | ||
544 | 30 | </object> | ||
545 | 31 | </child> | ||
546 | 32 | </object> | ||
547 | 33 | <packing> | ||
548 | 34 | <property name="position">0</property> | ||
549 | 35 | </packing> | ||
550 | 11 | </child> | 36 | </child> |
551 | 12 | </object> | 37 | </object> |
552 | 13 | </interface> | 38 | </interface> |
553 | 14 | 39 | ||
554 | === modified file 'data/management.ui' | |||
555 | --- data/management.ui 2010-12-06 12:27:11 +0000 | |||
556 | +++ data/management.ui 2010-12-22 14:37:52 +0000 | |||
557 | @@ -16,8 +16,23 @@ | |||
558 | 16 | <property name="border_width">10</property> | 16 | <property name="border_width">10</property> |
559 | 17 | <property name="spacing">10</property> | 17 | <property name="spacing">10</property> |
560 | 18 | <child> | 18 | <child> |
562 | 19 | <object class="GtkProgressBar" id="quota_progressbar"> | 19 | <object class="GtkHBox" id="quota_box"> |
563 | 20 | <property name="visible">True</property> | 20 | <property name="visible">True</property> |
564 | 21 | <child> | ||
565 | 22 | <object class="GtkAlignment" id="alignment1"> | ||
566 | 23 | <property name="visible">True</property> | ||
567 | 24 | <property name="xscale">0</property> | ||
568 | 25 | <property name="yscale">0</property> | ||
569 | 26 | <child> | ||
570 | 27 | <object class="GtkProgressBar" id="quota_progressbar"> | ||
571 | 28 | <property name="visible">True</property> | ||
572 | 29 | </object> | ||
573 | 30 | </child> | ||
574 | 31 | </object> | ||
575 | 32 | <packing> | ||
576 | 33 | <property name="position">0</property> | ||
577 | 34 | </packing> | ||
578 | 35 | </child> | ||
579 | 21 | </object> | 36 | </object> |
580 | 22 | <packing> | 37 | <packing> |
581 | 23 | <property name="expand">False</property> | 38 | <property name="expand">False</property> |
582 | 24 | 39 | ||
583 | === modified file 'debian/changelog' | |||
584 | --- debian/changelog 2010-12-21 14:09:31 +0000 | |||
585 | +++ debian/changelog 2010-12-22 14:37:52 +0000 | |||
586 | @@ -1,3 +1,63 @@ | |||
587 | 1 | ubuntuone-control-panel (0.1.0-0ubuntu1) UNRELEASED; urgency=low | ||
588 | 2 | |||
589 | 3 | * debian/control | ||
590 | 4 | - depends on ubutuone-client >= 1.5.1 | ||
591 | 5 | - depends on ubuntu-sso-client >= 1.1.7 | ||
592 | 6 | |||
593 | 7 | * New upstream release. | ||
594 | 8 | |||
595 | 9 | [ Natalia B. Bidart <natalia.bidart@canonical.com> ] | ||
596 | 10 | |||
597 | 11 | * Updated list of messages to be shown on the overview panel (LP: | ||
598 | 12 | #690379). | ||
599 | 13 | |||
600 | 14 | * Implemented callback for failure when loading devices. | ||
601 | 15 | |||
602 | 16 | * Added a spinner on every UbuntuOneBin to be shown while loading the | ||
603 | 17 | panel content. | ||
604 | 18 | |||
605 | 19 | * Devices can now be removed (LP: #691295). | ||
606 | 20 | |||
607 | 21 | * Migrated dbus_client to use non-deprectaed SSO D-Bus API. | ||
608 | 22 | |||
609 | 23 | * Added clear_credentials to dbus_client module. | ||
610 | 24 | |||
611 | 25 | * Implemented devices tab (LP: #690649). | ||
612 | 26 | |||
613 | 27 | * Maximun size is set using geometry hints (LP: #683164). | ||
614 | 28 | |||
615 | 29 | * Added logging to dbus_service and backend. | ||
616 | 30 | |||
617 | 31 | * Error signals now sent the object id when available (LP: #691292). | ||
618 | 32 | |||
619 | 33 | * After machine was added, Folders page is shown (LP: #674459).* | ||
620 | 34 | VolumesInfoError signal is now handled (LP: #690292). | ||
621 | 35 | |||
622 | 36 | * VolumesInfoError signal is now handled (LP: #690292). | ||
623 | 37 | |||
624 | 38 | * When FileSyncStatusError is received, no more DbusException messages | ||
625 | 39 | are leaked to the end user (LP: #690305). | ||
626 | 40 | |||
627 | 41 | * User can now subcribe and unsubscribe from folders (LP: #689646). | ||
628 | 42 | |||
629 | 43 | * Dbus booleans are now those strings whose bool() defines its value. So, | ||
630 | 44 | '' for False and any other non empty string for True (LP: #683619). | ||
631 | 45 | |||
632 | 46 | * File sync status is retrieved and displayed in the right top corner | ||
633 | 47 | (LP: #673670). | ||
634 | 48 | |||
635 | 49 | Â * Adding handling for file sync disabled (only backend for now). | ||
636 | 50 | |||
637 | 51 | Â * Management panel is not twined itself if CredentialsFound signal is | ||
638 | 52 | received several times (LP: #683649). | ||
639 | 53 | |||
640 | 54 | [ Rodney Dawes <rodney.dawes@canonical.com> ] | ||
641 | 55 | |||
642 | 56 | * Default to None and initialize if None in code, instead of mutable | ||
643 | 57 | defaults. | ||
644 | 58 | |||
645 | 59 | -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Wed, 22 Dec 2010 11:15:04 -0300 | ||
646 | 60 | |||
647 | 1 | ubuntuone-control-panel (0.0.9-0ubuntu3) natty; urgency=low | 61 | ubuntuone-control-panel (0.0.9-0ubuntu3) natty; urgency=low |
648 | 2 | 62 | ||
649 | 3 | * debian/control | 63 | * debian/control |
650 | 4 | 64 | ||
651 | === modified file 'debian/control' | |||
652 | --- debian/control 2010-12-21 14:09:31 +0000 | |||
653 | +++ debian/control 2010-12-22 14:37:52 +0000 | |||
654 | @@ -35,8 +35,8 @@ | |||
655 | 35 | python-simplejson, | 35 | python-simplejson, |
656 | 36 | python-twisted-core, | 36 | python-twisted-core, |
657 | 37 | python-twisted-web, | 37 | python-twisted-web, |
660 | 38 | python-ubuntuone-client (>= 1.5.0), | 38 | python-ubuntuone-client (>= 1.5.1), |
661 | 39 | ubuntu-sso-client (>= 1.1.2), | 39 | ubuntu-sso-client (>= 1.1.7), |
662 | 40 | Description: Ubuntu One Control Panel Python Libraries | 40 | Description: Ubuntu One Control Panel Python Libraries |
663 | 41 | Ubuntu One Control Panel provides a Python library to manage an Ubuntu One | 41 | Ubuntu One Control Panel provides a Python library to manage an Ubuntu One |
664 | 42 | account. | 42 | account. |
665 | @@ -50,8 +50,8 @@ | |||
666 | 50 | python-dbus, | 50 | python-dbus, |
667 | 51 | python-gobject, | 51 | python-gobject, |
668 | 52 | python-gtk2, | 52 | python-gtk2, |
671 | 53 | python-ubuntuone-client (>= 1.5.0), | 53 | python-ubuntuone-client (>= 1.5.1), |
672 | 54 | ubuntu-sso-client (>= 1.1.2), | 54 | ubuntu-sso-client (>= 1.1.7), |
673 | 55 | ubuntuone-control-panel (= ${binary:Version}), | 55 | ubuntuone-control-panel (= ${binary:Version}), |
674 | 56 | Description: Ubuntu One Control Panel | 56 | Description: Ubuntu One Control Panel |
675 | 57 | GTK+ desktop application to manage a Ubuntu One account. | 57 | GTK+ desktop application to manage a Ubuntu One account. |
676 | 58 | 58 | ||
677 | === modified file 'po/POTFILES.in' | |||
678 | --- po/POTFILES.in 2010-12-06 12:27:11 +0000 | |||
679 | +++ po/POTFILES.in 2010-12-22 14:37:52 +0000 | |||
680 | @@ -1,7 +1,6 @@ | |||
681 | 1 | ubuntuone/controlpanel/gtk/gui.py | 1 | ubuntuone/controlpanel/gtk/gui.py |
682 | 2 | [type: gettext/glade] data/account.ui | 2 | [type: gettext/glade] data/account.ui |
683 | 3 | [type: gettext/glade] data/applications.ui | 3 | [type: gettext/glade] data/applications.ui |
684 | 4 | [type: gettext/glade] data/controlpanel.ui | ||
685 | 5 | [type: gettext/glade] data/devices.ui | 4 | [type: gettext/glade] data/devices.ui |
686 | 6 | [type: gettext/glade] data/management.ui | 5 | [type: gettext/glade] data/management.ui |
687 | 7 | [type: gettext/glade] data/overview.ui | 6 | [type: gettext/glade] data/overview.ui |
688 | 8 | 7 | ||
689 | === modified file 'setup.py' | |||
690 | --- setup.py 2010-12-06 12:27:11 +0000 | |||
691 | +++ setup.py 2010-12-22 14:37:52 +0000 | |||
692 | @@ -75,7 +75,7 @@ | |||
693 | 75 | 75 | ||
694 | 76 | DistUtilsExtra.auto.setup( | 76 | DistUtilsExtra.auto.setup( |
695 | 77 | name='ubuntuone-control-panel', | 77 | name='ubuntuone-control-panel', |
697 | 78 | version='0.0.9', | 78 | version='0.1.0', |
698 | 79 | license='GPL v3', | 79 | license='GPL v3', |
699 | 80 | author='Natalia Bidart', | 80 | author='Natalia Bidart', |
700 | 81 | author_email='natalia.bidart@canonical.com', | 81 | author_email='natalia.bidart@canonical.com', |
701 | 82 | 82 | ||
702 | === modified file 'ubuntuone/controlpanel/__init__.py' | |||
703 | --- ubuntuone/controlpanel/__init__.py 2010-12-06 12:27:11 +0000 | |||
704 | +++ ubuntuone/controlpanel/__init__.py 2010-12-22 14:37:52 +0000 | |||
705 | @@ -29,4 +29,4 @@ | |||
706 | 29 | DBUS_PREFERENCES_PATH = "/preferences" | 29 | DBUS_PREFERENCES_PATH = "/preferences" |
707 | 30 | DBUS_PREFERENCES_IFACE = "com.ubuntuone.controlpanel.Preferences" | 30 | DBUS_PREFERENCES_IFACE = "com.ubuntuone.controlpanel.Preferences" |
708 | 31 | 31 | ||
710 | 32 | WEBSERVICE_BASE_URL = "https://one.ubuntu.com/api/" | 32 | WEBSERVICE_BASE_URL = u"https://one.ubuntu.com/api/" |
711 | 33 | 33 | ||
712 | === modified file 'ubuntuone/controlpanel/backend.py' | |||
713 | --- ubuntuone/controlpanel/backend.py 2010-12-06 12:27:11 +0000 | |||
714 | +++ ubuntuone/controlpanel/backend.py 2010-12-22 14:37:52 +0000 | |||
715 | @@ -22,8 +22,12 @@ | |||
716 | 22 | from twisted.internet.defer import inlineCallbacks, returnValue | 22 | from twisted.internet.defer import inlineCallbacks, returnValue |
717 | 23 | 23 | ||
718 | 24 | from ubuntuone.controlpanel import dbus_client | 24 | from ubuntuone.controlpanel import dbus_client |
719 | 25 | from ubuntuone.controlpanel.logger import setup_logging, log_call | ||
720 | 25 | from ubuntuone.controlpanel.webclient import WebClient | 26 | from ubuntuone.controlpanel.webclient import WebClient |
721 | 26 | 27 | ||
722 | 28 | |||
723 | 29 | logger = setup_logging('backend') | ||
724 | 30 | |||
725 | 27 | ACCOUNT_API = "account/" | 31 | ACCOUNT_API = "account/" |
726 | 28 | QUOTA_API = "quota/" | 32 | QUOTA_API = "quota/" |
727 | 29 | DEVICES_API = "1.0/devices/" | 33 | DEVICES_API = "1.0/devices/" |
728 | @@ -33,6 +37,22 @@ | |||
729 | 33 | UPLOAD_KEY = "max_upload_speed" | 37 | UPLOAD_KEY = "max_upload_speed" |
730 | 34 | DOWNLOAD_KEY = "max_download_speed" | 38 | DOWNLOAD_KEY = "max_download_speed" |
731 | 35 | 39 | ||
732 | 40 | FILE_SYNC_DISABLED = 'file-sync-disabled' | ||
733 | 41 | FILE_SYNC_DISCONNECTED = 'file-sync-disconnected' | ||
734 | 42 | FILE_SYNC_ERROR = 'file-sync-error' | ||
735 | 43 | FILE_SYNC_IDLE = 'file-sync-idle' | ||
736 | 44 | FILE_SYNC_STARTING = 'file-sync-starting' | ||
737 | 45 | FILE_SYNC_SYNCING = 'file-sync-syncing' | ||
738 | 46 | FILE_SYNC_UNKNOWN = 'file-sync-unknown' | ||
739 | 47 | |||
740 | 48 | MSG_KEY = 'message' | ||
741 | 49 | STATUS_KEY = 'status' | ||
742 | 50 | |||
743 | 51 | |||
744 | 52 | def bool_str(value): | ||
745 | 53 | """Return the string representation of a bool (dbus-compatible).""" | ||
746 | 54 | return 'True' if value else '' | ||
747 | 55 | |||
748 | 36 | 56 | ||
749 | 37 | class ControlBackend(object): | 57 | class ControlBackend(object): |
750 | 38 | """The control panel backend.""" | 58 | """The control panel backend.""" |
751 | @@ -40,20 +60,101 @@ | |||
752 | 40 | def __init__(self): | 60 | def __init__(self): |
753 | 41 | """Initialize the webclient.""" | 61 | """Initialize the webclient.""" |
754 | 42 | self.wc = WebClient(dbus_client.get_credentials) | 62 | self.wc = WebClient(dbus_client.get_credentials) |
755 | 63 | self._status_changed_handler = None | ||
756 | 64 | self.status_changed_handler = lambda *a: None | ||
757 | 65 | |||
758 | 66 | def _process_file_sync_status(self, status): | ||
759 | 67 | """Process raw file sync status into custom format. | ||
760 | 68 | |||
761 | 69 | Return a dictionary with two members: | ||
762 | 70 | * STATUS_KEY: the current status of syncdaemon, can be one of: | ||
763 | 71 | FILE_SYNC_DISABLED, FILE_SYNC_STARTING, FILE_SYNC_DISCONNECTED, | ||
764 | 72 | FILE_SYNC_SYNCING, FILE_SYNC_IDLE, FILE_SYNC_ERROR, | ||
765 | 73 | FILE_SYNC_UNKNOWN | ||
766 | 74 | * MSG_KEY: a non translatable but human readable string of the status. | ||
767 | 75 | |||
768 | 76 | """ | ||
769 | 77 | if not status: | ||
770 | 78 | return {MSG_KEY: '', STATUS_KEY: FILE_SYNC_DISABLED} | ||
771 | 79 | |||
772 | 80 | msg = '%s (%s)' % (status['description'], status['name']) | ||
773 | 81 | result = {MSG_KEY: msg} | ||
774 | 82 | |||
775 | 83 | # file synch is enabled | ||
776 | 84 | is_error = bool(status['is_error']) | ||
777 | 85 | is_synching = bool(status['is_connected']) | ||
778 | 86 | is_idle = bool(status['is_online']) and status['queues'] == 'IDLE' | ||
779 | 87 | is_disconnected = status['name'] == 'READY' and \ | ||
780 | 88 | 'Not User' in status['connection'] | ||
781 | 89 | is_starting = status['name'] in ('INIT', 'LOCAL_RESCAN', 'READY') | ||
782 | 90 | |||
783 | 91 | if is_error: | ||
784 | 92 | result[STATUS_KEY] = FILE_SYNC_ERROR | ||
785 | 93 | elif is_idle: | ||
786 | 94 | result[STATUS_KEY] = FILE_SYNC_IDLE | ||
787 | 95 | elif is_synching: | ||
788 | 96 | result[STATUS_KEY] = FILE_SYNC_SYNCING | ||
789 | 97 | elif is_disconnected: | ||
790 | 98 | result[STATUS_KEY] = FILE_SYNC_DISCONNECTED | ||
791 | 99 | elif is_starting: | ||
792 | 100 | result[STATUS_KEY] = FILE_SYNC_STARTING | ||
793 | 101 | else: | ||
794 | 102 | logger.warning('file_sync_status: unknown (got %r)', status) | ||
795 | 103 | result[STATUS_KEY] = FILE_SYNC_UNKNOWN | ||
796 | 104 | |||
797 | 105 | return result | ||
798 | 106 | |||
799 | 107 | def _set_status_changed_handler(self, handler): | ||
800 | 108 | """Set 'handler' to be called when file sync status changes.""" | ||
801 | 109 | logger.debug('setting status_changed_handler to %r', handler) | ||
802 | 110 | |||
803 | 111 | def process_and_callback(status): | ||
804 | 112 | """Process syncdaemon's status and callback 'handler'.""" | ||
805 | 113 | result = self._process_file_sync_status(status) | ||
806 | 114 | handler(result) | ||
807 | 115 | |||
808 | 116 | self._status_changed_handler = process_and_callback | ||
809 | 117 | dbus_client.set_status_changed_handler(process_and_callback) | ||
810 | 118 | |||
811 | 119 | def _get_status_changed_handler(self, handler): | ||
812 | 120 | """Return the handler to be called when file sync status changes.""" | ||
813 | 121 | return self._status_changed_handler | ||
814 | 122 | |||
815 | 123 | status_changed_handler = property(_get_status_changed_handler, | ||
816 | 124 | _set_status_changed_handler) | ||
817 | 43 | 125 | ||
818 | 44 | @inlineCallbacks | 126 | @inlineCallbacks |
819 | 45 | def get_token(self): | 127 | def get_token(self): |
821 | 46 | """Return the token from the credentials""" | 128 | """Return the token from the credentials.""" |
822 | 47 | credentials = yield dbus_client.get_credentials() | 129 | credentials = yield dbus_client.get_credentials() |
823 | 48 | returnValue(credentials["token"]) | 130 | returnValue(credentials["token"]) |
824 | 49 | 131 | ||
825 | 50 | @inlineCallbacks | 132 | @inlineCallbacks |
826 | 133 | def device_is_local(self, device_id): | ||
827 | 134 | """Return whether 'device_id' is the local devicew or not.""" | ||
828 | 135 | dtype, did = self.type_n_id(device_id) | ||
829 | 136 | local_token = yield self.get_token() | ||
830 | 137 | is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token) | ||
831 | 138 | logger.info('device_is_local: result %r, ', is_local) | ||
832 | 139 | returnValue(is_local) | ||
833 | 140 | |||
834 | 141 | @log_call(logger.debug) | ||
835 | 142 | @inlineCallbacks | ||
836 | 51 | def account_info(self): | 143 | def account_info(self): |
837 | 52 | """Get the user account info.""" | 144 | """Get the user account info.""" |
838 | 53 | result = {} | 145 | result = {} |
839 | 54 | 146 | ||
840 | 55 | account_info = yield self.wc.call_api(ACCOUNT_API) | 147 | account_info = yield self.wc.call_api(ACCOUNT_API) |
842 | 56 | result["type"] = account_info["subscription"]["description"] | 148 | |
843 | 149 | if "current_plan" in account_info: | ||
844 | 150 | desc = account_info["current_plan"] | ||
845 | 151 | elif "subscription" in account_info \ | ||
846 | 152 | and "description" in account_info["subscription"]: | ||
847 | 153 | desc = account_info["subscription"]["description"] | ||
848 | 154 | else: | ||
849 | 155 | desc = '' | ||
850 | 156 | |||
851 | 157 | result["type"] = desc | ||
852 | 57 | result["name"] = account_info["nickname"] | 158 | result["name"] = account_info["nickname"] |
853 | 58 | result["email"] = account_info["email"] | 159 | result["email"] = account_info["email"] |
854 | 59 | 160 | ||
855 | @@ -63,11 +164,11 @@ | |||
856 | 63 | 164 | ||
857 | 64 | returnValue(result) | 165 | returnValue(result) |
858 | 65 | 166 | ||
859 | 167 | @log_call(logger.debug) | ||
860 | 66 | @inlineCallbacks | 168 | @inlineCallbacks |
861 | 67 | def devices_info(self): | 169 | def devices_info(self): |
862 | 68 | """Get the user devices info.""" | 170 | """Get the user devices info.""" |
863 | 69 | result = [] | 171 | result = [] |
864 | 70 | this_token = yield self.get_token() | ||
865 | 71 | limit_bw = yield dbus_client.bandwidth_throttling_enabled() | 172 | limit_bw = yield dbus_client.bandwidth_throttling_enabled() |
866 | 72 | limits = yield dbus_client.get_throttling_limits() | 173 | limits = yield dbus_client.get_throttling_limits() |
867 | 73 | 174 | ||
868 | @@ -77,19 +178,25 @@ | |||
869 | 77 | result.append(di) | 178 | result.append(di) |
870 | 78 | di["type"] = d["kind"] | 179 | di["type"] = d["kind"] |
871 | 79 | di["name"] = d["description"] | 180 | di["name"] = d["description"] |
873 | 80 | di["configurable"] = "0" | 181 | di["configurable"] = '' |
874 | 81 | if di["type"] == DEVICE_TYPE_COMPUTER: | 182 | if di["type"] == DEVICE_TYPE_COMPUTER: |
875 | 82 | di["device_id"] = di["type"] + d["token"] | 183 | di["device_id"] = di["type"] + d["token"] |
876 | 83 | if d["token"] == this_token: | ||
877 | 84 | di["configurable"] = "1" | ||
878 | 85 | di["limit_bandwidth"] = "1" if limit_bw else "0" | ||
879 | 86 | upload = limits["upload"] | ||
880 | 87 | download = limits["download"] | ||
881 | 88 | di[UPLOAD_KEY] = str(upload) | ||
882 | 89 | di[DOWNLOAD_KEY] = str(download) | ||
883 | 90 | if di["type"] == DEVICE_TYPE_PHONE: | 184 | if di["type"] == DEVICE_TYPE_PHONE: |
884 | 91 | di["device_id"] = di["type"] + str(d["id"]) | 185 | di["device_id"] = di["type"] + str(d["id"]) |
885 | 92 | 186 | ||
886 | 187 | is_local = yield self.device_is_local(di["device_id"]) | ||
887 | 188 | di["is_local"] = bool_str(is_local) | ||
888 | 189 | # currently, only local devices are configurable. | ||
889 | 190 | # eventually, more the devices will be configurable. | ||
890 | 191 | di["configurable"] = bool_str(is_local) | ||
891 | 192 | |||
892 | 193 | if bool(di["configurable"]): | ||
893 | 194 | di["limit_bandwidth"] = bool_str(limit_bw) | ||
894 | 195 | upload = limits["upload"] | ||
895 | 196 | download = limits["download"] | ||
896 | 197 | di[UPLOAD_KEY] = str(upload) | ||
897 | 198 | di[DOWNLOAD_KEY] = str(download) | ||
898 | 199 | |||
899 | 93 | # date_added is not in the webservice yet (LP: #673668) | 200 | # date_added is not in the webservice yet (LP: #673668) |
900 | 94 | # di["date_added"] = "" | 201 | # di["date_added"] = "" |
901 | 95 | 202 | ||
902 | @@ -107,18 +214,17 @@ | |||
903 | 107 | return DEVICE_TYPE_PHONE, device_id[5:] | 214 | return DEVICE_TYPE_PHONE, device_id[5:] |
904 | 108 | return "No device", device_id | 215 | return "No device", device_id |
905 | 109 | 216 | ||
906 | 217 | @log_call(logger.info) | ||
907 | 110 | @inlineCallbacks | 218 | @inlineCallbacks |
908 | 111 | def change_device_settings(self, device_id, settings): | 219 | def change_device_settings(self, device_id, settings): |
909 | 112 | """Change the settings for the given device.""" | 220 | """Change the settings for the given device.""" |
913 | 113 | dtype, did = self.type_n_id(device_id) | 221 | is_local = yield self.device_is_local(device_id) |
911 | 114 | local_token = yield self.get_token() | ||
912 | 115 | is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token) | ||
914 | 116 | 222 | ||
915 | 117 | if is_local and "limit_bandwidth" in settings: | 223 | if is_local and "limit_bandwidth" in settings: |
918 | 118 | limit_bw = settings["limit_bandwidth"] | 224 | limit_bw = bool(settings["limit_bandwidth"]) |
919 | 119 | if limit_bw == "0": | 225 | if not limit_bw: |
920 | 120 | yield dbus_client.disable_bandwidth_throttling() | 226 | yield dbus_client.disable_bandwidth_throttling() |
922 | 121 | if limit_bw == "1": | 227 | else: |
923 | 122 | yield dbus_client.enable_bandwidth_throttling() | 228 | yield dbus_client.enable_bandwidth_throttling() |
924 | 123 | 229 | ||
925 | 124 | if is_local and (UPLOAD_KEY in settings or | 230 | if is_local and (UPLOAD_KEY in settings or |
926 | @@ -137,30 +243,78 @@ | |||
927 | 137 | # still pending: more work on the settings dict (LP: #673674) | 243 | # still pending: more work on the settings dict (LP: #673674) |
928 | 138 | returnValue(device_id) | 244 | returnValue(device_id) |
929 | 139 | 245 | ||
930 | 246 | @log_call(logger.warning) | ||
931 | 140 | @inlineCallbacks | 247 | @inlineCallbacks |
932 | 141 | def remove_device(self, device_id): | 248 | def remove_device(self, device_id): |
933 | 142 | """Remove a device's tokens from the sso server.""" | 249 | """Remove a device's tokens from the sso server.""" |
934 | 143 | dtype, did = self.type_n_id(device_id) | 250 | dtype, did = self.type_n_id(device_id) |
936 | 144 | api = DEVICE_REMOVE_API % (dtype, did) | 251 | is_local = yield self.device_is_local(device_id) |
937 | 252 | |||
938 | 253 | api = DEVICE_REMOVE_API % (dtype.lower(), did) | ||
939 | 145 | yield self.wc.call_api(api) | 254 | yield self.wc.call_api(api) |
940 | 255 | |||
941 | 256 | if is_local: | ||
942 | 257 | logger.warning('remove_device: device is local, id %r, ' | ||
943 | 258 | 'clearing credentials.', device_id) | ||
944 | 259 | yield dbus_client.clear_credentials() | ||
945 | 260 | |||
946 | 146 | returnValue(device_id) | 261 | returnValue(device_id) |
947 | 147 | 262 | ||
948 | 263 | @log_call(logger.debug) | ||
949 | 264 | @inlineCallbacks | ||
950 | 148 | def file_sync_status(self): | 265 | def file_sync_status(self): |
951 | 149 | """Return the status of the file sync service.""" | 266 | """Return the status of the file sync service.""" |
954 | 150 | # still pending (LP: #673670) | 267 | enabled = yield dbus_client.files_sync_enabled() |
955 | 151 | returnValue((True, "Synchronizing")) | 268 | if enabled: |
956 | 269 | status = yield dbus_client.get_current_status() | ||
957 | 270 | else: | ||
958 | 271 | status = {} | ||
959 | 272 | returnValue(self._process_file_sync_status(status)) | ||
960 | 152 | 273 | ||
961 | 274 | @log_call(logger.debug) | ||
962 | 153 | @inlineCallbacks | 275 | @inlineCallbacks |
963 | 154 | def volumes_info(self): | 276 | def volumes_info(self): |
966 | 155 | """Get the user defined volumes info.""" | 277 | """Get the volumes info.""" |
967 | 156 | result = yield dbus_client.get_volumes() | 278 | result = yield dbus_client.get_folders() |
968 | 279 | # later, we may request shares info as well | ||
969 | 157 | returnValue(result) | 280 | returnValue(result) |
970 | 158 | 281 | ||
971 | 282 | @log_call(logger.debug) | ||
972 | 283 | @inlineCallbacks | ||
973 | 284 | def change_volume_settings(self, volume_id, settings): | ||
974 | 285 | """Change settings for 'volume_id'. | ||
975 | 286 | |||
976 | 287 | Currently, only supported setting is boolean 'subscribed'. | ||
977 | 288 | |||
978 | 289 | """ | ||
979 | 290 | if 'subscribed' in settings: | ||
980 | 291 | subscribed = settings['subscribed'] | ||
981 | 292 | if subscribed: | ||
982 | 293 | yield self.subscribe_volume(volume_id) | ||
983 | 294 | else: | ||
984 | 295 | yield self.unsubscribe_volume(volume_id) | ||
985 | 296 | |||
986 | 297 | @inlineCallbacks | ||
987 | 298 | def subscribe_volume(self, volume_id): | ||
988 | 299 | """Subscribe to 'volume_id'.""" | ||
989 | 300 | # when dealing with shares, we need to check if 'volume_id' | ||
990 | 301 | # is a folder or a share | ||
991 | 302 | yield dbus_client.subscribe_folder(volume_id) | ||
992 | 303 | |||
993 | 304 | @inlineCallbacks | ||
994 | 305 | def unsubscribe_volume(self, volume_id): | ||
995 | 306 | """Unsubscribe from 'volume_id'.""" | ||
996 | 307 | # when dealing with shares, we need to check if 'volume_id' | ||
997 | 308 | # is a folder or a share | ||
998 | 309 | yield dbus_client.unsubscribe_folder(volume_id) | ||
999 | 310 | |||
1000 | 311 | @log_call(logger.debug) | ||
1001 | 159 | def query_bookmark_extension(self): | 312 | def query_bookmark_extension(self): |
1002 | 160 | """True if the bookmark extension has been installed.""" | 313 | """True if the bookmark extension has been installed.""" |
1003 | 161 | # still pending (LP: #673672) | 314 | # still pending (LP: #673672) |
1004 | 162 | returnValue(False) | 315 | returnValue(False) |
1005 | 163 | 316 | ||
1006 | 317 | @log_call(logger.debug) | ||
1007 | 164 | def install_bookmarks_extension(self): | 318 | def install_bookmarks_extension(self): |
1008 | 165 | """Install the extension to sync bookmarks.""" | 319 | """Install the extension to sync bookmarks.""" |
1009 | 166 | # still pending (LP: #673673) | 320 | # still pending (LP: #673673) |
1010 | 167 | 321 | ||
1011 | === modified file 'ubuntuone/controlpanel/dbus_client.py' | |||
1012 | --- ubuntuone/controlpanel/dbus_client.py 2010-12-06 12:27:11 +0000 | |||
1013 | +++ ubuntuone/controlpanel/dbus_client.py 2010-12-22 14:37:52 +0000 | |||
1014 | @@ -25,7 +25,8 @@ | |||
1015 | 25 | from twisted.internet import defer | 25 | from twisted.internet import defer |
1016 | 26 | 26 | ||
1017 | 27 | from ubuntuone.clientdefs import APP_NAME | 27 | from ubuntuone.clientdefs import APP_NAME |
1019 | 28 | from ubuntuone.syncdaemon import dbus_interface as sd_dbus_iface | 28 | from ubuntuone.platform.linux import dbus_interface as sd_dbus_iface |
1020 | 29 | from ubuntuone.platform.linux.tools import SyncDaemonTool | ||
1021 | 29 | 30 | ||
1022 | 30 | from ubuntuone.controlpanel.logger import setup_logging | 31 | from ubuntuone.controlpanel.logger import setup_logging |
1023 | 31 | 32 | ||
1024 | @@ -45,36 +46,88 @@ | |||
1025 | 45 | """Do nothing""" | 46 | """Do nothing""" |
1026 | 46 | 47 | ||
1027 | 47 | 48 | ||
1028 | 49 | def get_sso_proxy(): | ||
1029 | 50 | """Get a DBus proxy for credentials management.""" | ||
1030 | 51 | bus = dbus.SessionBus() | ||
1031 | 52 | obj = bus.get_object(bus_name=ubuntu_sso.DBUS_BUS_NAME, | ||
1032 | 53 | object_path=ubuntu_sso.DBUS_CREDENTIALS_PATH, | ||
1033 | 54 | follow_name_owner_changes=True) | ||
1034 | 55 | proxy = dbus.Interface(object=obj, | ||
1035 | 56 | dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE) | ||
1036 | 57 | return proxy | ||
1037 | 58 | |||
1038 | 59 | |||
1039 | 48 | def get_credentials(): | 60 | def get_credentials(): |
1040 | 49 | """Get the credentials from the ubuntu-sso-client.""" | 61 | """Get the credentials from the ubuntu-sso-client.""" |
1041 | 50 | d = defer.Deferred() | 62 | d = defer.Deferred() |
1048 | 51 | bus = dbus.SessionBus() | 63 | proxy = get_sso_proxy() |
1043 | 52 | obj = bus.get_object(bus_name=ubuntu_sso.DBUS_BUS_NAME, | ||
1044 | 53 | object_path=ubuntu_sso.DBUS_CRED_PATH, | ||
1045 | 54 | follow_name_owner_changes=True) | ||
1046 | 55 | proxy = dbus.Interface(object=obj, | ||
1047 | 56 | dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME) | ||
1049 | 57 | 64 | ||
1050 | 58 | def found_credentials(app_name, creds): | 65 | def found_credentials(app_name, creds): |
1051 | 59 | """Credentials have been found.""" | 66 | """Credentials have been found.""" |
1052 | 67 | logger.debug('credentials were found for app_name %r.', app_name) | ||
1053 | 60 | if app_name == APP_NAME: | 68 | if app_name == APP_NAME: |
1054 | 69 | logger.info('credentials were found! (%r).', APP_NAME) | ||
1055 | 61 | d.callback(creds) | 70 | d.callback(creds) |
1056 | 62 | 71 | ||
1058 | 63 | def credentials_error(app_name, error_message, detailed_error): | 72 | def credentials_error(app_name, error_dict=None): |
1059 | 64 | """No credentials could be retrieved.""" | 73 | """No credentials could be retrieved.""" |
1060 | 65 | if app_name == APP_NAME: | 74 | if app_name == APP_NAME: |
1062 | 66 | error = CredentialsError(app_name, error_message, detailed_error) | 75 | if error_dict is None: |
1063 | 76 | error_dict = {'error_message': 'Credentials were not found.', | ||
1064 | 77 | 'detailed_error': ''} | ||
1065 | 78 | |||
1066 | 79 | logger.error('credentials error (%r, %r).', app_name, error_dict) | ||
1067 | 80 | error = CredentialsError(app_name, error_dict) | ||
1068 | 67 | d.errback(error) | 81 | d.errback(error) |
1069 | 68 | 82 | ||
1070 | 69 | foundsig = proxy.connect_to_signal("CredentialsFound", found_credentials) | 83 | foundsig = proxy.connect_to_signal("CredentialsFound", found_credentials) |
1071 | 84 | notfoundsig = proxy.connect_to_signal("CredentialsNotFound", | ||
1072 | 85 | credentials_error) | ||
1073 | 70 | errorsig = proxy.connect_to_signal("CredentialsError", credentials_error) | 86 | errorsig = proxy.connect_to_signal("CredentialsError", credentials_error) |
1077 | 71 | proxy.login_or_register_to_get_credentials(APP_NAME, "tcurl", "help", 0, | 87 | logger.debug('calling get_credentials.') |
1078 | 72 | reply_handler=no_op, | 88 | proxy.find_credentials(APP_NAME, {'': ''}, |
1079 | 73 | error_handler=d.errback) | 89 | reply_handler=no_op, error_handler=d.errback) |
1080 | 74 | 90 | ||
1081 | 75 | def cleanup_signals(result): | 91 | def cleanup_signals(result): |
1082 | 76 | """Remove signals after use.""" | 92 | """Remove signals after use.""" |
1083 | 77 | foundsig.remove() | 93 | foundsig.remove() |
1084 | 94 | notfoundsig.remove() | ||
1085 | 95 | errorsig.remove() | ||
1086 | 96 | return result | ||
1087 | 97 | |||
1088 | 98 | d.addBoth(cleanup_signals) | ||
1089 | 99 | return d | ||
1090 | 100 | |||
1091 | 101 | |||
1092 | 102 | def clear_credentials(): | ||
1093 | 103 | """Clear the credentials using the ubuntu-sso-client.""" | ||
1094 | 104 | d = defer.Deferred() | ||
1095 | 105 | proxy = get_sso_proxy() | ||
1096 | 106 | |||
1097 | 107 | def credentials_cleared(app_name): | ||
1098 | 108 | """Credentials have been cleared.""" | ||
1099 | 109 | logger.debug('credentials were cleared for app_name %r.', app_name) | ||
1100 | 110 | if app_name == APP_NAME: | ||
1101 | 111 | logger.info('credentials were cleared! (%r).', APP_NAME) | ||
1102 | 112 | d.callback(app_name) | ||
1103 | 113 | |||
1104 | 114 | def credentials_error(app_name, error_dict=None): | ||
1105 | 115 | """No credentials could be retrieved.""" | ||
1106 | 116 | if app_name == APP_NAME: | ||
1107 | 117 | logger.error('credentials error (%r, %r).', app_name, error_dict) | ||
1108 | 118 | error = CredentialsError(app_name, error_dict) | ||
1109 | 119 | d.errback(error) | ||
1110 | 120 | |||
1111 | 121 | clearedsig = proxy.connect_to_signal("CredentialsCleared", | ||
1112 | 122 | credentials_cleared) | ||
1113 | 123 | errorsig = proxy.connect_to_signal("CredentialsError", credentials_error) | ||
1114 | 124 | logger.warning('calling clear_credentials.') | ||
1115 | 125 | proxy.clear_credentials(APP_NAME, {'': ''}, | ||
1116 | 126 | reply_handler=no_op, error_handler=d.errback) | ||
1117 | 127 | |||
1118 | 128 | def cleanup_signals(result): | ||
1119 | 129 | """Remove signals after use.""" | ||
1120 | 130 | clearedsig.remove() | ||
1121 | 78 | errorsig.remove() | 131 | errorsig.remove() |
1122 | 79 | return result | 132 | return result |
1123 | 80 | 133 | ||
1124 | @@ -106,6 +159,12 @@ | |||
1125 | 106 | sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME) | 159 | sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME) |
1126 | 107 | 160 | ||
1127 | 108 | 161 | ||
1128 | 162 | def get_status_syncdaemon_proxy(): | ||
1129 | 163 | """Get a DBus proxy for syncdaemon status calls.""" | ||
1130 | 164 | return get_syncdaemon_proxy("/status", | ||
1131 | 165 | sd_dbus_iface.DBUS_IFACE_STATUS_NAME) | ||
1132 | 166 | |||
1133 | 167 | |||
1134 | 109 | def get_throttling_limits(): | 168 | def get_throttling_limits(): |
1135 | 110 | """Get the speed limits from the syncdaemon.""" | 169 | """Get the speed limits from the syncdaemon.""" |
1136 | 111 | d = defer.Deferred() | 170 | d = defer.Deferred() |
1137 | @@ -154,18 +213,16 @@ | |||
1138 | 154 | return d | 213 | return d |
1139 | 155 | 214 | ||
1140 | 156 | 215 | ||
1144 | 157 | def get_volumes(): | 216 | def get_folders(): |
1145 | 158 | """Retrieve the volumes information from syncdaemon.""" | 217 | """Retrieve the folders information from syncdaemon.""" |
1143 | 159 | # grab folders | ||
1146 | 160 | d = defer.Deferred() | 218 | d = defer.Deferred() |
1147 | 161 | proxy = get_folders_syncdaemon_proxy() | 219 | proxy = get_folders_syncdaemon_proxy() |
1148 | 162 | proxy.get_folders(reply_handler=d.callback, error_handler=d.errback) | 220 | proxy.get_folders(reply_handler=d.callback, error_handler=d.errback) |
1149 | 163 | # grab shares? | ||
1150 | 164 | return d | 221 | return d |
1151 | 165 | 222 | ||
1152 | 166 | 223 | ||
1155 | 167 | def create_volume(path): | 224 | def create_folder(path): |
1156 | 168 | """Create a new volume through syncdaemon.""" | 225 | """Create a new folder through syncdaemon.""" |
1157 | 169 | d = defer.Deferred() | 226 | d = defer.Deferred() |
1158 | 170 | proxy = get_folders_syncdaemon_proxy() | 227 | proxy = get_folders_syncdaemon_proxy() |
1159 | 171 | 228 | ||
1160 | @@ -183,3 +240,72 @@ | |||
1161 | 183 | 240 | ||
1162 | 184 | d.addBoth(cleanup_signals) | 241 | d.addBoth(cleanup_signals) |
1163 | 185 | return d | 242 | return d |
1164 | 243 | |||
1165 | 244 | |||
1166 | 245 | def subscribe_folder(folder_id): | ||
1167 | 246 | """Subscribe to 'folder_id'.""" | ||
1168 | 247 | d = defer.Deferred() | ||
1169 | 248 | proxy = get_folders_syncdaemon_proxy() | ||
1170 | 249 | |||
1171 | 250 | sig = proxy.connect_to_signal('FolderSubscribed', d.callback) | ||
1172 | 251 | cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error)) | ||
1173 | 252 | esig = proxy.connect_to_signal('FolderSubscribeError', cb) | ||
1174 | 253 | |||
1175 | 254 | proxy.subscribe(folder_id, reply_handler=no_op, error_handler=no_op) | ||
1176 | 255 | |||
1177 | 256 | def cleanup_signals(result): | ||
1178 | 257 | """Remove signals after use.""" | ||
1179 | 258 | sig.remove() | ||
1180 | 259 | esig.remove() | ||
1181 | 260 | return result | ||
1182 | 261 | |||
1183 | 262 | d.addBoth(cleanup_signals) | ||
1184 | 263 | return d | ||
1185 | 264 | |||
1186 | 265 | |||
1187 | 266 | def unsubscribe_folder(folder_id): | ||
1188 | 267 | """Unsubscribe 'folder_id'.""" | ||
1189 | 268 | d = defer.Deferred() | ||
1190 | 269 | proxy = get_folders_syncdaemon_proxy() | ||
1191 | 270 | |||
1192 | 271 | sig = proxy.connect_to_signal('FolderUnSubscribed', d.callback) | ||
1193 | 272 | cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error)) | ||
1194 | 273 | esig = proxy.connect_to_signal('FolderUnSubscribeError', cb) | ||
1195 | 274 | |||
1196 | 275 | proxy.unsubscribe(folder_id, reply_handler=no_op, error_handler=no_op) | ||
1197 | 276 | |||
1198 | 277 | def cleanup_signals(result): | ||
1199 | 278 | """Remove signals after use.""" | ||
1200 | 279 | sig.remove() | ||
1201 | 280 | esig.remove() | ||
1202 | 281 | return result | ||
1203 | 282 | |||
1204 | 283 | d.addBoth(cleanup_signals) | ||
1205 | 284 | return d | ||
1206 | 285 | |||
1207 | 286 | |||
1208 | 287 | def get_current_status(): | ||
1209 | 288 | """Retrieve the current status from syncdaemon.""" | ||
1210 | 289 | d = defer.Deferred() | ||
1211 | 290 | proxy = get_status_syncdaemon_proxy() | ||
1212 | 291 | proxy.current_status(reply_handler=d.callback, error_handler=d.errback) | ||
1213 | 292 | return d | ||
1214 | 293 | |||
1215 | 294 | |||
1216 | 295 | def set_status_changed_handler(handler): | ||
1217 | 296 | """Connect 'handler' with syncdaemon's StatusChanged signal.""" | ||
1218 | 297 | proxy = get_status_syncdaemon_proxy() | ||
1219 | 298 | sig = proxy.connect_to_signal('StatusChanged', handler) | ||
1220 | 299 | return proxy, sig | ||
1221 | 300 | |||
1222 | 301 | |||
1223 | 302 | def files_sync_enabled(): | ||
1224 | 303 | """Get if file sync service is enabled.""" | ||
1225 | 304 | enabled = SyncDaemonTool(bus=None).is_files_sync_enabled() | ||
1226 | 305 | return defer.succeed(enabled) | ||
1227 | 306 | |||
1228 | 307 | |||
1229 | 308 | @defer.inlineCallbacks | ||
1230 | 309 | def set_files_sync_enabled(enabled): | ||
1231 | 310 | """Set the file sync service to be 'enabled'.""" | ||
1232 | 311 | yield SyncDaemonTool(bus=dbus.SessionBus()).enable_files_sync(enabled) | ||
1233 | 186 | 312 | ||
1234 | === modified file 'ubuntuone/controlpanel/dbus_service.py' | |||
1235 | --- ubuntuone/controlpanel/dbus_service.py 2010-12-06 12:27:11 +0000 | |||
1236 | +++ ubuntuone/controlpanel/dbus_service.py 2010-12-22 14:37:52 +0000 | |||
1237 | @@ -28,9 +28,15 @@ | |||
1238 | 28 | from twisted.python.failure import Failure | 28 | from twisted.python.failure import Failure |
1239 | 29 | 29 | ||
1240 | 30 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, | 30 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
1244 | 31 | DBUS_PREFERENCES_IFACE, utils) | 31 | DBUS_PREFERENCES_IFACE) |
1245 | 32 | from ubuntuone.controlpanel.backend import ControlBackend | 32 | from ubuntuone.controlpanel.backend import ( |
1246 | 33 | from ubuntuone.controlpanel.logger import setup_logging | 33 | ControlBackend, FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED, |
1247 | 34 | FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_SYNCING, | ||
1248 | 35 | MSG_KEY, STATUS_KEY, | ||
1249 | 36 | ) | ||
1250 | 37 | from ubuntuone.controlpanel.logger import setup_logging, log_call | ||
1251 | 38 | from ubuntuone.controlpanel.utils import (ERROR_TYPE, ERROR_MESSAGE, | ||
1252 | 39 | failure_to_error_dict, exception_to_error_dict) | ||
1253 | 34 | 40 | ||
1254 | 35 | 41 | ||
1255 | 36 | logger = setup_logging('dbus_service') | 42 | logger = setup_logging('dbus_service') |
1256 | @@ -55,15 +61,15 @@ | |||
1257 | 55 | """ | 61 | """ |
1258 | 56 | result = {} | 62 | result = {} |
1259 | 57 | if isinstance(error, Failure): | 63 | if isinstance(error, Failure): |
1261 | 58 | result = utils.failure_to_error_dict(error) | 64 | result = failure_to_error_dict(error) |
1262 | 59 | elif isinstance(error, Exception): | 65 | elif isinstance(error, Exception): |
1264 | 60 | result = utils.exception_to_error_dict(error) | 66 | result = exception_to_error_dict(error) |
1265 | 61 | elif isinstance(error, dict): | 67 | elif isinstance(error, dict): |
1266 | 62 | # ensure that both keys and values are unicodes | 68 | # ensure that both keys and values are unicodes |
1267 | 63 | result = dict(map(make_unicode, i) for i in error.iteritems()) | 69 | result = dict(map(make_unicode, i) for i in error.iteritems()) |
1268 | 64 | else: | 70 | else: |
1269 | 65 | msg = 'Got unexpected error argument %r' % error | 71 | msg = 'Got unexpected error argument %r' % error |
1271 | 66 | result = {utils.ERROR_TYPE: 'UnknownError', utils.ERROR_MESSAGE: msg} | 72 | result = {ERROR_TYPE: 'UnknownError', ERROR_MESSAGE: msg} |
1272 | 67 | 73 | ||
1273 | 68 | return result | 74 | return result |
1274 | 69 | 75 | ||
1275 | @@ -74,10 +80,14 @@ | |||
1276 | 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. |
1277 | 75 | 81 | ||
1278 | 76 | """ | 82 | """ |
1280 | 77 | def inner(error, *a): | 83 | def inner(error, _=None): |
1281 | 78 | """Do the Failure transformation.""" | 84 | """Do the Failure transformation.""" |
1282 | 79 | error_dict = error_handler(error) | 85 | error_dict = error_handler(error) |
1284 | 80 | return f(error_dict) | 86 | if _ is not None: |
1285 | 87 | result = f(_, error_dict) | ||
1286 | 88 | else: | ||
1287 | 89 | result = f(error_dict) | ||
1288 | 90 | return result | ||
1289 | 81 | 91 | ||
1290 | 82 | return inner | 92 | return inner |
1291 | 83 | 93 | ||
1292 | @@ -89,10 +99,14 @@ | |||
1293 | 89 | """Create this instance of the backend.""" | 99 | """Create this instance of the backend.""" |
1294 | 90 | super(ControlPanelBackend, self).__init__(*args, **kwargs) | 100 | super(ControlPanelBackend, self).__init__(*args, **kwargs) |
1295 | 91 | self.backend = backend | 101 | self.backend = backend |
1297 | 92 | logger.debug('ControlPanelBackend created with %r, %r', args, kwargs) | 102 | self.backend.status_changed_handler = self.process_status |
1298 | 103 | logger.debug('ControlPanelBackend: created with %r, %r.\n' | ||
1299 | 104 | 'status_changed_handler is %r.', | ||
1300 | 105 | args, kwargs, self.process_status) | ||
1301 | 93 | 106 | ||
1302 | 94 | # pylint: disable=C0103 | 107 | # pylint: disable=C0103 |
1303 | 95 | 108 | ||
1304 | 109 | @log_call(logger.debug) | ||
1305 | 96 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 110 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1306 | 97 | def account_info(self): | 111 | def account_info(self): |
1307 | 98 | """Find out the account info for the current logged in user.""" | 112 | """Find out the account info for the current logged in user.""" |
1308 | @@ -100,16 +114,19 @@ | |||
1309 | 100 | d.addCallback(self.AccountInfoReady) | 114 | d.addCallback(self.AccountInfoReady) |
1310 | 101 | d.addErrback(transform_failure(self.AccountInfoError)) | 115 | d.addErrback(transform_failure(self.AccountInfoError)) |
1311 | 102 | 116 | ||
1312 | 117 | @log_call(logger.debug) | ||
1313 | 103 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 118 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1314 | 104 | def AccountInfoReady(self, info): | 119 | def AccountInfoReady(self, info): |
1315 | 105 | """The info for the current user is available right now.""" | 120 | """The info for the current user is available right now.""" |
1316 | 106 | 121 | ||
1317 | 122 | @log_call(logger.error) | ||
1318 | 107 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 123 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1319 | 108 | def AccountInfoError(self, error): | 124 | def AccountInfoError(self, error): |
1320 | 109 | """The info for the current user is currently unavailable.""" | 125 | """The info for the current user is currently unavailable.""" |
1321 | 110 | 126 | ||
1322 | 111 | #--- | 127 | #--- |
1323 | 112 | 128 | ||
1324 | 129 | @log_call(logger.debug) | ||
1325 | 113 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 130 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1326 | 114 | def devices_info(self): | 131 | def devices_info(self): |
1327 | 115 | """Find out the devices info for the logged in user.""" | 132 | """Find out the devices info for the logged in user.""" |
1328 | @@ -117,67 +134,122 @@ | |||
1329 | 117 | d.addCallback(self.DevicesInfoReady) | 134 | d.addCallback(self.DevicesInfoReady) |
1330 | 118 | d.addErrback(transform_failure(self.DevicesInfoError)) | 135 | d.addErrback(transform_failure(self.DevicesInfoError)) |
1331 | 119 | 136 | ||
1332 | 137 | @log_call(logger.debug) | ||
1333 | 120 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") | 138 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
1334 | 121 | def DevicesInfoReady(self, info): | 139 | def DevicesInfoReady(self, info): |
1335 | 122 | """The info for the devices is available right now.""" | 140 | """The info for the devices is available right now.""" |
1336 | 123 | 141 | ||
1337 | 142 | @log_call(logger.error) | ||
1338 | 124 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 143 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1339 | 125 | def DevicesInfoError(self, error): | 144 | def DevicesInfoError(self, error): |
1340 | 126 | """The info for the devices is currently unavailable.""" | 145 | """The info for the devices is currently unavailable.""" |
1341 | 127 | 146 | ||
1342 | 128 | #--- | 147 | #--- |
1343 | 129 | 148 | ||
1344 | 149 | @log_call(logger.info) | ||
1345 | 130 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}") | 150 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}") |
1347 | 131 | def change_device_settings(self, token, settings): | 151 | def change_device_settings(self, device_id, settings): |
1348 | 132 | """Configure a given device.""" | 152 | """Configure a given device.""" |
1350 | 133 | d = self.backend.change_device_settings(token, settings) | 153 | d = self.backend.change_device_settings(device_id, settings) |
1351 | 134 | d.addCallback(self.DeviceSettingsChanged) | 154 | d.addCallback(self.DeviceSettingsChanged) |
1353 | 135 | d.addErrback(transform_failure(self.DeviceSettingsChangeError)) | 155 | d.addErrback(transform_failure(self.DeviceSettingsChangeError), |
1354 | 156 | device_id) | ||
1355 | 136 | 157 | ||
1356 | 158 | @log_call(logger.info) | ||
1357 | 137 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | 159 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
1359 | 138 | def DeviceSettingsChanged(self, token): | 160 | def DeviceSettingsChanged(self, device_id): |
1360 | 139 | """The settings for the device were changed.""" | 161 | """The settings for the device were changed.""" |
1361 | 140 | 162 | ||
1364 | 141 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 163 | @log_call(logger.error) |
1365 | 142 | def DeviceSettingsChangeError(self, error): | 164 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}") |
1366 | 165 | def DeviceSettingsChangeError(self, device_id, error): | ||
1367 | 143 | """Problem changing settings for the device.""" | 166 | """Problem changing settings for the device.""" |
1368 | 144 | 167 | ||
1369 | 145 | #--- | 168 | #--- |
1370 | 146 | 169 | ||
1371 | 170 | @log_call(logger.warning) | ||
1372 | 147 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s") | 171 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s") |
1374 | 148 | def remove_device(self, token): | 172 | def remove_device(self, device_id): |
1375 | 149 | """Remove a given device.""" | 173 | """Remove a given device.""" |
1377 | 150 | d = self.backend.remove_device(token) | 174 | d = self.backend.remove_device(device_id) |
1378 | 151 | d.addCallback(self.DeviceRemoved) | 175 | d.addCallback(self.DeviceRemoved) |
1380 | 152 | d.addErrback(transform_failure(self.DeviceRemovalError)) | 176 | d.addErrback(transform_failure(self.DeviceRemovalError), device_id) |
1381 | 153 | 177 | ||
1382 | 178 | @log_call(logger.warning) | ||
1383 | 154 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | 179 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
1385 | 155 | def DeviceRemoved(self, token): | 180 | def DeviceRemoved(self, device_id): |
1386 | 156 | """The removal for the device was completed.""" | 181 | """The removal for the device was completed.""" |
1387 | 157 | 182 | ||
1390 | 158 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 183 | @log_call(logger.error) |
1391 | 159 | def DeviceRemovalError(self, error): | 184 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}") |
1392 | 185 | def DeviceRemovalError(self, device_id, error): | ||
1393 | 160 | """Problem removing the device.""" | 186 | """Problem removing the device.""" |
1394 | 161 | 187 | ||
1395 | 162 | #--- | 188 | #--- |
1396 | 163 | 189 | ||
1397 | 190 | def process_status(self, status_dict): | ||
1398 | 191 | """Match status with signals.""" | ||
1399 | 192 | logger.info('process_status: new status received %r', status_dict) | ||
1400 | 193 | status = status_dict[STATUS_KEY] | ||
1401 | 194 | msg = status_dict[MSG_KEY] | ||
1402 | 195 | if status == FILE_SYNC_DISABLED: | ||
1403 | 196 | self.FileSyncStatusDisabled(msg) | ||
1404 | 197 | elif status == FILE_SYNC_STARTING: | ||
1405 | 198 | self.FileSyncStatusStarting(msg) | ||
1406 | 199 | elif status == FILE_SYNC_DISCONNECTED: | ||
1407 | 200 | self.FileSyncStatusDisconnected(msg) | ||
1408 | 201 | elif status == FILE_SYNC_SYNCING: | ||
1409 | 202 | self.FileSyncStatusSyncing(msg) | ||
1410 | 203 | elif status == FILE_SYNC_IDLE: | ||
1411 | 204 | self.FileSyncStatusIdle(msg) | ||
1412 | 205 | elif status == FILE_SYNC_ERROR: | ||
1413 | 206 | error_dict = {ERROR_TYPE: 'FileSyncStatusError', | ||
1414 | 207 | ERROR_MESSAGE: msg} | ||
1415 | 208 | self.FileSyncStatusError(error_dict) | ||
1416 | 209 | else: | ||
1417 | 210 | self.FileSyncStatusError(error_handler(status_dict)) | ||
1418 | 211 | |||
1419 | 212 | @log_call(logger.debug) | ||
1420 | 164 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 213 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1421 | 165 | def file_sync_status(self): | 214 | def file_sync_status(self): |
1422 | 166 | """Get the status of the file sync service.""" | 215 | """Get the status of the file sync service.""" |
1423 | 167 | d = self.backend.file_sync_status() | 216 | d = self.backend.file_sync_status() |
1425 | 168 | d.addCallback(lambda args: self.FileSyncStatusReady(*args)) | 217 | d.addCallback(self.process_status) |
1426 | 169 | d.addErrback(transform_failure(self.FileSyncStatusError)) | 218 | d.addErrback(transform_failure(self.FileSyncStatusError)) |
1427 | 170 | 219 | ||
1432 | 171 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="bs") | 220 | @log_call(logger.debug) |
1433 | 172 | def FileSyncStatusReady(self, enabled, status): | 221 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
1434 | 173 | """The new file sync status is available.""" | 222 | def FileSyncStatusDisabled(self, msg): |
1435 | 174 | 223 | """The file sync status is disabled.""" | |
1436 | 224 | |||
1437 | 225 | @log_call(logger.debug) | ||
1438 | 226 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | ||
1439 | 227 | def FileSyncStatusStarting(self, msg): | ||
1440 | 228 | """The file sync service is starting.""" | ||
1441 | 229 | |||
1442 | 230 | @log_call(logger.debug) | ||
1443 | 231 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | ||
1444 | 232 | def FileSyncStatusDisconnected(self, msg): | ||
1445 | 233 | """The file sync service is waiting for user to request connection.""" | ||
1446 | 234 | |||
1447 | 235 | @log_call(logger.debug) | ||
1448 | 236 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | ||
1449 | 237 | def FileSyncStatusSyncing(self, msg): | ||
1450 | 238 | """The file sync service is currently syncing.""" | ||
1451 | 239 | |||
1452 | 240 | @log_call(logger.debug) | ||
1453 | 241 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | ||
1454 | 242 | def FileSyncStatusIdle(self, msg): | ||
1455 | 243 | """The file sync service is idle.""" | ||
1456 | 244 | |||
1457 | 245 | @log_call(logger.error) | ||
1458 | 175 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 246 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1459 | 176 | def FileSyncStatusError(self, error): | 247 | def FileSyncStatusError(self, error): |
1460 | 177 | """Problem getting the file sync status.""" | 248 | """Problem getting the file sync status.""" |
1461 | 178 | 249 | ||
1462 | 179 | #--- | 250 | #--- |
1463 | 180 | 251 | ||
1464 | 252 | @log_call(logger.debug) | ||
1465 | 181 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 253 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1466 | 182 | def volumes_info(self): | 254 | def volumes_info(self): |
1467 | 183 | """Find out the volumes info for the logged in user.""" | 255 | """Find out the volumes info for the logged in user.""" |
1468 | @@ -185,35 +257,40 @@ | |||
1469 | 185 | d.addCallback(self.VolumesInfoReady) | 257 | d.addCallback(self.VolumesInfoReady) |
1470 | 186 | d.addErrback(transform_failure(self.VolumesInfoError)) | 258 | d.addErrback(transform_failure(self.VolumesInfoError)) |
1471 | 187 | 259 | ||
1473 | 188 | @utils.log_call(logger.info) | 260 | @log_call(logger.debug) |
1474 | 189 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") | 261 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
1475 | 190 | def VolumesInfoReady(self, info): | 262 | def VolumesInfoReady(self, info): |
1476 | 191 | """The info for the volumes is available right now.""" | 263 | """The info for the volumes is available right now.""" |
1477 | 192 | 264 | ||
1479 | 193 | @utils.log_call(logger.error) | 265 | @log_call(logger.error) |
1480 | 194 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 266 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1481 | 195 | def VolumesInfoError(self, error): | 267 | def VolumesInfoError(self, error): |
1482 | 196 | """The info for the volumes is currently unavailable.""" | 268 | """The info for the volumes is currently unavailable.""" |
1483 | 197 | 269 | ||
1484 | 198 | #--- | 270 | #--- |
1485 | 199 | 271 | ||
1486 | 272 | @log_call(logger.info) | ||
1487 | 200 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}") | 273 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}") |
1488 | 201 | def change_volume_settings(self, volume_id, settings): | 274 | def change_volume_settings(self, volume_id, settings): |
1489 | 202 | """Configure a given volume.""" | 275 | """Configure a given volume.""" |
1490 | 203 | d = self.backend.change_volume_settings(volume_id, settings) | 276 | d = self.backend.change_volume_settings(volume_id, settings) |
1491 | 204 | d.addCallback(self.VolumeSettingsChanged) | 277 | d.addCallback(self.VolumeSettingsChanged) |
1493 | 205 | d.addErrback(transform_failure(self.VolumeSettingsChangeError)) | 278 | d.addErrback(transform_failure(self.VolumeSettingsChangeError), |
1494 | 279 | volume_id) | ||
1495 | 206 | 280 | ||
1496 | 281 | @log_call(logger.info) | ||
1497 | 207 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") | 282 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
1499 | 208 | def VolumeSettingsChanged(self, token): | 283 | def VolumeSettingsChanged(self, volume_id): |
1500 | 209 | """The settings for the volume were changed.""" | 284 | """The settings for the volume were changed.""" |
1501 | 210 | 285 | ||
1504 | 211 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 286 | @log_call(logger.error) |
1505 | 212 | def VolumeSettingsChangeError(self, error): | 287 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}") |
1506 | 288 | def VolumeSettingsChangeError(self, volume_id, error): | ||
1507 | 213 | """Problem changing settings for the volume.""" | 289 | """Problem changing settings for the volume.""" |
1508 | 214 | 290 | ||
1509 | 215 | #--- | 291 | #--- |
1510 | 216 | 292 | ||
1511 | 293 | @log_call(logger.debug) | ||
1512 | 217 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 294 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1513 | 218 | def query_bookmark_extension(self): | 295 | def query_bookmark_extension(self): |
1514 | 219 | """Check if the extension to sync bookmarks is installed.""" | 296 | """Check if the extension to sync bookmarks is installed.""" |
1515 | @@ -221,16 +298,19 @@ | |||
1516 | 221 | d.addCallback(self.QueryBookmarksResult) | 298 | d.addCallback(self.QueryBookmarksResult) |
1517 | 222 | d.addErrback(transform_failure(self.QueryBookmarksError)) | 299 | d.addErrback(transform_failure(self.QueryBookmarksError)) |
1518 | 223 | 300 | ||
1519 | 301 | @log_call(logger.debug) | ||
1520 | 224 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b") | 302 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b") |
1521 | 225 | def QueryBookmarksResult(self, enabled): | 303 | def QueryBookmarksResult(self, enabled): |
1522 | 226 | """The bookmark extension is or is not installed.""" | 304 | """The bookmark extension is or is not installed.""" |
1523 | 227 | 305 | ||
1524 | 306 | @log_call(logger.error) | ||
1525 | 228 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 307 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1526 | 229 | def QueryBookmarksError(self, error): | 308 | def QueryBookmarksError(self, error): |
1527 | 230 | """Problem getting the status of the extension.""" | 309 | """Problem getting the status of the extension.""" |
1528 | 231 | 310 | ||
1529 | 232 | #--- | 311 | #--- |
1530 | 233 | 312 | ||
1531 | 313 | @log_call(logger.info) | ||
1532 | 234 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") | 314 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
1533 | 235 | def install_bookmarks_extension(self): | 315 | def install_bookmarks_extension(self): |
1534 | 236 | """Install the extension to sync bookmarks.""" | 316 | """Install the extension to sync bookmarks.""" |
1535 | @@ -238,10 +318,12 @@ | |||
1536 | 238 | d.addCallback(lambda _: self.InstallBookmarksSuccess()) | 318 | d.addCallback(lambda _: self.InstallBookmarksSuccess()) |
1537 | 239 | d.addErrback(transform_failure(self.InstallBookmarksError)) | 319 | d.addErrback(transform_failure(self.InstallBookmarksError)) |
1538 | 240 | 320 | ||
1539 | 321 | @log_call(logger.info) | ||
1540 | 241 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="") | 322 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="") |
1541 | 242 | def InstallBookmarksSuccess(self): | 323 | def InstallBookmarksSuccess(self): |
1542 | 243 | """The extension to sync bookmarks has been installed.""" | 324 | """The extension to sync bookmarks has been installed.""" |
1543 | 244 | 325 | ||
1544 | 326 | @log_call(logger.error) | ||
1545 | 245 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") | 327 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
1546 | 246 | def InstallBookmarksError(self, error): | 328 | def InstallBookmarksError(self, error): |
1547 | 247 | """Problem installing the extension to sync bookmarks.""" | 329 | """Problem installing the extension to sync bookmarks.""" |
1548 | @@ -274,8 +356,10 @@ | |||
1549 | 274 | return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus()) | 356 | return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus()) |
1550 | 275 | 357 | ||
1551 | 276 | 358 | ||
1553 | 277 | def publish_backend(backend=ControlBackend()): | 359 | def publish_backend(backend=None): |
1554 | 278 | """Publish the backend on the DBus.""" | 360 | """Publish the backend on the DBus.""" |
1555 | 361 | if backend is None: | ||
1556 | 362 | backend = ControlBackend() | ||
1557 | 279 | return ControlPanelBackend(backend=backend, | 363 | return ControlPanelBackend(backend=backend, |
1558 | 280 | object_path=DBUS_PREFERENCES_PATH, | 364 | object_path=DBUS_PREFERENCES_PATH, |
1559 | 281 | bus_name=get_busname()) | 365 | bus_name=get_busname()) |
1560 | 282 | 366 | ||
1561 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' | |||
1562 | --- ubuntuone/controlpanel/gtk/gui.py 2010-12-06 12:27:11 +0000 | |||
1563 | +++ ubuntuone/controlpanel/gtk/gui.py 2010-12-22 14:37:52 +0000 | |||
1564 | @@ -21,6 +21,7 @@ | |||
1565 | 21 | from __future__ import division | 21 | from __future__ import division |
1566 | 22 | 22 | ||
1567 | 23 | import gettext | 23 | import gettext |
1568 | 24 | import operator | ||
1569 | 24 | 25 | ||
1570 | 25 | from functools import wraps | 26 | from functools import wraps |
1571 | 26 | 27 | ||
1572 | @@ -46,8 +47,10 @@ | |||
1573 | 46 | 47 | ||
1574 | 47 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, | 48 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
1575 | 48 | DBUS_PREFERENCES_IFACE) | 49 | DBUS_PREFERENCES_IFACE) |
1578 | 49 | from ubuntuone.controlpanel.logger import setup_logging | 50 | from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE, |
1579 | 50 | from ubuntuone.controlpanel.utils import get_data_file, log_call | 51 | DEVICE_TYPE_COMPUTER, bool_str) |
1580 | 52 | from ubuntuone.controlpanel.logger import setup_logging, log_call | ||
1581 | 53 | from ubuntuone.controlpanel.utils import get_data_file | ||
1582 | 51 | 54 | ||
1583 | 52 | 55 | ||
1584 | 53 | logger = setup_logging('gtk.gui') | 56 | logger = setup_logging('gtk.gui') |
1585 | @@ -58,16 +61,22 @@ | |||
1586 | 58 | ORANGE = '#c95724' | 61 | ORANGE = '#c95724' |
1587 | 59 | LOADING = _('Loading...') | 62 | LOADING = _('Loading...') |
1588 | 60 | OVERVIEW_MSGS = [ | 63 | OVERVIEW_MSGS = [ |
1598 | 61 | _('<b>Ubuntu One</b> Music Store. ' | 64 | _('Backup and access your files from <b>any</b> computer (Ubuntu & ' |
1599 | 62 | 'A cloud enabled mobile shopping experience.\n' | 65 | 'Windows), mobile device (Android) or the web.'), |
1600 | 63 | '<small>«this is custom text #1»</small>'), | 66 | _('A <b>portable address book</b> that unifies your most important ' |
1601 | 64 | _('<i>Mobile</i> contacts sync. ' | 67 | 'contact sources: mobile phones, social networks, email clients and ' |
1602 | 65 | '<span foreground="red">Your portable address book.</span>\n' | 68 | 'services.'), |
1603 | 66 | '<small>«this is custom text #3»</small>'), | 69 | _('A <b>personal music library</b> that can be streamed to Android, ' |
1604 | 67 | _('...'), | 70 | 'iPhone and AirPlay-enabled devices.'), |
1605 | 68 | _('Sync data between computers.\n' | 71 | _('Add to that music library with a <b>Music Store</b> that automatically ' |
1606 | 69 | '<small>«this is custom text #<i>N</i>»</small>')] | 72 | 'delivers to your personal cloud and connected devices.'), |
1607 | 73 | _('Keep your <b>Firefox bookmarks</b> and <b>Tomboy notes</b> in sync ' | ||
1608 | 74 | 'across computers (Ubuntu & Windows).'), | ||
1609 | 75 | _('<b>2GB</b> of free storage.'), | ||
1610 | 76 | ] | ||
1611 | 77 | VALUE_ERROR = _('Value could not be retrieved.') | ||
1612 | 70 | WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE | 78 | WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE |
1613 | 79 | KILOBYTES = 1024 | ||
1614 | 71 | 80 | ||
1615 | 72 | 81 | ||
1616 | 73 | def filter_by_app_name(f): | 82 | def filter_by_app_name(f): |
1617 | @@ -85,16 +94,9 @@ | |||
1618 | 85 | return filter_by_app_name_inner | 94 | return filter_by_app_name_inner |
1619 | 86 | 95 | ||
1620 | 87 | 96 | ||
1621 | 88 | def dbus_str_to_bool(boolstr): | ||
1622 | 89 | """Transform a dbus string representation of a boolean to True/False.""" | ||
1623 | 90 | return False if (not boolstr or boolstr == '0') else True | ||
1624 | 91 | |||
1625 | 92 | |||
1626 | 93 | class ControlPanelMixin(object): | 97 | class ControlPanelMixin(object): |
1627 | 94 | """The main interface for the Ubuntu One control panel.""" | 98 | """The main interface for the Ubuntu One control panel.""" |
1628 | 95 | 99 | ||
1629 | 96 | VALUE_ERROR = _('Value could not be retrieved.') | ||
1630 | 97 | |||
1631 | 98 | def __init__(self, filename=None): | 100 | def __init__(self, filename=None): |
1632 | 99 | bus = dbus.SessionBus() | 101 | bus = dbus.SessionBus() |
1633 | 100 | try: | 102 | try: |
1634 | @@ -155,6 +157,8 @@ | |||
1635 | 155 | self.set_title(self.TITLE % {'app_name': U1_APP_NAME}) | 157 | self.set_title(self.TITLE % {'app_name': U1_APP_NAME}) |
1636 | 156 | self.set_position(gtk.WIN_POS_CENTER_ALWAYS) | 158 | self.set_position(gtk.WIN_POS_CENTER_ALWAYS) |
1637 | 157 | self.set_icon_name('ubuntuone') | 159 | self.set_icon_name('ubuntuone') |
1638 | 160 | self.set_size_request(-1, 525) # bug #683164 | ||
1639 | 161 | |||
1640 | 158 | self.connect('delete-event', lambda w, e: gtk.main_quit()) | 162 | self.connect('delete-event', lambda w, e: gtk.main_quit()) |
1641 | 159 | self.show() | 163 | self.show() |
1642 | 160 | 164 | ||
1643 | @@ -170,25 +174,49 @@ | |||
1644 | 170 | gtk.main() | 174 | gtk.main() |
1645 | 171 | 175 | ||
1646 | 172 | 176 | ||
1648 | 173 | class ControlPanel(gtk.VBox, ControlPanelMixin): | 177 | class ControlPanel(gtk.Notebook): |
1649 | 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.""" |
1650 | 175 | 179 | ||
1651 | 176 | # should not be any larger than 736x525 | 180 | # should not be any larger than 736x525 |
1652 | 177 | 181 | ||
1653 | 178 | def __init__(self, window_id=0): | 182 | def __init__(self, window_id=0): |
1656 | 179 | gtk.VBox.__init__(self) | 183 | gtk.Notebook.__init__(self) |
1655 | 180 | ControlPanelMixin.__init__(self, filename='controlpanel.ui') | ||
1657 | 181 | self._window_id = window_id | 184 | self._window_id = window_id |
1658 | 182 | 185 | ||
1660 | 183 | self.overview = OverviewPanel(window_id=window_id) | 186 | self.set_show_tabs(False) |
1661 | 187 | self.set_show_border(False) | ||
1662 | 188 | |||
1663 | 189 | self.overview = OverviewPanel(window_id=self._window_id) | ||
1664 | 190 | self.insert_page(self.overview, position=0) | ||
1665 | 191 | |||
1666 | 192 | self.management = ManagementPanel() | ||
1667 | 193 | self.insert_page(self.management, position=1) | ||
1668 | 194 | |||
1669 | 184 | self.overview.connect('credentials-found', | 195 | self.overview.connect('credentials-found', |
1670 | 185 | self.on_show_management_panel) | 196 | self.on_show_management_panel) |
1672 | 186 | self.add(self.overview) | 197 | self.management.connect('local-device-removed', |
1673 | 198 | self.on_show_overview_panel) | ||
1674 | 199 | |||
1675 | 187 | self.show() | 200 | self.show() |
1680 | 188 | 201 | self.on_show_overview_panel() | |
1681 | 189 | def on_show_management_panel(self, *args, **kwargs): | 202 | |
1682 | 190 | """Show the netbook (main panel).""" | 203 | logger.debug('%s: started (window size %r).', |
1683 | 191 | self.add(ManagementPanel()) | 204 | self.__class__.__name__, self.get_size_request()) |
1684 | 205 | |||
1685 | 206 | def on_show_overview_panel(self, widget=None): | ||
1686 | 207 | """Show the overview panel.""" | ||
1687 | 208 | self.set_current_page(0) | ||
1688 | 209 | |||
1689 | 210 | def on_show_management_panel(self, widget=None, | ||
1690 | 211 | credentials_are_new=False, token=None): | ||
1691 | 212 | """Show the notebook (main panel).""" | ||
1692 | 213 | if self.get_current_page() == 0: | ||
1693 | 214 | self.management.load() | ||
1694 | 215 | if credentials_are_new: | ||
1695 | 216 | # redirect user to Folders page to review folders subscription | ||
1696 | 217 | self.management.folders_button.clicked() | ||
1697 | 218 | |||
1698 | 219 | self.next_page() | ||
1699 | 192 | 220 | ||
1700 | 193 | 221 | ||
1701 | 194 | class UbuntuOneBin(gtk.VBox): | 222 | class UbuntuOneBin(gtk.VBox): |
1702 | @@ -204,8 +232,26 @@ | |||
1703 | 204 | self.title = PanelTitle(markup='<b>' + title + '</b>') | 232 | self.title = PanelTitle(markup='<b>' + title + '</b>') |
1704 | 205 | self.pack_start(self.title, expand=False) | 233 | self.pack_start(self.title, expand=False) |
1705 | 206 | 234 | ||
1706 | 235 | self.message = LabelLoading(LOADING) | ||
1707 | 236 | self.pack_start(self.message, expand=False) | ||
1708 | 237 | |||
1709 | 207 | self.show_all() | 238 | self.show_all() |
1710 | 208 | 239 | ||
1711 | 240 | @log_call(logger.debug) | ||
1712 | 241 | def on_success(self, message=''): | ||
1713 | 242 | """Use this callback to stop the Loading and show 'message'.""" | ||
1714 | 243 | self.message.stop() | ||
1715 | 244 | self.message.set_markup(message) | ||
1716 | 245 | |||
1717 | 246 | @log_call(logger.error) | ||
1718 | 247 | def on_error(self, message=None): | ||
1719 | 248 | """Use this callback to stop the Loading and set a warning message.""" | ||
1720 | 249 | if message == None: | ||
1721 | 250 | message = VALUE_ERROR | ||
1722 | 251 | |||
1723 | 252 | self.message.stop() | ||
1724 | 253 | self.message.set_markup(WARNING_MARKUP % message) | ||
1725 | 254 | |||
1726 | 209 | 255 | ||
1727 | 210 | class OverviewPanel(GreyableBin, ControlPanelMixin): | 256 | class OverviewPanel(GreyableBin, ControlPanelMixin): |
1728 | 211 | """The overview panel. Introduces Ubuntu One to the not logged user.""" | 257 | """The overview panel. Introduces Ubuntu One to the not logged user.""" |
1729 | @@ -222,7 +268,6 @@ | |||
1730 | 222 | BULLET = '<span foreground="%s">✔</span>' % ORANGE | 268 | BULLET = '<span foreground="%s">✔</span>' % ORANGE |
1731 | 223 | 269 | ||
1732 | 224 | def __init__(self, messages=None, window_id=0): | 270 | def __init__(self, messages=None, window_id=0): |
1733 | 225 | gtk.link_button_set_uri_hook(lambda *a: None) | ||
1734 | 226 | GreyableBin.__init__(self) | 271 | GreyableBin.__init__(self) |
1735 | 227 | ControlPanelMixin.__init__(self, filename='overview.ui') | 272 | ControlPanelMixin.__init__(self, filename='overview.ui') |
1736 | 228 | self.add(self.itself) | 273 | self.add(self.itself) |
1737 | @@ -230,6 +275,9 @@ | |||
1738 | 230 | self.warning_label.set_property('xalign', 0.5) | 275 | self.warning_label.set_property('xalign', 0.5) |
1739 | 231 | self.warning_label.connect('size-allocate', self.on_size_allocate) | 276 | self.warning_label.connect('size-allocate', self.on_size_allocate) |
1740 | 232 | 277 | ||
1741 | 278 | self.connect_button.set_uri('') | ||
1742 | 279 | |||
1743 | 280 | self._credentials_are_new = False | ||
1744 | 233 | self._messages = messages | 281 | self._messages = messages |
1745 | 234 | self._window_id = window_id | 282 | self._window_id = window_id |
1746 | 235 | self._build_messages() | 283 | self._build_messages() |
1747 | @@ -320,13 +368,14 @@ | |||
1748 | 320 | @log_call(logger.info) | 368 | @log_call(logger.info) |
1749 | 321 | def on_credentials_found(self, app_name, credentials): | 369 | def on_credentials_found(self, app_name, credentials): |
1750 | 322 | """SSO backend notifies of credentials found.""" | 370 | """SSO backend notifies of credentials found.""" |
1753 | 323 | self.emit('credentials-found', app_name, credentials) | 371 | self.set_property('greyed', False) |
1754 | 324 | self.hide() | 372 | self.emit('credentials-found', self._credentials_are_new, credentials) |
1755 | 325 | 373 | ||
1756 | 326 | @filter_by_app_name | 374 | @filter_by_app_name |
1757 | 327 | @log_call(logger.info) | 375 | @log_call(logger.info) |
1758 | 328 | def on_credentials_not_found(self, app_name): | 376 | def on_credentials_not_found(self, app_name): |
1759 | 329 | """SSO backend notifies of credentials not found.""" | 377 | """SSO backend notifies of credentials not found.""" |
1760 | 378 | self._credentials_are_new = True | ||
1761 | 330 | self.set_property('greyed', False) | 379 | self.set_property('greyed', False) |
1762 | 331 | 380 | ||
1763 | 332 | @filter_by_app_name | 381 | @filter_by_app_name |
1764 | @@ -365,42 +414,36 @@ | |||
1765 | 365 | """The account panel. The user can manage the subscription.""" | 414 | """The account panel. The user can manage the subscription.""" |
1766 | 366 | 415 | ||
1767 | 367 | TITLE = _('Welcome to Ubuntu One!') | 416 | TITLE = _('Welcome to Ubuntu One!') |
1768 | 417 | NAME = _('Name') | ||
1769 | 418 | TYPE = _('Account type') | ||
1770 | 419 | EMAIL = _('Email address') | ||
1771 | 368 | 420 | ||
1772 | 369 | def __init__(self): | 421 | def __init__(self): |
1773 | 370 | UbuntuOneBin.__init__(self) | 422 | UbuntuOneBin.__init__(self) |
1774 | 371 | ControlPanelMixin.__init__(self, filename='account.ui') | 423 | ControlPanelMixin.__init__(self, filename='account.ui') |
1776 | 372 | self.pack_start(self.itself) | 424 | self.add(self.itself) |
1777 | 373 | self.show() | 425 | self.show() |
1778 | 374 | 426 | ||
1779 | 375 | self.backend.connect_to_signal('AccountInfoReady', | 427 | self.backend.connect_to_signal('AccountInfoReady', |
1780 | 376 | self.on_account_info_ready) | 428 | self.on_account_info_ready) |
1781 | 377 | self.backend.connect_to_signal('AccountInfoError', | 429 | self.backend.connect_to_signal('AccountInfoError', |
1782 | 378 | self.on_account_info_error) | 430 | self.on_account_info_error) |
1792 | 379 | 431 | self.account.hide() | |
1784 | 380 | self.name_label = LabelLoading(LOADING) | ||
1785 | 381 | self.name_box.pack_start(self.name_label) | ||
1786 | 382 | |||
1787 | 383 | self.type_label = LabelLoading(LOADING) | ||
1788 | 384 | self.type_box.pack_start(self.type_label) | ||
1789 | 385 | |||
1790 | 386 | self.email_label = LabelLoading(LOADING) | ||
1791 | 387 | self.email_box.pack_start(self.email_label) | ||
1793 | 388 | 432 | ||
1794 | 389 | @log_call(logger.debug) | 433 | @log_call(logger.debug) |
1795 | 390 | def on_account_info_ready(self, info): | 434 | def on_account_info_ready(self, info): |
1796 | 391 | """Backend notifies of account info.""" | 435 | """Backend notifies of account info.""" |
1798 | 392 | for i in ('name', 'type', 'email'): | 436 | self.on_success() |
1799 | 437 | |||
1800 | 438 | for i in (u'name', u'type', u'email'): | ||
1801 | 393 | label = getattr(self, '%s_label' % i) | 439 | label = getattr(self, '%s_label' % i) |
1804 | 394 | label.set_markup('<b>%s</b>' % info[i]) | 440 | label.set_markup('%s' % (info[i])) |
1805 | 395 | label.stop() | 441 | self.account.show() |
1806 | 396 | 442 | ||
1807 | 397 | @log_call(logger.error) | 443 | @log_call(logger.error) |
1808 | 398 | def on_account_info_error(self, error_dict=None): | 444 | def on_account_info_error(self, error_dict=None): |
1809 | 399 | """Backend notifies of an error when fetching account info.""" | 445 | """Backend notifies of an error when fetching account info.""" |
1814 | 400 | for i in ('name', 'type', 'email'): | 446 | self.on_error() |
1811 | 401 | label = getattr(self, '%s_label' % i) | ||
1812 | 402 | label.set_markup(WARNING_MARKUP % self.VALUE_ERROR) | ||
1813 | 403 | label.stop() | ||
1815 | 404 | 447 | ||
1816 | 405 | 448 | ||
1817 | 406 | class FoldersPanel(UbuntuOneBin, ControlPanelMixin): | 449 | class FoldersPanel(UbuntuOneBin, ControlPanelMixin): |
1818 | @@ -408,48 +451,241 @@ | |||
1819 | 408 | 451 | ||
1820 | 409 | TITLE = _('Listed below are the folders available on this machine. ' | 452 | TITLE = _('Listed below are the folders available on this machine. ' |
1821 | 410 | 'Subscribed means the folder will receive and send updates.') | 453 | 'Subscribed means the folder will receive and send updates.') |
1822 | 454 | NO_VOLUMES = _('No folders to show.') | ||
1823 | 411 | 455 | ||
1824 | 412 | def __init__(self): | 456 | def __init__(self): |
1825 | 413 | UbuntuOneBin.__init__(self) | 457 | UbuntuOneBin.__init__(self) |
1826 | 414 | ControlPanelMixin.__init__(self, filename='folders.ui') | 458 | ControlPanelMixin.__init__(self, filename='folders.ui') |
1828 | 415 | self.pack_start(self.itself) | 459 | self.add(self.itself) |
1829 | 416 | self.show_all() | 460 | self.show_all() |
1830 | 417 | 461 | ||
1831 | 418 | self.header = (gtk.Label(), gtk.Label()) | ||
1832 | 419 | self.header[0].set_markup('<b>' + _('Local path') + '</b>') | ||
1833 | 420 | self.header[1].set_markup('<b>' + _('Subscribed') + '</b>') | ||
1834 | 421 | |||
1835 | 422 | self.backend.connect_to_signal('VolumesInfoReady', | 462 | self.backend.connect_to_signal('VolumesInfoReady', |
1836 | 423 | self.on_volumes_info_ready) | 463 | self.on_volumes_info_ready) |
1837 | 424 | self.backend.connect_to_signal('VolumesInfoError', | 464 | self.backend.connect_to_signal('VolumesInfoError', |
1838 | 425 | self.on_volumes_info_error) | 465 | self.on_volumes_info_error) |
1840 | 426 | self.backend.volumes_info() | 466 | self.volumes = None |
1841 | 467 | self._subscribed = [] | ||
1842 | 427 | 468 | ||
1843 | 428 | @log_call(logger.debug) | ||
1844 | 429 | def on_volumes_info_ready(self, info): | 469 | def on_volumes_info_ready(self, info): |
1845 | 430 | """Backend notifies of volumes info.""" | 470 | """Backend notifies of volumes info.""" |
1851 | 431 | folders = gtk.Table(rows=len(info) + 1, columns=2) | 471 | |
1852 | 432 | 472 | if self.volumes is not None: | |
1853 | 433 | folders.attach(self.header[0], 0, 1, 0, 1) | 473 | self.folders.remove(self.volumes) |
1854 | 434 | folders.attach(self.header[1], 1, 2, 0, 1, xoptions=0) | 474 | self.volumes = None |
1855 | 435 | 475 | ||
1856 | 476 | if not info: | ||
1857 | 477 | self.on_success(self.NO_VOLUMES) | ||
1858 | 478 | return | ||
1859 | 479 | else: | ||
1860 | 480 | self.on_success() | ||
1861 | 481 | |||
1862 | 482 | self.volumes = gtk.Table(rows=len(info) + 1, columns=2) | ||
1863 | 483 | |||
1864 | 484 | header = (gtk.Label(), gtk.Label()) | ||
1865 | 485 | header[0].set_markup('<b>' + _('Local path') + '</b>') | ||
1866 | 486 | header[1].set_markup('<b>' + _('Subscribed') + '</b>') | ||
1867 | 487 | self.volumes.attach(header[0], 0, 1, 0, 1) | ||
1868 | 488 | self.volumes.attach(header[1], 1, 2, 0, 1, xoptions=0) | ||
1869 | 489 | self.volumes.show_all() | ||
1870 | 490 | |||
1871 | 491 | info.sort(key=operator.itemgetter('suggested_path')) | ||
1872 | 436 | for i, volume in enumerate(info): | 492 | for i, volume in enumerate(info): |
1874 | 437 | path = gtk.Label(volume['path']) | 493 | path = gtk.Label(volume['suggested_path']) |
1875 | 438 | path.set_property('xalign', 0) | 494 | path.set_property('xalign', 0) |
1886 | 439 | folders.attach(path, 0, 1, i + 1, i + 2) | 495 | path.show() |
1887 | 440 | 496 | self.volumes.attach(path, 0, 1, i + 1, i + 2) | |
1888 | 441 | subscribed = gtk.CheckButton() | 497 | |
1889 | 442 | subscribed.set_active(dbus_str_to_bool(volume['subscribed'])) | 498 | subscribed = gtk.CheckButton(volume['volume_id']) |
1890 | 443 | folders.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0) | 499 | subscribed.set_active(bool(volume['subscribed'])) |
1891 | 444 | 500 | subscribed.show() | |
1892 | 445 | alig = gtk.Alignment(xalign=0.5) | 501 | subscribed.get_child().hide() |
1893 | 446 | alig.add(folders) | 502 | subscribed.connect('clicked', self.on_subscribed_clicked) |
1894 | 447 | alig.show_all() | 503 | self._subscribed.append(subscribed) |
1895 | 448 | self.itself.pack_start(alig, expand=False) | 504 | self.volumes.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0) |
1896 | 505 | |||
1897 | 506 | self.folders.add(self.volumes) | ||
1898 | 449 | 507 | ||
1899 | 450 | @log_call(logger.error) | 508 | @log_call(logger.error) |
1900 | 451 | def on_volumes_info_error(self, error_dict=None): | 509 | def on_volumes_info_error(self, error_dict=None): |
1901 | 452 | """Backend notifies of an error when fetching volumes info.""" | 510 | """Backend notifies of an error when fetching volumes info.""" |
1902 | 511 | self.on_error() | ||
1903 | 512 | |||
1904 | 513 | def on_subscribed_clicked(self, checkbutton): | ||
1905 | 514 | """The user toggled 'checkbutton'.""" | ||
1906 | 515 | volume_id = checkbutton.get_label() | ||
1907 | 516 | subscribed = bool_str(checkbutton.get_active()) | ||
1908 | 517 | self.backend.change_volume_settings(volume_id, | ||
1909 | 518 | {'subscribed': subscribed}) | ||
1910 | 519 | |||
1911 | 520 | def load(self): | ||
1912 | 521 | """Load the volume list.""" | ||
1913 | 522 | self.backend.volumes_info() | ||
1914 | 523 | self.message.start() | ||
1915 | 524 | |||
1916 | 525 | |||
1917 | 526 | class Device(gtk.VBox, ControlPanelMixin): | ||
1918 | 527 | """The device widget.""" | ||
1919 | 528 | |||
1920 | 529 | DEVICE_CHANGE_ERROR = _('The settings could not be changed,\n' | ||
1921 | 530 | 'previous values were restored.') | ||
1922 | 531 | DEVICE_REMOVAL_ERROR = _('The device could not be removed.') | ||
1923 | 532 | |||
1924 | 533 | def __init__(self): | ||
1925 | 534 | gtk.VBox.__init__(self) | ||
1926 | 535 | ControlPanelMixin.__init__(self, filename='device.ui') | ||
1927 | 536 | |||
1928 | 537 | self._updating = False | ||
1929 | 538 | self._last_settings = {} | ||
1930 | 539 | self.id = None | ||
1931 | 540 | self.is_local = False | ||
1932 | 541 | self.configurable = False | ||
1933 | 542 | |||
1934 | 543 | self.update(device_id=None, device_name='', | ||
1935 | 544 | is_local=False, configurable=False, limit_bandwidth=False, | ||
1936 | 545 | max_upload_speed=0, max_download_speed=0) | ||
1937 | 546 | |||
1938 | 547 | self.add(self.itself) | ||
1939 | 548 | self.show() | ||
1940 | 549 | |||
1941 | 550 | self.backend.connect_to_signal('DeviceSettingsChanged', | ||
1942 | 551 | self.on_device_settings_changed) | ||
1943 | 552 | self.backend.connect_to_signal('DeviceSettingsChangeError', | ||
1944 | 553 | self.on_device_settings_change_error) | ||
1945 | 554 | self.backend.connect_to_signal('DeviceRemoved', | ||
1946 | 555 | self.on_device_removed) | ||
1947 | 556 | self.backend.connect_to_signal('DeviceRemovalError', | ||
1948 | 557 | self.on_device_removal_error) | ||
1949 | 558 | |||
1950 | 559 | def _change_device_settings(self, *args): | ||
1951 | 560 | """Update backend settings for this device.""" | ||
1952 | 561 | if self._updating: | ||
1953 | 562 | return | ||
1954 | 563 | |||
1955 | 564 | # Not disabling the GUI to avoid annyong twitchings | ||
1956 | 565 | #self.set_sensitive(False) | ||
1957 | 566 | self.warning_label.set_text('') | ||
1958 | 567 | self.backend.change_device_settings(self.id, self.__dict__) | ||
1959 | 568 | |||
1960 | 569 | def _block_signals(f): | ||
1961 | 570 | """Execute 'f' while having the _updating flag set.""" | ||
1962 | 571 | |||
1963 | 572 | # pylint: disable=E0213,W0212,E1102 | ||
1964 | 573 | |||
1965 | 574 | @wraps(f) | ||
1966 | 575 | def inner(self, *args, **kwargs): | ||
1967 | 576 | """Execute 'f' while having the _updating flag set.""" | ||
1968 | 577 | old = self._updating | ||
1969 | 578 | self._updating = True | ||
1970 | 579 | |||
1971 | 580 | result = f(self, *args, **kwargs) | ||
1972 | 581 | |||
1973 | 582 | self._updating = old | ||
1974 | 583 | return result | ||
1975 | 584 | |||
1976 | 585 | return inner | ||
1977 | 586 | |||
1978 | 587 | on_limit_bandwidth_toggled = _change_device_settings | ||
1979 | 588 | on_max_upload_speed_value_changed = _change_device_settings | ||
1980 | 589 | on_max_download_speed_value_changed = _change_device_settings | ||
1981 | 590 | |||
1982 | 591 | def on_remove_clicked(self, widget): | ||
1983 | 592 | """Remove button was clicked or activated.""" | ||
1984 | 593 | self.backend.remove_device(self.id) | ||
1985 | 594 | self.set_sensitive(False) | ||
1986 | 595 | |||
1987 | 596 | @_block_signals | ||
1988 | 597 | def update(self, **kwargs): | ||
1989 | 598 | """Update according to named parameters. | ||
1990 | 599 | |||
1991 | 600 | Possible settings are: | ||
1992 | 601 | * device_id (string, not shown to the user) | ||
1993 | 602 | * device_name (string) | ||
1994 | 603 | * type (either DEVICE_TYPE_PHONE or DEVICE_TYPE_COMPUTER) | ||
1995 | 604 | * is_local (True/False) | ||
1996 | 605 | * configurable (True/False) | ||
1997 | 606 | * if configurable, the following can be set: | ||
1998 | 607 | * limit_bandwidth (True/False) | ||
1999 | 608 | * max_upload_speed (bytes) | ||
2000 | 609 | * max_download_speed (bytes) | ||
2001 | 610 | |||
2002 | 611 | """ | ||
2003 | 612 | if 'device_id' in kwargs: | ||
2004 | 613 | self.id = kwargs['device_id'] | ||
2005 | 614 | |||
2006 | 615 | if 'device_name' in kwargs: | ||
2007 | 616 | self.device_name.set_markup('<b>%s</b>' % kwargs['device_name']) | ||
2008 | 617 | |||
2009 | 618 | if 'device_type' in kwargs: | ||
2010 | 619 | dtype = kwargs['device_type'] | ||
2011 | 620 | if dtype in (DEVICE_TYPE_COMPUTER, DEVICE_TYPE_PHONE): | ||
2012 | 621 | self.device_type.set_from_icon_name(dtype.lower(), | ||
2013 | 622 | gtk.ICON_SIZE_BUTTON) | ||
2014 | 623 | |||
2015 | 624 | if 'is_local' in kwargs: | ||
2016 | 625 | self.is_local = bool(kwargs['is_local']) | ||
2017 | 626 | |||
2018 | 627 | if 'configurable' in kwargs: | ||
2019 | 628 | self.configurable = bool(kwargs['configurable']) | ||
2020 | 629 | self.throttling.set_visible(self.configurable) | ||
2021 | 630 | |||
2022 | 631 | if 'limit_bandwidth' in kwargs: | ||
2023 | 632 | self.limit_bandwidth.set_active(bool(kwargs['limit_bandwidth'])) | ||
2024 | 633 | |||
2025 | 634 | for speed in ('max_upload_speed', 'max_download_speed'): | ||
2026 | 635 | if speed in kwargs: | ||
2027 | 636 | value = int(kwargs[speed]) // KILOBYTES | ||
2028 | 637 | getattr(self, speed).set_value(value) | ||
2029 | 638 | |||
2030 | 639 | self._last_settings = self.__dict__ | ||
2031 | 640 | |||
2032 | 641 | @property | ||
2033 | 642 | def __dict__(self): | ||
2034 | 643 | result = { | ||
2035 | 644 | 'device_id': self.id, | ||
2036 | 645 | 'device_name': self.device_name.get_text(), | ||
2037 | 646 | 'device_type': self.device_type.get_icon_name()[0].capitalize(), | ||
2038 | 647 | 'is_local': bool_str(self.is_local), | ||
2039 | 648 | 'configurable': bool_str(self.configurable), | ||
2040 | 649 | 'limit_bandwidth': bool_str(self.limit_bandwidth.get_active()), | ||
2041 | 650 | 'max_upload_speed': \ | ||
2042 | 651 | str(self.max_upload_speed.get_value_as_int() * KILOBYTES), | ||
2043 | 652 | 'max_download_speed': \ | ||
2044 | 653 | str(self.max_download_speed.get_value_as_int() * KILOBYTES), | ||
2045 | 654 | } | ||
2046 | 655 | return result | ||
2047 | 656 | |||
2048 | 657 | @log_call(logger.info) | ||
2049 | 658 | def on_device_settings_changed(self, device_id): | ||
2050 | 659 | """The change of this device settings succeded.""" | ||
2051 | 660 | if device_id != self.id: | ||
2052 | 661 | return | ||
2053 | 662 | self.set_sensitive(True) | ||
2054 | 663 | self.warning_label.set_text('') | ||
2055 | 664 | self._last_settings = self.__dict__ | ||
2056 | 665 | |||
2057 | 666 | @log_call(logger.error) | ||
2058 | 667 | def on_device_settings_change_error(self, device_id, error_dict=None): | ||
2059 | 668 | """The change of this device settings failed.""" | ||
2060 | 669 | if device_id != self.id: | ||
2061 | 670 | return | ||
2062 | 671 | self.update(**self._last_settings) | ||
2063 | 672 | self._set_warning(self.DEVICE_CHANGE_ERROR, self.warning_label) | ||
2064 | 673 | self.set_sensitive(True) | ||
2065 | 674 | |||
2066 | 675 | @log_call(logger.warning) | ||
2067 | 676 | def on_device_removed(self, device_id): | ||
2068 | 677 | """The removal of this device succeded.""" | ||
2069 | 678 | if device_id != self.id: | ||
2070 | 679 | return | ||
2071 | 680 | self.hide() | ||
2072 | 681 | |||
2073 | 682 | @log_call(logger.error) | ||
2074 | 683 | def on_device_removal_error(self, device_id, error_dict=None): | ||
2075 | 684 | """The removal of this device failed.""" | ||
2076 | 685 | if device_id != self.id: | ||
2077 | 686 | return | ||
2078 | 687 | self._set_warning(self.DEVICE_REMOVAL_ERROR, self.warning_label) | ||
2079 | 688 | self.set_sensitive(True) | ||
2080 | 453 | 689 | ||
2081 | 454 | 690 | ||
2082 | 455 | class DevicesPanel(UbuntuOneBin, ControlPanelMixin): | 691 | class DevicesPanel(UbuntuOneBin, ControlPanelMixin): |
2083 | @@ -457,13 +693,62 @@ | |||
2084 | 457 | 693 | ||
2085 | 458 | TITLE = _('The devices connected with your personal cloud network are ' | 694 | TITLE = _('The devices connected with your personal cloud network are ' |
2086 | 459 | 'listed below:') | 695 | 'listed below:') |
2087 | 696 | NO_DEVICES = _('No devices to show.') | ||
2088 | 460 | 697 | ||
2089 | 461 | def __init__(self): | 698 | def __init__(self): |
2090 | 462 | UbuntuOneBin.__init__(self) | 699 | UbuntuOneBin.__init__(self) |
2091 | 463 | ControlPanelMixin.__init__(self, filename='devices.ui') | 700 | ControlPanelMixin.__init__(self, filename='devices.ui') |
2093 | 464 | self.pack_start(self.itself) | 701 | self.add(self.itself) |
2094 | 465 | self.show() | 702 | self.show() |
2095 | 466 | 703 | ||
2096 | 704 | self._devices = {} | ||
2097 | 705 | |||
2098 | 706 | self.backend.connect_to_signal('DevicesInfoReady', | ||
2099 | 707 | self.on_devices_info_ready) | ||
2100 | 708 | self.backend.connect_to_signal('DevicesInfoError', | ||
2101 | 709 | self.on_devices_info_error) | ||
2102 | 710 | self.backend.connect_to_signal('DeviceRemoved', | ||
2103 | 711 | self.on_device_removed) | ||
2104 | 712 | |||
2105 | 713 | def on_devices_info_ready(self, info): | ||
2106 | 714 | """Backend notifies of devices info.""" | ||
2107 | 715 | for child in self.devices.get_children(): | ||
2108 | 716 | self.devices.remove(child) | ||
2109 | 717 | |||
2110 | 718 | if not info: | ||
2111 | 719 | self.on_success(self.NO_DEVICES) | ||
2112 | 720 | else: | ||
2113 | 721 | self.on_success() | ||
2114 | 722 | |||
2115 | 723 | for device_info in info: | ||
2116 | 724 | device = Device() | ||
2117 | 725 | device_info['device_name'] = device_info.pop('name', '') | ||
2118 | 726 | device_info['device_type'] = device_info.pop('type', | ||
2119 | 727 | DEVICE_TYPE_COMPUTER) | ||
2120 | 728 | device.update(**device_info) | ||
2121 | 729 | self.devices.pack_start(device) | ||
2122 | 730 | self._devices[device.id] = device | ||
2123 | 731 | |||
2124 | 732 | @log_call(logger.error) | ||
2125 | 733 | def on_devices_info_error(self, error_dict=None): | ||
2126 | 734 | """Backend notifies of an error when fetching volumes info.""" | ||
2127 | 735 | self.on_error() | ||
2128 | 736 | |||
2129 | 737 | @log_call(logger.warning) | ||
2130 | 738 | def on_device_removed(self, device_id): | ||
2131 | 739 | """The removal of a device succeded.""" | ||
2132 | 740 | if device_id in self._devices: | ||
2133 | 741 | child = self._devices.pop(device_id) | ||
2134 | 742 | self.devices.remove(child) | ||
2135 | 743 | |||
2136 | 744 | if child.is_local: | ||
2137 | 745 | self.emit('local-device-removed') | ||
2138 | 746 | |||
2139 | 747 | def load(self): | ||
2140 | 748 | """Load the device list.""" | ||
2141 | 749 | self.backend.devices_info() | ||
2142 | 750 | self.message.start() | ||
2143 | 751 | |||
2144 | 467 | 752 | ||
2145 | 468 | class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin): | 753 | class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin): |
2146 | 469 | """The applications panel.""" | 754 | """The applications panel.""" |
2147 | @@ -474,9 +759,12 @@ | |||
2148 | 474 | def __init__(self): | 759 | def __init__(self): |
2149 | 475 | UbuntuOneBin.__init__(self) | 760 | UbuntuOneBin.__init__(self) |
2150 | 476 | ControlPanelMixin.__init__(self, filename='applications.ui') | 761 | ControlPanelMixin.__init__(self, filename='applications.ui') |
2152 | 477 | self.pack_start(self.itself) | 762 | self.add(self.itself) |
2153 | 478 | self.show() | 763 | self.show() |
2154 | 479 | 764 | ||
2155 | 765 | self.message.stop() | ||
2156 | 766 | self.message.set_text('Under construction') | ||
2157 | 767 | |||
2158 | 480 | 768 | ||
2159 | 481 | class ManagementPanel(gtk.VBox, ControlPanelMixin): | 769 | class ManagementPanel(gtk.VBox, ControlPanelMixin): |
2160 | 482 | """The management panel. | 770 | """The management panel. |
2161 | @@ -486,11 +774,21 @@ | |||
2162 | 486 | """ | 774 | """ |
2163 | 487 | 775 | ||
2164 | 488 | QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)') | 776 | QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)') |
2165 | 777 | FILE_SYNC_DISABLED = _('File synchronization service is not enabled.') | ||
2166 | 778 | FILE_SYNC_STARTING = _('File synchronization service is starting,\n' | ||
2167 | 779 | 'please wait...') | ||
2168 | 780 | FILE_SYNC_DISCONNECTED = _('File synchronization service is ready,\nplease' | ||
2169 | 781 | ' connect it to access your personal cloud. ') | ||
2170 | 782 | FILE_SYNC_SYNCING = _('File synchronization service is fully functional,\n' | ||
2171 | 783 | 'performing synchronization now...') | ||
2172 | 784 | FILE_SYNC_IDLE = _('File synchronization service is idle,\n' | ||
2173 | 785 | 'all the files are synchronized.') | ||
2174 | 786 | FILE_SYNC_ERROR = _('File synchronization status can not be retrieved.') | ||
2175 | 489 | 787 | ||
2176 | 490 | def __init__(self): | 788 | def __init__(self): |
2177 | 491 | gtk.VBox.__init__(self) | 789 | gtk.VBox.__init__(self) |
2178 | 492 | ControlPanelMixin.__init__(self, filename='management.ui') | 790 | ControlPanelMixin.__init__(self, filename='management.ui') |
2180 | 493 | self.pack_start(self.itself) | 791 | self.add(self.itself) |
2181 | 494 | self.show() | 792 | self.show() |
2182 | 495 | 793 | ||
2183 | 496 | self.backend.connect_to_signal('AccountInfoReady', | 794 | self.backend.connect_to_signal('AccountInfoReady', |
2184 | @@ -498,10 +796,25 @@ | |||
2185 | 498 | self.backend.connect_to_signal('AccountInfoError', | 796 | self.backend.connect_to_signal('AccountInfoError', |
2186 | 499 | self.on_account_info_error) | 797 | self.on_account_info_error) |
2187 | 500 | 798 | ||
2188 | 799 | self.backend.connect_to_signal('FileSyncStatusDisabled', | ||
2189 | 800 | self.on_file_sync_status_disabled) | ||
2190 | 801 | self.backend.connect_to_signal('FileSyncStatusStarting', | ||
2191 | 802 | self.on_file_sync_status_starting) | ||
2192 | 803 | self.backend.connect_to_signal('FileSyncStatusDisconnected', | ||
2193 | 804 | self.on_file_sync_status_disconnected) | ||
2194 | 805 | self.backend.connect_to_signal('FileSyncStatusSyncing', | ||
2195 | 806 | self.on_file_sync_status_syncing) | ||
2196 | 807 | self.backend.connect_to_signal('FileSyncStatusIdle', | ||
2197 | 808 | self.on_file_sync_status_idle) | ||
2198 | 809 | self.backend.connect_to_signal('FileSyncStatusError', | ||
2199 | 810 | self.on_file_sync_status_error) | ||
2200 | 811 | |||
2201 | 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)) |
2202 | 813 | |||
2203 | 502 | self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) | 814 | self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
2204 | 815 | self.quota_box.pack_start(self.quota_label, expand=False) | ||
2205 | 816 | |||
2206 | 503 | self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) | 817 | self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
2207 | 504 | self.status_box.pack_start(self.quota_label, expand=False) | ||
2208 | 505 | self.status_box.pack_end(self.status_label, expand=False) | 818 | self.status_box.pack_end(self.status_label, expand=False) |
2209 | 506 | 819 | ||
2210 | 507 | self.account = AccountPanel() | 820 | self.account = AccountPanel() |
2211 | @@ -520,7 +833,10 @@ | |||
2212 | 520 | gtk.gdk.Color(DEFAULT_FG)) | 833 | gtk.gdk.Color(DEFAULT_FG)) |
2213 | 521 | self.notebook.insert_page(getattr(self, tab), position=page_num) | 834 | self.notebook.insert_page(getattr(self, tab), position=page_num) |
2214 | 522 | 835 | ||
2216 | 523 | self.backend.account_info() | 836 | self.folders_button.connect('clicked', lambda b: self.folders.load()) |
2217 | 837 | self.devices_button.connect('clicked', lambda b: self.devices.load()) | ||
2218 | 838 | self.devices.connect('local-device-removed', | ||
2219 | 839 | lambda widget: self.emit('local-device-removed')) | ||
2220 | 524 | 840 | ||
2221 | 525 | def _update_quota(self, msg, data=None): | 841 | def _update_quota(self, msg, data=None): |
2222 | 526 | """Update the quota info.""" | 842 | """Update the quota info.""" |
2223 | @@ -532,6 +848,17 @@ | |||
2224 | 532 | fraction = data.get('percentage', 0.0) / 100 | 848 | fraction = data.get('percentage', 0.0) / 100 |
2225 | 533 | self.quota_progressbar.set_fraction(fraction) | 849 | self.quota_progressbar.set_fraction(fraction) |
2226 | 534 | 850 | ||
2227 | 851 | def _update_status(self, msg): | ||
2228 | 852 | """Update the status info.""" | ||
2229 | 853 | self.status_label.set_markup(msg) | ||
2230 | 854 | self.status_label.stop() | ||
2231 | 855 | |||
2232 | 856 | def load(self): | ||
2233 | 857 | """Load the account info and file sync status list.""" | ||
2234 | 858 | self.backend.account_info() | ||
2235 | 859 | self.backend.file_sync_status() | ||
2236 | 860 | self.account_button.clicked() | ||
2237 | 861 | |||
2238 | 535 | @log_call(logger.debug) | 862 | @log_call(logger.debug) |
2239 | 536 | def on_account_info_ready(self, info): | 863 | def on_account_info_ready(self, info): |
2240 | 537 | """Backend notifies of account info.""" | 864 | """Backend notifies of account info.""" |
2241 | @@ -544,9 +871,42 @@ | |||
2242 | 544 | @log_call(logger.error) | 871 | @log_call(logger.error) |
2243 | 545 | def on_account_info_error(self, error_dict=None): | 872 | def on_account_info_error(self, error_dict=None): |
2244 | 546 | """Backend notifies of an error when fetching account info.""" | 873 | """Backend notifies of an error when fetching account info.""" |
2246 | 547 | self._update_quota(WARNING_MARKUP % self.VALUE_ERROR) | 874 | self._update_quota(WARNING_MARKUP % VALUE_ERROR) |
2247 | 875 | |||
2248 | 876 | @log_call(logger.info) | ||
2249 | 877 | def on_file_sync_status_disabled(self, msg): | ||
2250 | 878 | """Backend notifies of file sync status being disabled.""" | ||
2251 | 879 | self._update_status(self.FILE_SYNC_DISABLED) | ||
2252 | 880 | |||
2253 | 881 | @log_call(logger.info) | ||
2254 | 882 | def on_file_sync_status_starting(self, msg): | ||
2255 | 883 | """Backend notifies of file sync status being starting.""" | ||
2256 | 884 | self._update_status(self.FILE_SYNC_STARTING) | ||
2257 | 885 | |||
2258 | 886 | @log_call(logger.info) | ||
2259 | 887 | def on_file_sync_status_disconnected(self, msg): | ||
2260 | 888 | """Backend notifies of file sync status being ready.""" | ||
2261 | 889 | self._update_status(self.FILE_SYNC_DISCONNECTED) | ||
2262 | 890 | |||
2263 | 891 | @log_call(logger.info) | ||
2264 | 892 | def on_file_sync_status_syncing(self, msg): | ||
2265 | 893 | """Backend notifies of file sync status being syncing.""" | ||
2266 | 894 | self._update_status(self.FILE_SYNC_SYNCING) | ||
2267 | 895 | |||
2268 | 896 | @log_call(logger.info) | ||
2269 | 897 | def on_file_sync_status_idle(self, msg): | ||
2270 | 898 | """Backend notifies of file sync status being idle.""" | ||
2271 | 899 | self._update_status(self.FILE_SYNC_IDLE) | ||
2272 | 900 | |||
2273 | 901 | @log_call(logger.error) | ||
2274 | 902 | def on_file_sync_status_error(self, error_dict=None): | ||
2275 | 903 | """Backend notifies of an error when fetching file sync status.""" | ||
2276 | 904 | self._update_status(WARNING_MARKUP % self.FILE_SYNC_ERROR) | ||
2277 | 548 | 905 | ||
2278 | 549 | 906 | ||
2279 | 550 | gobject.signal_new('credentials-found', OverviewPanel, | 907 | gobject.signal_new('credentials-found', OverviewPanel, |
2280 | 551 | gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, | 908 | gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, |
2282 | 552 | (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)) | 909 | (gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT)) |
2283 | 910 | |||
2284 | 911 | gobject.signal_new('local-device-removed', DevicesPanel, | ||
2285 | 912 | gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) | ||
2286 | 553 | 913 | ||
2287 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' | |||
2288 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-06 12:27:11 +0000 | |||
2289 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-22 14:37:52 +0000 | |||
2290 | @@ -40,9 +40,28 @@ | |||
2291 | 40 | 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'} | 40 | 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'} |
2292 | 41 | 41 | ||
2293 | 42 | FAKE_VOLUMES_INFO = [ | 42 | FAKE_VOLUMES_INFO = [ |
2297 | 43 | {'volume_id': '0', 'path': '~/foo', 'subscribed': '0'}, | 43 | {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''}, |
2298 | 44 | {'volume_id': '1', 'path': '~/bar', 'subscribed': '1'}, | 44 | {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'}, |
2299 | 45 | {'volume_id': '2', 'path': '~/baz', 'subscribed': '1'}, | 45 | {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'}, |
2300 | 46 | ] | ||
2301 | 47 | |||
2302 | 48 | FAKE_DEVICE_INFO = { | ||
2303 | 49 | 'device_id': '1258-6854', 'device_name': 'Baz', 'device_type': 'Computer', | ||
2304 | 50 | 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True', | ||
2305 | 51 | 'max_upload_speed': '1000', 'max_download_speed': '72548', | ||
2306 | 52 | } | ||
2307 | 53 | |||
2308 | 54 | FAKE_DEVICES_INFO = [ | ||
2309 | 55 | {'device_id': '0', 'name': 'Foo', 'type': 'Computer', | ||
2310 | 56 | 'is_local': '', 'configurable': ''}, | ||
2311 | 57 | {'device_id': '1', 'name': 'Bar', 'type': 'Phone', | ||
2312 | 58 | 'is_local': '', 'configurable': ''}, | ||
2313 | 59 | {'device_id': '2', 'name': 'Z', 'type': 'Computer', | ||
2314 | 60 | 'is_local': '', 'configurable': 'True', 'limit_bandwidth': '', | ||
2315 | 61 | 'max_upload_speed': '0', 'max_download_speed': '0'}, | ||
2316 | 62 | {'device_id': '1258-6854', 'name': 'Baz', 'type': 'Computer', | ||
2317 | 63 | 'is_local': 'True', 'configurable': 'True', 'limit_bandwidth': 'True', | ||
2318 | 64 | 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local | ||
2319 | 46 | ] | 65 | ] |
2320 | 47 | 66 | ||
2321 | 48 | 67 | ||
2322 | @@ -108,8 +127,11 @@ | |||
2323 | 108 | bus_name = gui.DBUS_BUS_NAME | 127 | bus_name = gui.DBUS_BUS_NAME |
2324 | 109 | object_path = gui.DBUS_PREFERENCES_PATH | 128 | object_path = gui.DBUS_PREFERENCES_PATH |
2325 | 110 | iface = gui.DBUS_PREFERENCES_IFACE | 129 | iface = gui.DBUS_PREFERENCES_IFACE |
2328 | 111 | exposed_methods = ['account_info', 'devices_info', 'volumes_info', | 130 | exposed_methods = [ |
2329 | 112 | 'file_sync_status'] | 131 | 'account_info', 'devices_info', 'change_device_settings', |
2330 | 132 | 'volumes_info', 'change_volume_settings', 'file_sync_status', | ||
2331 | 133 | 'remove_device', | ||
2332 | 134 | ] | ||
2333 | 113 | 135 | ||
2334 | 114 | 136 | ||
2335 | 115 | class FakedSessionBus(object): | 137 | class FakedSessionBus(object): |
2336 | @@ -193,7 +215,6 @@ | |||
2337 | 193 | def assert_warning_correct(self, warning, text): | 215 | def assert_warning_correct(self, warning, text): |
2338 | 194 | """Check that 'warning' is visible, showing 'text'.""" | 216 | """Check that 'warning' is visible, showing 'text'.""" |
2339 | 195 | self.assertTrue(warning.get_visible(), 'Must be visible.') | 217 | self.assertTrue(warning.get_visible(), 'Must be visible.') |
2340 | 196 | self.assertEqual(warning.get_text(), text) | ||
2341 | 197 | self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text) | 218 | self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text) |
2342 | 198 | 219 | ||
2343 | 199 | def assert_function_decorated(self, decorator, func): | 220 | def assert_function_decorated(self, decorator, func): |
2344 | @@ -268,29 +289,39 @@ | |||
2345 | 268 | self.assertEqual(self.ui.get_icon_name(), 'ubuntuone') | 289 | self.assertEqual(self.ui.get_icon_name(), 'ubuntuone') |
2346 | 269 | 290 | ||
2347 | 270 | def test_max_size(self): | 291 | def test_max_size(self): |
2353 | 271 | """Max size is not bigger than 966x576 (LP: #645526).""" | 292 | """Max size is not bigger than 736x525 (LP: #645526, LP: #683164).""" |
2354 | 272 | self.assertTrue(self.ui.get_size_request() <= (966, 576)) | 293 | self.assertTrue(self.ui.get_size_request() <= (736, 525)) |
2355 | 273 | 294 | ||
2356 | 274 | 295 | ||
2357 | 275 | class ControlPanelTestCase(ControlPanelMixinTestCase): | 296 | class ControlPanelTestCase(BaseTestCase): |
2358 | 276 | """The test suite for the control panel itself.""" | 297 | """The test suite for the control panel itself.""" |
2359 | 277 | 298 | ||
2360 | 278 | klass = gui.ControlPanel | 299 | klass = gui.ControlPanel |
2361 | 279 | kwargs = {'window_id': 7} | 300 | kwargs = {'window_id': 7} |
2365 | 280 | ui_filename = 'controlpanel.ui' | 301 | |
2366 | 281 | 302 | def assert_current_tab_correct(self, expected_tab): | |
2367 | 282 | def test_is_a_vbox(self): | 303 | """Check that the wiget 'expected_tab' is the current page.""" |
2368 | 304 | actual = self.ui.get_nth_page(self.ui.get_current_page()) | ||
2369 | 305 | self.assertTrue(expected_tab is actual) | ||
2370 | 306 | |||
2371 | 307 | def test_is_a_notebook(self): | ||
2372 | 283 | """Inherits from gtk.VBox.""" | 308 | """Inherits from gtk.VBox.""" |
2374 | 284 | self.assertIsInstance(self.ui, gui.gtk.VBox) | 309 | self.assertIsInstance(self.ui, gui.gtk.Notebook) |
2375 | 285 | 310 | ||
2376 | 286 | def test_startup_visibility(self): | 311 | def test_startup_visibility(self): |
2377 | 287 | """The widget is visible at startup.""" | 312 | """The widget is visible at startup.""" |
2379 | 288 | self.assertTrue(self.ui.itself.get_visible(), | 313 | self.assertTrue(self.ui.get_visible(), |
2380 | 289 | 'must be visible at startup.') | 314 | 'must be visible at startup.') |
2381 | 290 | 315 | ||
2382 | 316 | def test_startup_props(self): | ||
2383 | 317 | """The tabs and border are not shown.""" | ||
2384 | 318 | self.assertFalse(self.ui.get_show_border(), 'must not show border.') | ||
2385 | 319 | self.assertFalse(self.ui.get_show_tabs(), 'must not show tabs.') | ||
2386 | 320 | |||
2387 | 291 | def test_overview_is_shown_at_startup(self): | 321 | def test_overview_is_shown_at_startup(self): |
2388 | 292 | """The overview is shown at startup.""" | 322 | """The overview is shown at startup.""" |
2389 | 293 | self.assertIsInstance(self.ui.overview, gui.OverviewPanel) | 323 | self.assertIsInstance(self.ui.overview, gui.OverviewPanel) |
2390 | 324 | self.assert_current_tab_correct(self.ui.overview) | ||
2391 | 294 | 325 | ||
2392 | 295 | def test_window_id_is_passed_to_child(self): | 326 | def test_window_id_is_passed_to_child(self): |
2393 | 296 | """The child gets the window_id.""" | 327 | """The child gets the window_id.""" |
2394 | @@ -299,8 +330,48 @@ | |||
2395 | 299 | def test_on_show_management_panel(self): | 330 | def test_on_show_management_panel(self): |
2396 | 300 | """A ManagementPanel is shown when the callback is executed.""" | 331 | """A ManagementPanel is shown when the callback is executed.""" |
2397 | 301 | self.ui.on_show_management_panel() | 332 | self.ui.on_show_management_panel() |
2400 | 302 | children = self.ui.get_children() | 333 | self.assert_current_tab_correct(self.ui.management) |
2401 | 303 | self.assertIsInstance(children[-1], gui.ManagementPanel) | 334 | |
2402 | 335 | def test_on_show_management_panel_is_idempotent(self): | ||
2403 | 336 | """Only one ManagementPanel is shown.""" | ||
2404 | 337 | self.ui.on_show_management_panel() | ||
2405 | 338 | self.ui.on_show_management_panel() | ||
2406 | 339 | |||
2407 | 340 | self.assert_current_tab_correct(self.ui.management) | ||
2408 | 341 | |||
2409 | 342 | def test_credentials_found_shows_account_management_panel(self): | ||
2410 | 343 | """On 'credentials-found' signal, the management panel is shown. | ||
2411 | 344 | |||
2412 | 345 | If first signal parameter is False, visible tab should be account. | ||
2413 | 346 | |||
2414 | 347 | """ | ||
2415 | 348 | self.patch(self.ui.management, 'load', self._set_called) | ||
2416 | 349 | self.ui.overview.emit('credentials-found', False, object()) | ||
2417 | 350 | |||
2418 | 351 | self.assert_current_tab_correct(self.ui.management) | ||
2419 | 352 | self.assertEqual(self.ui.management.notebook.get_current_page(), | ||
2420 | 353 | self.ui.management.ACCOUNT_PAGE) | ||
2421 | 354 | self.assertEqual(self._called, ((), {})) | ||
2422 | 355 | |||
2423 | 356 | def test_credentials_found_shows_folders_management_panel(self): | ||
2424 | 357 | """On 'credentials-found' signal, the management panel is shown. | ||
2425 | 358 | |||
2426 | 359 | If first signal parameter is True, visible tab should be folders. | ||
2427 | 360 | |||
2428 | 361 | """ | ||
2429 | 362 | a_token = object() | ||
2430 | 363 | self.ui.overview.emit('credentials-found', True, a_token) | ||
2431 | 364 | |||
2432 | 365 | self.assert_current_tab_correct(self.ui.management) | ||
2433 | 366 | self.assertEqual(self.ui.management.notebook.get_current_page(), | ||
2434 | 367 | self.ui.management.FOLDERS_PAGE) | ||
2435 | 368 | |||
2436 | 369 | def test_local_device_removed_shows_overview_panel(self): | ||
2437 | 370 | """On 'local-device-removed' signal, the overview panel is shown.""" | ||
2438 | 371 | self.ui.overview.emit('credentials-found', True, object()) | ||
2439 | 372 | self.ui.management.emit('local-device-removed') | ||
2440 | 373 | |||
2441 | 374 | self.assert_current_tab_correct(self.ui.overview) | ||
2442 | 304 | 375 | ||
2443 | 305 | 376 | ||
2444 | 306 | class UbuntuOneBinTestCase(BaseTestCase): | 377 | class UbuntuOneBinTestCase(BaseTestCase): |
2445 | @@ -334,6 +405,37 @@ | |||
2446 | 334 | ui = self.klass() # no title given | 405 | ui = self.klass() # no title given |
2447 | 335 | self.assertEqual(ui.title.label.get_text(), '') | 406 | self.assertEqual(ui.title.label.get_text(), '') |
2448 | 336 | 407 | ||
2449 | 408 | def test_message_is_a_label_loading(self): | ||
2450 | 409 | """Message is the correct widget.""" | ||
2451 | 410 | self.assertIsInstance(self.ui.message, gui.LabelLoading) | ||
2452 | 411 | self.assertIn(self.ui.message, self.ui.get_children()) | ||
2453 | 412 | |||
2454 | 413 | def test_on_success(self): | ||
2455 | 414 | """Callback to stop the Loading and clear messages.""" | ||
2456 | 415 | self.ui.on_success() | ||
2457 | 416 | self.assertEqual(self.ui.message.get_label(), '') | ||
2458 | 417 | self.assertFalse(self.ui.message.active) | ||
2459 | 418 | |||
2460 | 419 | def test_on_success_with_message(self): | ||
2461 | 420 | """Callback to stop the Loading and show a info message.""" | ||
2462 | 421 | msg = 'WOW! <i>this rocks</i>' | ||
2463 | 422 | self.ui.on_success(message=msg) | ||
2464 | 423 | self.assertEqual(self.ui.message.get_label(), msg) | ||
2465 | 424 | self.assertFalse(self.ui.message.active) | ||
2466 | 425 | |||
2467 | 426 | def test_on_error(self): | ||
2468 | 427 | """Callback to stop the Loading and clear messages.""" | ||
2469 | 428 | self.ui.on_error() | ||
2470 | 429 | self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) | ||
2471 | 430 | self.assertFalse(self.ui.message.active) | ||
2472 | 431 | |||
2473 | 432 | def test_on_error_with_message(self): | ||
2474 | 433 | """Callback to stop the Loading and show a info message.""" | ||
2475 | 434 | msg = 'WOW! <i>this does not rock</i> :-/' | ||
2476 | 435 | self.ui.on_error(message=msg) | ||
2477 | 436 | self.assert_warning_correct(self.ui.message, msg) | ||
2478 | 437 | self.assertFalse(self.ui.message.active) | ||
2479 | 438 | |||
2480 | 337 | 439 | ||
2481 | 338 | class OverwiewPanelTestCase(ControlPanelMixinTestCase): | 440 | class OverwiewPanelTestCase(ControlPanelMixinTestCase): |
2482 | 339 | """The test suite for the overview panel.""" | 441 | """The test suite for the overview panel.""" |
2483 | @@ -418,6 +520,7 @@ | |||
2484 | 418 | 520 | ||
2485 | 419 | def test_find_credentials_is_called(self): | 521 | def test_find_credentials_is_called(self): |
2486 | 420 | """Credentials are asked to SSO backend.""" | 522 | """Credentials are asked to SSO backend.""" |
2487 | 523 | self.assertFalse(self.ui._credentials_are_new) | ||
2488 | 421 | self.assert_backend_called('find_credentials', (gui.U1_APP_NAME, {}), | 524 | self.assert_backend_called('find_credentials', (gui.U1_APP_NAME, {}), |
2489 | 422 | backend=self.ui.sso_backend) | 525 | backend=self.ui.sso_backend) |
2490 | 423 | 526 | ||
2491 | @@ -427,13 +530,28 @@ | |||
2492 | 427 | 530 | ||
2493 | 428 | self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN) | 531 | self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN) |
2494 | 429 | 532 | ||
2497 | 430 | self.assertFalse(self.ui.get_visible()) | 533 | self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') |
2498 | 431 | self.assertEqual(self._called, ((self.ui, gui.U1_APP_NAME, TOKEN), {})) | 534 | # assume credentials were in local keyring |
2499 | 535 | self.assertEqual(self._called, ((self.ui, False, TOKEN), {})) | ||
2500 | 536 | |||
2501 | 537 | def test_on_credentials_found_when_creds_are_not_new(self): | ||
2502 | 538 | """Callback 'on_credentials_found' distinguish if creds are new.""" | ||
2503 | 539 | self.ui.connect('credentials-found', self._set_called) | ||
2504 | 540 | |||
2505 | 541 | # credentials weren't in the system | ||
2506 | 542 | self.ui.on_credentials_not_found(gui.U1_APP_NAME) | ||
2507 | 543 | # now they are! | ||
2508 | 544 | self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN) | ||
2509 | 545 | |||
2510 | 546 | self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') | ||
2511 | 547 | # assume credentials were not in local keyring | ||
2512 | 548 | self.assertEqual(self._called, ((self.ui, True, TOKEN), {})) | ||
2513 | 432 | 549 | ||
2514 | 433 | def test_on_credentials_not_found(self): | 550 | def test_on_credentials_not_found(self): |
2515 | 434 | """Callback 'on_credentials_not_found' is correct.""" | 551 | """Callback 'on_credentials_not_found' is correct.""" |
2516 | 435 | self.ui.on_credentials_not_found(gui.U1_APP_NAME) | 552 | self.ui.on_credentials_not_found(gui.U1_APP_NAME) |
2517 | 436 | self.assertTrue(self.ui.get_visible()) | 553 | self.assertTrue(self.ui.get_visible()) |
2518 | 554 | self.assertTrue(self.ui._credentials_are_new) | ||
2519 | 437 | 555 | ||
2520 | 438 | def test_on_credentials_error(self): | 556 | def test_on_credentials_error(self): |
2521 | 439 | """Callback 'on_credentials_error' is correct.""" | 557 | """Callback 'on_credentials_error' is correct.""" |
2522 | @@ -626,11 +744,11 @@ | |||
2523 | 626 | 744 | ||
2524 | 627 | def assert_account_info_correct(self, info): | 745 | def assert_account_info_correct(self, info): |
2525 | 628 | """Check that the displayed account info matches 'info'.""" | 746 | """Check that the displayed account info matches 'info'.""" |
2527 | 629 | self.assertEqual(self.ui.name_label.get_text(), | 747 | self.assertEqual(self.ui.name_label.get_label(), |
2528 | 630 | FAKE_ACCOUNT_INFO['name']) | 748 | FAKE_ACCOUNT_INFO['name']) |
2530 | 631 | self.assertEqual(self.ui.type_label.get_text(), | 749 | self.assertEqual(self.ui.type_label.get_label(), |
2531 | 632 | FAKE_ACCOUNT_INFO['type']) | 750 | FAKE_ACCOUNT_INFO['type']) |
2533 | 633 | self.assertEqual(self.ui.email_label.get_text(), | 751 | self.assertEqual(self.ui.email_label.get_label(), |
2534 | 634 | FAKE_ACCOUNT_INFO['email']) | 752 | FAKE_ACCOUNT_INFO['email']) |
2535 | 635 | 753 | ||
2536 | 636 | def test_is_an_ubuntuone_bin(self): | 754 | def test_is_an_ubuntuone_bin(self): |
2537 | @@ -645,15 +763,9 @@ | |||
2538 | 645 | """Is visible.""" | 763 | """Is visible.""" |
2539 | 646 | self.assertTrue(self.ui.get_visible()) | 764 | self.assertTrue(self.ui.get_visible()) |
2540 | 647 | 765 | ||
2550 | 648 | def test_type_label_is_loading(self): | 766 | def test_account_info_is_not_visible(self): |
2551 | 649 | """Placeholder for type label is a Loading widget.""" | 767 | """Account info is not visible.""" |
2552 | 650 | self.assertIsInstance(self.ui.type_label, gui.LabelLoading) | 768 | self.assertFalse(self.ui.account.get_visible()) |
2544 | 651 | self.assertIn(self.ui.type_label, self.ui.type_box.get_children()) | ||
2545 | 652 | |||
2546 | 653 | def test_email_label_is_loading(self): | ||
2547 | 654 | """Placeholder for email label is a Loading widget.""" | ||
2548 | 655 | self.assertIsInstance(self.ui.email_label, gui.LabelLoading) | ||
2549 | 656 | self.assertIn(self.ui.email_label, self.ui.email_box.get_children()) | ||
2553 | 657 | 769 | ||
2554 | 658 | def test_backend_signals(self): | 770 | def test_backend_signals(self): |
2555 | 659 | """The proper signals are connected to the backend.""" | 771 | """The proper signals are connected to the backend.""" |
2556 | @@ -666,18 +778,16 @@ | |||
2557 | 666 | """The account info is processed when ready.""" | 778 | """The account info is processed when ready.""" |
2558 | 667 | self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO) | 779 | self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO) |
2559 | 668 | self.assert_account_info_correct(FAKE_ACCOUNT_INFO) | 780 | self.assert_account_info_correct(FAKE_ACCOUNT_INFO) |
2564 | 669 | 781 | self.assertTrue(self.ui.account.get_visible()) | |
2565 | 670 | for widget in (self.ui.name_label, self.ui.type_label, | 782 | self.assertFalse(self.ui.message.active) |
2566 | 671 | self.ui.email_label): | 783 | self.assertEqual(self.ui.message.get_label(), '') |
2563 | 672 | self.assertFalse(widget.active) | ||
2567 | 673 | 784 | ||
2568 | 674 | def test_on_account_info_error(self): | 785 | def test_on_account_info_error(self): |
2569 | 675 | """The account info couldn't be retrieved.""" | 786 | """The account info couldn't be retrieved.""" |
2570 | 676 | self.ui.on_account_info_error() | 787 | self.ui.on_account_info_error() |
2575 | 677 | for widget in (self.ui.name_label, self.ui.type_label, | 788 | self.assertFalse(self.ui.account.get_visible()) |
2576 | 678 | self.ui.email_label): | 789 | self.assertFalse(self.ui.message.active) |
2577 | 679 | self.assert_warning_correct(widget, self.ui.VALUE_ERROR) | 790 | self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) |
2574 | 680 | self.assertFalse(widget.active) | ||
2578 | 681 | 791 | ||
2579 | 682 | 792 | ||
2580 | 683 | class FoldersTestCase(ControlPanelMixinTestCase): | 793 | class FoldersTestCase(ControlPanelMixinTestCase): |
2581 | @@ -686,6 +796,10 @@ | |||
2582 | 686 | klass = gui.FoldersPanel | 796 | klass = gui.FoldersPanel |
2583 | 687 | ui_filename = 'folders.ui' | 797 | ui_filename = 'folders.ui' |
2584 | 688 | 798 | ||
2585 | 799 | def setUp(self): | ||
2586 | 800 | super(FoldersTestCase, self).setUp() | ||
2587 | 801 | self.ui.load() | ||
2588 | 802 | |||
2589 | 689 | def test_is_an_ubuntuone_bin(self): | 803 | def test_is_an_ubuntuone_bin(self): |
2590 | 690 | """Inherits from UbuntuOneBin.""" | 804 | """Inherits from UbuntuOneBin.""" |
2591 | 691 | self.assertIsInstance(self.ui, gui.UbuntuOneBin) | 805 | self.assertIsInstance(self.ui, gui.UbuntuOneBin) |
2592 | @@ -705,34 +819,393 @@ | |||
2593 | 705 | self.assertEqual(self.ui.backend._signals['VolumesInfoError'], | 819 | self.assertEqual(self.ui.backend._signals['VolumesInfoError'], |
2594 | 706 | [self.ui.on_volumes_info_error]) | 820 | [self.ui.on_volumes_info_error]) |
2595 | 707 | 821 | ||
2597 | 708 | def test_volumes_info_is_requested(self): | 822 | def test_volumes_info_is_requested_on_load(self): |
2598 | 709 | """The volumes info is requested to the backend.""" | 823 | """The volumes info is requested to the backend.""" |
2599 | 824 | # clean backend calls | ||
2600 | 825 | self.ui.backend._called.pop('volumes_info', None) | ||
2601 | 826 | self.ui.load() | ||
2602 | 827 | |||
2603 | 710 | self.assert_backend_called('volumes_info', ()) | 828 | self.assert_backend_called('volumes_info', ()) |
2604 | 711 | 829 | ||
2606 | 712 | def _test_on_volumes_info_ready(self): | 830 | def test_message_after_load(self): |
2607 | 831 | """The volumes label is active when contents are load.""" | ||
2608 | 832 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
2609 | 833 | self.ui.load() | ||
2610 | 834 | |||
2611 | 835 | self.assertTrue(self.ui.message.active) | ||
2612 | 836 | |||
2613 | 837 | def test_message_after_non_empty_volumes_info_ready(self): | ||
2614 | 838 | """The volumes label is a LabelLoading.""" | ||
2615 | 839 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
2616 | 840 | |||
2617 | 841 | self.assertFalse(self.ui.message.active) | ||
2618 | 842 | |||
2619 | 843 | def test_message_after_empty_volumes_info_ready(self): | ||
2620 | 844 | """When there are no volumes, a notification is shown.""" | ||
2621 | 845 | self.ui.on_volumes_info_ready([]) | ||
2622 | 846 | |||
2623 | 847 | self.assertFalse(self.ui.message.active) | ||
2624 | 848 | self.assertEqual(self.ui.message.get_text(), self.ui.NO_VOLUMES) | ||
2625 | 849 | |||
2626 | 850 | def test_on_volumes_info_ready(self): | ||
2627 | 713 | """The volumes info is processed when ready.""" | 851 | """The volumes info is processed when ready.""" |
2628 | 714 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 852 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
2629 | 715 | 853 | ||
2631 | 716 | volumes = self.ui.itself.get_children() | 854 | self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes]) |
2632 | 855 | |||
2633 | 856 | volumes = self.ui.volumes.get_children() | ||
2634 | 857 | volumes.reverse() | ||
2635 | 858 | |||
2636 | 859 | header = volumes[:2] # grab header | ||
2637 | 860 | self.assertEqual(header[0].get_text(), 'Local path') | ||
2638 | 861 | self.assertEqual(header[1].get_text(), 'Subscribed') | ||
2639 | 862 | |||
2640 | 863 | volumes = volumes[2:] # drop header | ||
2641 | 864 | labels = filter(lambda w: isinstance(w, gui.gtk.Label), volumes) | ||
2642 | 865 | checks = filter(lambda w: isinstance(w, gui.gtk.CheckButton), volumes) | ||
2643 | 866 | |||
2644 | 867 | self.assertEqual(len(checks), len(FAKE_VOLUMES_INFO)) | ||
2645 | 868 | |||
2646 | 869 | for label, check, volume in zip(labels, checks, FAKE_VOLUMES_INFO): | ||
2647 | 870 | self.assertEqual(volume['suggested_path'], label.get_text()) | ||
2648 | 871 | self.assertEqual(bool(volume['subscribed']), check.get_active()) | ||
2649 | 872 | self.assertEqual(volume['volume_id'], check.get_label()) | ||
2650 | 873 | self.assertFalse(check.get_child().get_visible()) | ||
2651 | 874 | |||
2652 | 875 | def test_on_volumes_info_ready_clears_the_list(self): | ||
2653 | 876 | """The old volumes info is cleared before updated.""" | ||
2654 | 877 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
2655 | 878 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
2656 | 879 | |||
2657 | 880 | self.assertEqual(len(self.ui.folders.get_children()), 1) | ||
2658 | 881 | child = self.ui.folders.get_children()[0] | ||
2659 | 882 | self.assertEqual(child, self.ui.volumes) | ||
2660 | 883 | |||
2661 | 884 | volumes = filter(lambda w: isinstance(w, gui.gtk.CheckButton), | ||
2662 | 885 | self.ui.volumes.get_children()) | ||
2663 | 717 | self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO)) | 886 | self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO)) |
2664 | 718 | 887 | ||
2671 | 719 | for i, volume in enumerate(FAKE_VOLUMES_INFO): | 888 | def test_on_volumes_info_ready_with_no_volumes(self): |
2672 | 720 | vol_widget = volumes[i] | 889 | """When there are no volumes, a notification is shown.""" |
2673 | 721 | self.assertEqual(vol_widget.id, volume['volume_id']) | 890 | self.ui.on_volumes_info_ready([]) |
2674 | 722 | self.assertEqual(vol_widget.path.get_text(), volume['path']) | 891 | # no volumes table |
2675 | 723 | subscribed = gui.dbus_str_to_bool(volume['subscribed']) | 892 | self.assertEqual(len(self.ui.folders.get_children()), 0) |
2676 | 724 | self.assertEqual(vol_widget.subscribed.get_active(), subscribed) | 893 | self.assertTrue(self.ui.volumes is None) |
2677 | 894 | |||
2678 | 895 | def test_on_subscribed_clicked(self): | ||
2679 | 896 | """Clicking on 'subscribed' updates the folder subscription.""" | ||
2680 | 897 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
2681 | 898 | |||
2682 | 899 | method = 'change_volume_settings' | ||
2683 | 900 | for checkbutton in self.ui._subscribed: | ||
2684 | 901 | checkbutton.clicked() | ||
2685 | 902 | fid = checkbutton.get_label() | ||
2686 | 903 | |||
2687 | 904 | subscribed = gui.bool_str(checkbutton.get_active()) | ||
2688 | 905 | self.assert_backend_called(method, | ||
2689 | 906 | (fid, {'subscribed': subscribed})) | ||
2690 | 907 | # clean backend calls | ||
2691 | 908 | self.ui.backend._called.pop(method) | ||
2692 | 909 | |||
2693 | 910 | checkbutton.clicked() | ||
2694 | 911 | subscribed = gui.bool_str(checkbutton.get_active()) | ||
2695 | 912 | self.assert_backend_called('change_volume_settings', | ||
2696 | 913 | (fid, {'subscribed': subscribed})) | ||
2697 | 725 | 914 | ||
2698 | 726 | def test_on_volumes_info_error(self): | 915 | def test_on_volumes_info_error(self): |
2699 | 727 | """The volumes info couldn't be retrieved.""" | 916 | """The volumes info couldn't be retrieved.""" |
2700 | 728 | self.ui.on_volumes_info_error() | 917 | self.ui.on_volumes_info_error() |
2708 | 729 | 918 | self.assert_warning_correct(warning=self.ui.message, | |
2709 | 730 | def _test_on_volumes_info_ready_clears_the_list(self): | 919 | text=gui.VALUE_ERROR) |
2710 | 731 | """The old volumes info is cleared before updated.""" | 920 | self.assertFalse(self.ui.message.active) |
2711 | 732 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 921 | |
2712 | 733 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 922 | def test_on_volumes_info_error_after_success(self): |
2713 | 734 | volumes = self.ui.itself.get_children() | 923 | """The volumes info couldn't be retrieved after a prior success.""" |
2714 | 735 | self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO)) | 924 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
2715 | 925 | |||
2716 | 926 | self.ui.on_volumes_info_error() | ||
2717 | 927 | |||
2718 | 928 | self.test_on_volumes_info_error() | ||
2719 | 929 | self.test_on_volumes_info_ready_with_no_volumes() | ||
2720 | 930 | |||
2721 | 931 | |||
2722 | 932 | class DeviceTestCase(ControlPanelMixinTestCase): | ||
2723 | 933 | """The test suite for the device widget.""" | ||
2724 | 934 | |||
2725 | 935 | klass = gui.Device | ||
2726 | 936 | ui_filename = 'device.ui' | ||
2727 | 937 | |||
2728 | 938 | def assert_device_equal(self, device, expected): | ||
2729 | 939 | """Assert that the device has the values from expected.""" | ||
2730 | 940 | self.assertEqual(device.id, | ||
2731 | 941 | expected['device_id']) | ||
2732 | 942 | self.assertEqual(device.device_name.get_text(), | ||
2733 | 943 | expected['device_name']) | ||
2734 | 944 | self.assertEqual(device.device_type.get_icon_name()[0], | ||
2735 | 945 | expected['device_type'].lower()) | ||
2736 | 946 | self.assertEqual(device.is_local, | ||
2737 | 947 | bool(expected['is_local'])) | ||
2738 | 948 | self.assertEqual(device.configurable, | ||
2739 | 949 | bool(expected['configurable'])) | ||
2740 | 950 | self.assertEqual(device.limit_bandwidth.get_active(), | ||
2741 | 951 | bool(expected['limit_bandwidth'])) | ||
2742 | 952 | |||
2743 | 953 | value = int(expected['max_upload_speed']) // gui.KILOBYTES | ||
2744 | 954 | self.assertEqual(device.max_upload_speed.get_value_as_int(), value) | ||
2745 | 955 | value = int(expected['max_download_speed']) // gui.KILOBYTES | ||
2746 | 956 | self.assertEqual(device.max_download_speed.get_value_as_int(), value) | ||
2747 | 957 | |||
2748 | 958 | def assert_device_settings_changed(self): | ||
2749 | 959 | """Changing throttling settings updates the backend properly.""" | ||
2750 | 960 | expected = self.ui.__dict__ | ||
2751 | 961 | self.assert_backend_called('change_device_settings', | ||
2752 | 962 | (self.ui.id, expected)) | ||
2753 | 963 | self.assertEqual(self.ui.warning_label.get_text(), '') | ||
2754 | 964 | |||
2755 | 965 | def modify_settings(self): | ||
2756 | 966 | """Modify settings so values actually change.""" | ||
2757 | 967 | new_val = not self.ui.limit_bandwidth.get_active() | ||
2758 | 968 | self.ui.limit_bandwidth.set_active(new_val) | ||
2759 | 969 | |||
2760 | 970 | new_val = self.ui.max_upload_speed.get_value_as_int() + 1 | ||
2761 | 971 | self.ui.max_upload_speed.set_value(new_val) | ||
2762 | 972 | |||
2763 | 973 | new_val = self.ui.max_download_speed.get_value_as_int() + 1 | ||
2764 | 974 | self.ui.max_download_speed.set_value(new_val) | ||
2765 | 975 | |||
2766 | 976 | def test_is_a_vbox(self): | ||
2767 | 977 | """Inherits from VBox.""" | ||
2768 | 978 | self.assertIsInstance(self.ui, gui.gtk.VBox) | ||
2769 | 979 | |||
2770 | 980 | def test_inner_widget_is_packed(self): | ||
2771 | 981 | """The 'itself' vbox is packed into the widget.""" | ||
2772 | 982 | self.assertIn(self.ui.itself, self.ui.get_children()) | ||
2773 | 983 | |||
2774 | 984 | def test_is_visible(self): | ||
2775 | 985 | """Is visible.""" | ||
2776 | 986 | self.assertTrue(self.ui.get_visible()) | ||
2777 | 987 | |||
2778 | 988 | def test_is_sensitive(self): | ||
2779 | 989 | """Is sensitive.""" | ||
2780 | 990 | self.assertTrue(self.ui.get_sensitive()) | ||
2781 | 991 | |||
2782 | 992 | def test_warning_label_is_cleared(self): | ||
2783 | 993 | """The warning label is cleared.""" | ||
2784 | 994 | self.assertEqual(self.ui.warning_label.get_text(), '') | ||
2785 | 995 | |||
2786 | 996 | def test_default_values(self): | ||
2787 | 997 | """Default values are correct.""" | ||
2788 | 998 | self.assertEqual(self.ui.id, None) | ||
2789 | 999 | self.assertEqual(self.ui.device_name.get_text(), '') | ||
2790 | 1000 | self.assertEqual(self.ui.device_type.get_icon_name()[0], | ||
2791 | 1001 | gui.DEVICE_TYPE_COMPUTER.lower()) | ||
2792 | 1002 | self.assertEqual(self.ui.is_local, False) | ||
2793 | 1003 | self.assertEqual(self.ui.configurable, False) | ||
2794 | 1004 | self.assertEqual(self.ui.limit_bandwidth.get_active(), False) | ||
2795 | 1005 | self.assertEqual(self.ui.max_upload_speed.get_value_as_int(), 0) | ||
2796 | 1006 | self.assertEqual(self.ui.max_download_speed.get_value_as_int(), 0) | ||
2797 | 1007 | |||
2798 | 1008 | def test_init_does_not_call_backend(self): | ||
2799 | 1009 | """When updating, the backend is not called.""" | ||
2800 | 1010 | self.assertEqual(self.ui.backend._called, {}) | ||
2801 | 1011 | |||
2802 | 1012 | def test_update_device_name(self): | ||
2803 | 1013 | """A device can be updated from a dict.""" | ||
2804 | 1014 | value = 'The death star' | ||
2805 | 1015 | self.ui.update(device_name=value) | ||
2806 | 1016 | self.assertEqual(value, self.ui.device_name.get_text()) | ||
2807 | 1017 | |||
2808 | 1018 | def test_update_unicode_device_name(self): | ||
2809 | 1019 | """A device can be updated from a dict.""" | ||
2810 | 1020 | value = u'Ñoño Ñandú' | ||
2811 | 1021 | self.ui.update(device_name=value) | ||
2812 | 1022 | self.assertEqual(value, self.ui.device_name.get_text()) | ||
2813 | 1023 | |||
2814 | 1024 | def test_update_device_type_computer(self): | ||
2815 | 1025 | """A device can be updated from a dict.""" | ||
2816 | 1026 | dtype = gui.DEVICE_TYPE_COMPUTER | ||
2817 | 1027 | self.ui.update(device_type=dtype) | ||
2818 | 1028 | self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON), | ||
2819 | 1029 | self.ui.device_type.get_icon_name()) | ||
2820 | 1030 | |||
2821 | 1031 | def test_update_device_type_phone(self): | ||
2822 | 1032 | """A device can be updated from a dict.""" | ||
2823 | 1033 | dtype = gui.DEVICE_TYPE_PHONE | ||
2824 | 1034 | self.ui.update(device_type=dtype) | ||
2825 | 1035 | self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_BUTTON), | ||
2826 | 1036 | self.ui.device_type.get_icon_name()) | ||
2827 | 1037 | |||
2828 | 1038 | def test_update_is_not_local(self): | ||
2829 | 1039 | """A device can be updated from a dict.""" | ||
2830 | 1040 | self.ui.update(is_local='') | ||
2831 | 1041 | self.assertFalse(self.ui.is_local) | ||
2832 | 1042 | |||
2833 | 1043 | def test_update_is_local(self): | ||
2834 | 1044 | """A device can be updated from a dict.""" | ||
2835 | 1045 | self.ui.update(is_local='True') | ||
2836 | 1046 | self.assertTrue(self.ui.is_local) | ||
2837 | 1047 | |||
2838 | 1048 | def test_update_non_configurable(self): | ||
2839 | 1049 | """A device can be updated from a dict.""" | ||
2840 | 1050 | self.ui.update(configurable='') | ||
2841 | 1051 | self.assertFalse(self.ui.configurable) | ||
2842 | 1052 | self.assertFalse(self.ui.throttling.get_visible()) | ||
2843 | 1053 | |||
2844 | 1054 | def test_update_configurable(self): | ||
2845 | 1055 | """A device can be updated from a dict.""" | ||
2846 | 1056 | self.ui.update(configurable='True') | ||
2847 | 1057 | self.assertTrue(self.ui.configurable) | ||
2848 | 1058 | self.assertTrue(self.ui.throttling.get_visible()) | ||
2849 | 1059 | |||
2850 | 1060 | def test_update_limit_bandwidth(self): | ||
2851 | 1061 | """A device can be updated from a dict.""" | ||
2852 | 1062 | self.ui.update(limit_bandwidth='') | ||
2853 | 1063 | self.assertFalse(self.ui.limit_bandwidth.get_active()) | ||
2854 | 1064 | |||
2855 | 1065 | self.ui.update(limit_bandwidth='True') | ||
2856 | 1066 | self.assertTrue(self.ui.limit_bandwidth.get_active()) | ||
2857 | 1067 | |||
2858 | 1068 | def test_update_upload_speed(self): | ||
2859 | 1069 | """A device can be updated from a dict.""" | ||
2860 | 1070 | value = '12345' | ||
2861 | 1071 | self.ui.update(max_upload_speed=value) | ||
2862 | 1072 | self.assertEqual(int(value) // gui.KILOBYTES, | ||
2863 | 1073 | self.ui.max_upload_speed.get_value_as_int()) | ||
2864 | 1074 | |||
2865 | 1075 | def test_update_download_speed(self): | ||
2866 | 1076 | """A device can be updated from a dict.""" | ||
2867 | 1077 | value = '987654' | ||
2868 | 1078 | self.ui.update(max_download_speed=value) | ||
2869 | 1079 | self.assertEqual(int(value) // gui.KILOBYTES, | ||
2870 | 1080 | self.ui.max_download_speed.get_value_as_int()) | ||
2871 | 1081 | |||
2872 | 1082 | def test_update_does_not_call_backend(self): | ||
2873 | 1083 | """When updating, the backend is not called.""" | ||
2874 | 1084 | self.ui.update(**FAKE_DEVICE_INFO) | ||
2875 | 1085 | self.assertEqual(self.ui.backend._called, {}) | ||
2876 | 1086 | self.assert_device_equal(self.ui, FAKE_DEVICE_INFO) | ||
2877 | 1087 | |||
2878 | 1088 | def test_on_limit_bandwidth_toggled(self): | ||
2879 | 1089 | """When toggling limit_bandwidth, backend is updated.""" | ||
2880 | 1090 | self.ui.limit_bandwidth.toggled() | ||
2881 | 1091 | self.assert_device_settings_changed() | ||
2882 | 1092 | |||
2883 | 1093 | def test_on_max_upload_speed_value_changed(self): | ||
2884 | 1094 | """When setting max_upload_speed, backend is updated.""" | ||
2885 | 1095 | self.ui.max_upload_speed.set_value(25) | ||
2886 | 1096 | self.assert_device_settings_changed() | ||
2887 | 1097 | |||
2888 | 1098 | def test_on_max_download_speed_value_changed(self): | ||
2889 | 1099 | """When setting max_download_speed, backend is updated.""" | ||
2890 | 1100 | self.ui.max_download_speed.set_value(52) | ||
2891 | 1101 | self.assert_device_settings_changed() | ||
2892 | 1102 | |||
2893 | 1103 | def test_backend_signals(self): | ||
2894 | 1104 | """The proper signals are connected to the backend.""" | ||
2895 | 1105 | self.assertEqual(self.ui.backend._signals['DeviceSettingsChanged'], | ||
2896 | 1106 | [self.ui.on_device_settings_changed]) | ||
2897 | 1107 | self.assertEqual(self.ui.backend._signals['DeviceSettingsChangeError'], | ||
2898 | 1108 | [self.ui.on_device_settings_change_error]) | ||
2899 | 1109 | self.assertEqual(self.ui.backend._signals['DeviceRemoved'], | ||
2900 | 1110 | [self.ui.on_device_removed]) | ||
2901 | 1111 | self.assertEqual(self.ui.backend._signals['DeviceRemovalError'], | ||
2902 | 1112 | [self.ui.on_device_removal_error]) | ||
2903 | 1113 | |||
2904 | 1114 | def test_on_device_settings_changed(self): | ||
2905 | 1115 | """When settings were changed for this device, enable it.""" | ||
2906 | 1116 | self.modify_settings() | ||
2907 | 1117 | self.ui.on_device_settings_changed(device_id=self.ui.id) | ||
2908 | 1118 | |||
2909 | 1119 | self.assertTrue(self.ui.get_sensitive()) | ||
2910 | 1120 | self.assertEqual(self.ui.warning_label.get_text(), '') | ||
2911 | 1121 | self.assertEqual(self.ui.__dict__, self.ui._last_settings) | ||
2912 | 1122 | |||
2913 | 1123 | def test_on_device_settings_change_after_error(self): | ||
2914 | 1124 | """Change success after error.""" | ||
2915 | 1125 | self.modify_settings() | ||
2916 | 1126 | self.ui.on_device_settings_change_error(device_id=self.ui.id) # error | ||
2917 | 1127 | |||
2918 | 1128 | self.test_on_device_settings_changed() | ||
2919 | 1129 | |||
2920 | 1130 | def test_on_device_settings_changed_different_id(self): | ||
2921 | 1131 | """When settings were changed for other device, nothing changes.""" | ||
2922 | 1132 | self.modify_settings() | ||
2923 | 1133 | self.ui.on_device_settings_changed(device_id='yadda') | ||
2924 | 1134 | |||
2925 | 1135 | self.assertEqual(self.ui.warning_label.get_text(), '') | ||
2926 | 1136 | |||
2927 | 1137 | def test_on_device_settings_change_error(self): | ||
2928 | 1138 | """When settings were not changed for this device, notify the user. | ||
2929 | 1139 | |||
2930 | 1140 | Also, confirm that old values were restored. | ||
2931 | 1141 | |||
2932 | 1142 | """ | ||
2933 | 1143 | self.ui.update(**FAKE_DEVICE_INFO) # use known values | ||
2934 | 1144 | |||
2935 | 1145 | self.modify_settings() | ||
2936 | 1146 | |||
2937 | 1147 | self.ui.on_device_settings_change_error(device_id=self.ui.id) # error | ||
2938 | 1148 | |||
2939 | 1149 | self.assertTrue(self.ui.get_sensitive()) | ||
2940 | 1150 | self.assert_warning_correct(self.ui.warning_label, | ||
2941 | 1151 | self.ui.DEVICE_CHANGE_ERROR) | ||
2942 | 1152 | self.assert_device_equal(self.ui, FAKE_DEVICE_INFO) # restored info | ||
2943 | 1153 | |||
2944 | 1154 | def test_on_device_settings_change_error_after_success(self): | ||
2945 | 1155 | """Change error after success.""" | ||
2946 | 1156 | self.modify_settings() | ||
2947 | 1157 | self.ui.on_device_settings_changed(device_id=self.ui.id) | ||
2948 | 1158 | |||
2949 | 1159 | self.test_on_device_settings_change_error() | ||
2950 | 1160 | |||
2951 | 1161 | def test_on_device_settings_change_error_different_id(self): | ||
2952 | 1162 | """When settings were not changed for other device, do nothing.""" | ||
2953 | 1163 | self.modify_settings() | ||
2954 | 1164 | self.ui.on_device_settings_change_error(device_id='yudo') | ||
2955 | 1165 | self.assertEqual(self.ui.warning_label.get_text(), '') | ||
2956 | 1166 | |||
2957 | 1167 | def test_remove(self): | ||
2958 | 1168 | """Clicking on remove calls the backend properly.""" | ||
2959 | 1169 | self.ui.is_local = False | ||
2960 | 1170 | self.ui.remove.clicked() | ||
2961 | 1171 | |||
2962 | 1172 | self.assert_backend_called('remove_device', (self.ui.id,)) | ||
2963 | 1173 | self.assertFalse(self.ui.get_sensitive(), | ||
2964 | 1174 | 'Must be disabled while removing.') | ||
2965 | 1175 | |||
2966 | 1176 | def test_on_device_removed(self): | ||
2967 | 1177 | """On this device removed, hide and destroy.""" | ||
2968 | 1178 | self.ui.remove.clicked() | ||
2969 | 1179 | self.ui.on_device_removed(device_id=self.ui.id) | ||
2970 | 1180 | |||
2971 | 1181 | self.assertFalse(self.ui.get_visible(), | ||
2972 | 1182 | 'Must not be visible after removed.') | ||
2973 | 1183 | |||
2974 | 1184 | def test_on_device_removed_other_id(self): | ||
2975 | 1185 | """On other device removed, do nothing.""" | ||
2976 | 1186 | self.ui.remove.clicked() | ||
2977 | 1187 | self.ui.on_device_removed(device_id='foo') | ||
2978 | 1188 | |||
2979 | 1189 | self.assertTrue(self.ui.get_visible(), | ||
2980 | 1190 | 'Must be visible after other device was removed.') | ||
2981 | 1191 | |||
2982 | 1192 | def test_on_device_removal_error(self): | ||
2983 | 1193 | """On this device removal error, re-enable and show error.""" | ||
2984 | 1194 | self.ui.remove.clicked() | ||
2985 | 1195 | self.ui.on_device_removal_error(device_id=self.ui.id) | ||
2986 | 1196 | |||
2987 | 1197 | self.assertTrue(self.ui.get_sensitive(), | ||
2988 | 1198 | 'Must be enabled after removal error.') | ||
2989 | 1199 | self.assert_warning_correct(self.ui.warning_label, | ||
2990 | 1200 | self.ui.DEVICE_REMOVAL_ERROR) | ||
2991 | 1201 | |||
2992 | 1202 | def test_on_device_removal_error_other_id(self): | ||
2993 | 1203 | """On other device removal error, do nothing.""" | ||
2994 | 1204 | self.ui.remove.clicked() | ||
2995 | 1205 | self.ui.on_device_removal_error(device_id='foo') | ||
2996 | 1206 | |||
2997 | 1207 | self.assertFalse(self.ui.get_sensitive(), | ||
2998 | 1208 | 'Must be disabled after other device removal error.') | ||
2999 | 736 | 1209 | ||
3000 | 737 | 1210 | ||
3001 | 738 | class DevicesTestCase(ControlPanelMixinTestCase): | 1211 | class DevicesTestCase(ControlPanelMixinTestCase): |
3002 | @@ -753,6 +1226,144 @@ | |||
3003 | 753 | """Is visible.""" | 1226 | """Is visible.""" |
3004 | 754 | self.assertTrue(self.ui.get_visible()) | 1227 | self.assertTrue(self.ui.get_visible()) |
3005 | 755 | 1228 | ||
3006 | 1229 | def test_backend_signals(self): | ||
3007 | 1230 | """The proper signals are connected to the backend.""" | ||
3008 | 1231 | self.assertEqual(self.ui.backend._signals['DevicesInfoReady'], | ||
3009 | 1232 | [self.ui.on_devices_info_ready]) | ||
3010 | 1233 | self.assertEqual(self.ui.backend._signals['DevicesInfoError'], | ||
3011 | 1234 | [self.ui.on_devices_info_error]) | ||
3012 | 1235 | self.assertEqual(self.ui.backend._signals['DeviceRemoved'], | ||
3013 | 1236 | [self.ui.on_device_removed]) | ||
3014 | 1237 | |||
3015 | 1238 | def test_devices_info_is_requested_on_load(self): | ||
3016 | 1239 | """The devices info is requested to the backend.""" | ||
3017 | 1240 | # clean backend calls | ||
3018 | 1241 | self.ui.backend._called.pop('devices_info', None) | ||
3019 | 1242 | self.ui.load() | ||
3020 | 1243 | |||
3021 | 1244 | self.assert_backend_called('devices_info', ()) | ||
3022 | 1245 | |||
3023 | 1246 | def test_message_after_load(self): | ||
3024 | 1247 | """The devices label is active when contents are load.""" | ||
3025 | 1248 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3026 | 1249 | self.ui.load() | ||
3027 | 1250 | |||
3028 | 1251 | self.assertTrue(self.ui.message.active) | ||
3029 | 1252 | |||
3030 | 1253 | def test_message_after_non_empty_devices_info_ready(self): | ||
3031 | 1254 | """The devices label is a LabelLoading.""" | ||
3032 | 1255 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3033 | 1256 | |||
3034 | 1257 | self.assertFalse(self.ui.message.active) | ||
3035 | 1258 | |||
3036 | 1259 | def test_message_after_empty_devices_info_ready(self): | ||
3037 | 1260 | """When there are no devices, a notification is shown.""" | ||
3038 | 1261 | self.ui.on_devices_info_ready([]) | ||
3039 | 1262 | |||
3040 | 1263 | self.assertFalse(self.ui.message.active) | ||
3041 | 1264 | self.assertEqual(self.ui.message.get_text(), self.ui.NO_DEVICES) | ||
3042 | 1265 | |||
3043 | 1266 | def test_on_devices_info_ready(self): | ||
3044 | 1267 | """The devices info is processed when ready.""" | ||
3045 | 1268 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3046 | 1269 | |||
3047 | 1270 | children = self.ui.devices.get_children() | ||
3048 | 1271 | self.assertEqual(len(children), len(FAKE_DEVICES_INFO)) | ||
3049 | 1272 | |||
3050 | 1273 | for child, device in zip(children, FAKE_DEVICES_INFO): | ||
3051 | 1274 | self.assertIsInstance(child, gui.Device) | ||
3052 | 1275 | |||
3053 | 1276 | self.assertEqual(device['device_id'], child.id) | ||
3054 | 1277 | self.assertEqual(device['device_name'], | ||
3055 | 1278 | child.device_name.get_text()) | ||
3056 | 1279 | self.assertEqual(device['device_type'].lower(), | ||
3057 | 1280 | child.device_type.get_icon_name()[0]) | ||
3058 | 1281 | self.assertEqual(bool(device['is_local']), | ||
3059 | 1282 | child.is_local) | ||
3060 | 1283 | self.assertEqual(bool(device['configurable']), | ||
3061 | 1284 | child.configurable) | ||
3062 | 1285 | |||
3063 | 1286 | if bool(device['configurable']): | ||
3064 | 1287 | self.assertEqual(bool(device['limit_bandwidth']), | ||
3065 | 1288 | child.limit_bandwidth.get_active()) | ||
3066 | 1289 | value = int(device['max_upload_speed']) // gui.KILOBYTES | ||
3067 | 1290 | self.assertEqual(value, | ||
3068 | 1291 | child.max_upload_speed.get_value_as_int()) | ||
3069 | 1292 | value = int(device['max_download_speed']) // gui.KILOBYTES | ||
3070 | 1293 | self.assertEqual(value, | ||
3071 | 1294 | child.max_download_speed.get_value_as_int()) | ||
3072 | 1295 | |||
3073 | 1296 | def test_on_devices_info_ready_have_devices_cached(self): | ||
3074 | 1297 | """The devices are cached for further removal.""" | ||
3075 | 1298 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3076 | 1299 | |||
3077 | 1300 | for child in self.ui.devices.get_children(): | ||
3078 | 1301 | self.assertTrue(self.ui._devices[child.id] is child) | ||
3079 | 1302 | |||
3080 | 1303 | def test_on_devices_info_ready_clears_the_list(self): | ||
3081 | 1304 | """The old devices info is cleared before updated.""" | ||
3082 | 1305 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3083 | 1306 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3084 | 1307 | |||
3085 | 1308 | devices = self.ui.devices.get_children() | ||
3086 | 1309 | self.assertEqual(len(devices), len(FAKE_DEVICES_INFO)) | ||
3087 | 1310 | |||
3088 | 1311 | def test_on_devices_info_ready_with_no_devices(self): | ||
3089 | 1312 | """When there are no devices, a notification is shown.""" | ||
3090 | 1313 | self.ui.on_devices_info_ready([]) | ||
3091 | 1314 | self.assertEqual(len(self.ui.devices.get_children()), 0) | ||
3092 | 1315 | |||
3093 | 1316 | def test_on_devices_info_error(self): | ||
3094 | 1317 | """The devices info couldn't be retrieved.""" | ||
3095 | 1318 | self.ui.on_devices_info_error() | ||
3096 | 1319 | |||
3097 | 1320 | self.assert_warning_correct(warning=self.ui.message, | ||
3098 | 1321 | text=gui.VALUE_ERROR) | ||
3099 | 1322 | self.assertFalse(self.ui.message.active) | ||
3100 | 1323 | |||
3101 | 1324 | def test_on_devices_info_error_after_success(self): | ||
3102 | 1325 | """The devices info couldn't be retrieved after a prior success.""" | ||
3103 | 1326 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3104 | 1327 | |||
3105 | 1328 | self.ui.on_devices_info_error() | ||
3106 | 1329 | |||
3107 | 1330 | self.test_on_devices_info_error() | ||
3108 | 1331 | self.test_on_devices_info_ready_with_no_devices() | ||
3109 | 1332 | |||
3110 | 1333 | def test_on_device_removed(self): | ||
3111 | 1334 | """When a child device was removed, remove and destroy.""" | ||
3112 | 1335 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3113 | 1336 | did = FAKE_DEVICES_INFO[0]['device_id'] | ||
3114 | 1337 | device = self.ui._devices[did] | ||
3115 | 1338 | self.ui.on_device_removed(device_id=did) | ||
3116 | 1339 | |||
3117 | 1340 | self.assertTrue(device not in self.ui.devices.get_children()) | ||
3118 | 1341 | self.assertTrue(did not in self.ui._devices) | ||
3119 | 1342 | |||
3120 | 1343 | def test_on_local_device_removed(self): | ||
3121 | 1344 | """Removing the local device emits local-device-removed.""" | ||
3122 | 1345 | self.ui.connect('local-device-removed', self._set_called) | ||
3123 | 1346 | |||
3124 | 1347 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3125 | 1348 | local_device = FAKE_DEVICES_INFO[-1] | ||
3126 | 1349 | assert bool(local_device['is_local']) | ||
3127 | 1350 | local_device_id = local_device['device_id'] | ||
3128 | 1351 | assert self.ui._devices[local_device_id].is_local | ||
3129 | 1352 | |||
3130 | 1353 | self.ui.on_device_removed(device_id=local_device_id) | ||
3131 | 1354 | |||
3132 | 1355 | self.assertEqual(self._called, ((self.ui,), {})) | ||
3133 | 1356 | |||
3134 | 1357 | def test_on_device_removed_for_no_child_device(self): | ||
3135 | 1358 | """On other device removed, do nothing.""" | ||
3136 | 1359 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) | ||
3137 | 1360 | old_devices = self.ui.devices.get_children() | ||
3138 | 1361 | |||
3139 | 1362 | self.ui.on_device_removed(device_id='foo') | ||
3140 | 1363 | |||
3141 | 1364 | new_devices = self.ui.devices.get_children() | ||
3142 | 1365 | self.assertEqual(new_devices, old_devices) | ||
3143 | 1366 | |||
3144 | 756 | 1367 | ||
3145 | 757 | class ApplicationsTestCase(ControlPanelMixinTestCase): | 1368 | class ApplicationsTestCase(ControlPanelMixinTestCase): |
3146 | 758 | """The test suite for the applications panel.""" | 1369 | """The test suite for the applications panel.""" |
3147 | @@ -842,15 +1453,16 @@ | |||
3148 | 842 | class ManagementPanelAccountTestCase(ManagementPanelTestCase): | 1453 | class ManagementPanelAccountTestCase(ManagementPanelTestCase): |
3149 | 843 | """The test suite for the management panel (account tab).""" | 1454 | """The test suite for the management panel (account tab).""" |
3150 | 844 | 1455 | ||
3152 | 845 | def test_backend_signals(self): | 1456 | def test_backend_account_signals(self): |
3153 | 846 | """The proper signals are connected to the backend.""" | 1457 | """The proper signals are connected to the backend.""" |
3154 | 847 | self.assertEqual(self.ui.backend._signals['AccountInfoReady'], | 1458 | self.assertEqual(self.ui.backend._signals['AccountInfoReady'], |
3155 | 848 | [self.ui.on_account_info_ready]) | 1459 | [self.ui.on_account_info_ready]) |
3156 | 849 | self.assertEqual(self.ui.backend._signals['AccountInfoError'], | 1460 | self.assertEqual(self.ui.backend._signals['AccountInfoError'], |
3157 | 850 | [self.ui.on_account_info_error]) | 1461 | [self.ui.on_account_info_error]) |
3158 | 851 | 1462 | ||
3160 | 852 | def test_account_info_is_requested(self): | 1463 | def test_account_info_is_requested_on_load(self): |
3161 | 853 | """The account info is requested to the backend.""" | 1464 | """The account info is requested to the backend.""" |
3162 | 1465 | self.ui.load() | ||
3163 | 854 | self.assert_backend_called('account_info', ()) | 1466 | self.assert_backend_called('account_info', ()) |
3164 | 855 | 1467 | ||
3165 | 856 | def test_account_panel_is_packed(self): | 1468 | def test_account_panel_is_packed(self): |
3166 | @@ -877,12 +1489,31 @@ | |||
3167 | 877 | actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE) | 1489 | actual = self.ui.notebook.get_nth_page(self.ui.APPLICATIONS_PAGE) |
3168 | 878 | self.assertTrue(self.ui.applications is actual) | 1490 | self.assertTrue(self.ui.applications is actual) |
3169 | 879 | 1491 | ||
3176 | 880 | def test_placeholders_are_loading(self): | 1492 | def test_entering_folders_tab_loads_content(self): |
3177 | 881 | """Placeholders for labels are a Loading widget.""" | 1493 | """The volumes info is loaded when entering the Folders tab.""" |
3178 | 882 | widgets = (self.ui.quota_label, self.ui.status_label) | 1494 | self.patch(self.ui.folders, 'load', self._set_called) |
3179 | 883 | for widget in widgets: | 1495 | # clean backend calls |
3180 | 884 | self.assertIsInstance(widget, gui.LabelLoading) | 1496 | self.ui.folders_button.clicked() |
3181 | 885 | self.assertIn(widget, self.ui.status_box.get_children()) | 1497 | |
3182 | 1498 | self.assertEqual(self._called, ((), {})) | ||
3183 | 1499 | |||
3184 | 1500 | def test_entering_devices_tab_loads_content(self): | ||
3185 | 1501 | """The devices info is loaded when entering the Devices tab.""" | ||
3186 | 1502 | self.patch(self.ui.devices, 'load', self._set_called) | ||
3187 | 1503 | # clean backend calls | ||
3188 | 1504 | self.ui.devices_button.clicked() | ||
3189 | 1505 | |||
3190 | 1506 | self.assertEqual(self._called, ((), {})) | ||
3191 | 1507 | |||
3192 | 1508 | def test_quota_placeholder_is_loading(self): | ||
3193 | 1509 | """Placeholders for quota label is a Loading widget.""" | ||
3194 | 1510 | self.assertIsInstance(self.ui.quota_label, gui.LabelLoading) | ||
3195 | 1511 | self.assertIn(self.ui.quota_label, self.ui.quota_box.get_children()) | ||
3196 | 1512 | |||
3197 | 1513 | def test_file_sync_status_placeholder_is_loading(self): | ||
3198 | 1514 | """Placeholders for file sync label is a Loading widget.""" | ||
3199 | 1515 | self.assertIsInstance(self.ui.status_label, gui.LabelLoading) | ||
3200 | 1516 | self.assertIn(self.ui.status_label, self.ui.status_box.get_children()) | ||
3201 | 886 | 1517 | ||
3202 | 887 | def test_on_account_info_ready(self): | 1518 | def test_on_account_info_ready(self): |
3203 | 888 | """The account info is processed when ready.""" | 1519 | """The account info is processed when ready.""" |
3204 | @@ -896,5 +1527,80 @@ | |||
3205 | 896 | """The account info couldn't be retrieved.""" | 1527 | """The account info couldn't be retrieved.""" |
3206 | 897 | self.ui.on_account_info_error() | 1528 | self.ui.on_account_info_error() |
3207 | 898 | for widget in (self.ui.quota_label,): | 1529 | for widget in (self.ui.quota_label,): |
3209 | 899 | self.assert_warning_correct(widget, self.ui.VALUE_ERROR) | 1530 | self.assert_warning_correct(widget, gui.VALUE_ERROR) |
3210 | 900 | self.assertFalse(widget.active) | 1531 | self.assertFalse(widget.active) |
3211 | 1532 | |||
3212 | 1533 | def test_backend_file_sync_signals(self): | ||
3213 | 1534 | """The proper signals are connected to the backend.""" | ||
3214 | 1535 | matches = ( | ||
3215 | 1536 | ('FileSyncStatusDisabled', [self.ui.on_file_sync_status_disabled]), | ||
3216 | 1537 | ('FileSyncStatusStarting', [self.ui.on_file_sync_status_starting]), | ||
3217 | 1538 | ('FileSyncStatusDisconnected', | ||
3218 | 1539 | [self.ui.on_file_sync_status_disconnected]), | ||
3219 | 1540 | ('FileSyncStatusSyncing', [self.ui.on_file_sync_status_syncing]), | ||
3220 | 1541 | ('FileSyncStatusIdle', [self.ui.on_file_sync_status_idle]), | ||
3221 | 1542 | ('FileSyncStatusError', [self.ui.on_file_sync_status_error]), | ||
3222 | 1543 | ) | ||
3223 | 1544 | for sig, handlers in matches: | ||
3224 | 1545 | self.assertEqual(self.ui.backend._signals[sig], handlers) | ||
3225 | 1546 | |||
3226 | 1547 | def test_file_sync_status_is_requested_on_load(self): | ||
3227 | 1548 | """The file sync status is requested to the backend.""" | ||
3228 | 1549 | self.ui.load() | ||
3229 | 1550 | self.assert_backend_called('file_sync_status', ()) | ||
3230 | 1551 | |||
3231 | 1552 | def test_on_file_sync_status_disabled(self): | ||
3232 | 1553 | """The file sync is disabled.""" | ||
3233 | 1554 | self.ui.on_file_sync_status_disabled('msg') | ||
3234 | 1555 | |||
3235 | 1556 | self.assertFalse(self.ui.status_label.active) | ||
3236 | 1557 | self.assertEqual(self.ui.status_label.get_text(), | ||
3237 | 1558 | self.ui.FILE_SYNC_DISABLED) | ||
3238 | 1559 | |||
3239 | 1560 | def test_on_file_sync_status_starting(self): | ||
3240 | 1561 | """The file sync status is starting.""" | ||
3241 | 1562 | self.ui.on_file_sync_status_starting('msg') | ||
3242 | 1563 | |||
3243 | 1564 | self.assertFalse(self.ui.status_label.active) | ||
3244 | 1565 | self.assertEqual(self.ui.status_label.get_text(), | ||
3245 | 1566 | self.ui.FILE_SYNC_STARTING) | ||
3246 | 1567 | |||
3247 | 1568 | def test_on_file_sync_status_disconnected(self): | ||
3248 | 1569 | """The file sync status is disconnected.""" | ||
3249 | 1570 | self.ui.on_file_sync_status_disconnected('msg') | ||
3250 | 1571 | |||
3251 | 1572 | self.assertFalse(self.ui.status_label.active) | ||
3252 | 1573 | self.assertEqual(self.ui.status_label.get_text(), | ||
3253 | 1574 | self.ui.FILE_SYNC_DISCONNECTED) | ||
3254 | 1575 | |||
3255 | 1576 | def test_on_file_sync_status_syncing(self): | ||
3256 | 1577 | """The file sync status is syncing.""" | ||
3257 | 1578 | self.ui.on_file_sync_status_syncing('msg') | ||
3258 | 1579 | |||
3259 | 1580 | self.assertFalse(self.ui.status_label.active) | ||
3260 | 1581 | self.assertEqual(self.ui.status_label.get_text(), | ||
3261 | 1582 | self.ui.FILE_SYNC_SYNCING) | ||
3262 | 1583 | |||
3263 | 1584 | def test_on_file_sync_status_idle(self): | ||
3264 | 1585 | """The file sync status is idle.""" | ||
3265 | 1586 | self.ui.on_file_sync_status_idle('msg') | ||
3266 | 1587 | |||
3267 | 1588 | self.assertFalse(self.ui.status_label.active) | ||
3268 | 1589 | self.assertEqual(self.ui.status_label.get_text(), | ||
3269 | 1590 | self.ui.FILE_SYNC_IDLE) | ||
3270 | 1591 | |||
3271 | 1592 | def test_on_file_sync_status_error(self): | ||
3272 | 1593 | """The file sync status couldn't be retrieved.""" | ||
3273 | 1594 | self.ui.on_file_sync_status_error({'error_msg': 'error msg'}) | ||
3274 | 1595 | |||
3275 | 1596 | self.assert_warning_correct(self.ui.status_label, | ||
3276 | 1597 | self.ui.FILE_SYNC_ERROR) | ||
3277 | 1598 | self.assertFalse(self.ui.status_label.active) | ||
3278 | 1599 | |||
3279 | 1600 | def test_local_device_removed_is_emitted(self): | ||
3280 | 1601 | """Signal local-device-removed is sent when DevicesPanel emits it.""" | ||
3281 | 1602 | self.ui.connect('local-device-removed', self._set_called) | ||
3282 | 1603 | |||
3283 | 1604 | self.ui.devices.emit('local-device-removed') | ||
3284 | 1605 | |||
3285 | 1606 | self.assertEqual(self._called, ((self.ui,), {})) | ||
3286 | 901 | 1607 | ||
3287 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_widgets.py' | |||
3288 | --- ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-06 12:27:11 +0000 | |||
3289 | +++ ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-22 14:37:52 +0000 | |||
3290 | @@ -75,33 +75,26 @@ | |||
3291 | 75 | def test_creation(self): | 75 | def test_creation(self): |
3292 | 76 | """A LabelLoading can be created.""" | 76 | """A LabelLoading can be created.""" |
3293 | 77 | self.assertEqual(self.widget.label.get_text(), '') | 77 | self.assertEqual(self.widget.label.get_text(), '') |
3295 | 78 | self.assertFalse(self.widget.label.get_visible()) | 78 | self.assertTrue(self.widget.label.get_visible()) |
3296 | 79 | 79 | ||
3297 | 80 | self.assertIsInstance(self.widget.loading, widgets.Loading) | 80 | self.assertIsInstance(self.widget.loading, widgets.Loading) |
3298 | 81 | self.assertTrue(self.widget.loading.get_visible()) | 81 | self.assertTrue(self.widget.loading.get_visible()) |
3300 | 82 | self.assertTrue(self.widget.active) | 82 | self.assertTrue(self.widget.active) # loading label is packed |
3301 | 83 | 83 | ||
3302 | 84 | def test_stop(self): | 84 | def test_stop(self): |
3303 | 85 | """Stop hides the Loading widget.""" | 85 | """Stop hides the Loading widget.""" |
3304 | 86 | self.widget.stop() | 86 | self.widget.stop() |
3307 | 87 | self.assertTrue(self.widget.label.get_visible()) | 87 | |
3308 | 88 | self.assertFalse(self.widget.loading.get_visible()) | 88 | self.assertTrue(self.widget.get_child() is self.widget.label) |
3309 | 89 | self.assertFalse(self.widget.active) | 89 | self.assertFalse(self.widget.active) |
3310 | 90 | 90 | ||
3311 | 91 | def test_start(self): | 91 | def test_start(self): |
3312 | 92 | """Start shows the Loading widget.""" | 92 | """Start shows the Loading widget.""" |
3313 | 93 | self.widget.start() | 93 | self.widget.start() |
3316 | 94 | self.assertFalse(self.widget.label.get_visible()) | 94 | |
3317 | 95 | self.assertTrue(self.widget.loading.get_visible()) | 95 | self.assertTrue(self.widget.get_child() is self.widget.loading) |
3318 | 96 | self.assertTrue(self.widget.active) | 96 | self.assertTrue(self.widget.active) |
3319 | 97 | 97 | ||
3320 | 98 | def test_children(self): | ||
3321 | 99 | """A LabelLoading have proper children.""" | ||
3322 | 100 | children = self.widget.get_children() | ||
3323 | 101 | self.assertEqual(2, len(children)) | ||
3324 | 102 | self.assertTrue(self.widget.label is children[0]) | ||
3325 | 103 | self.assertTrue(self.widget.loading is children[-1]) | ||
3326 | 104 | |||
3327 | 105 | def test_get_text(self): | 98 | def test_get_text(self): |
3328 | 106 | """Text can be queried.""" | 99 | """Text can be queried.""" |
3329 | 107 | text = 'Test me.' | 100 | text = 'Test me.' |
3330 | 108 | 101 | ||
3331 | === modified file 'ubuntuone/controlpanel/gtk/widgets.py' | |||
3332 | --- ubuntuone/controlpanel/gtk/widgets.py 2010-12-06 12:27:11 +0000 | |||
3333 | +++ ubuntuone/controlpanel/gtk/widgets.py 2010-12-22 14:37:52 +0000 | |||
3334 | @@ -49,7 +49,7 @@ | |||
3335 | 49 | self.show_all() | 49 | self.show_all() |
3336 | 50 | 50 | ||
3337 | 51 | 51 | ||
3339 | 52 | class LabelLoading(gtk.HBox): | 52 | class LabelLoading(gtk.Alignment): |
3340 | 53 | """A spinner and a label.""" | 53 | """A spinner and a label.""" |
3341 | 54 | 54 | ||
3342 | 55 | def __init__(self, loading_label, fg_color=None, *args, **kwargs): | 55 | def __init__(self, loading_label, fg_color=None, *args, **kwargs): |
3343 | @@ -57,29 +57,36 @@ | |||
3344 | 57 | self.loading = Loading(loading_label, fg_color=fg_color) | 57 | self.loading = Loading(loading_label, fg_color=fg_color) |
3345 | 58 | 58 | ||
3346 | 59 | self.label = gtk.Label() | 59 | self.label = gtk.Label() |
3347 | 60 | self.label.show() | ||
3348 | 60 | if fg_color is not None: | 61 | if fg_color is not None: |
3349 | 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)) |
3350 | 62 | 63 | ||
3353 | 63 | self.pack_start(self.label, expand=False) | 64 | self.add(self.loading) |
3352 | 64 | self.pack_start(self.loading, expand=False) | ||
3354 | 65 | 65 | ||
3355 | 66 | self.show() | 66 | self.show() |
3356 | 67 | self.set(xalign=0.5, yalign=0.5, xscale=0, yscale=0) | ||
3357 | 68 | self.set_padding(padding_top=5, padding_bottom=0, | ||
3358 | 69 | padding_left=5, padding_right=5) | ||
3359 | 67 | self.start() | 70 | self.start() |
3360 | 68 | 71 | ||
3361 | 69 | @property | 72 | @property |
3362 | 70 | def active(self): | 73 | def active(self): |
3363 | 71 | """Whether the Loading widget is visible or not.""" | 74 | """Whether the Loading widget is visible or not.""" |
3365 | 72 | return not self.label.get_visible() and self.loading.get_visible() | 75 | return self.get_child() is self.loading |
3366 | 73 | 76 | ||
3367 | 74 | def start(self): | 77 | def start(self): |
3368 | 75 | """Show the Loading instead of the Label widget.""" | 78 | """Show the Loading instead of the Label widget.""" |
3371 | 76 | self.label.hide() | 79 | for child in self.get_children(): |
3372 | 77 | self.loading.show() | 80 | self.remove(child) |
3373 | 81 | |||
3374 | 82 | self.add(self.loading) | ||
3375 | 78 | 83 | ||
3376 | 79 | def stop(self): | 84 | def stop(self): |
3377 | 80 | """Show the label instead of the Loading widget.""" | 85 | """Show the label instead of the Loading widget.""" |
3380 | 81 | self.label.show() | 86 | for child in self.get_children(): |
3381 | 82 | self.loading.hide() | 87 | self.remove(child) |
3382 | 88 | |||
3383 | 89 | self.add(self.label) | ||
3384 | 83 | 90 | ||
3385 | 84 | def set_text(self, text): | 91 | def set_text(self, text): |
3386 | 85 | """Set 'text' to be the label's text.""" | 92 | """Set 'text' to be the label's text.""" |
3387 | 86 | 93 | ||
3388 | === modified file 'ubuntuone/controlpanel/integrationtests/__init__.py' | |||
3389 | --- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-06 12:27:11 +0000 | |||
3390 | +++ ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-22 14:37:52 +0000 | |||
3391 | @@ -43,8 +43,13 @@ | |||
3392 | 43 | def setUp(self): | 43 | def setUp(self): |
3393 | 44 | super(DBusClientTestCase, self).setUp() | 44 | super(DBusClientTestCase, self).setUp() |
3394 | 45 | self.mock = None | 45 | self.mock = None |
3395 | 46 | self._called = False | ||
3396 | 46 | dbus_service.init_mainloop() | 47 | dbus_service.init_mainloop() |
3397 | 47 | 48 | ||
3398 | 49 | def _set_called(self, *args, **kwargs): | ||
3399 | 50 | """Keep track of function calls, useful for monkeypatching.""" | ||
3400 | 51 | self._called = (args, kwargs) | ||
3401 | 52 | |||
3402 | 48 | def register_mockserver(self, bus_name, object_path, object_class, | 53 | def register_mockserver(self, bus_name, object_path, object_class, |
3403 | 49 | **kwargs): | 54 | **kwargs): |
3404 | 50 | """The mock service is registered on the DBus.""" | 55 | """The mock service is registered on the DBus.""" |
3405 | 51 | 56 | ||
3406 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py' | |||
3407 | --- ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-06 12:27:11 +0000 | |||
3408 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-22 14:37:52 +0000 | |||
3409 | @@ -204,13 +204,13 @@ | |||
3410 | 204 | 204 | ||
3411 | 205 | 205 | ||
3412 | 206 | class FoldersMockDBusSyncDaemon(sd_dbus_iface.Folders): | 206 | class FoldersMockDBusSyncDaemon(sd_dbus_iface.Folders): |
3414 | 207 | """A mock object that mimicks syncdaemon regarding the folders iface.""" | 207 | """A mock object that mimicks syncdaemon regarding the Folders iface.""" |
3415 | 208 | 208 | ||
3416 | 209 | # __init__ method from a non direct base class 'Object' is called | 209 | # __init__ method from a non direct base class 'Object' is called |
3417 | 210 | # __init__ method from base class 'Folders' is not called | 210 | # __init__ method from base class 'Folders' is not called |
3418 | 211 | # pylint: disable=W0231, W0233 | 211 | # pylint: disable=W0231, W0233 |
3419 | 212 | 212 | ||
3421 | 213 | def __init__(self, object_path, conn, vm=None): | 213 | def __init__(self, object_path, conn): |
3422 | 214 | self.udfs = {} | 214 | self.udfs = {} |
3423 | 215 | self.udf_id = 1 | 215 | self.udf_id = 1 |
3424 | 216 | dbus.service.Object.__init__(self, | 216 | dbus.service.Object.__init__(self, |
3425 | @@ -220,9 +220,15 @@ | |||
3426 | 220 | def _new_udf(cls, uid, path, subscribed=False): | 220 | def _new_udf(cls, uid, path, subscribed=False): |
3427 | 221 | """Create a new faked udf.""" | 221 | """Create a new faked udf.""" |
3428 | 222 | udf = {} | 222 | udf = {} |
3432 | 223 | udf['path'] = udf['suggested_path'] = path | 223 | if isinstance(path, str): |
3433 | 224 | udf['id'] = uid | 224 | path = path.decode('utf-8') |
3434 | 225 | udf['subscribed'] = subscribed | 225 | udf[u'path'] = udf[u'suggested_path'] = path |
3435 | 226 | udf[u'volume_id'] = unicode(uid) | ||
3436 | 227 | udf[u'subscribed'] = sd_dbus_iface.bool_str(subscribed) | ||
3437 | 228 | udf[u'node_id'] = u'a18f4cbd-a846-4405-aaa1-b28904817089' | ||
3438 | 229 | udf[u'generation'] = u'' | ||
3439 | 230 | udf[u'type'] = u'UDF' | ||
3440 | 231 | |||
3441 | 226 | return udf | 232 | return udf |
3442 | 227 | 233 | ||
3443 | 228 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, | 234 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, |
3444 | @@ -231,9 +237,10 @@ | |||
3445 | 231 | """Create a user defined folder in the specified path.""" | 237 | """Create a user defined folder in the specified path.""" |
3446 | 232 | if path == '': # simulate an error | 238 | if path == '': # simulate an error |
3447 | 233 | self.emit_folder_create_error(path, 'create failed!') | 239 | self.emit_folder_create_error(path, 'create failed!') |
3448 | 240 | return | ||
3449 | 234 | 241 | ||
3450 | 235 | udf = self._new_udf(self.udf_id, path) | 242 | udf = self._new_udf(self.udf_id, path) |
3452 | 236 | self.udfs[self.udf_id] = udf | 243 | self.udfs[udf['volume_id']] = udf |
3453 | 237 | self.udf_id += 1 | 244 | self.udf_id += 1 |
3454 | 238 | self.emit_folder_created(udf) | 245 | self.emit_folder_created(udf) |
3455 | 239 | 246 | ||
3456 | @@ -243,6 +250,7 @@ | |||
3457 | 243 | """Delete the folder specified by folder_id""" | 250 | """Delete the folder specified by folder_id""" |
3458 | 244 | if folder_id not in self.udfs: | 251 | if folder_id not in self.udfs: |
3459 | 245 | self.emit_folder_delete_error(folder_id, 'failed!') | 252 | self.emit_folder_delete_error(folder_id, 'failed!') |
3460 | 253 | return | ||
3461 | 246 | 254 | ||
3462 | 247 | udf = self.udfs.pop(folder_id) | 255 | udf = self.udfs.pop(folder_id) |
3463 | 248 | self.emit_folder_deleted(udf) | 256 | self.emit_folder_deleted(udf) |
3464 | @@ -260,8 +268,9 @@ | |||
3465 | 260 | if folder_id not in self.udfs: | 268 | if folder_id not in self.udfs: |
3466 | 261 | # simulate error | 269 | # simulate error |
3467 | 262 | self.emit_folder_subscribe_error(folder_id, 'some error') | 270 | self.emit_folder_subscribe_error(folder_id, 'some error') |
3468 | 271 | return | ||
3469 | 263 | 272 | ||
3471 | 264 | self.udfs[folder_id]['subscribed'] = True | 273 | self.udfs[folder_id]['subscribed'] = sd_dbus_iface.bool_str(True) |
3472 | 265 | self.emit_folder_subscribed(self.udfs[folder_id]) | 274 | self.emit_folder_subscribed(self.udfs[folder_id]) |
3473 | 266 | 275 | ||
3474 | 267 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, | 276 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, |
3475 | @@ -271,8 +280,9 @@ | |||
3476 | 271 | if folder_id not in self.udfs: | 280 | if folder_id not in self.udfs: |
3477 | 272 | # simulate error | 281 | # simulate error |
3478 | 273 | self.emit_folder_unsubscribe_error(folder_id, 'some error') | 282 | self.emit_folder_unsubscribe_error(folder_id, 'some error') |
3479 | 283 | return | ||
3480 | 274 | 284 | ||
3482 | 275 | self.udfs[folder_id]['subscribed'] = False | 285 | self.udfs[folder_id]['subscribed'] = sd_dbus_iface.bool_str(False) |
3483 | 276 | self.emit_folder_unsubscribed(self.udfs[folder_id]) | 286 | self.emit_folder_unsubscribed(self.udfs[folder_id]) |
3484 | 277 | 287 | ||
3485 | 278 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, | 288 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME, |
3486 | @@ -296,49 +306,208 @@ | |||
3487 | 296 | 306 | ||
3488 | 297 | def setUp(self): | 307 | def setUp(self): |
3489 | 298 | super(FoldersTestCase, self).setUp() | 308 | super(FoldersTestCase, self).setUp() |
3506 | 299 | 309 | self.patch(sd_dbus_iface, '_get_udf_dict', lambda udf: udf) | |
3491 | 300 | def _get_udf_dict(udf): | ||
3492 | 301 | """Get a dict with all the attributes of: udf.""" | ||
3493 | 302 | udf_dict = udf.copy() | ||
3494 | 303 | for k, val in udf_dict.items(): | ||
3495 | 304 | if val is None: | ||
3496 | 305 | udf_dict[unicode(k)] = '' | ||
3497 | 306 | elif k == 'subscribed': | ||
3498 | 307 | udf_dict[unicode(k)] = sd_dbus_iface.bool_str(val) | ||
3499 | 308 | elif k in ('path', 'suggested_path') and isinstance(val, str): | ||
3500 | 309 | udf_dict[unicode(k)] = val.decode('utf-8') | ||
3501 | 310 | else: | ||
3502 | 311 | udf_dict[unicode(k)] = unicode(val) | ||
3503 | 312 | return udf_dict | ||
3504 | 313 | |||
3505 | 314 | self.patch(sd_dbus_iface, '_get_udf_dict', _get_udf_dict) | ||
3507 | 315 | self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME, | 310 | self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME, |
3508 | 316 | "/folders", FoldersMockDBusSyncDaemon) | 311 | "/folders", FoldersMockDBusSyncDaemon) |
3509 | 317 | 312 | ||
3510 | 318 | @inlineCallbacks | 313 | @inlineCallbacks |
3513 | 319 | def test_get_volumes(self): | 314 | def test_get_folders(self): |
3514 | 320 | """Retrieve volumes info list.""" | 315 | """Retrieve folders info list.""" |
3515 | 321 | path = '~/bar/baz' | 316 | path = '~/bar/baz' |
3517 | 322 | yield dbus_client.create_volume(path) | 317 | yield dbus_client.create_folder(path) |
3518 | 323 | 318 | ||
3520 | 324 | result = yield dbus_client.get_volumes() | 319 | result = yield dbus_client.get_folders() |
3521 | 325 | 320 | ||
3522 | 326 | expected = FoldersMockDBusSyncDaemon._new_udf(1, path) | 321 | expected = FoldersMockDBusSyncDaemon._new_udf(1, path) |
3523 | 327 | self.assertEqual(result, [sd_dbus_iface._get_udf_dict(expected)]) | 322 | self.assertEqual(result, [sd_dbus_iface._get_udf_dict(expected)]) |
3524 | 328 | 323 | ||
3525 | 329 | @inlineCallbacks | 324 | @inlineCallbacks |
3530 | 330 | def test_create_volume(self): | 325 | def test_get_folders_error(self): |
3531 | 331 | """Create a new volume.""" | 326 | """Handle error when retrieving current syncdaemon status.""" |
3532 | 332 | path = '~/bar/baz' | 327 | path = '~/bar/baz' |
3533 | 333 | volume_info = yield dbus_client.create_volume(path) | 328 | yield dbus_client.create_folder(path) |
3534 | 329 | |||
3535 | 330 | def fail(value): | ||
3536 | 331 | """Fake an error.""" | ||
3537 | 332 | raise TestDBusException(value) | ||
3538 | 333 | |||
3539 | 334 | self.patch(sd_dbus_iface, '_get_udf_dict', fail) | ||
3540 | 335 | |||
3541 | 336 | try: | ||
3542 | 337 | yield dbus_client.get_folders() | ||
3543 | 338 | except dbus.DBusException: | ||
3544 | 339 | pass # test passes! | ||
3545 | 340 | else: | ||
3546 | 341 | self.fail('dbus_client.get_folders should be errbacking') | ||
3547 | 342 | |||
3548 | 343 | @inlineCallbacks | ||
3549 | 344 | def test_create_folder(self): | ||
3550 | 345 | """Create a new folder.""" | ||
3551 | 346 | path = '~/bar/baz' | ||
3552 | 347 | folder_info = yield dbus_client.create_folder(path) | ||
3553 | 334 | 348 | ||
3554 | 335 | expected = FoldersMockDBusSyncDaemon._new_udf(1, path) | 349 | expected = FoldersMockDBusSyncDaemon._new_udf(1, path) |
3564 | 336 | self.assertEqual(sd_dbus_iface._get_udf_dict(expected), volume_info) | 350 | self.assertEqual(sd_dbus_iface._get_udf_dict(expected), folder_info) |
3565 | 337 | 351 | ||
3566 | 338 | @inlineCallbacks | 352 | @inlineCallbacks |
3567 | 339 | def test_create_volume_error(self): | 353 | def test_create_folder_error(self): |
3568 | 340 | """Create a new volume fails.""" | 354 | """Create a new folder fails.""" |
3569 | 341 | try: | 355 | path = '' |
3570 | 342 | yield dbus_client.create_volume(path='') | 356 | try: |
3571 | 343 | except dbus_client.VolumesError, e: | 357 | yield dbus_client.create_folder(path=path) |
3572 | 344 | self.assertEqual(e[0], {'path': ''}) | 358 | except dbus_client.VolumesError, e: |
3573 | 359 | self.assertEqual(e[0], {'path': path}) | ||
3574 | 360 | else: | ||
3575 | 361 | self.fail('dbus_client.create_folder should be errbacking') | ||
3576 | 362 | |||
3577 | 363 | @inlineCallbacks | ||
3578 | 364 | def test_subscribe_folder(self): | ||
3579 | 365 | """Subscribe to a folder.""" | ||
3580 | 366 | path = '~/bar/baz' | ||
3581 | 367 | folder_info = yield dbus_client.create_folder(path) | ||
3582 | 368 | fid = folder_info['volume_id'] | ||
3583 | 369 | yield dbus_client.subscribe_folder(fid) | ||
3584 | 370 | |||
3585 | 371 | result = yield dbus_client.get_folders() | ||
3586 | 372 | expected, = filter(lambda folder: folder['volume_id'] == fid, result) | ||
3587 | 373 | self.assertEqual(expected['subscribed'], 'True') | ||
3588 | 374 | |||
3589 | 375 | @inlineCallbacks | ||
3590 | 376 | def test_subscribe_folder_error(self): | ||
3591 | 377 | """Subscribe to a folder.""" | ||
3592 | 378 | fid = u'does not exist' | ||
3593 | 379 | try: | ||
3594 | 380 | yield dbus_client.subscribe_folder(fid) | ||
3595 | 381 | except dbus_client.VolumesError, e: | ||
3596 | 382 | self.assertEqual(e[0], {'id': fid}) | ||
3597 | 383 | else: | ||
3598 | 384 | self.fail('dbus_client.subscribe_folder should be errbacking') | ||
3599 | 385 | |||
3600 | 386 | @inlineCallbacks | ||
3601 | 387 | def test_unsubscribe_folder(self): | ||
3602 | 388 | """Unsubscribe to a folder.""" | ||
3603 | 389 | path = '~/bar/baz' | ||
3604 | 390 | folder_info = yield dbus_client.create_folder(path) | ||
3605 | 391 | fid = folder_info['volume_id'] | ||
3606 | 392 | yield dbus_client.subscribe_folder(fid) | ||
3607 | 393 | # folder is subscribed | ||
3608 | 394 | |||
3609 | 395 | yield dbus_client.unsubscribe_folder(fid) | ||
3610 | 396 | |||
3611 | 397 | result = yield dbus_client.get_folders() | ||
3612 | 398 | expected, = filter(lambda folder: folder['volume_id'] == fid, result) | ||
3613 | 399 | self.assertEqual(expected['subscribed'], '') | ||
3614 | 400 | |||
3615 | 401 | @inlineCallbacks | ||
3616 | 402 | def test_unsubscribe_folder_error(self): | ||
3617 | 403 | """Unsubscribe to a folder.""" | ||
3618 | 404 | fid = u'does not exist' | ||
3619 | 405 | try: | ||
3620 | 406 | yield dbus_client.unsubscribe_folder(fid) | ||
3621 | 407 | except dbus_client.VolumesError, e: | ||
3622 | 408 | self.assertEqual(e[0], {'id': fid}) | ||
3623 | 409 | else: | ||
3624 | 410 | self.fail('dbus_client.unsubscribe_folder should be errbacking') | ||
3625 | 411 | |||
3626 | 412 | |||
3627 | 413 | class StatusMockDBusSyncDaemon(dbus.service.Object): | ||
3628 | 414 | """A mock object that mimicks syncdaemon regarding the Status iface.""" | ||
3629 | 415 | |||
3630 | 416 | state_dict = { | ||
3631 | 417 | 'name': 'TEST', | ||
3632 | 418 | 'description': 'Some test state, nothing else.', | ||
3633 | 419 | 'is_error': '', | ||
3634 | 420 | 'is_connected': 'True', | ||
3635 | 421 | 'is_online': '', | ||
3636 | 422 | 'queues': 'GORGEOUS', | ||
3637 | 423 | 'connection': '', | ||
3638 | 424 | } | ||
3639 | 425 | |||
3640 | 426 | def _get_current_state(self): | ||
3641 | 427 | """Get the current status of the system.""" | ||
3642 | 428 | return self.state_dict | ||
3643 | 429 | |||
3644 | 430 | @dbus.service.method(sd_dbus_iface.DBUS_IFACE_STATUS_NAME, | ||
3645 | 431 | in_signature='', out_signature='a{ss}') | ||
3646 | 432 | def current_status(self): | ||
3647 | 433 | """Return the current faked status of the system.""" | ||
3648 | 434 | return self._get_current_state() | ||
3649 | 435 | |||
3650 | 436 | # pylint: disable=C0103 | ||
3651 | 437 | # Invalid name "StatusChanged" | ||
3652 | 438 | |||
3653 | 439 | @dbus.service.signal(sd_dbus_iface.DBUS_IFACE_STATUS_NAME) | ||
3654 | 440 | def StatusChanged(self, status): | ||
3655 | 441 | """Fire a signal to notify that the status of the system changed.""" | ||
3656 | 442 | |||
3657 | 443 | def emit_status_changed(self, state=None): | ||
3658 | 444 | """Emit StatusChanged.""" | ||
3659 | 445 | self.StatusChanged(self._get_current_state()) | ||
3660 | 446 | |||
3661 | 447 | |||
3662 | 448 | class StatusTestCase(DBusClientTestCase): | ||
3663 | 449 | """Test for the status dbus client methods.""" | ||
3664 | 450 | |||
3665 | 451 | def setUp(self): | ||
3666 | 452 | super(StatusTestCase, self).setUp() | ||
3667 | 453 | self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME, | ||
3668 | 454 | "/status", StatusMockDBusSyncDaemon) | ||
3669 | 455 | |||
3670 | 456 | @inlineCallbacks | ||
3671 | 457 | def test_get_current_status(self): | ||
3672 | 458 | """Retrieve current syncdaemon status.""" | ||
3673 | 459 | status = yield dbus_client.get_current_status() | ||
3674 | 460 | |||
3675 | 461 | self.assertEqual(StatusMockDBusSyncDaemon.state_dict, status) | ||
3676 | 462 | |||
3677 | 463 | @inlineCallbacks | ||
3678 | 464 | def test_get_current_status_error(self): | ||
3679 | 465 | """Handle error when retrieving current syncdaemon status.""" | ||
3680 | 466 | |||
3681 | 467 | def fail(value): | ||
3682 | 468 | """Fake an error.""" | ||
3683 | 469 | raise TestDBusException(value) | ||
3684 | 470 | |||
3685 | 471 | self.patch(StatusMockDBusSyncDaemon, '_get_current_state', fail) | ||
3686 | 472 | |||
3687 | 473 | try: | ||
3688 | 474 | yield dbus_client.get_current_status() | ||
3689 | 475 | except dbus.DBusException: | ||
3690 | 476 | pass # test passes! | ||
3691 | 477 | else: | ||
3692 | 478 | self.fail('dbus_client.get_current_status should be errbacking') | ||
3693 | 479 | |||
3694 | 480 | def test_set_status_changed_handler(self): | ||
3695 | 481 | """A proper callback can be connected to StatusChanged signal.""" | ||
3696 | 482 | _, sig = dbus_client.set_status_changed_handler(self._set_called) | ||
3697 | 483 | |||
3698 | 484 | self.assertEqual(sig._handler, self._set_called) | ||
3699 | 485 | self.assertEqual(sig._member, 'StatusChanged') | ||
3700 | 486 | self.assertEqual(sig._path, '/status') | ||
3701 | 487 | self.assertEqual(sig._interface, sd_dbus_iface.DBUS_IFACE_STATUS_NAME) | ||
3702 | 488 | |||
3703 | 489 | |||
3704 | 490 | class FileSyncTestCase(DBusClientTestCase): | ||
3705 | 491 | """Test for the files sync enabled dbus client methods.""" | ||
3706 | 492 | |||
3707 | 493 | @inlineCallbacks | ||
3708 | 494 | def test_files_sync_enabled(self): | ||
3709 | 495 | """Retrieve whether file sync is enabled.""" | ||
3710 | 496 | expected = object() | ||
3711 | 497 | self.patch(dbus_client.SyncDaemonTool, 'is_files_sync_enabled', | ||
3712 | 498 | lambda _: expected) | ||
3713 | 499 | |||
3714 | 500 | enabled = yield dbus_client.files_sync_enabled() | ||
3715 | 501 | |||
3716 | 502 | self.assertEqual(expected, enabled) | ||
3717 | 503 | |||
3718 | 504 | @inlineCallbacks | ||
3719 | 505 | def test_set_files_sync_enabled(self): | ||
3720 | 506 | """Set if file sync is enabled or not.""" | ||
3721 | 507 | self.patch(dbus_client.SyncDaemonTool, 'enable_files_sync', | ||
3722 | 508 | self._set_called) | ||
3723 | 509 | expected = object() | ||
3724 | 510 | # set the opposite value | ||
3725 | 511 | yield dbus_client.set_files_sync_enabled(expected) | ||
3726 | 512 | |||
3727 | 513 | self.assertEqual(self._called, ((expected,), {})) | ||
3728 | 345 | 514 | ||
3729 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py' | |||
3730 | --- ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py 2010-12-06 12:27:11 +0000 | |||
3731 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sso.py 2010-12-22 14:37:52 +0000 | |||
3732 | @@ -23,8 +23,9 @@ | |||
3733 | 23 | # DBus signals have CamelCased names | 23 | # DBus signals have CamelCased names |
3734 | 24 | 24 | ||
3735 | 25 | import dbus | 25 | import dbus |
3736 | 26 | import ubuntu_sso | ||
3737 | 27 | 26 | ||
3738 | 27 | from ubuntu_sso import (DBUS_BUS_NAME, | ||
3739 | 28 | DBUS_CREDENTIALS_PATH, DBUS_CREDENTIALS_IFACE) | ||
3740 | 28 | from twisted.internet.defer import inlineCallbacks | 29 | from twisted.internet.defer import inlineCallbacks |
3741 | 29 | 30 | ||
3742 | 30 | from ubuntuone.controlpanel import dbus_client | 31 | from ubuntuone.controlpanel import dbus_client |
3743 | @@ -36,6 +37,8 @@ | |||
3744 | 36 | "token": "ABCDEF12345678", | 37 | "token": "ABCDEF12345678", |
3745 | 37 | "access_token": "DEADCAFE2010", | 38 | "access_token": "DEADCAFE2010", |
3746 | 38 | } | 39 | } |
3747 | 40 | OTHER_CREDS = {"token": "other!"} | ||
3748 | 41 | SAMPLE_ERROR = {"error message": "test", "detailed_error": "error details"} | ||
3749 | 39 | 42 | ||
3750 | 40 | # pylint: disable=C0322 | 43 | # pylint: disable=C0322 |
3751 | 41 | # pylint, you have to go to decorator's school | 44 | # pylint, you have to go to decorator's school |
3752 | @@ -44,116 +47,161 @@ | |||
3753 | 44 | class MockDBusSSOService(dbus.service.Object): | 47 | class MockDBusSSOService(dbus.service.Object): |
3754 | 45 | """A mock object that mimicks ussoc.""" | 48 | """A mock object that mimicks ussoc.""" |
3755 | 46 | 49 | ||
3818 | 47 | @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 50 | found = True |
3819 | 48 | in_signature="sssx") | 51 | wrong_app = None |
3820 | 49 | def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid): | 52 | error = None |
3821 | 50 | """Get creds from the keyring, login/register if needed.""" | 53 | |
3822 | 51 | self.CredentialsFound(app_name, SAMPLE_CREDS) | 54 | @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE, |
3823 | 52 | 55 | in_signature='sa{ss}', out_signature='') | |
3824 | 53 | @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 56 | def find_credentials(self, app_name, args): |
3825 | 54 | signature="sa{ss}") | 57 | """Get creds from the keyring, login/register if needed.""" |
3826 | 55 | def CredentialsFound(self, app_name, credentials): | 58 | if self.wrong_app is None and self.error is None: |
3827 | 56 | """Credentials were finally found.""" | 59 | if self.found: |
3828 | 57 | 60 | self.CredentialsFound(app_name, SAMPLE_CREDS) | |
3829 | 58 | 61 | else: | |
3830 | 59 | class MockDBusSSOServiceOther(dbus.service.Object): | 62 | self.CredentialsNotFound(app_name) |
3831 | 60 | """A mock object that mimicks ussoc.""" | 63 | elif self.wrong_app is not None and self.error is None: |
3832 | 61 | 64 | self.CredentialsFound(self.wrong_app, OTHER_CREDS) | |
3833 | 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: |
3834 | 63 | in_signature="sssx") | 66 | self.CredentialsError(app_name, self.error) |
3835 | 64 | def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid): | 67 | else: |
3836 | 65 | """Get creds from the keyring, login/register if needed.""" | 68 | self.CredentialsError(self.wrong_app, self.error) |
3837 | 66 | self.CredentialsFound("wrong app name", {}) | 69 | |
3838 | 67 | self.CredentialsFound(app_name, SAMPLE_CREDS) | 70 | self.CredentialsFound(app_name, SAMPLE_CREDS) |
3839 | 68 | 71 | ||
3840 | 69 | @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 72 | @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE, |
3841 | 70 | signature="sa{ss}") | 73 | in_signature='sa{ss}', out_signature='') |
3842 | 71 | def CredentialsFound(self, app_name, credentials): | 74 | def clear_credentials(self, app_name, args): |
3843 | 72 | """Credentials were finally found.""" | 75 | """Clear the credentials for an application.""" |
3844 | 73 | 76 | self.found = False | |
3845 | 74 | 77 | ||
3846 | 75 | class MockDBusSSOClientOtherFailing(dbus.service.Object): | 78 | if self.wrong_app is None and self.error is None: |
3847 | 76 | """A mock object that mimicks ussoc.""" | 79 | self.CredentialsCleared(app_name) |
3848 | 77 | 80 | elif self.wrong_app is not None and self.error is None: | |
3849 | 78 | @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 81 | self.CredentialsCleared(self.wrong_app) |
3850 | 79 | in_signature="sssx") | 82 | elif self.wrong_app is None and self.error is not None: |
3851 | 80 | def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid): | 83 | self.CredentialsError(app_name, self.error) |
3852 | 81 | """Get creds from the keyring, login/register if needed.""" | 84 | else: |
3853 | 82 | self.CredentialsError("wrong app", "error message", "error details") | 85 | self.CredentialsError(self.wrong_app, self.error) |
3854 | 83 | self.CredentialsFound(app_name, SAMPLE_CREDS) | 86 | |
3855 | 84 | 87 | self.CredentialsCleared(app_name) | |
3856 | 85 | @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 88 | |
3857 | 86 | signature="sa{ss}") | 89 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}') |
3858 | 87 | def CredentialsFound(self, app_name, credentials): | 90 | def CredentialsFound(self, app_name, credentials): |
3859 | 88 | """Credentials were finally found.""" | 91 | """Signal thrown when the credentials are found.""" |
3860 | 89 | 92 | ||
3861 | 90 | @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 93 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') |
3862 | 91 | signature="sss") | 94 | def CredentialsNotFound(self, app_name): |
3863 | 92 | def CredentialsError(self, app_name, error_message, detailed_error): | 95 | """Signal thrown when the credentials are not found.""" |
3864 | 93 | """Some error happened and credentials were not found.""" | 96 | |
3865 | 94 | 97 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') | |
3866 | 95 | 98 | def CredentialsCleared(self, app_name): | |
3867 | 96 | class MockDBusSSOClientFailing(dbus.service.Object): | 99 | """Signal thrown when the credentials were cleared.""" |
3868 | 97 | """A mock object that mimicks ussoc but fails.""" | 100 | |
3869 | 98 | 101 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') | |
3870 | 99 | @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | 102 | def CredentialsStored(self, app_name): |
3871 | 100 | in_signature="sssx") | 103 | """Signal thrown when the credentials were cleared.""" |
3872 | 101 | def login_or_register_to_get_credentials(self, app_name, tcurl, hlp, wid): | 104 | |
3873 | 102 | """Fail while trying to get creds from the keyring.""" | 105 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}') |
3874 | 103 | self.CredentialsError(app_name, "error message", "error details") | 106 | def CredentialsError(self, app_name, error_dict): |
3875 | 104 | 107 | """Signal thrown when there is a problem getting the credentials.""" | |
3814 | 105 | @dbus.service.signal(dbus_interface=ubuntu_sso.DBUS_IFACE_CRED_NAME, | ||
3815 | 106 | signature="sss") | ||
3816 | 107 | def CredentialsError(self, app_name, error_message, detailed_error): | ||
3817 | 108 | """Some error happened and credentials were not found.""" | ||
3876 | 109 | 108 | ||
3877 | 110 | 109 | ||
3878 | 111 | class SSOClientTestCase(DBusClientTestCase): | 110 | class SSOClientTestCase(DBusClientTestCase): |
3879 | 112 | """Test for the SSO dbus client.""" | 111 | """Test for the SSO dbus client.""" |
3880 | 113 | 112 | ||
3881 | 113 | def setUp(self): | ||
3882 | 114 | super(SSOClientTestCase, self).setUp() | ||
3883 | 115 | self.register_mockserver(DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH, | ||
3884 | 116 | MockDBusSSOService) | ||
3885 | 117 | MockDBusSSOService.wrong_app = None | ||
3886 | 118 | MockDBusSSOService.error = None | ||
3887 | 119 | MockDBusSSOService.found = True | ||
3888 | 120 | |||
3889 | 121 | # get_credentials | ||
3890 | 122 | |||
3891 | 114 | @inlineCallbacks | 123 | @inlineCallbacks |
3892 | 115 | def test_get_credentials_ok(self): | 124 | def test_get_credentials_ok(self): |
3893 | 116 | """Test the success case for get_credentials.""" | 125 | """Test the success case for get_credentials.""" |
3894 | 117 | self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME, | ||
3895 | 118 | ubuntu_sso.DBUS_CRED_PATH, MockDBusSSOService) | ||
3896 | 119 | creds = yield dbus_client.get_credentials() | 126 | creds = yield dbus_client.get_credentials() |
3897 | 120 | self.assertEqual(creds, SAMPLE_CREDS) | 127 | self.assertEqual(creds, SAMPLE_CREDS) |
3898 | 121 | 128 | ||
3899 | 122 | @inlineCallbacks | 129 | @inlineCallbacks |
3900 | 130 | def test_get_credentials_not_found(self): | ||
3901 | 131 | """Credentials were not found.""" | ||
3902 | 132 | yield dbus_client.clear_credentials() # not found will be sent | ||
3903 | 133 | yield self.assertFailure(dbus_client.get_credentials(), | ||
3904 | 134 | dbus_client.CredentialsError) | ||
3905 | 135 | |||
3906 | 136 | @inlineCallbacks | ||
3907 | 123 | def test_get_credentials_other(self): | 137 | def test_get_credentials_other(self): |
3908 | 124 | """Creds for other apps are ignored.""" | 138 | """Creds for other apps are ignored.""" |
3912 | 125 | self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME, | 139 | MockDBusSSOService.wrong_app = 'other app!' |
3910 | 126 | ubuntu_sso.DBUS_CRED_PATH, | ||
3911 | 127 | MockDBusSSOServiceOther) | ||
3913 | 128 | creds = yield dbus_client.get_credentials() | 140 | creds = yield dbus_client.get_credentials() |
3914 | 129 | self.assertEqual(creds, SAMPLE_CREDS) | 141 | self.assertEqual(creds, SAMPLE_CREDS) |
3915 | 130 | 142 | ||
3916 | 131 | @inlineCallbacks | 143 | @inlineCallbacks |
3917 | 132 | def test_get_credentials_error(self): | 144 | def test_get_credentials_error(self): |
3918 | 133 | """Test what happens when the creds can't be retrieved.""" | 145 | """Test what happens when the creds can't be retrieved.""" |
3923 | 134 | self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME, | 146 | MockDBusSSOService.error = SAMPLE_ERROR |
3920 | 135 | ubuntu_sso.DBUS_CRED_PATH, | ||
3921 | 136 | MockDBusSSOClientFailing) | ||
3922 | 137 | |||
3924 | 138 | yield self.assertFailure(dbus_client.get_credentials(), | 147 | yield self.assertFailure(dbus_client.get_credentials(), |
3925 | 139 | dbus_client.CredentialsError) | 148 | dbus_client.CredentialsError) |
3926 | 140 | 149 | ||
3927 | 141 | @inlineCallbacks | 150 | @inlineCallbacks |
3928 | 142 | def test_get_credentials_other_error(self): | 151 | def test_get_credentials_other_error(self): |
3929 | 143 | """Other creds err before ours are retrieved.""" | 152 | """Other creds err before ours are retrieved.""" |
3934 | 144 | self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME, | 153 | MockDBusSSOService.wrong_app = 'other app!' |
3935 | 145 | ubuntu_sso.DBUS_CRED_PATH, | 154 | MockDBusSSOService.error = SAMPLE_ERROR |
3932 | 146 | MockDBusSSOClientOtherFailing) | ||
3933 | 147 | |||
3936 | 148 | creds = yield dbus_client.get_credentials() | 155 | creds = yield dbus_client.get_credentials() |
3937 | 149 | self.assertEqual(creds, SAMPLE_CREDS) | 156 | self.assertEqual(creds, SAMPLE_CREDS) |
3938 | 150 | 157 | ||
3939 | 158 | # clear_credentials | ||
3940 | 159 | |||
3941 | 160 | @inlineCallbacks | ||
3942 | 161 | def test_clear_credentials_ok(self): | ||
3943 | 162 | """Test the success case for clear_credentials.""" | ||
3944 | 163 | result = yield dbus_client.clear_credentials() | ||
3945 | 164 | self.assertEqual(result, dbus_client.APP_NAME) | ||
3946 | 165 | |||
3947 | 166 | @inlineCallbacks | ||
3948 | 167 | def test_clear_credentials_other(self): | ||
3949 | 168 | """Creds for other apps are ignored.""" | ||
3950 | 169 | MockDBusSSOService.wrong_app = 'other app!' | ||
3951 | 170 | result = yield dbus_client.clear_credentials() | ||
3952 | 171 | self.assertEqual(result, dbus_client.APP_NAME) | ||
3953 | 172 | |||
3954 | 173 | @inlineCallbacks | ||
3955 | 174 | def test_clear_credentials_error(self): | ||
3956 | 175 | """Test what happens when the creds can't be retrieved.""" | ||
3957 | 176 | MockDBusSSOService.error = SAMPLE_ERROR | ||
3958 | 177 | yield self.assertFailure(dbus_client.clear_credentials(), | ||
3959 | 178 | dbus_client.CredentialsError) | ||
3960 | 179 | |||
3961 | 180 | @inlineCallbacks | ||
3962 | 181 | def test_clear_credentials_other_error(self): | ||
3963 | 182 | """Other creds err before ours are retrieved.""" | ||
3964 | 183 | MockDBusSSOService.wrong_app = 'other app!' | ||
3965 | 184 | MockDBusSSOService.error = SAMPLE_ERROR | ||
3966 | 185 | result = yield dbus_client.clear_credentials() | ||
3967 | 186 | self.assertEqual(result, dbus_client.APP_NAME) | ||
3968 | 187 | |||
3969 | 188 | |||
3970 | 189 | class NoMethodsSSOClientTestCase(DBusClientTestCase): | ||
3971 | 190 | """Test for the SSO dbus client when the service provides no methods.""" | ||
3972 | 191 | |||
3973 | 192 | def setUp(self): | ||
3974 | 193 | super(NoMethodsSSOClientTestCase, self).setUp() | ||
3975 | 194 | self.register_mockserver(DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH, | ||
3976 | 195 | MockDBusNoMethods) | ||
3977 | 196 | |||
3978 | 151 | @inlineCallbacks | 197 | @inlineCallbacks |
3979 | 152 | def test_get_credentials_dbus_error(self): | 198 | def test_get_credentials_dbus_error(self): |
3980 | 153 | """Test what happens when there's a DBus error.""" | 199 | """Test what happens when there's a DBus error.""" |
3984 | 154 | self.register_mockserver(ubuntu_sso.DBUS_BUS_NAME, | 200 | yield self.assertFailure(dbus_client.get_credentials(), |
3985 | 155 | ubuntu_sso.DBUS_CRED_PATH, | 201 | dbus.DBusException) |
3983 | 156 | MockDBusNoMethods) | ||
3986 | 157 | 202 | ||
3988 | 158 | yield self.assertFailure(dbus_client.get_credentials(), | 203 | @inlineCallbacks |
3989 | 204 | def test_clear_credentials_dbus_error(self): | ||
3990 | 205 | """Test what happens when there's a DBus error.""" | ||
3991 | 206 | yield self.assertFailure(dbus_client.clear_credentials(), | ||
3992 | 159 | dbus.DBusException) | 207 | dbus.DBusException) |
3993 | 160 | 208 | ||
3994 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py' | |||
3995 | --- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-06 12:27:11 +0000 | |||
3996 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-22 14:37:52 +0000 | |||
3997 | @@ -45,8 +45,8 @@ | |||
3998 | 45 | "name": "Ubuntu One @ darkstar", | 45 | "name": "Ubuntu One @ darkstar", |
3999 | 46 | "date_added": "2008-12-06T18:15:38.0", | 46 | "date_added": "2008-12-06T18:15:38.0", |
4000 | 47 | "type": "computer", | 47 | "type": "computer", |
4003 | 48 | "configurable": "1", | 48 | "configurable": 'True', |
4004 | 49 | "limit_bandwidth": "1", | 49 | "limit_bandwidth": 'True', |
4005 | 50 | "max_upload_speed": "12345", | 50 | "max_upload_speed": "12345", |
4006 | 51 | "max_download_speed": "54321", | 51 | "max_download_speed": "54321", |
4007 | 52 | "available_services": "files, contacts, music, bookmarks", | 52 | "available_services": "files, contacts, music, bookmarks", |
4008 | @@ -57,7 +57,7 @@ | |||
4009 | 57 | "name": "Ubuntu One @ brightmoon", | 57 | "name": "Ubuntu One @ brightmoon", |
4010 | 58 | "date_added": "2010-09-22T20:45:38.0", | 58 | "date_added": "2010-09-22T20:45:38.0", |
4011 | 59 | "type": "computer", | 59 | "type": "computer", |
4013 | 60 | "configurable": "0", | 60 | "configurable": '', |
4014 | 61 | "available_services": "files, contacts, bookmarks", | 61 | "available_services": "files, contacts, bookmarks", |
4015 | 62 | "enabled_services": "files, bookmarks", | 62 | "enabled_services": "files, bookmarks", |
4016 | 63 | }, | 63 | }, |
4017 | @@ -68,19 +68,19 @@ | |||
4018 | 68 | "volume_id": "volume-0", | 68 | "volume_id": "volume-0", |
4019 | 69 | "path": "/home/user/Documents", | 69 | "path": "/home/user/Documents", |
4020 | 70 | "suggested_path": "~/Documents", | 70 | "suggested_path": "~/Documents", |
4022 | 71 | "subscribed": "1", | 71 | "subscribed": 'True', |
4023 | 72 | }, | 72 | }, |
4024 | 73 | { | 73 | { |
4025 | 74 | "volume_id": "volume-1", | 74 | "volume_id": "volume-1", |
4026 | 75 | "path": "/home/user/Music", | 75 | "path": "/home/user/Music", |
4027 | 76 | "suggested_path": "~/Music", | 76 | "suggested_path": "~/Music", |
4029 | 77 | "subscribed": "0", | 77 | "subscribed": '', |
4030 | 78 | }, | 78 | }, |
4031 | 79 | { | 79 | { |
4032 | 80 | "volume_id": "volume-2", | 80 | "volume_id": "volume-2", |
4033 | 81 | "path": "/home/user/Pictures/Photos", | 81 | "path": "/home/user/Pictures/Photos", |
4034 | 82 | "suggested_path": "~/Pictures/Photos", | 82 | "suggested_path": "~/Pictures/Photos", |
4036 | 83 | "subscribed": "0", | 83 | "subscribed": '', |
4037 | 84 | }, | 84 | }, |
4038 | 85 | ] | 85 | ] |
4039 | 86 | 86 | ||
4040 | @@ -115,7 +115,13 @@ | |||
4041 | 115 | 115 | ||
4042 | 116 | class MockBackend(object): | 116 | class MockBackend(object): |
4043 | 117 | """A mock backend.""" | 117 | """A mock backend.""" |
4044 | 118 | |||
4045 | 118 | exception = None | 119 | exception = None |
4046 | 120 | sample_status = { | ||
4047 | 121 | dbus_service.MSG_KEY: 'test me please', | ||
4048 | 122 | dbus_service.STATUS_KEY: dbus_service.FILE_SYNC_IDLE, | ||
4049 | 123 | } | ||
4050 | 124 | status_changed_handler = None | ||
4051 | 119 | 125 | ||
4052 | 120 | def _process(self, result): | 126 | def _process(self, result): |
4053 | 121 | """Process the request with the given result.""" | 127 | """Process the request with the given result.""" |
4054 | @@ -142,7 +148,7 @@ | |||
4055 | 142 | 148 | ||
4056 | 143 | def file_sync_status(self): | 149 | def file_sync_status(self): |
4057 | 144 | """Return the status of the file sync service.""" | 150 | """Return the status of the file sync service.""" |
4059 | 145 | return self._process((True, "Synchronizing")) | 151 | return self._process(self.sample_status) |
4060 | 146 | 152 | ||
4061 | 147 | def volumes_info(self): | 153 | def volumes_info(self): |
4062 | 148 | """Get the user volumes info.""" | 154 | """Get the user volumes info.""" |
4063 | @@ -170,6 +176,11 @@ | |||
4064 | 170 | """Initialize each test run.""" | 176 | """Initialize each test run.""" |
4065 | 171 | super(DBusServiceTestCase, self).setUp() | 177 | super(DBusServiceTestCase, self).setUp() |
4066 | 172 | dbus_service.init_mainloop() | 178 | dbus_service.init_mainloop() |
4067 | 179 | self._called = False | ||
4068 | 180 | |||
4069 | 181 | def _set_called(self, *args, **kwargs): | ||
4070 | 182 | """Keep track of function calls, useful for monkeypatching.""" | ||
4071 | 183 | self._called = (args, kwargs) | ||
4072 | 173 | 184 | ||
4073 | 174 | def test_register_service(self): | 185 | def test_register_service(self): |
4074 | 175 | """The DBus service is successfully registered.""" | 186 | """The DBus service is successfully registered.""" |
4075 | @@ -193,7 +204,7 @@ | |||
4076 | 193 | def test_error_handler_with_failure(self): | 204 | def test_error_handler_with_failure(self): |
4077 | 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.""" |
4078 | 195 | error = dbus_service.Failure(TypeError('oh no!')) | 206 | error = dbus_service.Failure(TypeError('oh no!')) |
4080 | 196 | expected = dbus_service.utils.failure_to_error_dict(error) | 207 | expected = dbus_service.failure_to_error_dict(error) |
4081 | 197 | 208 | ||
4082 | 198 | result = dbus_service.error_handler(error) | 209 | result = dbus_service.error_handler(error) |
4083 | 199 | 210 | ||
4084 | @@ -202,7 +213,7 @@ | |||
4085 | 202 | def test_error_handler_with_exception(self): | 213 | def test_error_handler_with_exception(self): |
4086 | 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.""" |
4087 | 204 | error = TypeError('oh no, no again!') | 215 | error = TypeError('oh no, no again!') |
4089 | 205 | expected = dbus_service.utils.exception_to_error_dict(error) | 216 | expected = dbus_service.exception_to_error_dict(error) |
4090 | 206 | 217 | ||
4091 | 207 | result = dbus_service.error_handler(error) | 218 | result = dbus_service.error_handler(error) |
4092 | 208 | 219 | ||
4093 | @@ -231,8 +242,8 @@ | |||
4094 | 231 | def test_error_handler_default(self): | 242 | def test_error_handler_default(self): |
4095 | 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.""" |
4096 | 233 | msg = 'Got unexpected error argument %r' % None | 244 | msg = 'Got unexpected error argument %r' % None |
4099 | 234 | expected = {dbus_service.utils.ERROR_TYPE: 'UnknownError', | 245 | expected = {dbus_service.ERROR_TYPE: 'UnknownError', |
4100 | 235 | dbus_service.utils.ERROR_MESSAGE: msg} | 246 | dbus_service.ERROR_MESSAGE: msg} |
4101 | 236 | 247 | ||
4102 | 237 | result = dbus_service.error_handler(None) | 248 | result = dbus_service.error_handler(None) |
4103 | 238 | 249 | ||
4104 | @@ -330,7 +341,7 @@ | |||
4105 | 330 | self.assertIn("date_added", device_info) | 341 | self.assertIn("date_added", device_info) |
4106 | 331 | self.assertIn("type", device_info) | 342 | self.assertIn("type", device_info) |
4107 | 332 | self.assertIn("configurable", device_info) | 343 | self.assertIn("configurable", device_info) |
4109 | 333 | if int(device_info["configurable"]): | 344 | if bool(device_info["configurable"]): |
4110 | 334 | self.assertIn("limit_bandwidth", device_info) | 345 | self.assertIn("limit_bandwidth", device_info) |
4111 | 335 | self.assertIn("max_upload_speed", device_info) | 346 | self.assertIn("max_upload_speed", device_info) |
4112 | 336 | self.assertIn("max_download_speed", device_info) | 347 | self.assertIn("max_download_speed", device_info) |
4113 | @@ -372,19 +383,6 @@ | |||
4114 | 372 | self.backend.remove_device, sample_token) | 383 | self.backend.remove_device, sample_token) |
4115 | 373 | return self.assert_correct_method_call(*args) | 384 | return self.assert_correct_method_call(*args) |
4116 | 374 | 385 | ||
4117 | 375 | def test_file_sync_status(self): | ||
4118 | 376 | """The file sync status is reported.""" | ||
4119 | 377 | |||
4120 | 378 | def got_signal(enabled, status): | ||
4121 | 379 | """The correct status was received.""" | ||
4122 | 380 | self.assertEqual(enabled, True) | ||
4123 | 381 | self.assertEqual(status, "Synchronizing") | ||
4124 | 382 | self.deferred.callback("success") | ||
4125 | 383 | |||
4126 | 384 | args = ("FileSyncStatusReady", "FileSyncStatusError", got_signal, | ||
4127 | 385 | self.backend.file_sync_status) | ||
4128 | 386 | return self.assert_correct_method_call(*args) | ||
4129 | 387 | |||
4130 | 388 | def test_volumes_info(self): | 386 | def test_volumes_info(self): |
4131 | 389 | """The volumes info is reported.""" | 387 | """The volumes info is reported.""" |
4132 | 390 | 388 | ||
4133 | @@ -408,7 +406,7 @@ | |||
4134 | 408 | 406 | ||
4135 | 409 | args = ("VolumeSettingsChanged", "VolumeSettingsChangeError", | 407 | args = ("VolumeSettingsChanged", "VolumeSettingsChangeError", |
4136 | 410 | got_signal, self.backend.change_volume_settings, | 408 | got_signal, self.backend.change_volume_settings, |
4138 | 411 | expected_volume_id, {'subscribed': '0'}) | 409 | expected_volume_id, {'subscribed': ''}) |
4139 | 412 | return self.assert_correct_method_call(*args) | 410 | return self.assert_correct_method_call(*args) |
4140 | 413 | 411 | ||
4141 | 414 | def test_query_bookmarks_extension(self): | 412 | def test_query_bookmarks_extension(self): |
4142 | @@ -451,11 +449,87 @@ | |||
4143 | 451 | 449 | ||
4144 | 452 | """ | 450 | """ |
4145 | 453 | 451 | ||
4147 | 454 | def got_error_signal(error_dict): | 452 | def got_error_signal(*a): |
4148 | 455 | """The error signal was received.""" | 453 | """The error signal was received.""" |
4150 | 456 | self.assertEqual(error_dict[dbus_service.utils.ERROR_TYPE], | 454 | if len(a) == 1: |
4151 | 455 | error_dict = a[0] | ||
4152 | 456 | else: | ||
4153 | 457 | an_id, error_dict = a | ||
4154 | 458 | self.assertEqual(an_id, args[0]) | ||
4155 | 459 | |||
4156 | 460 | self.assertEqual(error_dict[dbus_service.ERROR_TYPE], | ||
4157 | 457 | 'AssertionError') | 461 | 'AssertionError') |
4158 | 458 | self.deferred.callback("success") | 462 | self.deferred.callback("success") |
4159 | 459 | 463 | ||
4160 | 460 | return super(OperationsErrorTestCase, self).assert_correct_method_call( | 464 | return super(OperationsErrorTestCase, self).assert_correct_method_call( |
4161 | 461 | error_sig, success_sig, got_error_signal, method, *args) | 465 | error_sig, success_sig, got_error_signal, method, *args) |
4162 | 466 | |||
4163 | 467 | |||
4164 | 468 | class FileSyncTestCase(OperationsTestCase): | ||
4165 | 469 | """Test for the DBus service when requesting file sync status.""" | ||
4166 | 470 | |||
4167 | 471 | def assert_correct_status_signal(self, status, sync_signal, | ||
4168 | 472 | error_signal="FileSyncStatusError", | ||
4169 | 473 | expected_msg=None): | ||
4170 | 474 | """The file sync status is reported properly.""" | ||
4171 | 475 | MockBackend.sample_status[dbus_service.STATUS_KEY] = status | ||
4172 | 476 | if expected_msg is None: | ||
4173 | 477 | expected_msg = MockBackend.sample_status[dbus_service.MSG_KEY] | ||
4174 | 478 | |||
4175 | 479 | def got_signal(msg): | ||
4176 | 480 | """The correct status was received.""" | ||
4177 | 481 | self.assertEqual(msg, expected_msg) | ||
4178 | 482 | self.deferred.callback("success") | ||
4179 | 483 | |||
4180 | 484 | args = (sync_signal, error_signal, got_signal, | ||
4181 | 485 | self.backend.file_sync_status) | ||
4182 | 486 | return self.assert_correct_method_call(*args) | ||
4183 | 487 | |||
4184 | 488 | def test_file_sync_status_unknown(self): | ||
4185 | 489 | """The file sync status is reported properly.""" | ||
4186 | 490 | msg = MockBackend.sample_status | ||
4187 | 491 | args = ('invalid-file-sync-status', "FileSyncStatusError", | ||
4188 | 492 | "FileSyncStatusIdle", msg) | ||
4189 | 493 | return self.assert_correct_status_signal(*args) | ||
4190 | 494 | |||
4191 | 495 | def test_file_sync_status_error(self): | ||
4192 | 496 | """The file sync status is reported properly.""" | ||
4193 | 497 | msg = MockBackend.sample_status[dbus_service.MSG_KEY] | ||
4194 | 498 | err_dict = {dbus_service.ERROR_TYPE: 'FileSyncStatusError', | ||
4195 | 499 | dbus_service.ERROR_MESSAGE: msg} | ||
4196 | 500 | args = (dbus_service.FILE_SYNC_ERROR, "FileSyncStatusError", | ||
4197 | 501 | "FileSyncStatusIdle", err_dict) | ||
4198 | 502 | return self.assert_correct_status_signal(*args) | ||
4199 | 503 | |||
4200 | 504 | def test_file_sync_status_disabled(self): | ||
4201 | 505 | """The file sync status is reported properly.""" | ||
4202 | 506 | args = (dbus_service.FILE_SYNC_DISABLED, "FileSyncStatusDisabled") | ||
4203 | 507 | return self.assert_correct_status_signal(*args) | ||
4204 | 508 | |||
4205 | 509 | def test_file_sync_status_starting(self): | ||
4206 | 510 | """The file sync status is reported properly.""" | ||
4207 | 511 | args = (dbus_service.FILE_SYNC_STARTING, "FileSyncStatusStarting") | ||
4208 | 512 | return self.assert_correct_status_signal(*args) | ||
4209 | 513 | |||
4210 | 514 | def test_file_sync_status_disconnected(self): | ||
4211 | 515 | """The file sync status is reported properly.""" | ||
4212 | 516 | args = (dbus_service.FILE_SYNC_DISCONNECTED, | ||
4213 | 517 | "FileSyncStatusDisconnected") | ||
4214 | 518 | return self.assert_correct_status_signal(*args) | ||
4215 | 519 | |||
4216 | 520 | def test_file_sync_status_syncing(self): | ||
4217 | 521 | """The file sync status is reported properly.""" | ||
4218 | 522 | args = (dbus_service.FILE_SYNC_SYNCING, "FileSyncStatusSyncing") | ||
4219 | 523 | return self.assert_correct_status_signal(*args) | ||
4220 | 524 | |||
4221 | 525 | def test_file_sync_status_idle(self): | ||
4222 | 526 | """The file sync status is reported properly.""" | ||
4223 | 527 | args = (dbus_service.FILE_SYNC_IDLE, "FileSyncStatusIdle") | ||
4224 | 528 | return self.assert_correct_status_signal(*args) | ||
4225 | 529 | |||
4226 | 530 | def test_status_changed_handler(self): | ||
4227 | 531 | """The status changed handler is properly set.""" | ||
4228 | 532 | be = MockBackend() | ||
4229 | 533 | cpbe = dbus_service.ControlPanelBackend(backend=be) | ||
4230 | 534 | |||
4231 | 535 | self.assertEqual(be.status_changed_handler, cpbe.process_status) | ||
4232 | 462 | 536 | ||
4233 | === modified file 'ubuntuone/controlpanel/logger.py' | |||
4234 | --- ubuntuone/controlpanel/logger.py 2010-12-06 12:27:11 +0000 | |||
4235 | +++ ubuntuone/controlpanel/logger.py 2010-12-22 14:37:52 +0000 | |||
4236 | @@ -22,6 +22,7 @@ | |||
4237 | 22 | import os | 22 | import os |
4238 | 23 | import sys | 23 | import sys |
4239 | 24 | 24 | ||
4240 | 25 | from functools import wraps | ||
4241 | 25 | from logging.handlers import RotatingFileHandler | 26 | from logging.handlers import RotatingFileHandler |
4242 | 26 | 27 | ||
4243 | 27 | # pylint: disable=F0401,E0611 | 28 | # pylint: disable=F0401,E0611 |
4244 | @@ -41,7 +42,7 @@ | |||
4245 | 41 | MAIN_HANDLER.setLevel(LOG_LEVEL) | 42 | MAIN_HANDLER.setLevel(LOG_LEVEL) |
4246 | 42 | 43 | ||
4247 | 43 | 44 | ||
4249 | 44 | def setup_logging(log_domain): | 45 | def setup_logging(log_domain, prefix=None): |
4250 | 45 | """Create a logger for 'log_domain'. | 46 | """Create a logger for 'log_domain'. |
4251 | 46 | 47 | ||
4252 | 47 | Final domain will be 'ubuntuone.controlpanel.<log_domain>. | 48 | Final domain will be 'ubuntuone.controlpanel.<log_domain>. |
4253 | @@ -52,6 +53,28 @@ | |||
4254 | 52 | logger.addHandler(MAIN_HANDLER) | 53 | logger.addHandler(MAIN_HANDLER) |
4255 | 53 | if os.environ.get('DEBUG'): | 54 | if os.environ.get('DEBUG'): |
4256 | 54 | debug_handler = logging.StreamHandler(sys.stderr) | 55 | debug_handler = logging.StreamHandler(sys.stderr) |
4257 | 56 | if prefix is not None: | ||
4258 | 57 | fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n" | ||
4259 | 58 | formatter = logging.Formatter(fmt) | ||
4260 | 59 | debug_handler.setFormatter(formatter) | ||
4261 | 55 | logger.addHandler(debug_handler) | 60 | logger.addHandler(debug_handler) |
4262 | 56 | 61 | ||
4263 | 57 | return logger | 62 | return logger |
4264 | 63 | |||
4265 | 64 | |||
4266 | 65 | def log_call(log_func): | ||
4267 | 66 | """Decorator to add log info using 'log_func'.""" | ||
4268 | 67 | |||
4269 | 68 | def middle(f): | ||
4270 | 69 | """Add logging when calling 'f'.""" | ||
4271 | 70 | |||
4272 | 71 | @wraps(f) | ||
4273 | 72 | def inner(*args, **kwargs): | ||
4274 | 73 | """Call f(*args, **kwargs).""" | ||
4275 | 74 | log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs) | ||
4276 | 75 | res = f(*args, **kwargs) | ||
4277 | 76 | return res | ||
4278 | 77 | |||
4279 | 78 | return inner | ||
4280 | 79 | |||
4281 | 80 | return middle | ||
4282 | 58 | 81 | ||
4283 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' | |||
4284 | --- ubuntuone/controlpanel/tests/test_backend.py 2010-12-06 12:27:11 +0000 | |||
4285 | +++ ubuntuone/controlpanel/tests/test_backend.py 2010-12-22 14:37:52 +0000 | |||
4286 | @@ -23,10 +23,21 @@ | |||
4287 | 23 | 23 | ||
4288 | 24 | from twisted.internet import defer | 24 | from twisted.internet import defer |
4289 | 25 | from twisted.internet.defer import inlineCallbacks | 25 | from twisted.internet.defer import inlineCallbacks |
4290 | 26 | from ubuntuone.devtools.handlers import MementoHandler | ||
4291 | 26 | 27 | ||
4292 | 27 | from ubuntuone.controlpanel import backend | 28 | from ubuntuone.controlpanel import backend |
4295 | 28 | from ubuntuone.controlpanel.backend import (ACCOUNT_API, QUOTA_API, | 29 | from ubuntuone.controlpanel.backend import (ACCOUNT_API, |
4296 | 29 | DEVICES_API, DEVICE_REMOVE_API) | 30 | DEVICES_API, DEVICE_REMOVE_API, QUOTA_API, |
4297 | 31 | FILE_SYNC_DISABLED, | ||
4298 | 32 | FILE_SYNC_DISCONNECTED, | ||
4299 | 33 | FILE_SYNC_ERROR, | ||
4300 | 34 | FILE_SYNC_IDLE, | ||
4301 | 35 | FILE_SYNC_STARTING, | ||
4302 | 36 | FILE_SYNC_SYNCING, | ||
4303 | 37 | FILE_SYNC_UNKNOWN, | ||
4304 | 38 | MSG_KEY, STATUS_KEY, | ||
4305 | 39 | ) | ||
4306 | 40 | |||
4307 | 30 | from ubuntuone.controlpanel.tests import TestCase | 41 | from ubuntuone.controlpanel.tests import TestCase |
4308 | 31 | from ubuntuone.controlpanel.webclient import WebClientError | 42 | from ubuntuone.controlpanel.webclient import WebClientError |
4309 | 32 | 43 | ||
4310 | @@ -44,7 +55,7 @@ | |||
4311 | 44 | "dbpath": "u/abc/def/12345" | 55 | "dbpath": "u/abc/def/12345" |
4312 | 45 | }, | 56 | }, |
4313 | 46 | "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345", | 57 | "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345", |
4315 | 47 | "email": "andrewpz@protocultura.net", | 58 | "email": "andrewpz@protocultura.net",%s |
4316 | 48 | "nickname": "Andrew P. Zoilo", | 59 | "nickname": "Andrew P. Zoilo", |
4317 | 49 | "id": 12345, | 60 | "id": 12345, |
4318 | 50 | "subscription": { | 61 | "subscription": { |
4319 | @@ -63,6 +74,13 @@ | |||
4320 | 63 | } | 74 | } |
4321 | 64 | """ | 75 | """ |
4322 | 65 | 76 | ||
4323 | 77 | CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)" | ||
4324 | 78 | SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN | ||
4325 | 79 | |||
4326 | 80 | SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % '' | ||
4327 | 81 | SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN | ||
4328 | 82 | |||
4329 | 83 | |||
4330 | 66 | SAMPLE_QUOTA_JSON = """ | 84 | SAMPLE_QUOTA_JSON = """ |
4331 | 67 | { | 85 | { |
4332 | 68 | "total": 53687091200, | 86 | "total": 53687091200, |
4333 | @@ -78,6 +96,14 @@ | |||
4334 | 78 | "email": "andrewpz@protocultura.net", | 96 | "email": "andrewpz@protocultura.net", |
4335 | 79 | } | 97 | } |
4336 | 80 | 98 | ||
4337 | 99 | EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = { | ||
4338 | 100 | "quota_used": "2350345156", | ||
4339 | 101 | "quota_total": "53687091200", | ||
4340 | 102 | "type": CURRENT_PLAN, | ||
4341 | 103 | "name": "Andrew P. Zoilo", | ||
4342 | 104 | "email": "andrewpz@protocultura.net", | ||
4343 | 105 | } | ||
4344 | 106 | |||
4345 | 81 | SAMPLE_DEVICES_JSON = """ | 107 | SAMPLE_DEVICES_JSON = """ |
4346 | 82 | [ | 108 | [ |
4347 | 83 | { | 109 | { |
4348 | @@ -103,12 +129,14 @@ | |||
4349 | 103 | "device_id": "ComputerABCDEF01234token", | 129 | "device_id": "ComputerABCDEF01234token", |
4350 | 104 | "name": "Ubuntu One @ darkstar", | 130 | "name": "Ubuntu One @ darkstar", |
4351 | 105 | "type": "Computer", | 131 | "type": "Computer", |
4353 | 106 | "configurable": "0", | 132 | "is_local": '', |
4354 | 133 | "configurable": '', | ||
4355 | 107 | }, | 134 | }, |
4356 | 108 | { | 135 | { |
4358 | 109 | 'configurable': '1', | 136 | 'is_local': 'True', |
4359 | 137 | 'configurable': 'True', | ||
4360 | 110 | 'device_id': 'ComputerABC1234DEF', | 138 | 'device_id': 'ComputerABC1234DEF', |
4362 | 111 | 'limit_bandwidth': "0", | 139 | 'limit_bandwidth': '', |
4363 | 112 | 'max_download_speed': '-1', | 140 | 'max_download_speed': '-1', |
4364 | 113 | 'max_upload_speed': '-1', | 141 | 'max_upload_speed': '-1', |
4365 | 114 | 'name': 'Ubuntu One @ localhost', | 142 | 'name': 'Ubuntu One @ localhost', |
4366 | @@ -118,13 +146,49 @@ | |||
4367 | 118 | "device_id": "Phone1000", | 146 | "device_id": "Phone1000", |
4368 | 119 | "name": "Nokia E65", | 147 | "name": "Nokia E65", |
4369 | 120 | "type": "Phone", | 148 | "type": "Phone", |
4371 | 121 | "configurable": "0", | 149 | "configurable": '', |
4372 | 150 | "is_local": '', | ||
4373 | 122 | }, | 151 | }, |
4374 | 123 | ] | 152 | ] |
4375 | 124 | 153 | ||
4379 | 125 | SAMPLE_VOLUMES = [ | 154 | SAMPLE_FOLDERS = [ |
4380 | 126 | {'volume_id': 'test1', 'path': '~/test1', 'subscribed': False}, | 155 | {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba', |
4381 | 127 | {'volume_id': 'test2', 'path': '~/test2', 'subscribed': True}, | 156 | u'path': u'/home/tester/Public', u'subscribed': u'True', |
4382 | 157 | u'suggested_path': u'~/Public', | ||
4383 | 158 | u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'}, | ||
4384 | 159 | {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a', | ||
4385 | 160 | u'path': u'/home/tester/Documents', u'subscribed': u'', | ||
4386 | 161 | u'suggested_path': u'~/Documents', | ||
4387 | 162 | u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'}, | ||
4388 | 163 | {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48', | ||
4389 | 164 | u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True', | ||
4390 | 165 | u'suggested_path': u'~/Pictures/Photos', | ||
4391 | 166 | u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'}, | ||
4392 | 167 | ] | ||
4393 | 168 | |||
4394 | 169 | SAMPLE_SHARES = [ | ||
4395 | 170 | {u'accepted': u'True', u'access_level': u'View', | ||
4396 | 171 | u'free_bytes': u'39892622746', u'generation': u'2704', | ||
4397 | 172 | u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795', | ||
4398 | 173 | u'other_username': u'otheruser', u'other_visible_name': u'Other User', | ||
4399 | 174 | u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User', | ||
4400 | 175 | u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'}, | ||
4401 | 176 | {u'accepted': u'True', u'access_level': u'Modify', | ||
4402 | 177 | u'free_bytes': u'39892622746', u'generation': u'2704', | ||
4403 | 178 | u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf', | ||
4404 | 179 | u'other_username': u'otheruser', u'other_visible_name': u'Other User', | ||
4405 | 180 | u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User', | ||
4406 | 181 | u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'}, | ||
4407 | 182 | ] | ||
4408 | 183 | |||
4409 | 184 | SAMPLE_SHARED = [ | ||
4410 | 185 | {u'accepted': u'True', u'access_level': u'View', | ||
4411 | 186 | u'free_bytes': u'', u'generation': u'', | ||
4412 | 187 | u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225', | ||
4413 | 188 | u'other_username': u'otheruser', u'other_visible_name': u'Other User', | ||
4414 | 189 | u'path': u'/home/tester/Ubuntu One/bar', | ||
4415 | 190 | u'type': u'Shared', | ||
4416 | 191 | u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'}, | ||
4417 | 128 | ] | 192 | ] |
4418 | 129 | 193 | ||
4419 | 130 | 194 | ||
4420 | @@ -152,11 +216,24 @@ | |||
4421 | 152 | creds = SAMPLE_CREDENTIALS | 216 | creds = SAMPLE_CREDENTIALS |
4422 | 153 | throttling = False | 217 | throttling = False |
4423 | 154 | limits = {"download": -1, "upload": -1} | 218 | limits = {"download": -1, "upload": -1} |
4424 | 219 | file_sync = True | ||
4425 | 220 | status = { | ||
4426 | 221 | 'name': 'TEST', 'queues': 'GORGEOUS', 'connection': '', | ||
4427 | 222 | 'description': 'Some test state, nothing else.', | ||
4428 | 223 | 'is_error': '', 'is_connected': 'True', 'is_online': '', | ||
4429 | 224 | } | ||
4430 | 225 | status_changed_handler = None | ||
4431 | 226 | subscribed_folders = [] | ||
4432 | 155 | 227 | ||
4433 | 156 | def get_credentials(self): | 228 | def get_credentials(self): |
4434 | 157 | """Return the mock credentials.""" | 229 | """Return the mock credentials.""" |
4435 | 158 | return defer.succeed(self.creds) | 230 | return defer.succeed(self.creds) |
4436 | 159 | 231 | ||
4437 | 232 | def clear_credentials(self): | ||
4438 | 233 | """Clear the mock credentials.""" | ||
4439 | 234 | MockDBusClient.creds = None | ||
4440 | 235 | return defer.succeed(None) | ||
4441 | 236 | |||
4442 | 160 | def get_throttling_limits(self): | 237 | def get_throttling_limits(self): |
4443 | 161 | """Return the sample speed limits.""" | 238 | """Return the sample speed limits.""" |
4444 | 162 | return self.limits | 239 | return self.limits |
4445 | @@ -178,9 +255,41 @@ | |||
4446 | 178 | """Disable bw throttling.""" | 255 | """Disable bw throttling.""" |
4447 | 179 | self.throttling = False | 256 | self.throttling = False |
4448 | 180 | 257 | ||
4452 | 181 | def get_volumes(self): | 258 | def files_sync_enabled(self): |
4453 | 182 | """Grab list of folders and shares.""" | 259 | """Get if file sync service is enabled.""" |
4454 | 183 | return SAMPLE_VOLUMES | 260 | return self.file_sync |
4455 | 261 | |||
4456 | 262 | def set_files_sync_enabled(self, enabled): | ||
4457 | 263 | """Set the file sync service to be 'enabled'.""" | ||
4458 | 264 | self.file_sync = enabled | ||
4459 | 265 | |||
4460 | 266 | def get_folders(self): | ||
4461 | 267 | """Grab list of folders.""" | ||
4462 | 268 | return SAMPLE_FOLDERS | ||
4463 | 269 | |||
4464 | 270 | def subscribe_folder(self, volume_id): | ||
4465 | 271 | """Subcribe to 'volume_id'.""" | ||
4466 | 272 | self.subscribed_folders.append(volume_id) | ||
4467 | 273 | |||
4468 | 274 | def unsubscribe_folder(self, volume_id): | ||
4469 | 275 | """Unsubcribe from 'volume_id'.""" | ||
4470 | 276 | self.subscribed_folders.remove(volume_id) | ||
4471 | 277 | |||
4472 | 278 | def get_current_status(self): | ||
4473 | 279 | """Grab syncdaemon status.""" | ||
4474 | 280 | return self.status | ||
4475 | 281 | |||
4476 | 282 | def set_status_changed_handler(self, handler): | ||
4477 | 283 | """Connect a handler for tracking syncdaemon status changes.""" | ||
4478 | 284 | self.status_changed_handler = handler | ||
4479 | 285 | |||
4480 | 286 | def get_shares(self): | ||
4481 | 287 | """Grab list of shares (shares from others to the user).""" | ||
4482 | 288 | return SAMPLE_SHARES | ||
4483 | 289 | |||
4484 | 290 | def get_shared(self): | ||
4485 | 291 | """Grab list of shared (shares from the user to others).""" | ||
4486 | 292 | return SAMPLE_SHARED | ||
4487 | 184 | 293 | ||
4488 | 185 | 294 | ||
4489 | 186 | class BackendBasicTestCase(TestCase): | 295 | class BackendBasicTestCase(TestCase): |
4490 | @@ -189,11 +298,17 @@ | |||
4491 | 189 | timeout = 3 | 298 | timeout = 3 |
4492 | 190 | 299 | ||
4493 | 191 | def setUp(self): | 300 | def setUp(self): |
4494 | 301 | super(BackendBasicTestCase, self).setUp() | ||
4495 | 192 | self.patch(backend, "WebClient", MockWebClient) | 302 | self.patch(backend, "WebClient", MockWebClient) |
4496 | 193 | self.patch(backend, "dbus_client", MockDBusClient()) | 303 | self.patch(backend, "dbus_client", MockDBusClient()) |
4497 | 194 | self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"] | 304 | self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"] |
4498 | 195 | self.be = backend.ControlBackend() | 305 | self.be = backend.ControlBackend() |
4499 | 196 | 306 | ||
4500 | 307 | self.memento = MementoHandler() | ||
4501 | 308 | backend.logger.addHandler(self.memento) | ||
4502 | 309 | |||
4503 | 310 | MockDBusClient.creds = SAMPLE_CREDENTIALS | ||
4504 | 311 | |||
4505 | 197 | def test_backend_creation(self): | 312 | def test_backend_creation(self): |
4506 | 198 | """The backend instance is successfully created.""" | 313 | """The backend instance is successfully created.""" |
4507 | 199 | self.assertEqual(self.be.wc.__class__, MockWebClient) | 314 | self.assertEqual(self.be.wc.__class__, MockWebClient) |
4508 | @@ -204,6 +319,16 @@ | |||
4509 | 204 | token = yield self.be.get_token() | 319 | token = yield self.be.get_token() |
4510 | 205 | self.assertEqual(token, SAMPLE_CREDENTIALS["token"]) | 320 | self.assertEqual(token, SAMPLE_CREDENTIALS["token"]) |
4511 | 206 | 321 | ||
4512 | 322 | @inlineCallbacks | ||
4513 | 323 | def test_device_is_local(self): | ||
4514 | 324 | """The device_is_local returns the right result.""" | ||
4515 | 325 | result = yield self.be.device_is_local(self.local_token) | ||
4516 | 326 | self.assertTrue(result) | ||
4517 | 327 | |||
4518 | 328 | did = EXPECTED_DEVICES_INFO[0]['device_id'] | ||
4519 | 329 | result = yield self.be.device_is_local(did) | ||
4520 | 330 | self.assertFalse(result) | ||
4521 | 331 | |||
4522 | 207 | 332 | ||
4523 | 208 | class BackendAccountTestCase(BackendBasicTestCase): | 333 | class BackendAccountTestCase(BackendBasicTestCase): |
4524 | 209 | """Account tests for the backend.""" | 334 | """Account tests for the backend.""" |
4525 | @@ -212,12 +337,21 @@ | |||
4526 | 212 | def test_account_info(self): | 337 | def test_account_info(self): |
4527 | 213 | """The account_info method exercises its callback.""" | 338 | """The account_info method exercises its callback.""" |
4528 | 214 | # pylint: disable=E1101 | 339 | # pylint: disable=E1101 |
4530 | 215 | self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_JSON | 340 | self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN |
4531 | 216 | self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON | 341 | self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON |
4532 | 217 | result = yield self.be.account_info() | 342 | result = yield self.be.account_info() |
4533 | 218 | self.assertEqual(result, EXPECTED_ACCOUNT_INFO) | 343 | self.assertEqual(result, EXPECTED_ACCOUNT_INFO) |
4534 | 219 | 344 | ||
4535 | 220 | @inlineCallbacks | 345 | @inlineCallbacks |
4536 | 346 | def test_account_info_with_current_plan(self): | ||
4537 | 347 | """The account_info method exercises its callback.""" | ||
4538 | 348 | # pylint: disable=E1101 | ||
4539 | 349 | self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_WITH_CURRENT_PLAN | ||
4540 | 350 | self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON | ||
4541 | 351 | result = yield self.be.account_info() | ||
4542 | 352 | self.assertEqual(result, EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN) | ||
4543 | 353 | |||
4544 | 354 | @inlineCallbacks | ||
4545 | 221 | def test_account_info_fails(self): | 355 | def test_account_info_fails(self): |
4546 | 222 | """The account_info method exercises its errback.""" | 356 | """The account_info method exercises its errback.""" |
4547 | 223 | # pylint: disable=E1101 | 357 | # pylint: disable=E1101 |
4548 | @@ -248,11 +382,23 @@ | |||
4549 | 248 | """The remove_device method calls the right api.""" | 382 | """The remove_device method calls the right api.""" |
4550 | 249 | dtype, did = "Computer", "SAMPLE-TOKEN" | 383 | dtype, did = "Computer", "SAMPLE-TOKEN" |
4551 | 250 | device_id = dtype + did | 384 | device_id = dtype + did |
4553 | 251 | apiurl = DEVICE_REMOVE_API % (dtype, did) | 385 | apiurl = DEVICE_REMOVE_API % (dtype.lower(), did) |
4554 | 252 | # pylint: disable=E1101 | 386 | # pylint: disable=E1101 |
4555 | 253 | self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON | 387 | self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON |
4556 | 254 | result = yield self.be.remove_device(device_id) | 388 | result = yield self.be.remove_device(device_id) |
4557 | 255 | self.assertEqual(result, device_id) | 389 | self.assertEqual(result, device_id) |
4558 | 390 | # credentials were not cleared | ||
4559 | 391 | self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS) | ||
4560 | 392 | |||
4561 | 393 | @inlineCallbacks | ||
4562 | 394 | def test_remove_device_clear_credentials_if_local_device(self): | ||
4563 | 395 | """The remove_device method clears the credentials if is local.""" | ||
4564 | 396 | apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token']) | ||
4565 | 397 | # pylint: disable=E1101 | ||
4566 | 398 | self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON | ||
4567 | 399 | yield self.be.remove_device(self.local_token) | ||
4568 | 400 | # credentials were cleared | ||
4569 | 401 | self.assertEqual(MockDBusClient.creds, None) | ||
4570 | 256 | 402 | ||
4571 | 257 | @inlineCallbacks | 403 | @inlineCallbacks |
4572 | 258 | def test_remove_device_fails(self): | 404 | def test_remove_device_fails(self): |
4573 | @@ -266,10 +412,10 @@ | |||
4574 | 266 | """The device settings are updated.""" | 412 | """The device settings are updated.""" |
4575 | 267 | backend.dbus_client.throttling = False | 413 | backend.dbus_client.throttling = False |
4576 | 268 | yield self.be.change_device_settings(self.local_token, | 414 | yield self.be.change_device_settings(self.local_token, |
4578 | 269 | {"limit_bandwidth": "1"}) | 415 | {"limit_bandwidth": 'True'}) |
4579 | 270 | self.assertEqual(backend.dbus_client.throttling, True) | 416 | self.assertEqual(backend.dbus_client.throttling, True) |
4580 | 271 | yield self.be.change_device_settings(self.local_token, | 417 | yield self.be.change_device_settings(self.local_token, |
4582 | 272 | {"limit_bandwidth": "0"}) | 418 | {"limit_bandwidth": ''}) |
4583 | 273 | self.assertEqual(backend.dbus_client.throttling, False) | 419 | self.assertEqual(backend.dbus_client.throttling, False) |
4584 | 274 | 420 | ||
4585 | 275 | @inlineCallbacks | 421 | @inlineCallbacks |
4586 | @@ -298,7 +444,7 @@ | |||
4587 | 298 | new_settings = { | 444 | new_settings = { |
4588 | 299 | "max_download_speed": "99", | 445 | "max_download_speed": "99", |
4589 | 300 | "max_upload_speed": "99", | 446 | "max_upload_speed": "99", |
4591 | 301 | "limit_bandwidth": "1", | 447 | "limit_bandwidth": 'True', |
4592 | 302 | } | 448 | } |
4593 | 303 | yield self.be.change_device_settings("wrong token!", new_settings) | 449 | yield self.be.change_device_settings("wrong token!", new_settings) |
4594 | 304 | self.assertEqual(backend.dbus_client.throttling, False) | 450 | self.assertEqual(backend.dbus_client.throttling, False) |
4595 | @@ -313,4 +459,197 @@ | |||
4596 | 313 | def test_volumes_info(self): | 459 | def test_volumes_info(self): |
4597 | 314 | """The volumes_info method exercises its callback.""" | 460 | """The volumes_info method exercises its callback.""" |
4598 | 315 | result = yield self.be.volumes_info() | 461 | result = yield self.be.volumes_info() |
4600 | 316 | self.assertEqual(result, SAMPLE_VOLUMES) | 462 | # later, we should unify folders and shares info |
4601 | 463 | self.assertEqual(result, SAMPLE_FOLDERS) | ||
4602 | 464 | |||
4603 | 465 | @inlineCallbacks | ||
4604 | 466 | def test_subscribe_volume(self): | ||
4605 | 467 | """The subscribe_volume method subscribes a folder.""" | ||
4606 | 468 | fid = '0123-4567' | ||
4607 | 469 | yield self.be.subscribe_volume(volume_id=fid) | ||
4608 | 470 | self.addCleanup(lambda: MockDBusClient.subscribed_folders.remove(fid)) | ||
4609 | 471 | |||
4610 | 472 | self.assertEqual(MockDBusClient.subscribed_folders, [fid]) | ||
4611 | 473 | |||
4612 | 474 | @inlineCallbacks | ||
4613 | 475 | def test_unsubscribe_volume(self): | ||
4614 | 476 | """The unsubscribe_volume method unsubscribes a folder.""" | ||
4615 | 477 | fid = '0123-4567' | ||
4616 | 478 | yield self.be.subscribe_volume(volume_id=fid) | ||
4617 | 479 | self.assertEqual(MockDBusClient.subscribed_folders, [fid]) | ||
4618 | 480 | |||
4619 | 481 | yield self.be.unsubscribe_volume(volume_id=fid) | ||
4620 | 482 | |||
4621 | 483 | self.assertEqual(MockDBusClient.subscribed_folders, []) | ||
4622 | 484 | |||
4623 | 485 | @inlineCallbacks | ||
4624 | 486 | def test_change_volume_settings(self): | ||
4625 | 487 | """The volume settings can be changed.""" | ||
4626 | 488 | fid = '0123-4567' | ||
4627 | 489 | |||
4628 | 490 | yield self.be.change_volume_settings(fid, {'subscribed': True}) | ||
4629 | 491 | self.assertEqual(MockDBusClient.subscribed_folders, [fid]) | ||
4630 | 492 | |||
4631 | 493 | yield self.be.change_volume_settings(fid, {'subscribed': False}) | ||
4632 | 494 | self.assertEqual(MockDBusClient.subscribed_folders, []) | ||
4633 | 495 | |||
4634 | 496 | @inlineCallbacks | ||
4635 | 497 | def test_change_volume_settings_no_setting(self): | ||
4636 | 498 | """The change volume settings does not fail on empty settings.""" | ||
4637 | 499 | yield self.be.change_volume_settings('test', {}) | ||
4638 | 500 | self.assertEqual(MockDBusClient.subscribed_folders, []) | ||
4639 | 501 | |||
4640 | 502 | |||
4641 | 503 | class BackendSyncStatusTestCase(BackendBasicTestCase): | ||
4642 | 504 | """Syncdaemon state for the backend.""" | ||
4643 | 505 | |||
4644 | 506 | def _build_msg(self): | ||
4645 | 507 | """Build expected message regarding file sync status.""" | ||
4646 | 508 | return '%s (%s)' % (MockDBusClient.status['description'], | ||
4647 | 509 | MockDBusClient.status['name']) | ||
4648 | 510 | |||
4649 | 511 | @inlineCallbacks | ||
4650 | 512 | def assert_correct_status(self, status, msg=None): | ||
4651 | 513 | """Check that the resulting status is correct.""" | ||
4652 | 514 | expected = {MSG_KEY: self._build_msg() if msg is None else msg, | ||
4653 | 515 | STATUS_KEY: status} | ||
4654 | 516 | result = yield self.be.file_sync_status() | ||
4655 | 517 | self.assertEqual(expected, result) | ||
4656 | 518 | |||
4657 | 519 | @inlineCallbacks | ||
4658 | 520 | def test_disabled(self): | ||
4659 | 521 | """The syncdaemon status is processed and emitted.""" | ||
4660 | 522 | self.patch(MockDBusClient, 'file_sync', False) | ||
4661 | 523 | yield self.assert_correct_status(FILE_SYNC_DISABLED, msg='') | ||
4662 | 524 | |||
4663 | 525 | @inlineCallbacks | ||
4664 | 526 | def test_error(self): | ||
4665 | 527 | """The syncdaemon status is processed and emitted.""" | ||
4666 | 528 | MockDBusClient.status = { | ||
4667 | 529 | 'is_error': 'True', # nothing else matters | ||
4668 | 530 | 'is_online': '', 'is_connected': '', | ||
4669 | 531 | 'name': 'AUTH_FAILED', 'connection': '', 'queues': '', | ||
4670 | 532 | 'description': 'auth failed', | ||
4671 | 533 | } | ||
4672 | 534 | yield self.assert_correct_status(FILE_SYNC_ERROR) | ||
4673 | 535 | |||
4674 | 536 | @inlineCallbacks | ||
4675 | 537 | def test_starting_when_init_not_user(self): | ||
4676 | 538 | """The syncdaemon status is processed and emitted.""" | ||
4677 | 539 | MockDBusClient.status = { | ||
4678 | 540 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4679 | 541 | 'connection': 'Not User With Network', 'queues': '', | ||
4680 | 542 | 'name': 'INIT', 'description': 'something new', | ||
4681 | 543 | } | ||
4682 | 544 | yield self.assert_correct_status(FILE_SYNC_STARTING) | ||
4683 | 545 | |||
4684 | 546 | @inlineCallbacks | ||
4685 | 547 | def test_starting_when_init_with_user(self): | ||
4686 | 548 | """The syncdaemon status is processed and emitted.""" | ||
4687 | 549 | MockDBusClient.status = { | ||
4688 | 550 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4689 | 551 | 'connection': 'With User With Network', 'queues': '', | ||
4690 | 552 | 'name': 'INIT', 'description': 'something new', | ||
4691 | 553 | } | ||
4692 | 554 | yield self.assert_correct_status(FILE_SYNC_STARTING) | ||
4693 | 555 | |||
4694 | 556 | @inlineCallbacks | ||
4695 | 557 | def test_starting_when_local_rescan_not_user(self): | ||
4696 | 558 | """The syncdaemon status is processed and emitted.""" | ||
4697 | 559 | MockDBusClient.status = { | ||
4698 | 560 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4699 | 561 | 'connection': 'Not User With Network', 'queues': '', | ||
4700 | 562 | 'name': 'LOCAL_RESCAN', 'description': 'something new', | ||
4701 | 563 | } | ||
4702 | 564 | yield self.assert_correct_status(FILE_SYNC_STARTING) | ||
4703 | 565 | |||
4704 | 566 | @inlineCallbacks | ||
4705 | 567 | def test_starting_when_local_rescan_with_user(self): | ||
4706 | 568 | """The syncdaemon status is processed and emitted.""" | ||
4707 | 569 | MockDBusClient.status = { | ||
4708 | 570 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4709 | 571 | 'connection': 'With User With Network', 'queues': '', | ||
4710 | 572 | 'name': 'LOCAL_RESCAN', 'description': 'something new', | ||
4711 | 573 | } | ||
4712 | 574 | yield self.assert_correct_status(FILE_SYNC_STARTING) | ||
4713 | 575 | |||
4714 | 576 | @inlineCallbacks | ||
4715 | 577 | def test_starting_when_ready_with_user(self): | ||
4716 | 578 | """The syncdaemon status is processed and emitted.""" | ||
4717 | 579 | MockDBusClient.status = { | ||
4718 | 580 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4719 | 581 | 'connection': 'With User With Network', 'queues': '', | ||
4720 | 582 | 'name': 'READY', 'description': 'something nicer', | ||
4721 | 583 | } | ||
4722 | 584 | yield self.assert_correct_status(FILE_SYNC_STARTING) | ||
4723 | 585 | |||
4724 | 586 | @inlineCallbacks | ||
4725 | 587 | def test_disconnected(self): | ||
4726 | 588 | """The syncdaemon status is processed and emitted.""" | ||
4727 | 589 | MockDBusClient.status = { | ||
4728 | 590 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4729 | 591 | 'queues': '', 'description': 'something new', | ||
4730 | 592 | 'connection': 'Not User With Network', # user didn't connect | ||
4731 | 593 | 'name': 'READY', # must be READY, otherwise is STARTING | ||
4732 | 594 | } | ||
4733 | 595 | yield self.assert_correct_status(FILE_SYNC_DISCONNECTED) | ||
4734 | 596 | |||
4735 | 597 | @inlineCallbacks | ||
4736 | 598 | def test_syncing_if_online(self): | ||
4737 | 599 | """The syncdaemon status is processed and emitted.""" | ||
4738 | 600 | MockDBusClient.status = { | ||
4739 | 601 | 'is_error': '', 'is_online': 'True', 'is_connected': 'True', | ||
4740 | 602 | 'name': 'QUEUE_MANAGER', 'connection': '', | ||
4741 | 603 | 'queues': 'WORKING_ON_CONTENT', # anything but IDLE | ||
4742 | 604 | 'description': 'something nicer', | ||
4743 | 605 | } | ||
4744 | 606 | yield self.assert_correct_status(FILE_SYNC_SYNCING) | ||
4745 | 607 | |||
4746 | 608 | @inlineCallbacks | ||
4747 | 609 | def test_syncing_even_if_not_online(self): | ||
4748 | 610 | """The syncdaemon status is processed and emitted.""" | ||
4749 | 611 | MockDBusClient.status = { | ||
4750 | 612 | 'is_error': '', 'is_online': '', 'is_connected': 'True', | ||
4751 | 613 | 'name': 'CHECK_VERSION', 'connection': '', | ||
4752 | 614 | 'queues': 'WORKING_ON_CONTENT', | ||
4753 | 615 | 'description': 'something nicer', | ||
4754 | 616 | } | ||
4755 | 617 | yield self.assert_correct_status(FILE_SYNC_SYNCING) | ||
4756 | 618 | |||
4757 | 619 | @inlineCallbacks | ||
4758 | 620 | def test_idle(self): | ||
4759 | 621 | """The syncdaemon status is processed and emitted.""" | ||
4760 | 622 | MockDBusClient.status = { | ||
4761 | 623 | 'is_error': '', 'is_online': 'True', 'is_connected': 'True', | ||
4762 | 624 | 'name': 'QUEUE_MANAGER', 'connection': '', | ||
4763 | 625 | 'queues': 'IDLE', | ||
4764 | 626 | 'description': 'something nice', | ||
4765 | 627 | } | ||
4766 | 628 | yield self.assert_correct_status(FILE_SYNC_IDLE) | ||
4767 | 629 | |||
4768 | 630 | @inlineCallbacks | ||
4769 | 631 | def test_unknown(self): | ||
4770 | 632 | """The syncdaemon status is processed and emitted.""" | ||
4771 | 633 | MockDBusClient.status = { | ||
4772 | 634 | 'is_error': '', 'is_online': '', 'is_connected': '', | ||
4773 | 635 | 'name': '', 'connection': '', 'queues': '', | ||
4774 | 636 | 'description': '', | ||
4775 | 637 | } | ||
4776 | 638 | yield self.assert_correct_status(FILE_SYNC_UNKNOWN) | ||
4777 | 639 | |||
4778 | 640 | has_warning = self.memento.check_warning('file_sync_status: unknown', | ||
4779 | 641 | repr(MockDBusClient.status)) | ||
4780 | 642 | self.assertTrue(has_warning) | ||
4781 | 643 | |||
4782 | 644 | def test_status_changed(self): | ||
4783 | 645 | """The file_sync_status is the status changed handler.""" | ||
4784 | 646 | self.be.status_changed_handler = self._set_called | ||
4785 | 647 | status = {'name': 'foo', 'description': 'bar', 'is_error': '', | ||
4786 | 648 | 'is_connected': '', 'is_online': '', 'queues': ''} | ||
4787 | 649 | # pylint: disable=E1101 | ||
4788 | 650 | backend.dbus_client.status_changed_handler(status) | ||
4789 | 651 | |||
4790 | 652 | # pylint: disable=W0212 | ||
4791 | 653 | # Access to a protected member _process_file_sync_status | ||
4792 | 654 | expected_status = self.be._process_file_sync_status(status) | ||
4793 | 655 | self.assertEqual(self._called, ((expected_status,), {})) | ||
4794 | 317 | 656 | ||
4795 | === modified file 'ubuntuone/controlpanel/utils.py' | |||
4796 | --- ubuntuone/controlpanel/utils.py 2010-12-06 12:27:11 +0000 | |||
4797 | +++ ubuntuone/controlpanel/utils.py 2010-12-22 14:37:52 +0000 | |||
4798 | @@ -20,8 +20,6 @@ | |||
4799 | 20 | 20 | ||
4800 | 21 | import os | 21 | import os |
4801 | 22 | 22 | ||
4802 | 23 | from functools import wraps | ||
4803 | 24 | |||
4804 | 25 | import dbus | 23 | import dbus |
4805 | 26 | 24 | ||
4806 | 27 | from ubuntuone.controlpanel.logger import setup_logging | 25 | from ubuntuone.controlpanel.logger import setup_logging |
4807 | @@ -68,24 +66,6 @@ | |||
4808 | 68 | return os.path.join(get_project_dir(), filename) | 66 | return os.path.join(get_project_dir(), filename) |
4809 | 69 | 67 | ||
4810 | 70 | 68 | ||
4811 | 71 | def log_call(log_func): | ||
4812 | 72 | """Decorator to add log info using 'log_func'.""" | ||
4813 | 73 | |||
4814 | 74 | def middle(f): | ||
4815 | 75 | """Add logging when calling 'f'.""" | ||
4816 | 76 | |||
4817 | 77 | @wraps(f) | ||
4818 | 78 | def inner(*args, **kwargs): | ||
4819 | 79 | """Call f(*args, **kwargs).""" | ||
4820 | 80 | log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs) | ||
4821 | 81 | res = f(*args, **kwargs) | ||
4822 | 82 | return res | ||
4823 | 83 | |||
4824 | 84 | return inner | ||
4825 | 85 | |||
4826 | 86 | return middle | ||
4827 | 87 | |||
4828 | 88 | |||
4829 | 89 | def is_dbus_no_reply(failure): | 69 | def is_dbus_no_reply(failure): |
4830 | 90 | """Decide if 'failure' is a DBus NoReply Error.""" | 70 | """Decide if 'failure' is a DBus NoReply Error.""" |
4831 | 91 | exc = failure.value | 71 | exc = failure.value |
4832 | 92 | 72 | ||
4833 | === modified file 'ubuntuone/controlpanel/webclient.py' | |||
4834 | --- ubuntuone/controlpanel/webclient.py 2010-12-06 12:27:11 +0000 | |||
4835 | +++ ubuntuone/controlpanel/webclient.py 2010-12-22 14:37:52 +0000 | |||
4836 | @@ -48,22 +48,24 @@ | |||
4837 | 48 | 48 | ||
4838 | 49 | def _handler(self, session, msg, d): | 49 | def _handler(self, session, msg, d): |
4839 | 50 | """Handle the result of an http message.""" | 50 | """Handle the result of an http message.""" |
4841 | 51 | logger.debug("WebClient: got http response: %d", msg.status_code) | 51 | logger.debug("WebClient: got http response %d for uri %r", |
4842 | 52 | msg.status_code, msg.get_uri().to_string(False)) | ||
4843 | 53 | data = msg.response_body.data | ||
4844 | 52 | if msg.status_code == 200: | 54 | if msg.status_code == 200: |
4846 | 53 | result = simplejson.loads(msg.response_body.data) | 55 | result = simplejson.loads(data) |
4847 | 54 | d.callback(result) | 56 | d.callback(result) |
4848 | 55 | else: | 57 | else: |
4850 | 56 | e = WebClientError(msg.status_code, msg) | 58 | e = WebClientError(msg.status_code, data) |
4851 | 57 | d.errback(e) | 59 | d.errback(e) |
4852 | 58 | 60 | ||
4853 | 59 | def _call_api_with_creds(self, credentials, api_name): | 61 | def _call_api_with_creds(self, credentials, api_name): |
4854 | 60 | """Get a given url from the webservice with credentials.""" | 62 | """Get a given url from the webservice with credentials.""" |
4856 | 61 | url = self.base_url + api_name | 63 | url = (self.base_url + api_name).encode('utf-8') |
4857 | 62 | method = "GET" | 64 | method = "GET" |
4858 | 65 | logger.debug("WebClient: getting url: %s, %s", method, url) | ||
4859 | 63 | msg = Soup.Message.new(method, url) | 66 | msg = Soup.Message.new(method, url) |
4860 | 64 | add_oauth_headers(msg.request_headers.append, method, url, credentials) | 67 | add_oauth_headers(msg.request_headers.append, method, url, credentials) |
4861 | 65 | d = defer.Deferred() | 68 | d = defer.Deferred() |
4862 | 66 | logger.debug("WebClient: getting url: %s", url) | ||
4863 | 67 | self.session.queue_message(msg, self._handler, d) | 69 | self.session.queue_message(msg, self._handler, d) |
4864 | 68 | return d | 70 | return d |
4865 | 69 | 71 |
Pushed, with slight debian/changelog typo corrections.