Merge lp:~dobey/ubuntu/natty/ubuntuone-control-panel/release-0-8-3 into lp:ubuntu/natty/ubuntuone-control-panel

Proposed by dobey
Status: Merged
Merged at revision: 12
Proposed branch: lp:~dobey/ubuntu/natty/ubuntuone-control-panel/release-0-8-3
Merge into: lp:ubuntu/natty/ubuntuone-control-panel
Diff against target: 2036 lines (+930/-264)
19 files modified
PKG-INFO (+23/-1)
data/device.ui (+1/-1)
data/management.ui (+3/-29)
data/overview.ui (+235/-68)
data/volumes.ui (+22/-19)
debian/changelog (+17/-0)
debian/python-ubuntuone-control-panel.install (+4/-1)
debian/ubuntuone-control-panel-gtk.install (+2/-1)
setup.py (+2/-1)
ubuntuone-control-panel-gtk.desktop.in (+1/-0)
ubuntuone.controlpanel.pth (+2/-0)
ubuntuone/controlpanel/backend.py (+56/-8)
ubuntuone/controlpanel/dbus_client.py (+43/-2)
ubuntuone/controlpanel/gtk/gui.py (+60/-61)
ubuntuone/controlpanel/gtk/tests/__init__.py (+2/-2)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+47/-41)
ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py (+184/-4)
ubuntuone/controlpanel/tests/__init__.py (+79/-3)
ubuntuone/controlpanel/tests/test_backend.py (+147/-22)
To merge this branch: bzr merge lp:~dobey/ubuntu/natty/ubuntuone-control-panel/release-0-8-3
Reviewer Review Type Date Requested Status
Ken VanDine Approve
Review via email: mp+49491@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2011-02-04 20:02:28 +0000
3+++ PKG-INFO 2011-02-12 03:22:55 +0000
4@@ -1,6 +1,6 @@
5 Metadata-Version: 1.1
6 Name: ubuntuone-control-panel
7-Version: 0.8.2
8+Version: 0.8.3
9 Summary: Ubuntu One Control Panel
10 Home-page: https://launchpad.net/ubuntuone-control-panel
11 Author: Natalia Bidart
12@@ -8,4 +8,26 @@
13 License: GPL v3
14 Description: Application to manage a Ubuntu One account. Provides aDBus service to query/modify all the Ubuntu One bits.
15 Platform: UNKNOWN
16+Requires: apt
17+Requires: aptdaemon
18+Requires: dbus
19+Requires: defer
20+Requires: desktopcouch.application.replication_services
21+Requires: gi.repository
22+Requires: gobject
23+Requires: gtk
24+Requires: mocker
25+Requires: oauth
26+Requires: pango
27+Requires: simplejson
28+Requires: twisted.application
29+Requires: twisted.internet
30+Requires: twisted.python.failure
31+Requires: twisted.web
32+Requires: ubuntu_sso
33+Requires: ubuntuone.clientdefs
34+Requires: ubuntuone.devtools.handlers
35+Requires: ubuntuone.devtools.testcase
36+Requires: ubuntuone.logger
37+Requires: ubuntuone.platform.linux
38 Provides: ubuntuone.controlpanel
39
40=== added file 'data/contacts.png'
41Binary files data/contacts.png 1970-01-01 00:00:00 +0000 and data/contacts.png 2011-02-12 03:22:55 +0000 differ
42=== modified file 'data/device.ui'
43--- data/device.ui 2011-01-25 19:08:59 +0000
44+++ data/device.ui 2011-02-12 03:22:55 +0000
45@@ -154,7 +154,7 @@
46 <child>
47 <object class="GtkVButtonBox" id="vbuttonbox1">
48 <property name="visible">True</property>
49- <property name="layout_style">center</property>
50+ <property name="layout_style">start</property>
51 <child>
52 <object class="GtkButton" id="remove">
53 <property name="label">gtk-remove</property>
54
55=== added file 'data/files.png'
56Binary files data/files.png 1970-01-01 00:00:00 +0000 and data/files.png 2011-02-12 03:22:55 +0000 differ
57=== modified file 'data/management.ui'
58--- data/management.ui 2011-01-25 19:08:59 +0000
59+++ data/management.ui 2011-02-12 03:22:55 +0000
60@@ -20,25 +20,14 @@
61 <property name="border_width">10</property>
62 <property name="spacing">10</property>
63 <child>
64- <object class="GtkHBox" id="quota_box">
65+ <object class="GtkVBox" id="quota_box">
66 <property name="visible">True</property>
67- <property name="can_focus">False</property>
68+ <property name="spacing">5</property>
69 <child>
70- <object class="GtkAlignment" id="alignment1">
71+ <object class="GtkProgressBar" id="quota_progressbar">
72 <property name="visible">True</property>
73- <property name="can_focus">False</property>
74- <property name="xscale">0</property>
75- <property name="yscale">0</property>
76- <child>
77- <object class="GtkProgressBar" id="quota_progressbar">
78- <property name="visible">True</property>
79- <property name="can_focus">False</property>
80- </object>
81- </child>
82 </object>
83 <packing>
84- <property name="expand">True</property>
85- <property name="fill">True</property>
86 <property name="position">0</property>
87 </packing>
88 </child>
89@@ -117,7 +106,6 @@
90 <property name="label" translatable="yes">Shares</property>
91 <property name="can_focus">True</property>
92 <property name="receives_default">False</property>
93- <property name="use_action_appearance">False</property>
94 <property name="draw_indicator">False</property>
95 <property name="group">dashboard_button</property>
96 </object>
97@@ -133,7 +121,6 @@
98 <property name="visible">True</property>
99 <property name="can_focus">True</property>
100 <property name="receives_default">False</property>
101- <property name="use_action_appearance">False</property>
102 <property name="draw_indicator">False</property>
103 <property name="group">dashboard_button</property>
104 </object>
105@@ -148,7 +135,6 @@
106 <property name="label" translatable="yes">Services</property>
107 <property name="can_focus">True</property>
108 <property name="receives_default">False</property>
109- <property name="use_action_appearance">False</property>
110 <property name="draw_indicator">False</property>
111 <property name="group">dashboard_button</property>
112 </object>
113@@ -161,44 +147,36 @@
114 </object>
115 <packing>
116 <property name="expand">False</property>
117- <property name="fill">True</property>
118 <property name="position">1</property>
119 </packing>
120 </child>
121 <child>
122 <object class="GtkHSeparator" id="hseparator2">
123 <property name="visible">True</property>
124- <property name="can_focus">False</property>
125 </object>
126 <packing>
127- <property name="expand">True</property>
128- <property name="fill">True</property>
129 <property name="position">2</property>
130 </packing>
131 </child>
132 </object>
133 <packing>
134 <property name="expand">False</property>
135- <property name="fill">True</property>
136 <property name="position">0</property>
137 </packing>
138 </child>
139 <child>
140 <object class="GtkImage" id="image1">
141 <property name="visible">True</property>
142- <property name="can_focus">False</property>
143 <property name="pixbuf">banner.png</property>
144 </object>
145 <packing>
146 <property name="expand">False</property>
147- <property name="fill">True</property>
148 <property name="position">1</property>
149 </packing>
150 </child>
151 </object>
152 <packing>
153 <property name="expand">False</property>
154- <property name="fill">True</property>
155 <property name="position">1</property>
156 </packing>
157 </child>
158@@ -207,21 +185,17 @@
159 </object>
160 <packing>
161 <property name="expand">False</property>
162- <property name="fill">True</property>
163 <property name="position">0</property>
164 </packing>
165 </child>
166 <child>
167 <object class="GtkNotebook" id="notebook">
168 <property name="visible">True</property>
169- <property name="can_focus">False</property>
170 <property name="show_tabs">False</property>
171 <property name="show_border">False</property>
172 <property name="homogeneous">True</property>
173 </object>
174 <packing>
175- <property name="expand">True</property>
176- <property name="fill">True</property>
177 <property name="position">1</property>
178 </packing>
179 </child>
180
181=== added file 'data/music.png'
182Binary files data/music.png 1970-01-01 00:00:00 +0000 and data/music.png 2011-02-12 03:22:55 +0000 differ
183=== added file 'data/notes.png'
184Binary files data/notes.png 1970-01-01 00:00:00 +0000 and data/notes.png 2011-02-12 03:22:55 +0000 differ
185=== modified file 'data/overview.ui'
186--- data/overview.ui 2011-01-25 19:08:59 +0000
187+++ data/overview.ui 2011-02-12 03:22:55 +0000
188@@ -4,74 +4,263 @@
189 <!-- interface-naming-policy project-wide -->
190 <object class="GtkVBox" id="itself">
191 <property name="visible">True</property>
192+ <property name="can_focus">False</property>
193 <child>
194- <object class="GtkImage" id="image">
195+ <object class="GtkEventBox" id="header">
196 <property name="visible">True</property>
197- <property name="pixbuf">overview.png</property>
198+ <property name="can_focus">False</property>
199+ <child>
200+ <object class="GtkImage" id="image">
201+ <property name="visible">True</property>
202+ <property name="can_focus">False</property>
203+ <property name="pixbuf">overview.png</property>
204+ </object>
205+ </child>
206 </object>
207 <packing>
208 <property name="expand">False</property>
209+ <property name="fill">True</property>
210 <property name="position">0</property>
211 </packing>
212 </child>
213 <child>
214- <object class="GtkAlignment" id="alignment1">
215+ <object class="GtkScrolledWindow" id="scrolledwindow1">
216 <property name="visible">True</property>
217- <property name="yscale">0</property>
218+ <property name="can_focus">True</property>
219+ <property name="hscrollbar_policy">automatic</property>
220+ <property name="vscrollbar_policy">automatic</property>
221 <child>
222- <object class="GtkHBox" id="hbox1">
223+ <object class="GtkViewport" id="viewport1">
224 <property name="visible">True</property>
225- <property name="border_width">20</property>
226- <property name="spacing">5</property>
227+ <property name="can_focus">False</property>
228+ <property name="shadow_type">none</property>
229 <child>
230- <object class="GtkVBox" id="messages">
231+ <object class="GtkHBox" id="hbox1">
232 <property name="visible">True</property>
233- <property name="spacing">10</property>
234+ <property name="can_focus">False</property>
235+ <property name="border_width">20</property>
236+ <property name="spacing">5</property>
237 <child>
238- <placeholder/>
239+ <object class="GtkTable" id="table1">
240+ <property name="visible">True</property>
241+ <property name="can_focus">False</property>
242+ <property name="n_rows">4</property>
243+ <property name="n_columns">2</property>
244+ <property name="column_spacing">10</property>
245+ <property name="row_spacing">10</property>
246+ <child>
247+ <object class="GtkImage" id="image1">
248+ <property name="visible">True</property>
249+ <property name="can_focus">False</property>
250+ <property name="pixbuf">files.png</property>
251+ </object>
252+ <packing>
253+ <property name="x_options">GTK_FILL</property>
254+ <property name="y_options">GTK_FILL</property>
255+ </packing>
256+ </child>
257+ <child>
258+ <object class="GtkImage" id="image2">
259+ <property name="visible">True</property>
260+ <property name="can_focus">False</property>
261+ <property name="pixbuf">contacts.png</property>
262+ </object>
263+ <packing>
264+ <property name="top_attach">1</property>
265+ <property name="bottom_attach">2</property>
266+ <property name="x_options">GTK_FILL</property>
267+ <property name="y_options">GTK_FILL</property>
268+ </packing>
269+ </child>
270+ <child>
271+ <object class="GtkImage" id="image3">
272+ <property name="visible">True</property>
273+ <property name="can_focus">False</property>
274+ <property name="pixbuf">music.png</property>
275+ </object>
276+ <packing>
277+ <property name="top_attach">2</property>
278+ <property name="bottom_attach">3</property>
279+ <property name="x_options">GTK_FILL</property>
280+ <property name="y_options">GTK_FILL</property>
281+ </packing>
282+ </child>
283+ <child>
284+ <object class="GtkImage" id="image4">
285+ <property name="visible">True</property>
286+ <property name="can_focus">False</property>
287+ <property name="pixbuf">notes.png</property>
288+ </object>
289+ <packing>
290+ <property name="top_attach">3</property>
291+ <property name="bottom_attach">4</property>
292+ <property name="x_options">GTK_FILL</property>
293+ <property name="y_options">GTK_FILL</property>
294+ </packing>
295+ </child>
296+ <child>
297+ <object class="GtkLabel" id="label3">
298+ <property name="visible">True</property>
299+ <property name="can_focus">False</property>
300+ <property name="xalign">0</property>
301+ <property name="label" translatable="yes">Files Anywhere
302+&lt;span foreground="#909090"&gt;Backup and access your files from Windows, Ubuntu, or Mobile&lt;/span&gt;</property>
303+ <property name="use_markup">True</property>
304+ <property name="wrap">True</property>
305+ </object>
306+ <packing>
307+ <property name="left_attach">1</property>
308+ <property name="right_attach">2</property>
309+ </packing>
310+ </child>
311+ <child>
312+ <object class="GtkLabel" id="label4">
313+ <property name="visible">True</property>
314+ <property name="can_focus">False</property>
315+ <property name="xalign">0</property>
316+ <property name="label" translatable="yes">Keep connected
317+&lt;span foreground="#909090"&gt;Unify your contacts accress Desktop, Mobile and Web&lt;/span&gt;</property>
318+ <property name="use_markup">True</property>
319+ <property name="wrap">True</property>
320+ </object>
321+ <packing>
322+ <property name="left_attach">1</property>
323+ <property name="right_attach">2</property>
324+ <property name="top_attach">1</property>
325+ <property name="bottom_attach">2</property>
326+ </packing>
327+ </child>
328+ <child>
329+ <object class="GtkLabel" id="label5">
330+ <property name="visible">True</property>
331+ <property name="can_focus">False</property>
332+ <property name="xalign">0</property>
333+ <property name="label" translatable="yes">Rock Out
334+&lt;span foreground="#909090"&gt;Your library at your fingertips with Android, iPhone, and AirPlay
335+Plus the Ubuntu One Music store to grow your collection&lt;/span&gt;</property>
336+ <property name="use_markup">True</property>
337+ <property name="wrap">True</property>
338+ </object>
339+ <packing>
340+ <property name="left_attach">1</property>
341+ <property name="right_attach">2</property>
342+ <property name="top_attach">2</property>
343+ <property name="bottom_attach">3</property>
344+ </packing>
345+ </child>
346+ <child>
347+ <object class="GtkLabel" id="label6">
348+ <property name="visible">True</property>
349+ <property name="can_focus">False</property>
350+ <property name="xalign">0</property>
351+ <property name="label" translatable="yes">Stay Productive
352+&lt;span foreground="#909090"&gt;Keep your Firefox bookmarks and Tomboy notes synced&lt;/span&gt;</property>
353+ <property name="use_markup">True</property>
354+ <property name="wrap">True</property>
355+ </object>
356+ <packing>
357+ <property name="left_attach">1</property>
358+ <property name="right_attach">2</property>
359+ <property name="top_attach">3</property>
360+ <property name="bottom_attach">4</property>
361+ </packing>
362+ </child>
363+ </object>
364+ <packing>
365+ <property name="expand">True</property>
366+ <property name="fill">True</property>
367+ <property name="position">0</property>
368+ </packing>
369 </child>
370- </object>
371- <packing>
372- <property name="position">0</property>
373- </packing>
374- </child>
375- <child>
376- <object class="GtkAlignment" id="alignment2">
377- <property name="visible">True</property>
378- <property name="xscale">0</property>
379- <property name="yscale">0</property>
380 <child>
381- <object class="GtkVBox" id="vbox2">
382+ <object class="GtkVBox" id="vbox1">
383 <property name="visible">True</property>
384- <property name="spacing">10</property>
385- <child>
386- <object class="GtkButton" id="join_now_button">
387- <property name="visible">True</property>
388- <property name="can_focus">True</property>
389- <property name="can_default">True</property>
390- <property name="receives_default">True</property>
391- <signal name="clicked" handler="on_join_now_button_clicked"/>
392+ <property name="can_focus">False</property>
393+ <child>
394+ <object class="GtkLabel" id="warning_label">
395+ <property name="visible">True</property>
396+ <property name="can_focus">False</property>
397+ <property name="label">A warning label that can be long. Possible really long, let's see how it behaves.</property>
398+ <property name="wrap">True</property>
399+ </object>
400+ <packing>
401+ <property name="expand">False</property>
402+ <property name="fill">True</property>
403+ <property name="position">0</property>
404+ </packing>
405+ </child>
406+ <child>
407+ <object class="GtkAlignment" id="alignment2">
408+ <property name="visible">True</property>
409+ <property name="can_focus">False</property>
410+ <property name="xscale">0</property>
411+ <property name="yscale">0</property>
412 <child>
413- <object class="GtkVBox" id="vbox1">
414+ <object class="GtkVBox" id="vbox2">
415 <property name="visible">True</property>
416- <property name="spacing">5</property>
417+ <property name="can_focus">False</property>
418+ <property name="spacing">10</property>
419 <child>
420- <object class="GtkLabel" id="label1">
421+ <object class="GtkButton" id="join_now_button">
422 <property name="visible">True</property>
423- <property name="label" translatable="yes">&lt;span font_size="xx-large"&gt;Join now&lt;/span&gt;</property>
424- <property name="use_markup">True</property>
425+ <property name="can_focus">True</property>
426+ <property name="can_default">True</property>
427+ <property name="receives_default">True</property>
428+ <property name="use_action_appearance">False</property>
429+ <signal name="clicked" handler="on_join_now_button_clicked" swapped="no"/>
430+ <child>
431+ <object class="GtkVBox" id="vbox3">
432+ <property name="visible">True</property>
433+ <property name="can_focus">False</property>
434+ <property name="spacing">5</property>
435+ <child>
436+ <object class="GtkLabel" id="label1">
437+ <property name="visible">True</property>
438+ <property name="can_focus">False</property>
439+ <property name="label" translatable="yes">&lt;span font_size="xx-large"&gt;Join now&lt;/span&gt;</property>
440+ <property name="use_markup">True</property>
441+ </object>
442+ <packing>
443+ <property name="expand">True</property>
444+ <property name="fill">True</property>
445+ <property name="position">0</property>
446+ </packing>
447+ </child>
448+ <child>
449+ <object class="GtkLabel" id="label2">
450+ <property name="visible">True</property>
451+ <property name="can_focus">False</property>
452+ <property name="label" translatable="yes">&lt;span foreground="#909090"&gt;2GB of free storage&lt;/span&gt;</property>
453+ <property name="use_markup">True</property>
454+ </object>
455+ <packing>
456+ <property name="expand">True</property>
457+ <property name="fill">True</property>
458+ <property name="position">1</property>
459+ </packing>
460+ </child>
461+ </object>
462+ </child>
463 </object>
464 <packing>
465+ <property name="expand">False</property>
466+ <property name="fill">True</property>
467 <property name="position">0</property>
468 </packing>
469 </child>
470 <child>
471- <object class="GtkLabel" id="label2">
472+ <object class="GtkLinkButton" id="connect_button">
473+ <property name="label" translatable="yes">I already have an account!</property>
474 <property name="visible">True</property>
475- <property name="label" translatable="yes">&lt;span foreground="grey"&gt;2GB of free storage&lt;/span&gt;</property>
476- <property name="use_markup">True</property>
477+ <property name="can_focus">True</property>
478+ <property name="receives_default">True</property>
479+ <property name="use_action_appearance">False</property>
480+ <property name="relief">none</property>
481+ <signal name="clicked" handler="on_connect_button_clicked" swapped="no"/>
482 </object>
483 <packing>
484+ <property name="expand">False</property>
485+ <property name="fill">False</property>
486 <property name="position">1</property>
487 </packing>
488 </child>
489@@ -79,50 +268,28 @@
490 </child>
491 </object>
492 <packing>
493- <property name="expand">False</property>
494- <property name="position">0</property>
495- </packing>
496- </child>
497- <child>
498- <object class="GtkLinkButton" id="connect_button">
499- <property name="label" translatable="yes">I already have an account!</property>
500- <property name="visible">True</property>
501- <property name="can_focus">True</property>
502- <property name="receives_default">True</property>
503- <property name="relief">none</property>
504- <signal name="clicked" handler="on_connect_button_clicked"/>
505- </object>
506- <packing>
507- <property name="expand">False</property>
508- <property name="fill">False</property>
509+ <property name="expand">True</property>
510+ <property name="fill">True</property>
511 <property name="position">1</property>
512 </packing>
513 </child>
514 </object>
515+ <packing>
516+ <property name="expand">False</property>
517+ <property name="fill">True</property>
518+ <property name="position">1</property>
519+ </packing>
520 </child>
521 </object>
522- <packing>
523- <property name="expand">False</property>
524- <property name="position">1</property>
525- </packing>
526 </child>
527 </object>
528 </child>
529 </object>
530 <packing>
531+ <property name="expand">True</property>
532+ <property name="fill">True</property>
533 <property name="position">1</property>
534 </packing>
535 </child>
536- <child>
537- <object class="GtkLabel" id="warning_label">
538- <property name="visible">True</property>
539- <property name="wrap">True</property>
540- <property name="ellipsize">end</property>
541- </object>
542- <packing>
543- <property name="expand">False</property>
544- <property name="position">2</property>
545- </packing>
546- </child>
547 </object>
548 </interface>
549
550=== modified file 'data/volumes.ui'
551--- data/volumes.ui 2011-01-25 19:08:59 +0000
552+++ data/volumes.ui 2011-02-12 03:22:55 +0000
553@@ -2,6 +2,26 @@
554 <interface>
555 <requires lib="gtk+" version="2.22"/>
556 <!-- interface-naming-policy project-wide -->
557+ <object class="GtkTreeStore" id="volumes_store">
558+ <columns>
559+ <!-- column-name description -->
560+ <column type="gchararray"/>
561+ <!-- column-name subscribed -->
562+ <column type="gboolean"/>
563+ <!-- column-name icon-name -->
564+ <column type="gchararray"/>
565+ <!-- column-name subscribed-visible -->
566+ <column type="gboolean"/>
567+ <!-- column-name subscribed-sensitive -->
568+ <column type="gboolean"/>
569+ <!-- column-name icon-size -->
570+ <column type="gint"/>
571+ <!-- column-name identifier -->
572+ <column type="gchararray"/>
573+ <!-- column-name path -->
574+ <column type="gchararray"/>
575+ </columns>
576+ </object>
577 <object class="GtkAlignment" id="itself">
578 <property name="visible">True</property>
579 <property name="can_focus">False</property>
580@@ -18,6 +38,7 @@
581 <property name="model">volumes_store</property>
582 <property name="rules_hint">True</property>
583 <property name="tooltip_column">0</property>
584+ <signal name="row-activated" handler="on_volumes_view_row_activated" swapped="no"/>
585 <child>
586 <object class="GtkTreeViewColumn" id="treeviewcolumn2">
587 <property name="resizable">True</property>
588@@ -46,7 +67,7 @@
589 <child>
590 <object class="GtkTreeViewColumn" id="treeviewcolumn3">
591 <property name="sizing">autosize</property>
592- <property name="title">On this device?</property>
593+ <property name="title" translatable="yes">Sync locally?</property>
594 <child>
595 <object class="GtkCellRendererToggle" id="cellrenderertoggle1">
596 <property name="indicator_size">15</property>
597@@ -73,22 +94,4 @@
598 </object>
599 </child>
600 </object>
601- <object class="GtkTreeStore" id="volumes_store">
602- <columns>
603- <!-- column-name description -->
604- <column type="gchararray"/>
605- <!-- column-name subscribed -->
606- <column type="gboolean"/>
607- <!-- column-name icon-name -->
608- <column type="gchararray"/>
609- <!-- column-name subscribed-visible -->
610- <column type="gboolean"/>
611- <!-- column-name subscribed-sensitive -->
612- <column type="gboolean"/>
613- <!-- column-name icon-size -->
614- <column type="gint"/>
615- <!-- column-name identifier -->
616- <column type="gchararray"/>
617- </columns>
618- </object>
619 </interface>
620
621=== modified file 'debian/changelog'
622--- debian/changelog 2011-02-07 07:55:07 +0000
623+++ debian/changelog 2011-02-12 03:22:55 +0000
624@@ -1,3 +1,20 @@
625+ubuntuone-control-panel (0.8.3-0ubuntu1) natty; urgency=low
626+
627+ * New upstream release.
628+ - Support share subscription in Folders tab (LP: #714583)
629+ - Use more concise text on overview page (LP: #715732)
630+ - Set widget names to style properly (LP: #716678)
631+ - Place usage bar label on top (LP: #715713)
632+ - Inconsistent placement of 'remove' button (LP: #715804)
633+ - Message text should say 'your personal cloud' (LP: #715858)
634+ - Shares to me path looks awful (LP: #716431)
635+ - Clicking a folder should open it (LP: #716499)
636+ - Subtext in 'join now' button is hard to read (LP: #716504)
637+ - "On this device?" string is inconsistent with its action (LP: #717159)
638+ - Devices don't necessarily have a sync service (LP: #717230)
639+
640+ -- Rodney Dawes <rodney.dawes@ubuntu.com> Fri, 11 Feb 2011 22:07:23 -0500
641+
642 ubuntuone-control-panel (0.8.2-0ubuntu1) natty; urgency=low
643
644 * New upstream release:
645
646=== modified file 'debian/python-ubuntuone-control-panel.install'
647--- debian/python-ubuntuone-control-panel.install 2010-12-06 12:27:11 +0000
648+++ debian/python-ubuntuone-control-panel.install 2011-02-12 03:22:55 +0000
649@@ -1,1 +1,4 @@
650-debian/tmp/usr/lib/python2.*/*-packages/ubuntuone/controlpanel/*.py
651+debian/tmp/usr/lib/python2.*/*-packages/*.pth
652+debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/__init__.py
653+debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/controlpanel/*.py
654+
655
656=== modified file 'debian/ubuntuone-control-panel-gtk.install'
657--- debian/ubuntuone-control-panel-gtk.install 2011-01-27 22:13:48 +0000
658+++ debian/ubuntuone-control-panel-gtk.install 2011-02-12 03:22:55 +0000
659@@ -4,4 +4,5 @@
660 debian/tmp/usr/share/ubuntuone-control-panel/*.ui
661 debian/tmp/usr/share/ubuntuone-control-panel/*.png
662 debian/tmp/usr/share/man/man1/ubuntuone-control-panel-gtk.*
663-debian/tmp/usr/lib/python2.*/*-packages/ubuntuone/controlpanel/gtk/*.py
664+debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/controlpanel/gtk
665+
666
667=== modified file 'setup.py'
668--- setup.py 2011-02-04 20:02:28 +0000
669+++ setup.py 2011-02-12 03:22:55 +0000
670@@ -76,7 +76,7 @@
671
672 DistUtilsExtra.auto.setup(
673 name='ubuntuone-control-panel',
674- version='0.8.2',
675+ version='0.8.3',
676 license='GPL v3',
677 author='Natalia Bidart',
678 author_email='natalia.bidart@canonical.com',
679@@ -85,6 +85,7 @@
680 'DBus service to query/modify all the Ubuntu One bits.',
681 url='https://launchpad.net/ubuntuone-control-panel',
682 packages=['ubuntuone.controlpanel', 'ubuntuone.controlpanel.gtk'],
683+ extra_path='ubuntuone-control-panel',
684 data_files=[
685 ('lib/ubuntuone-control-panel',
686 ['bin/ubuntuone-control-panel-backend']),
687
688=== modified file 'ubuntuone-control-panel-gtk.desktop.in'
689--- ubuntuone-control-panel-gtk.desktop.in 2011-01-07 20:07:39 +0000
690+++ ubuntuone-control-panel-gtk.desktop.in 2011-02-12 03:22:55 +0000
691@@ -8,6 +8,7 @@
692 StartupNotify=true
693 Categories=GNOME;GTK;Settings;
694 X-Ayatana-Desktop-Shortcuts=U1
695+X-Ayatana-Appmenu-Show-Stubs=False
696
697 [U1 Shortcut Group]
698 Name=Ubuntu One
699
700=== added file 'ubuntuone.controlpanel.pth'
701--- ubuntuone.controlpanel.pth 1970-01-01 00:00:00 +0000
702+++ ubuntuone.controlpanel.pth 2011-02-12 03:22:55 +0000
703@@ -0,0 +1,2 @@
704+ubuntuone-control-panel
705+
706
707=== modified file 'ubuntuone/controlpanel/backend.py'
708--- ubuntuone/controlpanel/backend.py 2011-01-25 19:08:59 +0000
709+++ ubuntuone/controlpanel/backend.py 2011-02-12 03:22:55 +0000
710@@ -19,6 +19,8 @@
711
712 """A backend for the Ubuntu One Control Panel."""
713
714+from collections import defaultdict
715+
716 from twisted.internet.defer import inlineCallbacks, returnValue
717
718 from ubuntuone.controlpanel import dbus_client
719@@ -62,12 +64,19 @@
720 class ControlBackend(object):
721 """The control panel backend."""
722
723+ ROOT_TYPE = u'ROOT'
724+ FOLDER_TYPE = u'UDF'
725+ SHARE_TYPE = u'SHARE'
726+ NAME_NOT_SET = u'ENAMENOTSET'
727+
728 def __init__(self):
729 """Initialize the webclient."""
730 self.wc = WebClient(dbus_client.get_credentials)
731 self._status_changed_handler = None
732 self.status_changed_handler = lambda *a: None
733
734+ self._volumes = {} # cache last known volume info
735+
736 def _process_file_sync_status(self, status):
737 """Process raw file sync status into custom format.
738
739@@ -328,17 +337,54 @@
740 @inlineCallbacks
741 def volumes_info(self):
742 """Get the volumes info."""
743+ self._volumes = {}
744+
745 account = yield self.account_info()
746 root_dir = yield dbus_client.get_root_dir()
747+ shares_dir = yield dbus_client.get_shares_dir()
748+ shares_dir_link = yield dbus_client.get_shares_dir_link()
749 folders = yield dbus_client.get_folders()
750+ shares = yield dbus_client.get_shares()
751
752 free_bytes = int(account['quota_total']) - int(account['quota_used'])
753 root_volume = {u'volume_id': u'', u'path': root_dir,
754- u'subscribed': 'True', u'type': u'ROOT'}
755+ u'subscribed': 'True', u'type': self.ROOT_TYPE}
756+ self._volumes[u''] = root_volume
757+
758+ # group shares by the offering user
759+ shares_result = defaultdict(list)
760+ for share in shares:
761+ share[u'type'] = self.SHARE_TYPE
762+
763+ vid = share['volume_id']
764+ assert vid not in self._volumes
765+ self._volumes[vid] = share
766+
767+ if not bool(share['accepted']):
768+ continue
769+
770+ nicer_path = share[u'path'].replace(shares_dir, shares_dir_link)
771+ share[u'path'] = nicer_path
772+
773+ username = share['other_visible_name']
774+ if not username:
775+ username = u'%s (%s)' % (share['other_username'],
776+ self.NAME_NOT_SET)
777+
778+ shares_result[username].append(share)
779+
780+ for folder in folders:
781+ folder[u'type'] = self.FOLDER_TYPE
782+
783+ vid = folder['volume_id']
784+ assert vid not in self._volumes
785+ self._volumes[vid] = folder
786
787 result = [(u'', unicode(free_bytes), [root_volume] + folders)]
788
789- # later, we may request shares info as well
790+ for other_username, shares in shares_result.iteritems():
791+ result.append((other_username, shares[0][u'free_bytes'], shares))
792+
793 returnValue(result)
794
795 @log_call(logger.debug)
796@@ -359,16 +405,18 @@
797 @inlineCallbacks
798 def subscribe_volume(self, volume_id):
799 """Subscribe to 'volume_id'."""
800- # when dealing with shares, we need to check if 'volume_id'
801- # is a folder or a share
802- yield dbus_client.subscribe_folder(volume_id)
803+ if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
804+ yield dbus_client.subscribe_folder(volume_id)
805+ elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
806+ yield dbus_client.subscribe_share(volume_id)
807
808 @inlineCallbacks
809 def unsubscribe_volume(self, volume_id):
810 """Unsubscribe from 'volume_id'."""
811- # when dealing with shares, we need to check if 'volume_id'
812- # is a folder or a share
813- yield dbus_client.unsubscribe_folder(volume_id)
814+ if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
815+ yield dbus_client.unsubscribe_folder(volume_id)
816+ elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
817+ yield dbus_client.unsubscribe_share(volume_id)
818
819 @log_call(logger.debug)
820 @inlineCallbacks
821
822=== modified file 'ubuntuone/controlpanel/dbus_client.py'
823--- ubuntuone/controlpanel/dbus_client.py 2011-01-25 19:08:59 +0000
824+++ ubuntuone/controlpanel/dbus_client.py 2011-02-12 03:22:55 +0000
825@@ -221,6 +221,22 @@
826 return d
827
828
829+def get_shares_dir():
830+ """Retrieve the shares information from syncdaemon."""
831+ d = defer.Deferred()
832+ proxy = get_syncdaemon_proxy("/", sd_dbus_iface.DBUS_IFACE_SYNC_NAME)
833+ proxy.get_sharesdir(reply_handler=d.callback, error_handler=d.errback)
834+ return d
835+
836+
837+def get_shares_dir_link():
838+ """Retrieve the shares information from syncdaemon."""
839+ d = defer.Deferred()
840+ proxy = get_syncdaemon_proxy("/", sd_dbus_iface.DBUS_IFACE_SYNC_NAME)
841+ proxy.get_sharesdir_link(reply_handler=d.callback, error_handler=d.errback)
842+ return d
843+
844+
845 def get_folders():
846 """Retrieve the folders information from syncdaemon."""
847 d = defer.Deferred()
848@@ -256,7 +272,7 @@
849 proxy = get_folders_syncdaemon_proxy()
850
851 sig = proxy.connect_to_signal('FolderSubscribed', d.callback)
852- cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))
853+ cb = lambda info, error: d.errback(VolumesError(info['id'], error))
854 esig = proxy.connect_to_signal('FolderSubscribeError', cb)
855
856 proxy.subscribe(folder_id, reply_handler=no_op, error_handler=no_op)
857@@ -277,7 +293,7 @@
858 proxy = get_folders_syncdaemon_proxy()
859
860 sig = proxy.connect_to_signal('FolderUnSubscribed', d.callback)
861- cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))
862+ cb = lambda info, error: d.errback(VolumesError(info['id'], error))
863 esig = proxy.connect_to_signal('FolderUnSubscribeError', cb)
864
865 proxy.unsubscribe(folder_id, reply_handler=no_op, error_handler=no_op)
866@@ -292,6 +308,31 @@
867 return d
868
869
870+@defer.inlineCallbacks
871+def get_shares():
872+ """Retrieve the shares information from syncdaemon."""
873+ result = yield SyncDaemonTool(bus=dbus.SessionBus()).get_shares()
874+ defer.returnValue(result)
875+
876+
877+@defer.inlineCallbacks
878+def subscribe_share(share_id):
879+ """Subscribe to 'share_id'."""
880+ try:
881+ yield SyncDaemonTool(bus=dbus.SessionBus()).subscribe_share(share_id)
882+ except Exception, e:
883+ raise VolumesError(share_id, e)
884+
885+
886+@defer.inlineCallbacks
887+def unsubscribe_share(share_id):
888+ """Unsubscribe 'share_id'."""
889+ try:
890+ yield SyncDaemonTool(bus=dbus.SessionBus()).unsubscribe_share(share_id)
891+ except Exception, e:
892+ raise VolumesError(share_id, e)
893+
894+
895 def get_current_status():
896 """Retrieve the current status from syncdaemon."""
897 d = defer.Deferred()
898
899=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
900--- ubuntuone/controlpanel/gtk/gui.py 2011-02-04 20:02:28 +0000
901+++ ubuntuone/controlpanel/gtk/gui.py 2011-02-12 03:22:55 +0000
902@@ -61,24 +61,13 @@
903
904 # To be replaced by values from the theme or Ubuntu One' specific (LP: #673663)
905 ORANGE = '#c95724'
906+ERROR_COLOR = 'red'
907 LOADING = _('Loading...')
908-OVERVIEW_MSGS = [
909- _('Backup and access your files from <b>any</b> computer (Ubuntu &amp; '
910- 'Windows), mobile device (Android) or the web.'),
911- _('A <b>portable address book</b> that unifies your most important '
912- 'contact sources: mobile phones, social networks, email clients and '
913- 'services.'),
914- _('A <b>personal music library</b> that can be streamed to Android, '
915- 'iPhone and AirPlay-enabled devices.'),
916- _('Add to that music library with a <b>Music Store</b> that automatically '
917- 'delivers to your personal cloud and connected devices.'),
918- _('Keep your <b>Firefox bookmarks</b> and <b>Tomboy notes</b> in sync '
919- 'across computers (Ubuntu &amp; Windows).'),
920-]
921 VALUE_ERROR = _('Value could not be retrieved.')
922-WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
923+WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ERROR_COLOR
924 KILOBYTES = 1024
925 NO_OP = lambda *a, **kw: None
926+FILE_URI_PREFIX = 'file://'
927
928
929 def error_handler(*args, **kwargs):
930@@ -109,7 +98,7 @@
931 @log_call(logger.debug)
932 def uri_hook(button, uri, *args, **kwargs):
933 """Open an URI or do nothing if URI is not an URL."""
934- if uri.startswith('http'):
935+ if uri.startswith('http') or uri.startswith(FILE_URI_PREFIX):
936 gtk.show_uri(None, uri, gtk.gdk.CURRENT_TIME)
937
938
939@@ -222,30 +211,27 @@
940
941 CREDENTIALS_ERROR = _('There was a problem while retrieving the '
942 'credentials.')
943- AUTHORIZATION_DENIED = _('The authentication was cancelled. Please either '
944- 'click join or connect to continue.')
945+ AUTHORIZATION_DENIED = _('The authentication was cancelled.')
946 NETWORK_OFFLINE = _('An internet connection is required to join or sign '
947 'in to %(app_name)s.')
948 NETWORK_UNKNOWN = _('The internet connection state couldn\'t be '
949 'discovered. Maybe NetworkManager is not running?')
950
951 CONNECT = _('Connect to Ubuntu One')
952- BULLET = '<span foreground="%s">✔</span>' % ORANGE
953
954- def __init__(self, main_window, messages=None):
955+ def __init__(self, main_window):
956 GreyableBin.__init__(self)
957 ControlPanelMixin.__init__(self, filename='overview.ui')
958 self.add(self.itself)
959 self.warning_label.set_text('')
960 self.warning_label.set_property('xalign', 0.5)
961- self.connect('size-allocate', on_size_allocate, self.warning_label)
962+
963+ self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))
964
965 self.connect_button.set_uri(self.CONNECT)
966
967 self.main_window = main_window
968 self._credentials_are_new = False
969- self._messages = messages
970- self._build_messages()
971 self.show()
972
973 bus = dbus.SessionBus()
974@@ -274,21 +260,6 @@
975 self.network_manager_state = networkstate.NetworkManagerState(**kw)
976 self.network_manager_state.find_online_state()
977
978- def _build_messages(self):
979- """List messages in a itemized list."""
980- if self._messages is None:
981- self._messages = OVERVIEW_MSGS
982-
983- for msg in self._messages:
984- label = gtk.Label()
985- label.set_markup(self.BULLET + ' ' + msg)
986- label.set_property('xalign', 0)
987- label.set_property('wrap', True)
988- self.messages.connect('size-allocate', on_size_allocate, label)
989-
990- self.messages.pack_start(label)
991- self.messages.show_all()
992-
993 def _set_warning(self, message, label=None):
994 """Set 'message' as global warning."""
995 ControlPanelMixin._set_warning(self, message,
996@@ -419,18 +390,22 @@
997 class VolumesPanel(UbuntuOneBin, ControlPanelMixin):
998 """The volumes panel."""
999
1000- TITLE = _('Select the folders from your cloud that you want synchronized '
1001- 'in this device.')
1002+ TITLE = _('Select the folders from your personal cloud that you want '
1003+ 'synchronized in this device.')
1004 MY_FOLDERS = _('My folders')
1005 ALWAYS_SUBSCRIBED = _('Always in sync!')
1006 FREE_SPACE = _('%(free_space)s available storage')
1007+ NO_VOLUMES = _('No folders to show.')
1008+ NAME_NOT_SET = _('[unknown user name]')
1009+
1010+ MAX_COLS = 8
1011+
1012 CONTACT_ICON_NAME = 'system-users'
1013 FOLDER_ICON_NAME = 'folder'
1014 SHARE_ICON_NAME = 'folder-remote'
1015 ROW_HEADER = '<span font_size="large"><b>%s</b></span> ' \
1016 '<span foreground="grey">%s</span>'
1017 ROOT = '%s - <span foreground="%s" font_size="small">%s</span>'
1018- NO_VOLUMES = _('No folders to show.')
1019
1020 def __init__(self, main_window=None):
1021 UbuntuOneBin.__init__(self)
1022@@ -438,6 +413,11 @@
1023 self.add(self.itself)
1024 self.show_all()
1025
1026+ # name, subscribed, icon name, show toggle, sensitive, icon size,
1027+ # id, path
1028+ self._empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU,
1029+ None, None)
1030+
1031 self.backend.connect_to_signal('VolumesInfoReady',
1032 self.on_volumes_info_ready)
1033 self.backend.connect_to_signal('VolumesInfoError',
1034@@ -462,14 +442,16 @@
1035 else:
1036 self.on_success()
1037
1038- # pylint: disable=W0612
1039- # name, subscribed, icon name, show toggle, sensitive, icon size, id
1040- empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU, None)
1041-
1042 for name, free_bytes, volumes in info:
1043+ if backend.ControlBackend.NAME_NOT_SET in name:
1044+ name = self.NAME_NOT_SET
1045+
1046 if name:
1047 name = name + "'s"
1048 icon_name = self.SHARE_ICON_NAME
1049+
1050+ # we already added user folders, let's add an empty row
1051+ treeiter = self.volumes_store.append(None, self._empty_row)
1052 else:
1053 name = self.MY_FOLDERS
1054 icon_name = self.FOLDER_ICON_NAME
1055@@ -477,30 +459,33 @@
1056 free_bytes_args = {'free_space': self.humanize(int(free_bytes))}
1057 row = (self.ROW_HEADER % (name, self.FREE_SPACE % free_bytes_args),
1058 True, self.CONTACT_ICON_NAME, False, False,
1059- gtk.ICON_SIZE_LARGE_TOOLBAR, None)
1060+ gtk.ICON_SIZE_LARGE_TOOLBAR, None, None)
1061 treeiter = self.volumes_store.append(None, row)
1062
1063 volumes.sort(key=operator.itemgetter('path'))
1064 for volume in volumes:
1065 sensitive = True
1066- path = self._process_path(volume['path'])
1067- if volume['type'] == u'ROOT':
1068+ name = self._process_path(volume[u'path'])
1069+
1070+ is_root = volume[u'type'] == backend.ControlBackend.ROOT_TYPE
1071+ is_share = volume[u'type'] == backend.ControlBackend.SHARE_TYPE
1072+
1073+ if is_root:
1074 sensitive = False
1075- path = self.ROOT % (path, ORANGE, self.ALWAYS_SUBSCRIBED)
1076-
1077- row = (path, bool(volume['subscribed']), icon_name, True,
1078- sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'])
1079-
1080- if volume['type'] == u'ROOT': # root should go first!
1081+ name = self.ROOT % (name, ORANGE, self.ALWAYS_SUBSCRIBED)
1082+ elif is_share:
1083+ name = volume[u'name']
1084+
1085+ row = (name, bool(volume[u'subscribed']), icon_name, True,
1086+ sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'],
1087+ volume[u'path'])
1088+
1089+ if is_root: # root should go first!
1090 self.volumes_store.prepend(treeiter, row)
1091 else:
1092 self.volumes_store.append(treeiter, row)
1093
1094- # When we display shares info, we'll need to smartly add
1095- # an empty row to the tree view to separate volume groups
1096- #treeiter = self.volumes_store.append(None, empty_row)
1097-
1098- self.volumes_view.expand_row(0, True)
1099+ self.volumes_view.expand_all()
1100 self.volumes_view.show_all()
1101
1102 self.is_processing = False
1103@@ -534,6 +519,12 @@
1104
1105 self.is_processing = True
1106
1107+ def on_volumes_view_row_activated(self, widget, path, *args, **kwargs):
1108+ """The user double clicked on a row."""
1109+ treeiter = self.volumes_store.get_iter(path)
1110+ volume_path = self.volumes_store.get_value(treeiter, 7)
1111+ uri_hook(None, FILE_URI_PREFIX + volume_path)
1112+
1113 def load(self):
1114 """Load the volume list."""
1115 self.backend.volumes_info(reply_handler=NO_OP,
1116@@ -739,7 +730,8 @@
1117 gobject.TYPE_NONE, ()),
1118 }
1119
1120- TITLE = _('The devices synced with your personal cloud are listed below.')
1121+ TITLE = _('The devices connected with your personal cloud are listed '
1122+ 'below.')
1123 NO_DEVICES = _('No devices to show.')
1124 CONFIRM_REMOVE = _('Are you sure you want to remove this device '
1125 'from Ubuntu One?')
1126@@ -1270,6 +1262,8 @@
1127 }
1128
1129 QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)')
1130+ DASHBOARD_BUTTON_NAME = 'Account'
1131+ DEVICES_BUTTON_NAME = 'Devices'
1132
1133 def __init__(self, main_window=None):
1134 gtk.VBox.__init__(self)
1135@@ -1287,6 +1281,7 @@
1136 self.quota_progressbar.set_sensitive(False)
1137 self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
1138 self.quota_box.pack_start(self.quota_label, expand=False)
1139+ self.quota_box.reorder_child(self.quota_label, 0)
1140
1141 self.status_label = FileSyncStatus()
1142 self.status_box.pack_end(self.status_label, expand=False)
1143@@ -1309,9 +1304,13 @@
1144 gtk.gdk.Color(DEFAULT_FG))
1145 self.notebook.insert_page(getattr(self, tab), position=page_num)
1146
1147+ self.dashboard_button.set_name(self.DASHBOARD_BUTTON_NAME)
1148+
1149 self.volumes_button.connect('clicked', lambda b: self.volumes.load())
1150+ self.services_button.connect('clicked', lambda b: self.services.load())
1151+
1152+ self.devices_button.set_name(self.DEVICES_BUTTON_NAME)
1153 self.devices_button.connect('clicked', lambda b: self.devices.load())
1154- self.services_button.connect('clicked', lambda b: self.services.load())
1155 self.devices.connect('local-device-removed',
1156 lambda widget: self.emit('local-device-removed'))
1157
1158@@ -1397,7 +1396,7 @@
1159 class ControlPanelWindow(gtk.Window):
1160 """The main window for the Ubuntu One control panel."""
1161
1162- TITLE = _('%(app_name)s Dashboard')
1163+ TITLE = _('%(app_name)s Control Panel')
1164
1165 def __init__(self, switch_to=None):
1166 super(ControlPanelWindow, self).__init__()
1167
1168=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
1169--- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-25 19:08:59 +0000
1170+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-02-12 03:22:55 +0000
1171@@ -45,11 +45,11 @@
1172 ]
1173
1174 FAKE_SHARES_INFO = [
1175- {u'volume_id': u'1234',
1176+ {u'volume_id': u'1234', u'name': u'do',
1177 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
1178 u'subscribed': u'', u'type': u'SHARE'},
1179
1180- {u'volume_id': u'5678',
1181+ {u'volume_id': u'5678', u'name': u're',
1182 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
1183 u'subscribed': u'True', u'type': u'SHARE'},
1184 ]
1185
1186=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
1187--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-25 19:08:59 +0000
1188+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-02-12 03:22:55 +0000
1189@@ -27,7 +27,7 @@
1190 from ubuntuone.controlpanel.gtk import gui
1191 from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
1192 FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
1193- FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, USER_HOME,
1194+ FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, ROOT, USER_HOME,
1195 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
1196 FakedPackageManager, FakedConfirmDialog,
1197 )
1198@@ -82,21 +82,6 @@
1199 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
1200 self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
1201
1202- def assert_messages_equal(self, widget, msgs):
1203- """Check that 'widget' has all the entries in 'msgs'."""
1204- children = list(reversed(widget.get_children()))
1205- self.assertEqual(len(children), len(msgs))
1206- for label, msg in zip(reversed(children), msgs):
1207- full_msg = self.ui.BULLET + ' ' + msg
1208- self.assertTrue(label.get_visible())
1209- self.assertEqual(label.get_property('xalign'), 0)
1210- self.assertEqual(label.get_property('wrap'), True)
1211- self.assertEqual(label.get_label(), full_msg)
1212-
1213- expected = gui.gtk.Label()
1214- expected.set_markup(full_msg)
1215- self.assertEqual(label.get_text(), expected.get_text())
1216-
1217 def assert_backend_called(self, method_name, args, backend=None):
1218 """Check that the control panel backend 'method_name' was called."""
1219 if backend is None:
1220@@ -392,7 +377,7 @@
1221 """The test suite for the overview panel."""
1222
1223 klass = gui.OverviewPanel
1224- kwargs = {'messages': None, 'main_window': gui.gtk.Window()}
1225+ kwargs = {'main_window': gui.gtk.Window()}
1226 ui_filename = 'overview.ui'
1227
1228 def test_is_a_greyable_bin(self):
1229@@ -561,10 +546,7 @@
1230 class OverwiewPanelNoCredsTestCase(OverwiewPanelTestCase):
1231 """The test suite for the overview panel when no credentials are found."""
1232
1233- messages = None
1234-
1235 def setUp(self):
1236- self.kwargs['messages'] = self.messages
1237 super(OverwiewPanelNoCredsTestCase, self).setUp()
1238 self.ui.on_credentials_not_found(gui.U1_APP_NAME)
1239
1240@@ -581,10 +563,6 @@
1241 """There is an image attribute and is correct."""
1242 self.assert_image_equal(self.ui.image, 'overview.png')
1243
1244- def test_messages(self):
1245- """If no messages are passed, the default list is used."""
1246- self.assert_messages_equal(self.ui.messages, gui.OVERVIEW_MSGS)
1247-
1248 def test_join_now_is_default_widget(self):
1249 """The join now button is the default widget."""
1250 self.assertTrue(self.ui.join_now_button.get_property('can_default'))
1251@@ -683,18 +661,6 @@
1252 self.assertTrue(self.ui.connect_button.is_sensitive())
1253
1254
1255-class OverwiewPanelMessagesTestCase(OverwiewPanelTestCase):
1256- """The test suite for the overview panel when messages are set."""
1257-
1258- messages = ['Test me', 'A little bit more']
1259-
1260-
1261-class OverwiewPanelMarkupMessagesTestCase(OverwiewPanelTestCase):
1262- """The test suite for the overview panel when markup messages are set."""
1263-
1264- messages = ['<small>Test me</small>', 'A <b>little</b> bit more']
1265-
1266-
1267 class DashboardTestCase(ControlPanelMixinTestCase):
1268 """The test suite for the dashboard panel."""
1269
1270@@ -835,7 +801,8 @@
1271 """The volumes info is processed when ready."""
1272 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
1273
1274- self.assertEqual(len(FAKE_VOLUMES_INFO), len(self.ui.volumes_store))
1275+ self.assertEqual(len(FAKE_VOLUMES_INFO) + 1, # count the empty row
1276+ len(self.ui.volumes_store))
1277 treeiter = self.ui.volumes_store.get_iter_root()
1278 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:
1279 name = "%s's" % name if name else self.ui.MY_FOLDERS
1280@@ -843,7 +810,8 @@
1281 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})
1282
1283 # check parent row
1284- row = self.ui.volumes_store.get(treeiter, *xrange(7))
1285+ row = self.ui.volumes_store.get(treeiter,
1286+ *xrange(self.ui.MAX_COLS))
1287
1288 self.assertEqual(row[0], self.ui.ROW_HEADER % header)
1289 self.assertTrue(row[1], 'parent will always be subscribed')
1290@@ -852,6 +820,7 @@
1291 self.assertFalse(row[4], 'toggle should be non sensitive.')
1292 self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR)
1293 self.assertEqual(row[6], None)
1294+ self.assertEqual(row[7], None)
1295
1296 # check children
1297 self.assertEqual(len(volumes),
1298@@ -860,7 +829,8 @@
1299
1300 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
1301 for volume in sorted_vols:
1302- row = self.ui.volumes_store.get(childiter, *xrange(7))
1303+ row = self.ui.volumes_store.get(childiter,
1304+ *xrange(self.ui.MAX_COLS))
1305
1306 sensitive = True
1307 path = volume['path'].replace(USER_HOME + '/', '')
1308@@ -868,6 +838,8 @@
1309 sensitive = False
1310 path = self.ui.ROOT % (path, gui.ORANGE,
1311 self.ui.ALWAYS_SUBSCRIBED)
1312+ elif volume['type'] == 'SHARE':
1313+ path = volume['name']
1314
1315 self.assertEqual(row[0], path)
1316 self.assertEqual(row[1], bool(volume['subscribed']))
1317@@ -879,17 +851,28 @@
1318 self.assertEqual(row[4], sensitive)
1319 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)
1320 self.assertEqual(row[6], volume['volume_id'])
1321+ self.assertEqual(row[7], volume['path'])
1322
1323 childiter = self.ui.volumes_store.iter_next(childiter)
1324
1325 treeiter = self.ui.volumes_store.iter_next(treeiter)
1326
1327+ if treeiter is not None:
1328+ # skip the empty row
1329+ row = self.ui.volumes_store.get(treeiter,
1330+ *xrange(self.ui.MAX_COLS))
1331+ self.assertEqual(row, self.ui._empty_row)
1332+
1333+ # grab next non-empty row
1334+ treeiter = self.ui.volumes_store.iter_next(treeiter)
1335+
1336 def test_on_volumes_info_ready_clears_the_list(self):
1337 """The old volumes info is cleared before updated."""
1338 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
1339 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
1340
1341- self.assertEqual(len(self.ui.volumes_store), len(FAKE_VOLUMES_INFO))
1342+ self.assertEqual(len(FAKE_VOLUMES_INFO) + 1,
1343+ len(self.ui.volumes_store))
1344
1345 def test_on_volumes_info_ready_with_no_volumes(self):
1346 """When there are no volumes, a notification is shown."""
1347@@ -917,7 +900,9 @@
1348 """Clicking on 'subscribed' updates the folder subscription."""
1349 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
1350
1351- for parent, (_, _, volumes) in enumerate(FAKE_VOLUMES_INFO):
1352+ real_rows = len(FAKE_VOLUMES_INFO)
1353+ data = zip(range(real_rows)[::2], FAKE_VOLUMES_INFO) # skip emtpy rows
1354+ for parent, (_, _, volumes) in data:
1355
1356 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
1357 for child, volume in enumerate(sorted_vols):
1358@@ -964,6 +949,17 @@
1359 # reload folders list to sanitize the info in volumes_store
1360 self.assertTrue(self._called, ((), {}))
1361
1362+ def test_clicking_on_row_opens_folder(self):
1363+ """The folder activated is opened."""
1364+ self.patch(gui, 'uri_hook', self._set_called)
1365+ self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
1366+
1367+ self.ui.volumes_view.row_activated('0:0',
1368+ self.ui.volumes_view.get_column(0))
1369+
1370+ self.assertEqual(self._called,
1371+ ((None, gui.FILE_URI_PREFIX + ROOT['path']), {}))
1372+
1373
1374 class DeviceTestCase(ControlPanelMixinTestCase):
1375 """The test suite for the device widget."""
1376@@ -2358,3 +2354,13 @@
1377 self.ui.devices.emit('local-device-removed')
1378
1379 self.assertEqual(self._called, ((self.ui,), {}))
1380+
1381+ def test_dashboard_button_name(self):
1382+ """The dashboard_button widget has the proper name."""
1383+ self.assertEqual(self.ui.dashboard_button.get_name(),
1384+ self.ui.DASHBOARD_BUTTON_NAME)
1385+
1386+ def test_devices_button_name(self):
1387+ """The devices_button widget has the proper name."""
1388+ self.assertEqual(self.ui.devices_button.get_name(),
1389+ self.ui.DEVICES_BUTTON_NAME)
1390
1391=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py'
1392--- ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2011-01-25 19:08:59 +0000
1393+++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2011-02-12 03:22:55 +0000
1394@@ -19,9 +19,11 @@
1395
1396 """Tests for the DBus service when accessing SyncDaemon."""
1397
1398+import uuid
1399+
1400 import dbus
1401
1402-from twisted.internet.defer import inlineCallbacks
1403+from twisted.internet.defer import inlineCallbacks, returnValue
1404
1405 from ubuntuone.controlpanel import dbus_client
1406 from ubuntuone.controlpanel.dbus_client import sd_dbus_iface
1407@@ -34,6 +36,8 @@
1408 "download": "838",
1409 }
1410
1411+SHARES = {}
1412+
1413 # pylint, you have to go to decorator's school
1414 # pylint: disable=C0322
1415
1416@@ -302,7 +306,7 @@
1417
1418
1419 class FoldersTestCase(DBusClientTestCase):
1420- """Test for the volumes dbus client methods."""
1421+ """Test for the shares dbus client methods."""
1422
1423 def setUp(self):
1424 super(FoldersTestCase, self).setUp()
1425@@ -379,7 +383,7 @@
1426 try:
1427 yield dbus_client.subscribe_folder(fid)
1428 except dbus_client.VolumesError, e:
1429- self.assertEqual(e[0], {'id': fid})
1430+ self.assertEqual(e[0], fid)
1431 else:
1432 self.fail('dbus_client.subscribe_folder should be errbacking')
1433
1434@@ -405,11 +409,159 @@
1435 try:
1436 yield dbus_client.unsubscribe_folder(fid)
1437 except dbus_client.VolumesError, e:
1438- self.assertEqual(e[0], {'id': fid})
1439+ self.assertEqual(e[0], fid)
1440 else:
1441 self.fail('dbus_client.unsubscribe_folder should be errbacking')
1442
1443
1444+class FakedSyncDaemonTool(object):
1445+ """Fake the FakedSyncDaemonTool."""
1446+
1447+ def __init__(self, bus):
1448+ """New instance."""
1449+
1450+ def _set_share_attr(self, share_id, attr, value):
1451+ """Set values to shares."""
1452+ if share_id not in SHARES:
1453+ raise dbus.DBusException('share_id not in SHARES.')
1454+
1455+ value = sd_dbus_iface.bool_str(value)
1456+ SHARES[share_id][attr] = value
1457+ return share_id
1458+
1459+ @inlineCallbacks
1460+ def accept_share(self, share_id):
1461+ """Accept the share with id: share_id."""
1462+ yield self._set_share_attr(share_id, u'accepted', True)
1463+
1464+ @inlineCallbacks
1465+ def reject_share(self, share_id):
1466+ """Reject the share with id: share_id."""
1467+ yield self._set_share_attr(share_id, u'accepted', False)
1468+
1469+ @inlineCallbacks
1470+ def subscribe_share(self, share_id):
1471+ """Subscribe to a share given its id."""
1472+ yield self._set_share_attr(share_id, u'subscribed', True)
1473+
1474+ @inlineCallbacks
1475+ def unsubscribe_share(self, share_id):
1476+ """Unsubscribe from a share given its id."""
1477+ yield self._set_share_attr(share_id, u'subscribed', False)
1478+
1479+ @inlineCallbacks
1480+ def get_shares(self):
1481+ """Get the list of shares (accepted or not)."""
1482+ result = yield SHARES.values()
1483+ returnValue(sorted(result))
1484+
1485+ @inlineCallbacks
1486+ def refresh_shares(self):
1487+ """Call refresh_shares method via DBus."""
1488+ yield
1489+
1490+ @classmethod
1491+ def create_share(cls, name, accepted=False, subscribed=False):
1492+ """Add a new share (fake)."""
1493+ sid = unicode(uuid.uuid4())
1494+ path = u'/home/tester/.hidden/shares/%s from The Othr User' % name
1495+ share = {
1496+ u'name': name, u'subscribed': sd_dbus_iface.bool_str(subscribed),
1497+ u'accepted': sd_dbus_iface.bool_str(accepted),
1498+ u'generation': u'36', u'type': u'Share',
1499+ u'node_id': u'ca3a1cec-09d2-485e-9685-1a5180bd6441',
1500+ u'volume_id': sid, u'access_level': u'View',
1501+ u'other_username': u'https://login.ubuntu.com/+id/nHRnYmz',
1502+ u'other_visible_name': u'The Other User',
1503+ u'free_bytes': u'2146703403', u'path': path,
1504+ }
1505+ SHARES[sid] = share
1506+ return share
1507+
1508+
1509+class SharesTestCase(DBusClientTestCase):
1510+ """Test for the shares dbus client methods."""
1511+
1512+ def setUp(self):
1513+ super(SharesTestCase, self).setUp()
1514+ self.patch(dbus_client, 'SyncDaemonTool', FakedSyncDaemonTool)
1515+ SHARES.clear()
1516+
1517+ @inlineCallbacks
1518+ def test_get_shares(self):
1519+ """Retrieve shares info list."""
1520+ share1 = FakedSyncDaemonTool.create_share(name='first, test me!')
1521+ share2 = FakedSyncDaemonTool.create_share(name='and test me more!',
1522+ accepted=True)
1523+ share3 = FakedSyncDaemonTool.create_share(name='last but not least',
1524+ accepted=True, subscribed=True)
1525+
1526+ result = yield dbus_client.get_shares()
1527+
1528+ self.assertEqual(result, sorted([share1, share2, share3]))
1529+
1530+ @inlineCallbacks
1531+ def test_get_shares_error(self):
1532+ """Handle error when retrieving current syncdaemon status."""
1533+ self.patch(FakedSyncDaemonTool, 'get_shares', self.fail)
1534+ try:
1535+ yield dbus_client.get_shares()
1536+ except: # pylint: disable=W0702
1537+ pass # test passes!
1538+ else:
1539+ self.fail('dbus_client.get_shares should be errbacking')
1540+
1541+ @inlineCallbacks
1542+ def test_subscribe_share(self):
1543+ """Subscribe to a share."""
1544+ share = FakedSyncDaemonTool.create_share(name='to be subscribed',
1545+ accepted=True, subscribed=False)
1546+ sid = share['volume_id']
1547+
1548+ yield dbus_client.subscribe_share(sid)
1549+
1550+ result = yield dbus_client.get_shares()
1551+ expected, = filter(lambda share: share['volume_id'] == sid, result)
1552+ self.assertEqual(expected['subscribed'], 'True')
1553+
1554+ @inlineCallbacks
1555+ def test_subscribe_share_error(self):
1556+ """Subscribe to a share."""
1557+ sid = u'does not exist'
1558+ try:
1559+ yield dbus_client.subscribe_share(sid)
1560+ except dbus_client.VolumesError, e:
1561+ self.assertEqual(e[0], sid)
1562+ else:
1563+ self.fail('dbus_client.subscribe_share should be errbacking')
1564+
1565+ @inlineCallbacks
1566+ def test_unsubscribe_share(self):
1567+ """Unsubscribe to a share."""
1568+ share = FakedSyncDaemonTool.create_share(name='to be unsubscribed',
1569+ accepted=True, subscribed=True)
1570+ sid = share['volume_id']
1571+ yield dbus_client.subscribe_share(sid)
1572+ # share is subscribed
1573+
1574+ yield dbus_client.unsubscribe_share(sid)
1575+
1576+ result = yield dbus_client.get_shares()
1577+ expected, = filter(lambda share: share['volume_id'] == sid, result)
1578+ self.assertEqual(expected['subscribed'], '')
1579+
1580+ @inlineCallbacks
1581+ def test_unsubscribe_share_error(self):
1582+ """Unsubscribe to a share."""
1583+ sid = u'does not exist'
1584+ try:
1585+ yield dbus_client.unsubscribe_share(sid)
1586+ except dbus_client.VolumesError, e:
1587+ self.assertEqual(e[0], sid)
1588+ else:
1589+ self.fail('dbus_client.unsubscribe_share should be errbacking')
1590+
1591+
1592 class StatusMockDBusSyncDaemon(dbus.service.Object):
1593 """A mock object that mimicks syncdaemon regarding the Status iface."""
1594
1595@@ -549,6 +701,8 @@
1596 """A mock object that mimicks syncdaemon."""
1597
1598 ROOT_DIR = u'/yadda/yoda/Test me'
1599+ SHARES_DIR = u'/yadda/yoda/.hidden/ugly/dir/name/shares'
1600+ SHARES_DIR_LINK = u'/yadda/yoda/Test me/Shared With Me'
1601
1602 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
1603 in_signature='', out_signature='s')
1604@@ -556,6 +710,18 @@
1605 """Return the root dir/mount point."""
1606 return self.ROOT_DIR
1607
1608+ @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
1609+ in_signature='', out_signature='s')
1610+ def get_sharesdir(self):
1611+ """Return the shares dir/mount point."""
1612+ return self.SHARES_DIR
1613+
1614+ @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
1615+ in_signature='', out_signature='s')
1616+ def get_sharesdir_link(self):
1617+ """Return the root dir/mount point."""
1618+ return self.SHARES_DIR_LINK
1619+
1620
1621 class BasicTestCase(DBusClientTestCase):
1622 """Test for the basic dbus client methods."""
1623@@ -571,3 +737,17 @@
1624 root = yield dbus_client.get_root_dir()
1625
1626 self.assertEqual(MockDBusSyncDaemon.ROOT_DIR, root)
1627+
1628+ @inlineCallbacks
1629+ def test_get_shares_dir(self):
1630+ """Retrieve current syncdaemon shares dir."""
1631+ result = yield dbus_client.get_shares_dir()
1632+
1633+ self.assertEqual(MockDBusSyncDaemon.SHARES_DIR, result)
1634+
1635+ @inlineCallbacks
1636+ def test_get_shares_dir_link(self):
1637+ """Retrieve current syncdaemon shares dir."""
1638+ result = yield dbus_client.get_shares_dir_link()
1639+
1640+ self.assertEqual(MockDBusSyncDaemon.SHARES_DIR_LINK, result)
1641
1642=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
1643--- ubuntuone/controlpanel/tests/__init__.py 2011-01-25 19:08:59 +0000
1644+++ ubuntuone/controlpanel/tests/__init__.py 2011-02-12 03:22:55 +0000
1645@@ -161,8 +161,14 @@
1646 u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
1647 ]
1648
1649+ROOT_PATH = u'/home/tester/Ubuntu One'
1650+SHARES_PATH = u'/home/tester/.local/share/ubuntuone/shares'
1651+SHARES_PATH_LINK = ROOT_PATH + u'/Shared With Me'
1652+
1653 SAMPLE_SHARES = [
1654+
1655 {u'accepted': u'True',
1656+ u'subscribed': u'True',
1657 u'access_level': u'View',
1658 u'free_bytes': u'39892622746',
1659 u'generation': u'2704',
1660@@ -170,11 +176,12 @@
1661 u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
1662 u'other_username': u'otheruser',
1663 u'other_visible_name': u'Other User',
1664- u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
1665+ u'path': SHARES_PATH + u'/re from Other User',
1666 u'type': u'Share',
1667 u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
1668
1669 {u'accepted': u'True',
1670+ u'subscribed': u'True',
1671 u'access_level': u'Modify',
1672 u'free_bytes': u'39892622746',
1673 u'generation': u'2704',
1674@@ -182,9 +189,78 @@
1675 u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
1676 u'other_username': u'otheruser',
1677 u'other_visible_name': u'Other User',
1678- u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
1679+ u'path': SHARES_PATH + u'/do from Other User',
1680 u'type': u'Share',
1681 u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
1682+
1683+ {u'name': u'yadda',
1684+ u'subscribed': u'True',
1685+ u'generation': u'36',
1686+ u'other_username': u'https://login.ubuntu.com/+id/nHRnYmz',
1687+ u'other_visible_name': u'',
1688+ u'access_level': u'View',
1689+ u'node_id': u'ca3a1cec-09d2-485e-9685-1a5180bd6441',
1690+ u'volume_id': u'f1f1741f-ba49-46ef-b682-816c97e6e3d6',
1691+ u'free_bytes': u'2146703403',
1692+ u'path': SHARES_PATH + u'/yadda from ',
1693+ u'accepted': u'True',
1694+ u'type': u'Share'},
1695+
1696+ {u'name': u'images',
1697+ u'subscribed': u'True',
1698+ u'generation': u'36',
1699+ u'other_username':
1700+ u'https://login.ubuntu.com/+id/nHRnYmz',
1701+ u'other_visible_name': u'',
1702+ u'access_level': u'Modify',
1703+ u'node_id':
1704+ u'b932f0d3-6998-423f-9225-7683a4adbd6f',
1705+ u'volume_id':
1706+ u'a73f889d-ffd3-4447-b351-f0d8130d1e1a',
1707+ u'free_bytes': u'2146703403',
1708+ u'path': SHARES_PATH + u'/images from ',
1709+ u'accepted': u'True',
1710+ u'type': u'Share'},
1711+
1712+ {u'name': u'read-only',
1713+ u'subscribed': u'True',
1714+ u'generation': u'315',
1715+ u'other_username': u'sharing-user',
1716+ u'other_visible_name': u'A Sharing User',
1717+ u'access_level': u'View',
1718+ u'node_id': u'fd944823-e4c2-45a4-8c95-05997698bdbc',
1719+ u'volume_id':
1720+ u'64b43c96-6c7c-4135-994f-03a1a3774512',
1721+ u'free_bytes': u'108786811673',
1722+ u'path': SHARES_PATH + u'/read-only from A Sharing User',
1723+ u'accepted': u'True',
1724+ u'type': u'Share'},
1725+
1726+ {u'name': u'read-write',
1727+ u'subscribed': u'True',
1728+ u'generation': u'314',
1729+ u'other_username': u'sharing-user',
1730+ u'other_visible_name': u'A Sharing User',
1731+ u'access_level': u'Modify',
1732+ u'node_id': u'e95662f7-9979-4745-8c21-8edaf893f143',
1733+ u'volume_id': u'8896e8f8-57a3-4bf9-8558-fc54b7a3a777',
1734+ u'free_bytes': u'108786811673',
1735+ u'path': SHARES_PATH + u'/read-write from A Sharing User',
1736+ u'accepted': u'True',
1737+ u'type': u'Share'},
1738+
1739+ {u'accepted': u'',
1740+ u'access_level': u'View',
1741+ u'free_bytes': u'',
1742+ u'generation': u'',
1743+ u'name': u'unaccepted',
1744+ u'node_id': u'67b61c92-855c-49d8-8e09-6d201d15c999',
1745+ u'other_username': u'bad guy',
1746+ u'other_visible_name': u'',
1747+ u'path': SHARES_PATH + u'/unaccepted from ',
1748+ u'subscribed': u'',
1749+ u'type': u'Share',
1750+ u'volume_id': u'19963c95-c684-48db-8668-ebe6d820d5c3'},
1751 ]
1752
1753 SAMPLE_SHARED = [
1754@@ -196,7 +272,7 @@
1755 u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
1756 u'other_username': u'otheruser',
1757 u'other_visible_name': u'Other User',
1758- u'path': u'/home/tester/Ubuntu One/bar',
1759+ u'path': ROOT_PATH + u'/bar',
1760 u'type': u'Shared',
1761 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
1762 ]
1763
1764=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
1765--- ubuntuone/controlpanel/tests/test_backend.py 2011-01-25 19:08:59 +0000
1766+++ ubuntuone/controlpanel/tests/test_backend.py 2011-02-12 03:22:55 +0000
1767@@ -19,6 +19,8 @@
1768
1769 """Tests for the control panel backend."""
1770
1771+from collections import defaultdict
1772+
1773 import simplejson
1774
1775 from twisted.internet import defer
1776@@ -42,6 +44,7 @@
1777 EXPECTED_ACCOUNT_INFO,
1778 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
1779 EXPECTED_DEVICES_INFO,
1780+ ROOT_PATH,
1781 SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
1782 SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
1783 SAMPLE_DEVICES_JSON,
1784@@ -49,6 +52,8 @@
1785 SAMPLE_QUOTA_JSON,
1786 SAMPLE_SHARED,
1787 SAMPLE_SHARES,
1788+ SHARES_PATH,
1789+ SHARES_PATH_LINK,
1790 TOKEN,
1791 )
1792 from ubuntuone.controlpanel.webclient import WebClientError
1793@@ -86,8 +91,8 @@
1794 }
1795 status_changed_handler = None
1796 subscribed_folders = []
1797+ subscribed_shares = []
1798 actions = []
1799- root_dir = u'/home/tester/Something/Pepe Mosquito'
1800
1801 def get_credentials(self):
1802 """Return the mock credentials."""
1803@@ -145,7 +150,15 @@
1804
1805 def get_root_dir(self):
1806 """Grab the root dir."""
1807- return self.root_dir
1808+ return ROOT_PATH
1809+
1810+ def get_shares_dir(self):
1811+ """Grab the shares dir."""
1812+ return SHARES_PATH
1813+
1814+ def get_shares_dir_link(self):
1815+ """Grab the shares dir."""
1816+ return SHARES_PATH_LINK
1817
1818 def get_folders(self):
1819 """Grab list of folders."""
1820@@ -153,11 +166,23 @@
1821
1822 def subscribe_folder(self, volume_id):
1823 """Subcribe to 'volume_id'."""
1824- self.subscribed_folders.append(volume_id)
1825+ MockDBusClient.subscribed_folders.append(volume_id)
1826
1827 def unsubscribe_folder(self, volume_id):
1828 """Unsubcribe from 'volume_id'."""
1829- self.subscribed_folders.remove(volume_id)
1830+ MockDBusClient.subscribed_folders.remove(volume_id)
1831+
1832+ def get_shares(self):
1833+ """Grab list of shares."""
1834+ return SAMPLE_SHARES
1835+
1836+ def subscribe_share(self, volume_id):
1837+ """Subcribe to 'volume_id'."""
1838+ MockDBusClient.subscribed_shares.append(volume_id)
1839+
1840+ def unsubscribe_share(self, volume_id):
1841+ """Unsubcribe from 'volume_id'."""
1842+ MockDBusClient.subscribed_shares.remove(volume_id)
1843
1844 def get_current_status(self):
1845 """Grab syncdaemon status."""
1846@@ -167,10 +192,6 @@
1847 """Connect a handler for tracking syncdaemon status changes."""
1848 self.status_changed_handler = handler
1849
1850- def get_shares(self):
1851- """Grab list of shares (shares from others to the user)."""
1852- return SAMPLE_SHARES
1853-
1854 def get_shared(self):
1855 """Grab list of shared (shares from the user to others)."""
1856 return SAMPLE_SHARED
1857@@ -370,40 +391,106 @@
1858 class BackendVolumesTestCase(BackendBasicTestCase):
1859 """Volumes tests for the backend."""
1860
1861+ def setUp(self):
1862+ super(BackendVolumesTestCase, self).setUp()
1863+ # fake quota info and calculate free bytes
1864+ # pylint: disable=E1101
1865+ self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN
1866+ self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
1867+
1868 @inlineCallbacks
1869 def test_volumes_info(self):
1870 """The volumes_info method exercises its callback."""
1871- # fake quota info and calculate free bytes
1872- # pylint: disable=E1101
1873- self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN
1874- self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
1875 result = yield self.be.account_info()
1876 free_bytes = int(result['quota_total']) - int(result['quota_used'])
1877
1878 # get root dir info
1879- root_dir = MockDBusClient.root_dir
1880- root_volume = {u'volume_id': u'', u'path': root_dir,
1881- u'subscribed': 'True', u'type': u'ROOT'}
1882+ root_volume = {u'volume_id': u'', u'path': ROOT_PATH,
1883+ u'subscribed': 'True', u'type': self.be.ROOT_TYPE}
1884+
1885+ # get shares and group by sharing user
1886+ shares = defaultdict(list)
1887+ for share in SAMPLE_SHARES:
1888+ # filter out non accepted values
1889+ if not bool(share['accepted']):
1890+ continue
1891+
1892+ share = share.copy()
1893+
1894+ nicer_path = share[u'path'].replace(SHARES_PATH, SHARES_PATH_LINK)
1895+ share[u'path'] = nicer_path
1896+ share[u'type'] = self.be.SHARE_TYPE
1897+
1898+ username = share['other_visible_name']
1899+ if not username:
1900+ username = share['other_username'] + \
1901+ ' (%s)' % self.be.NAME_NOT_SET
1902+
1903+ shares[username].append(share)
1904+
1905+ folders = []
1906+ for folder in SAMPLE_FOLDERS:
1907+ folder = folder.copy()
1908+ folder[u'type'] = self.be.FOLDER_TYPE
1909+ folders.append(folder)
1910+
1911+ expected = [(u'', unicode(free_bytes), [root_volume] + folders)]
1912+ for other_user, data in shares.iteritems():
1913+ expected.append((other_user, data[0][u'free_bytes'], data))
1914
1915 result = yield self.be.volumes_info()
1916-
1917- expected = [('', unicode(free_bytes), [root_volume] + SAMPLE_FOLDERS)]
1918- # later, we should unify folders and shares info
1919 self.assertEqual(result, expected)
1920
1921- @inlineCallbacks
1922- def test_subscribe_volume(self):
1923+ # pylint: disable=W0212
1924+
1925+ def test_cached_volumes_are_initially_empty(self):
1926+ """The cached volume list is empty."""
1927+ self.assertEqual(self.be._volumes, {})
1928+
1929+ @inlineCallbacks
1930+ def test_volumes_are_cached(self):
1931+ """The volume list is cached."""
1932+ # get root dir info
1933+ root_volume = {u'volume_id': u'', u'path': ROOT_PATH,
1934+ u'subscribed': 'True', u'type': self.be.ROOT_TYPE}
1935+ expected = {u'': root_volume}
1936+ for volume in SAMPLE_SHARES + SAMPLE_FOLDERS:
1937+ if volume[u'type'] == u'UDF':
1938+ volume[u'type'] = self.be.FOLDER_TYPE
1939+ else:
1940+ volume[u'type'] = self.be.SHARE_TYPE
1941+
1942+ sid = volume['volume_id']
1943+ assert sid not in expected
1944+ expected[sid] = volume
1945+
1946+ _ = yield self.be.volumes_info()
1947+
1948+ self.assertEqual(self.be._volumes, expected)
1949+
1950+ @inlineCallbacks
1951+ def test_cached_volumes_are_updated_with_volume_info(self):
1952+ """The cached volume list is updated."""
1953+ yield self.test_volumes_are_cached()
1954+ yield self.test_volumes_are_cached()
1955+
1956+ @inlineCallbacks
1957+ def test_subscribe_volume_folder(self):
1958 """The subscribe_volume method subscribes a folder."""
1959 fid = '0123-4567'
1960+ self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
1961+
1962 yield self.be.subscribe_volume(volume_id=fid)
1963 self.addCleanup(lambda: MockDBusClient.subscribed_folders.remove(fid))
1964
1965 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
1966
1967 @inlineCallbacks
1968- def test_unsubscribe_volume(self):
1969+ def test_unsubscribe_volume_folder(self):
1970 """The unsubscribe_volume method unsubscribes a folder."""
1971 fid = '0123-4567'
1972+ self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
1973+
1974 yield self.be.subscribe_volume(volume_id=fid)
1975 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
1976
1977@@ -412,9 +499,34 @@
1978 self.assertEqual(MockDBusClient.subscribed_folders, [])
1979
1980 @inlineCallbacks
1981- def test_change_volume_settings(self):
1982+ def test_subscribe_volume_share(self):
1983+ """The subscribe_volume method subscribes a share."""
1984+ sid = '0123-4567'
1985+ self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
1986+
1987+ yield self.be.subscribe_volume(volume_id=sid)
1988+ self.addCleanup(lambda: MockDBusClient.subscribed_shares.remove(sid))
1989+
1990+ self.assertEqual(MockDBusClient.subscribed_shares, [sid])
1991+
1992+ @inlineCallbacks
1993+ def test_unsubscribe_volume_share(self):
1994+ """The unsubscribe_volume method unsubscribes a share."""
1995+ sid = '0123-4567'
1996+ self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
1997+
1998+ yield self.be.subscribe_volume(volume_id=sid)
1999+ self.assertEqual(MockDBusClient.subscribed_shares, [sid])
2000+
2001+ yield self.be.unsubscribe_volume(volume_id=sid)
2002+
2003+ self.assertEqual(MockDBusClient.subscribed_shares, [])
2004+
2005+ @inlineCallbacks
2006+ def test_change_volume_settings_folder(self):
2007 """The volume settings can be changed."""
2008 fid = '0123-4567'
2009+ self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
2010
2011 yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
2012 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
2013@@ -423,10 +535,23 @@
2014 self.assertEqual(MockDBusClient.subscribed_folders, [])
2015
2016 @inlineCallbacks
2017+ def test_change_volume_settings_share(self):
2018+ """The volume settings can be changed."""
2019+ sid = '0123-4567'
2020+ self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
2021+
2022+ yield self.be.change_volume_settings(sid, {'subscribed': 'True'})
2023+ self.assertEqual(MockDBusClient.subscribed_shares, [sid])
2024+
2025+ yield self.be.change_volume_settings(sid, {'subscribed': ''})
2026+ self.assertEqual(MockDBusClient.subscribed_shares, [])
2027+
2028+ @inlineCallbacks
2029 def test_change_volume_settings_no_setting(self):
2030 """The change volume settings does not fail on empty settings."""
2031 yield self.be.change_volume_settings('test', {})
2032 self.assertEqual(MockDBusClient.subscribed_folders, [])
2033+ self.assertEqual(MockDBusClient.subscribed_shares, [])
2034
2035
2036 class BackendSyncStatusTestCase(BackendBasicTestCase):

Subscribers

People subscribed via source and target branches

to all changes: