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
=== modified file 'PKG-INFO'
--- PKG-INFO 2011-02-04 20:02:28 +0000
+++ PKG-INFO 2011-02-12 03:22:55 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.11Metadata-Version: 1.1
2Name: ubuntuone-control-panel2Name: ubuntuone-control-panel
3Version: 0.8.23Version: 0.8.3
4Summary: Ubuntu One Control Panel4Summary: Ubuntu One Control Panel
5Home-page: https://launchpad.net/ubuntuone-control-panel5Home-page: https://launchpad.net/ubuntuone-control-panel
6Author: Natalia Bidart6Author: Natalia Bidart
@@ -8,4 +8,26 @@
8License: GPL v38License: GPL v3
9Description: Application to manage a Ubuntu One account. Provides aDBus service to query/modify all the Ubuntu One bits.9Description: Application to manage a Ubuntu One account. Provides aDBus service to query/modify all the Ubuntu One bits.
10Platform: UNKNOWN10Platform: UNKNOWN
11Requires: apt
12Requires: aptdaemon
13Requires: dbus
14Requires: defer
15Requires: desktopcouch.application.replication_services
16Requires: gi.repository
17Requires: gobject
18Requires: gtk
19Requires: mocker
20Requires: oauth
21Requires: pango
22Requires: simplejson
23Requires: twisted.application
24Requires: twisted.internet
25Requires: twisted.python.failure
26Requires: twisted.web
27Requires: ubuntu_sso
28Requires: ubuntuone.clientdefs
29Requires: ubuntuone.devtools.handlers
30Requires: ubuntuone.devtools.testcase
31Requires: ubuntuone.logger
32Requires: ubuntuone.platform.linux
11Provides: ubuntuone.controlpanel33Provides: ubuntuone.controlpanel
1234
=== added file 'data/contacts.png'
13Binary files data/contacts.png 1970-01-01 00:00:00 +0000 and data/contacts.png 2011-02-12 03:22:55 +0000 differ35Binary files data/contacts.png 1970-01-01 00:00:00 +0000 and data/contacts.png 2011-02-12 03:22:55 +0000 differ
=== modified file 'data/device.ui'
--- data/device.ui 2011-01-25 19:08:59 +0000
+++ data/device.ui 2011-02-12 03:22:55 +0000
@@ -154,7 +154,7 @@
154 <child>154 <child>
155 <object class="GtkVButtonBox" id="vbuttonbox1">155 <object class="GtkVButtonBox" id="vbuttonbox1">
156 <property name="visible">True</property>156 <property name="visible">True</property>
157 <property name="layout_style">center</property>157 <property name="layout_style">start</property>
158 <child>158 <child>
159 <object class="GtkButton" id="remove">159 <object class="GtkButton" id="remove">
160 <property name="label">gtk-remove</property>160 <property name="label">gtk-remove</property>
161161
=== added file 'data/files.png'
162Binary files data/files.png 1970-01-01 00:00:00 +0000 and data/files.png 2011-02-12 03:22:55 +0000 differ162Binary files data/files.png 1970-01-01 00:00:00 +0000 and data/files.png 2011-02-12 03:22:55 +0000 differ
=== modified file 'data/management.ui'
--- data/management.ui 2011-01-25 19:08:59 +0000
+++ data/management.ui 2011-02-12 03:22:55 +0000
@@ -20,25 +20,14 @@
20 <property name="border_width">10</property>20 <property name="border_width">10</property>
21 <property name="spacing">10</property>21 <property name="spacing">10</property>
22 <child>22 <child>
23 <object class="GtkHBox" id="quota_box">23 <object class="GtkVBox" id="quota_box">
24 <property name="visible">True</property>24 <property name="visible">True</property>
25 <property name="can_focus">False</property>25 <property name="spacing">5</property>
26 <child>26 <child>
27 <object class="GtkAlignment" id="alignment1">27 <object class="GtkProgressBar" id="quota_progressbar">
28 <property name="visible">True</property>28 <property name="visible">True</property>
29 <property name="can_focus">False</property>
30 <property name="xscale">0</property>
31 <property name="yscale">0</property>
32 <child>
33 <object class="GtkProgressBar" id="quota_progressbar">
34 <property name="visible">True</property>
35 <property name="can_focus">False</property>
36 </object>
37 </child>
38 </object>29 </object>
39 <packing>30 <packing>
40 <property name="expand">True</property>
41 <property name="fill">True</property>
42 <property name="position">0</property>31 <property name="position">0</property>
43 </packing>32 </packing>
44 </child>33 </child>
@@ -117,7 +106,6 @@
117 <property name="label" translatable="yes">Shares</property>106 <property name="label" translatable="yes">Shares</property>
118 <property name="can_focus">True</property>107 <property name="can_focus">True</property>
119 <property name="receives_default">False</property>108 <property name="receives_default">False</property>
120 <property name="use_action_appearance">False</property>
121 <property name="draw_indicator">False</property>109 <property name="draw_indicator">False</property>
122 <property name="group">dashboard_button</property>110 <property name="group">dashboard_button</property>
123 </object>111 </object>
@@ -133,7 +121,6 @@
133 <property name="visible">True</property>121 <property name="visible">True</property>
134 <property name="can_focus">True</property>122 <property name="can_focus">True</property>
135 <property name="receives_default">False</property>123 <property name="receives_default">False</property>
136 <property name="use_action_appearance">False</property>
137 <property name="draw_indicator">False</property>124 <property name="draw_indicator">False</property>
138 <property name="group">dashboard_button</property>125 <property name="group">dashboard_button</property>
139 </object>126 </object>
@@ -148,7 +135,6 @@
148 <property name="label" translatable="yes">Services</property>135 <property name="label" translatable="yes">Services</property>
149 <property name="can_focus">True</property>136 <property name="can_focus">True</property>
150 <property name="receives_default">False</property>137 <property name="receives_default">False</property>
151 <property name="use_action_appearance">False</property>
152 <property name="draw_indicator">False</property>138 <property name="draw_indicator">False</property>
153 <property name="group">dashboard_button</property>139 <property name="group">dashboard_button</property>
154 </object>140 </object>
@@ -161,44 +147,36 @@
161 </object>147 </object>
162 <packing>148 <packing>
163 <property name="expand">False</property>149 <property name="expand">False</property>
164 <property name="fill">True</property>
165 <property name="position">1</property>150 <property name="position">1</property>
166 </packing>151 </packing>
167 </child>152 </child>
168 <child>153 <child>
169 <object class="GtkHSeparator" id="hseparator2">154 <object class="GtkHSeparator" id="hseparator2">
170 <property name="visible">True</property>155 <property name="visible">True</property>
171 <property name="can_focus">False</property>
172 </object>156 </object>
173 <packing>157 <packing>
174 <property name="expand">True</property>
175 <property name="fill">True</property>
176 <property name="position">2</property>158 <property name="position">2</property>
177 </packing>159 </packing>
178 </child>160 </child>
179 </object>161 </object>
180 <packing>162 <packing>
181 <property name="expand">False</property>163 <property name="expand">False</property>
182 <property name="fill">True</property>
183 <property name="position">0</property>164 <property name="position">0</property>
184 </packing>165 </packing>
185 </child>166 </child>
186 <child>167 <child>
187 <object class="GtkImage" id="image1">168 <object class="GtkImage" id="image1">
188 <property name="visible">True</property>169 <property name="visible">True</property>
189 <property name="can_focus">False</property>
190 <property name="pixbuf">banner.png</property>170 <property name="pixbuf">banner.png</property>
191 </object>171 </object>
192 <packing>172 <packing>
193 <property name="expand">False</property>173 <property name="expand">False</property>
194 <property name="fill">True</property>
195 <property name="position">1</property>174 <property name="position">1</property>
196 </packing>175 </packing>
197 </child>176 </child>
198 </object>177 </object>
199 <packing>178 <packing>
200 <property name="expand">False</property>179 <property name="expand">False</property>
201 <property name="fill">True</property>
202 <property name="position">1</property>180 <property name="position">1</property>
203 </packing>181 </packing>
204 </child>182 </child>
@@ -207,21 +185,17 @@
207 </object>185 </object>
208 <packing>186 <packing>
209 <property name="expand">False</property>187 <property name="expand">False</property>
210 <property name="fill">True</property>
211 <property name="position">0</property>188 <property name="position">0</property>
212 </packing>189 </packing>
213 </child>190 </child>
214 <child>191 <child>
215 <object class="GtkNotebook" id="notebook">192 <object class="GtkNotebook" id="notebook">
216 <property name="visible">True</property>193 <property name="visible">True</property>
217 <property name="can_focus">False</property>
218 <property name="show_tabs">False</property>194 <property name="show_tabs">False</property>
219 <property name="show_border">False</property>195 <property name="show_border">False</property>
220 <property name="homogeneous">True</property>196 <property name="homogeneous">True</property>
221 </object>197 </object>
222 <packing>198 <packing>
223 <property name="expand">True</property>
224 <property name="fill">True</property>
225 <property name="position">1</property>199 <property name="position">1</property>
226 </packing>200 </packing>
227 </child>201 </child>
228202
=== added file 'data/music.png'
229Binary files data/music.png 1970-01-01 00:00:00 +0000 and data/music.png 2011-02-12 03:22:55 +0000 differ203Binary files data/music.png 1970-01-01 00:00:00 +0000 and data/music.png 2011-02-12 03:22:55 +0000 differ
=== added file 'data/notes.png'
230Binary files data/notes.png 1970-01-01 00:00:00 +0000 and data/notes.png 2011-02-12 03:22:55 +0000 differ204Binary files data/notes.png 1970-01-01 00:00:00 +0000 and data/notes.png 2011-02-12 03:22:55 +0000 differ
=== modified file 'data/overview.ui'
--- data/overview.ui 2011-01-25 19:08:59 +0000
+++ data/overview.ui 2011-02-12 03:22:55 +0000
@@ -4,74 +4,263 @@
4 <!-- interface-naming-policy project-wide -->4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
7 <child>8 <child>
8 <object class="GtkImage" id="image">9 <object class="GtkEventBox" id="header">
9 <property name="visible">True</property>10 <property name="visible">True</property>
10 <property name="pixbuf">overview.png</property>11 <property name="can_focus">False</property>
12 <child>
13 <object class="GtkImage" id="image">
14 <property name="visible">True</property>
15 <property name="can_focus">False</property>
16 <property name="pixbuf">overview.png</property>
17 </object>
18 </child>
11 </object>19 </object>
12 <packing>20 <packing>
13 <property name="expand">False</property>21 <property name="expand">False</property>
22 <property name="fill">True</property>
14 <property name="position">0</property>23 <property name="position">0</property>
15 </packing>24 </packing>
16 </child>25 </child>
17 <child>26 <child>
18 <object class="GtkAlignment" id="alignment1">27 <object class="GtkScrolledWindow" id="scrolledwindow1">
19 <property name="visible">True</property>28 <property name="visible">True</property>
20 <property name="yscale">0</property>29 <property name="can_focus">True</property>
30 <property name="hscrollbar_policy">automatic</property>
31 <property name="vscrollbar_policy">automatic</property>
21 <child>32 <child>
22 <object class="GtkHBox" id="hbox1">33 <object class="GtkViewport" id="viewport1">
23 <property name="visible">True</property>34 <property name="visible">True</property>
24 <property name="border_width">20</property>35 <property name="can_focus">False</property>
25 <property name="spacing">5</property>36 <property name="shadow_type">none</property>
26 <child>37 <child>
27 <object class="GtkVBox" id="messages">38 <object class="GtkHBox" id="hbox1">
28 <property name="visible">True</property>39 <property name="visible">True</property>
29 <property name="spacing">10</property>40 <property name="can_focus">False</property>
41 <property name="border_width">20</property>
42 <property name="spacing">5</property>
30 <child>43 <child>
31 <placeholder/>44 <object class="GtkTable" id="table1">
45 <property name="visible">True</property>
46 <property name="can_focus">False</property>
47 <property name="n_rows">4</property>
48 <property name="n_columns">2</property>
49 <property name="column_spacing">10</property>
50 <property name="row_spacing">10</property>
51 <child>
52 <object class="GtkImage" id="image1">
53 <property name="visible">True</property>
54 <property name="can_focus">False</property>
55 <property name="pixbuf">files.png</property>
56 </object>
57 <packing>
58 <property name="x_options">GTK_FILL</property>
59 <property name="y_options">GTK_FILL</property>
60 </packing>
61 </child>
62 <child>
63 <object class="GtkImage" id="image2">
64 <property name="visible">True</property>
65 <property name="can_focus">False</property>
66 <property name="pixbuf">contacts.png</property>
67 </object>
68 <packing>
69 <property name="top_attach">1</property>
70 <property name="bottom_attach">2</property>
71 <property name="x_options">GTK_FILL</property>
72 <property name="y_options">GTK_FILL</property>
73 </packing>
74 </child>
75 <child>
76 <object class="GtkImage" id="image3">
77 <property name="visible">True</property>
78 <property name="can_focus">False</property>
79 <property name="pixbuf">music.png</property>
80 </object>
81 <packing>
82 <property name="top_attach">2</property>
83 <property name="bottom_attach">3</property>
84 <property name="x_options">GTK_FILL</property>
85 <property name="y_options">GTK_FILL</property>
86 </packing>
87 </child>
88 <child>
89 <object class="GtkImage" id="image4">
90 <property name="visible">True</property>
91 <property name="can_focus">False</property>
92 <property name="pixbuf">notes.png</property>
93 </object>
94 <packing>
95 <property name="top_attach">3</property>
96 <property name="bottom_attach">4</property>
97 <property name="x_options">GTK_FILL</property>
98 <property name="y_options">GTK_FILL</property>
99 </packing>
100 </child>
101 <child>
102 <object class="GtkLabel" id="label3">
103 <property name="visible">True</property>
104 <property name="can_focus">False</property>
105 <property name="xalign">0</property>
106 <property name="label" translatable="yes">Files Anywhere
107&lt;span foreground="#909090"&gt;Backup and access your files from Windows, Ubuntu, or Mobile&lt;/span&gt;</property>
108 <property name="use_markup">True</property>
109 <property name="wrap">True</property>
110 </object>
111 <packing>
112 <property name="left_attach">1</property>
113 <property name="right_attach">2</property>
114 </packing>
115 </child>
116 <child>
117 <object class="GtkLabel" id="label4">
118 <property name="visible">True</property>
119 <property name="can_focus">False</property>
120 <property name="xalign">0</property>
121 <property name="label" translatable="yes">Keep connected
122&lt;span foreground="#909090"&gt;Unify your contacts accress Desktop, Mobile and Web&lt;/span&gt;</property>
123 <property name="use_markup">True</property>
124 <property name="wrap">True</property>
125 </object>
126 <packing>
127 <property name="left_attach">1</property>
128 <property name="right_attach">2</property>
129 <property name="top_attach">1</property>
130 <property name="bottom_attach">2</property>
131 </packing>
132 </child>
133 <child>
134 <object class="GtkLabel" id="label5">
135 <property name="visible">True</property>
136 <property name="can_focus">False</property>
137 <property name="xalign">0</property>
138 <property name="label" translatable="yes">Rock Out
139&lt;span foreground="#909090"&gt;Your library at your fingertips with Android, iPhone, and AirPlay
140Plus the Ubuntu One Music store to grow your collection&lt;/span&gt;</property>
141 <property name="use_markup">True</property>
142 <property name="wrap">True</property>
143 </object>
144 <packing>
145 <property name="left_attach">1</property>
146 <property name="right_attach">2</property>
147 <property name="top_attach">2</property>
148 <property name="bottom_attach">3</property>
149 </packing>
150 </child>
151 <child>
152 <object class="GtkLabel" id="label6">
153 <property name="visible">True</property>
154 <property name="can_focus">False</property>
155 <property name="xalign">0</property>
156 <property name="label" translatable="yes">Stay Productive
157&lt;span foreground="#909090"&gt;Keep your Firefox bookmarks and Tomboy notes synced&lt;/span&gt;</property>
158 <property name="use_markup">True</property>
159 <property name="wrap">True</property>
160 </object>
161 <packing>
162 <property name="left_attach">1</property>
163 <property name="right_attach">2</property>
164 <property name="top_attach">3</property>
165 <property name="bottom_attach">4</property>
166 </packing>
167 </child>
168 </object>
169 <packing>
170 <property name="expand">True</property>
171 <property name="fill">True</property>
172 <property name="position">0</property>
173 </packing>
32 </child>174 </child>
33 </object>
34 <packing>
35 <property name="position">0</property>
36 </packing>
37 </child>
38 <child>
39 <object class="GtkAlignment" id="alignment2">
40 <property name="visible">True</property>
41 <property name="xscale">0</property>
42 <property name="yscale">0</property>
43 <child>175 <child>
44 <object class="GtkVBox" id="vbox2">176 <object class="GtkVBox" id="vbox1">
45 <property name="visible">True</property>177 <property name="visible">True</property>
46 <property name="spacing">10</property>178 <property name="can_focus">False</property>
47 <child>179 <child>
48 <object class="GtkButton" id="join_now_button">180 <object class="GtkLabel" id="warning_label">
49 <property name="visible">True</property>181 <property name="visible">True</property>
50 <property name="can_focus">True</property>182 <property name="can_focus">False</property>
51 <property name="can_default">True</property>183 <property name="label">A warning label that can be long. Possible really long, let's see how it behaves.</property>
52 <property name="receives_default">True</property>184 <property name="wrap">True</property>
53 <signal name="clicked" handler="on_join_now_button_clicked"/>185 </object>
186 <packing>
187 <property name="expand">False</property>
188 <property name="fill">True</property>
189 <property name="position">0</property>
190 </packing>
191 </child>
192 <child>
193 <object class="GtkAlignment" id="alignment2">
194 <property name="visible">True</property>
195 <property name="can_focus">False</property>
196 <property name="xscale">0</property>
197 <property name="yscale">0</property>
54 <child>198 <child>
55 <object class="GtkVBox" id="vbox1">199 <object class="GtkVBox" id="vbox2">
56 <property name="visible">True</property>200 <property name="visible">True</property>
57 <property name="spacing">5</property>201 <property name="can_focus">False</property>
202 <property name="spacing">10</property>
58 <child>203 <child>
59 <object class="GtkLabel" id="label1">204 <object class="GtkButton" id="join_now_button">
60 <property name="visible">True</property>205 <property name="visible">True</property>
61 <property name="label" translatable="yes">&lt;span font_size="xx-large"&gt;Join now&lt;/span&gt;</property>206 <property name="can_focus">True</property>
62 <property name="use_markup">True</property>207 <property name="can_default">True</property>
208 <property name="receives_default">True</property>
209 <property name="use_action_appearance">False</property>
210 <signal name="clicked" handler="on_join_now_button_clicked" swapped="no"/>
211 <child>
212 <object class="GtkVBox" id="vbox3">
213 <property name="visible">True</property>
214 <property name="can_focus">False</property>
215 <property name="spacing">5</property>
216 <child>
217 <object class="GtkLabel" id="label1">
218 <property name="visible">True</property>
219 <property name="can_focus">False</property>
220 <property name="label" translatable="yes">&lt;span font_size="xx-large"&gt;Join now&lt;/span&gt;</property>
221 <property name="use_markup">True</property>
222 </object>
223 <packing>
224 <property name="expand">True</property>
225 <property name="fill">True</property>
226 <property name="position">0</property>
227 </packing>
228 </child>
229 <child>
230 <object class="GtkLabel" id="label2">
231 <property name="visible">True</property>
232 <property name="can_focus">False</property>
233 <property name="label" translatable="yes">&lt;span foreground="#909090"&gt;2GB of free storage&lt;/span&gt;</property>
234 <property name="use_markup">True</property>
235 </object>
236 <packing>
237 <property name="expand">True</property>
238 <property name="fill">True</property>
239 <property name="position">1</property>
240 </packing>
241 </child>
242 </object>
243 </child>
63 </object>244 </object>
64 <packing>245 <packing>
246 <property name="expand">False</property>
247 <property name="fill">True</property>
65 <property name="position">0</property>248 <property name="position">0</property>
66 </packing>249 </packing>
67 </child>250 </child>
68 <child>251 <child>
69 <object class="GtkLabel" id="label2">252 <object class="GtkLinkButton" id="connect_button">
253 <property name="label" translatable="yes">I already have an account!</property>
70 <property name="visible">True</property>254 <property name="visible">True</property>
71 <property name="label" translatable="yes">&lt;span foreground="grey"&gt;2GB of free storage&lt;/span&gt;</property>255 <property name="can_focus">True</property>
72 <property name="use_markup">True</property>256 <property name="receives_default">True</property>
257 <property name="use_action_appearance">False</property>
258 <property name="relief">none</property>
259 <signal name="clicked" handler="on_connect_button_clicked" swapped="no"/>
73 </object>260 </object>
74 <packing>261 <packing>
262 <property name="expand">False</property>
263 <property name="fill">False</property>
75 <property name="position">1</property>264 <property name="position">1</property>
76 </packing>265 </packing>
77 </child>266 </child>
@@ -79,50 +268,28 @@
79 </child>268 </child>
80 </object>269 </object>
81 <packing>270 <packing>
82 <property name="expand">False</property>271 <property name="expand">True</property>
83 <property name="position">0</property>272 <property name="fill">True</property>
84 </packing>
85 </child>
86 <child>
87 <object class="GtkLinkButton" id="connect_button">
88 <property name="label" translatable="yes">I already have an account!</property>
89 <property name="visible">True</property>
90 <property name="can_focus">True</property>
91 <property name="receives_default">True</property>
92 <property name="relief">none</property>
93 <signal name="clicked" handler="on_connect_button_clicked"/>
94 </object>
95 <packing>
96 <property name="expand">False</property>
97 <property name="fill">False</property>
98 <property name="position">1</property>273 <property name="position">1</property>
99 </packing>274 </packing>
100 </child>275 </child>
101 </object>276 </object>
277 <packing>
278 <property name="expand">False</property>
279 <property name="fill">True</property>
280 <property name="position">1</property>
281 </packing>
102 </child>282 </child>
103 </object>283 </object>
104 <packing>
105 <property name="expand">False</property>
106 <property name="position">1</property>
107 </packing>
108 </child>284 </child>
109 </object>285 </object>
110 </child>286 </child>
111 </object>287 </object>
112 <packing>288 <packing>
289 <property name="expand">True</property>
290 <property name="fill">True</property>
113 <property name="position">1</property>291 <property name="position">1</property>
114 </packing>292 </packing>
115 </child>293 </child>
116 <child>
117 <object class="GtkLabel" id="warning_label">
118 <property name="visible">True</property>
119 <property name="wrap">True</property>
120 <property name="ellipsize">end</property>
121 </object>
122 <packing>
123 <property name="expand">False</property>
124 <property name="position">2</property>
125 </packing>
126 </child>
127 </object>294 </object>
128</interface>295</interface>
129296
=== modified file 'data/volumes.ui'
--- data/volumes.ui 2011-01-25 19:08:59 +0000
+++ data/volumes.ui 2011-02-12 03:22:55 +0000
@@ -2,6 +2,26 @@
2<interface>2<interface>
3 <requires lib="gtk+" version="2.22"/>3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkTreeStore" id="volumes_store">
6 <columns>
7 <!-- column-name description -->
8 <column type="gchararray"/>
9 <!-- column-name subscribed -->
10 <column type="gboolean"/>
11 <!-- column-name icon-name -->
12 <column type="gchararray"/>
13 <!-- column-name subscribed-visible -->
14 <column type="gboolean"/>
15 <!-- column-name subscribed-sensitive -->
16 <column type="gboolean"/>
17 <!-- column-name icon-size -->
18 <column type="gint"/>
19 <!-- column-name identifier -->
20 <column type="gchararray"/>
21 <!-- column-name path -->
22 <column type="gchararray"/>
23 </columns>
24 </object>
5 <object class="GtkAlignment" id="itself">25 <object class="GtkAlignment" id="itself">
6 <property name="visible">True</property>26 <property name="visible">True</property>
7 <property name="can_focus">False</property>27 <property name="can_focus">False</property>
@@ -18,6 +38,7 @@
18 <property name="model">volumes_store</property>38 <property name="model">volumes_store</property>
19 <property name="rules_hint">True</property>39 <property name="rules_hint">True</property>
20 <property name="tooltip_column">0</property>40 <property name="tooltip_column">0</property>
41 <signal name="row-activated" handler="on_volumes_view_row_activated" swapped="no"/>
21 <child>42 <child>
22 <object class="GtkTreeViewColumn" id="treeviewcolumn2">43 <object class="GtkTreeViewColumn" id="treeviewcolumn2">
23 <property name="resizable">True</property>44 <property name="resizable">True</property>
@@ -46,7 +67,7 @@
46 <child>67 <child>
47 <object class="GtkTreeViewColumn" id="treeviewcolumn3">68 <object class="GtkTreeViewColumn" id="treeviewcolumn3">
48 <property name="sizing">autosize</property>69 <property name="sizing">autosize</property>
49 <property name="title">On this device?</property>70 <property name="title" translatable="yes">Sync locally?</property>
50 <child>71 <child>
51 <object class="GtkCellRendererToggle" id="cellrenderertoggle1">72 <object class="GtkCellRendererToggle" id="cellrenderertoggle1">
52 <property name="indicator_size">15</property>73 <property name="indicator_size">15</property>
@@ -73,22 +94,4 @@
73 </object>94 </object>
74 </child>95 </child>
75 </object>96 </object>
76 <object class="GtkTreeStore" id="volumes_store">
77 <columns>
78 <!-- column-name description -->
79 <column type="gchararray"/>
80 <!-- column-name subscribed -->
81 <column type="gboolean"/>
82 <!-- column-name icon-name -->
83 <column type="gchararray"/>
84 <!-- column-name subscribed-visible -->
85 <column type="gboolean"/>
86 <!-- column-name subscribed-sensitive -->
87 <column type="gboolean"/>
88 <!-- column-name icon-size -->
89 <column type="gint"/>
90 <!-- column-name identifier -->
91 <column type="gchararray"/>
92 </columns>
93 </object>
94</interface>97</interface>
9598
=== modified file 'debian/changelog'
--- debian/changelog 2011-02-07 07:55:07 +0000
+++ debian/changelog 2011-02-12 03:22:55 +0000
@@ -1,3 +1,20 @@
1ubuntuone-control-panel (0.8.3-0ubuntu1) natty; urgency=low
2
3 * New upstream release.
4 - Support share subscription in Folders tab (LP: #714583)
5 - Use more concise text on overview page (LP: #715732)
6 - Set widget names to style properly (LP: #716678)
7 - Place usage bar label on top (LP: #715713)
8 - Inconsistent placement of 'remove' button (LP: #715804)
9 - Message text should say 'your personal cloud' (LP: #715858)
10 - Shares to me path looks awful (LP: #716431)
11 - Clicking a folder should open it (LP: #716499)
12 - Subtext in 'join now' button is hard to read (LP: #716504)
13 - "On this device?" string is inconsistent with its action (LP: #717159)
14 - Devices don't necessarily have a sync service (LP: #717230)
15
16 -- Rodney Dawes <rodney.dawes@ubuntu.com> Fri, 11 Feb 2011 22:07:23 -0500
17
1ubuntuone-control-panel (0.8.2-0ubuntu1) natty; urgency=low18ubuntuone-control-panel (0.8.2-0ubuntu1) natty; urgency=low
219
3 * New upstream release:20 * New upstream release:
421
=== modified file 'debian/python-ubuntuone-control-panel.install'
--- debian/python-ubuntuone-control-panel.install 2010-12-06 12:27:11 +0000
+++ debian/python-ubuntuone-control-panel.install 2011-02-12 03:22:55 +0000
@@ -1,1 +1,4 @@
1debian/tmp/usr/lib/python2.*/*-packages/ubuntuone/controlpanel/*.py1debian/tmp/usr/lib/python2.*/*-packages/*.pth
2debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/__init__.py
3debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/controlpanel/*.py
4
25
=== modified file 'debian/ubuntuone-control-panel-gtk.install'
--- debian/ubuntuone-control-panel-gtk.install 2011-01-27 22:13:48 +0000
+++ debian/ubuntuone-control-panel-gtk.install 2011-02-12 03:22:55 +0000
@@ -4,4 +4,5 @@
4debian/tmp/usr/share/ubuntuone-control-panel/*.ui4debian/tmp/usr/share/ubuntuone-control-panel/*.ui
5debian/tmp/usr/share/ubuntuone-control-panel/*.png5debian/tmp/usr/share/ubuntuone-control-panel/*.png
6debian/tmp/usr/share/man/man1/ubuntuone-control-panel-gtk.*6debian/tmp/usr/share/man/man1/ubuntuone-control-panel-gtk.*
7debian/tmp/usr/lib/python2.*/*-packages/ubuntuone/controlpanel/gtk/*.py7debian/tmp/usr/lib/python2.*/*-packages/*/ubuntuone/controlpanel/gtk
8
89
=== modified file 'setup.py'
--- setup.py 2011-02-04 20:02:28 +0000
+++ setup.py 2011-02-12 03:22:55 +0000
@@ -76,7 +76,7 @@
7676
77DistUtilsExtra.auto.setup(77DistUtilsExtra.auto.setup(
78 name='ubuntuone-control-panel',78 name='ubuntuone-control-panel',
79 version='0.8.2',79 version='0.8.3',
80 license='GPL v3',80 license='GPL v3',
81 author='Natalia Bidart',81 author='Natalia Bidart',
82 author_email='natalia.bidart@canonical.com',82 author_email='natalia.bidart@canonical.com',
@@ -85,6 +85,7 @@
85 'DBus service to query/modify all the Ubuntu One bits.',85 'DBus service to query/modify all the Ubuntu One bits.',
86 url='https://launchpad.net/ubuntuone-control-panel',86 url='https://launchpad.net/ubuntuone-control-panel',
87 packages=['ubuntuone.controlpanel', 'ubuntuone.controlpanel.gtk'],87 packages=['ubuntuone.controlpanel', 'ubuntuone.controlpanel.gtk'],
88 extra_path='ubuntuone-control-panel',
88 data_files=[89 data_files=[
89 ('lib/ubuntuone-control-panel',90 ('lib/ubuntuone-control-panel',
90 ['bin/ubuntuone-control-panel-backend']),91 ['bin/ubuntuone-control-panel-backend']),
9192
=== modified file 'ubuntuone-control-panel-gtk.desktop.in'
--- ubuntuone-control-panel-gtk.desktop.in 2011-01-07 20:07:39 +0000
+++ ubuntuone-control-panel-gtk.desktop.in 2011-02-12 03:22:55 +0000
@@ -8,6 +8,7 @@
8StartupNotify=true8StartupNotify=true
9Categories=GNOME;GTK;Settings;9Categories=GNOME;GTK;Settings;
10X-Ayatana-Desktop-Shortcuts=U110X-Ayatana-Desktop-Shortcuts=U1
11X-Ayatana-Appmenu-Show-Stubs=False
1112
12[U1 Shortcut Group]13[U1 Shortcut Group]
13Name=Ubuntu One14Name=Ubuntu One
1415
=== added file 'ubuntuone.controlpanel.pth'
--- ubuntuone.controlpanel.pth 1970-01-01 00:00:00 +0000
+++ ubuntuone.controlpanel.pth 2011-02-12 03:22:55 +0000
@@ -0,0 +1,2 @@
1ubuntuone-control-panel
2
03
=== modified file 'ubuntuone/controlpanel/backend.py'
--- ubuntuone/controlpanel/backend.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/backend.py 2011-02-12 03:22:55 +0000
@@ -19,6 +19,8 @@
1919
20"""A backend for the Ubuntu One Control Panel."""20"""A backend for the Ubuntu One Control Panel."""
2121
22from collections import defaultdict
23
22from twisted.internet.defer import inlineCallbacks, returnValue24from twisted.internet.defer import inlineCallbacks, returnValue
2325
24from ubuntuone.controlpanel import dbus_client26from ubuntuone.controlpanel import dbus_client
@@ -62,12 +64,19 @@
62class ControlBackend(object):64class ControlBackend(object):
63 """The control panel backend."""65 """The control panel backend."""
6466
67 ROOT_TYPE = u'ROOT'
68 FOLDER_TYPE = u'UDF'
69 SHARE_TYPE = u'SHARE'
70 NAME_NOT_SET = u'ENAMENOTSET'
71
65 def __init__(self):72 def __init__(self):
66 """Initialize the webclient."""73 """Initialize the webclient."""
67 self.wc = WebClient(dbus_client.get_credentials)74 self.wc = WebClient(dbus_client.get_credentials)
68 self._status_changed_handler = None75 self._status_changed_handler = None
69 self.status_changed_handler = lambda *a: None76 self.status_changed_handler = lambda *a: None
7077
78 self._volumes = {} # cache last known volume info
79
71 def _process_file_sync_status(self, status):80 def _process_file_sync_status(self, status):
72 """Process raw file sync status into custom format.81 """Process raw file sync status into custom format.
7382
@@ -328,17 +337,54 @@
328 @inlineCallbacks337 @inlineCallbacks
329 def volumes_info(self):338 def volumes_info(self):
330 """Get the volumes info."""339 """Get the volumes info."""
340 self._volumes = {}
341
331 account = yield self.account_info()342 account = yield self.account_info()
332 root_dir = yield dbus_client.get_root_dir()343 root_dir = yield dbus_client.get_root_dir()
344 shares_dir = yield dbus_client.get_shares_dir()
345 shares_dir_link = yield dbus_client.get_shares_dir_link()
333 folders = yield dbus_client.get_folders()346 folders = yield dbus_client.get_folders()
347 shares = yield dbus_client.get_shares()
334348
335 free_bytes = int(account['quota_total']) - int(account['quota_used'])349 free_bytes = int(account['quota_total']) - int(account['quota_used'])
336 root_volume = {u'volume_id': u'', u'path': root_dir,350 root_volume = {u'volume_id': u'', u'path': root_dir,
337 u'subscribed': 'True', u'type': u'ROOT'}351 u'subscribed': 'True', u'type': self.ROOT_TYPE}
352 self._volumes[u''] = root_volume
353
354 # group shares by the offering user
355 shares_result = defaultdict(list)
356 for share in shares:
357 share[u'type'] = self.SHARE_TYPE
358
359 vid = share['volume_id']
360 assert vid not in self._volumes
361 self._volumes[vid] = share
362
363 if not bool(share['accepted']):
364 continue
365
366 nicer_path = share[u'path'].replace(shares_dir, shares_dir_link)
367 share[u'path'] = nicer_path
368
369 username = share['other_visible_name']
370 if not username:
371 username = u'%s (%s)' % (share['other_username'],
372 self.NAME_NOT_SET)
373
374 shares_result[username].append(share)
375
376 for folder in folders:
377 folder[u'type'] = self.FOLDER_TYPE
378
379 vid = folder['volume_id']
380 assert vid not in self._volumes
381 self._volumes[vid] = folder
338382
339 result = [(u'', unicode(free_bytes), [root_volume] + folders)]383 result = [(u'', unicode(free_bytes), [root_volume] + folders)]
340384
341 # later, we may request shares info as well385 for other_username, shares in shares_result.iteritems():
386 result.append((other_username, shares[0][u'free_bytes'], shares))
387
342 returnValue(result)388 returnValue(result)
343389
344 @log_call(logger.debug)390 @log_call(logger.debug)
@@ -359,16 +405,18 @@
359 @inlineCallbacks405 @inlineCallbacks
360 def subscribe_volume(self, volume_id):406 def subscribe_volume(self, volume_id):
361 """Subscribe to 'volume_id'."""407 """Subscribe to 'volume_id'."""
362 # when dealing with shares, we need to check if 'volume_id'408 if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
363 # is a folder or a share409 yield dbus_client.subscribe_folder(volume_id)
364 yield dbus_client.subscribe_folder(volume_id)410 elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
411 yield dbus_client.subscribe_share(volume_id)
365412
366 @inlineCallbacks413 @inlineCallbacks
367 def unsubscribe_volume(self, volume_id):414 def unsubscribe_volume(self, volume_id):
368 """Unsubscribe from 'volume_id'."""415 """Unsubscribe from 'volume_id'."""
369 # when dealing with shares, we need to check if 'volume_id'416 if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
370 # is a folder or a share417 yield dbus_client.unsubscribe_folder(volume_id)
371 yield dbus_client.unsubscribe_folder(volume_id)418 elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
419 yield dbus_client.unsubscribe_share(volume_id)
372420
373 @log_call(logger.debug)421 @log_call(logger.debug)
374 @inlineCallbacks422 @inlineCallbacks
375423
=== modified file 'ubuntuone/controlpanel/dbus_client.py'
--- ubuntuone/controlpanel/dbus_client.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/dbus_client.py 2011-02-12 03:22:55 +0000
@@ -221,6 +221,22 @@
221 return d221 return d
222222
223223
224def get_shares_dir():
225 """Retrieve the shares information from syncdaemon."""
226 d = defer.Deferred()
227 proxy = get_syncdaemon_proxy("/", sd_dbus_iface.DBUS_IFACE_SYNC_NAME)
228 proxy.get_sharesdir(reply_handler=d.callback, error_handler=d.errback)
229 return d
230
231
232def get_shares_dir_link():
233 """Retrieve the shares information from syncdaemon."""
234 d = defer.Deferred()
235 proxy = get_syncdaemon_proxy("/", sd_dbus_iface.DBUS_IFACE_SYNC_NAME)
236 proxy.get_sharesdir_link(reply_handler=d.callback, error_handler=d.errback)
237 return d
238
239
224def get_folders():240def get_folders():
225 """Retrieve the folders information from syncdaemon."""241 """Retrieve the folders information from syncdaemon."""
226 d = defer.Deferred()242 d = defer.Deferred()
@@ -256,7 +272,7 @@
256 proxy = get_folders_syncdaemon_proxy()272 proxy = get_folders_syncdaemon_proxy()
257273
258 sig = proxy.connect_to_signal('FolderSubscribed', d.callback)274 sig = proxy.connect_to_signal('FolderSubscribed', d.callback)
259 cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))275 cb = lambda info, error: d.errback(VolumesError(info['id'], error))
260 esig = proxy.connect_to_signal('FolderSubscribeError', cb)276 esig = proxy.connect_to_signal('FolderSubscribeError', cb)
261277
262 proxy.subscribe(folder_id, reply_handler=no_op, error_handler=no_op)278 proxy.subscribe(folder_id, reply_handler=no_op, error_handler=no_op)
@@ -277,7 +293,7 @@
277 proxy = get_folders_syncdaemon_proxy()293 proxy = get_folders_syncdaemon_proxy()
278294
279 sig = proxy.connect_to_signal('FolderUnSubscribed', d.callback)295 sig = proxy.connect_to_signal('FolderUnSubscribed', d.callback)
280 cb = lambda folder_info, error: d.errback(VolumesError(folder_info, error))296 cb = lambda info, error: d.errback(VolumesError(info['id'], error))
281 esig = proxy.connect_to_signal('FolderUnSubscribeError', cb)297 esig = proxy.connect_to_signal('FolderUnSubscribeError', cb)
282298
283 proxy.unsubscribe(folder_id, reply_handler=no_op, error_handler=no_op)299 proxy.unsubscribe(folder_id, reply_handler=no_op, error_handler=no_op)
@@ -292,6 +308,31 @@
292 return d308 return d
293309
294310
311@defer.inlineCallbacks
312def get_shares():
313 """Retrieve the shares information from syncdaemon."""
314 result = yield SyncDaemonTool(bus=dbus.SessionBus()).get_shares()
315 defer.returnValue(result)
316
317
318@defer.inlineCallbacks
319def subscribe_share(share_id):
320 """Subscribe to 'share_id'."""
321 try:
322 yield SyncDaemonTool(bus=dbus.SessionBus()).subscribe_share(share_id)
323 except Exception, e:
324 raise VolumesError(share_id, e)
325
326
327@defer.inlineCallbacks
328def unsubscribe_share(share_id):
329 """Unsubscribe 'share_id'."""
330 try:
331 yield SyncDaemonTool(bus=dbus.SessionBus()).unsubscribe_share(share_id)
332 except Exception, e:
333 raise VolumesError(share_id, e)
334
335
295def get_current_status():336def get_current_status():
296 """Retrieve the current status from syncdaemon."""337 """Retrieve the current status from syncdaemon."""
297 d = defer.Deferred()338 d = defer.Deferred()
298339
=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
--- ubuntuone/controlpanel/gtk/gui.py 2011-02-04 20:02:28 +0000
+++ ubuntuone/controlpanel/gtk/gui.py 2011-02-12 03:22:55 +0000
@@ -61,24 +61,13 @@
6161
62# To be replaced by values from the theme or Ubuntu One' specific (LP: #673663)62# To be replaced by values from the theme or Ubuntu One' specific (LP: #673663)
63ORANGE = '#c95724'63ORANGE = '#c95724'
64ERROR_COLOR = 'red'
64LOADING = _('Loading...')65LOADING = _('Loading...')
65OVERVIEW_MSGS = [
66 _('Backup and access your files from <b>any</b> computer (Ubuntu &amp; '
67 'Windows), mobile device (Android) or the web.'),
68 _('A <b>portable address book</b> that unifies your most important '
69 'contact sources: mobile phones, social networks, email clients and '
70 'services.'),
71 _('A <b>personal music library</b> that can be streamed to Android, '
72 'iPhone and AirPlay-enabled devices.'),
73 _('Add to that music library with a <b>Music Store</b> that automatically '
74 'delivers to your personal cloud and connected devices.'),
75 _('Keep your <b>Firefox bookmarks</b> and <b>Tomboy notes</b> in sync '
76 'across computers (Ubuntu &amp; Windows).'),
77]
78VALUE_ERROR = _('Value could not be retrieved.')66VALUE_ERROR = _('Value could not be retrieved.')
79WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE67WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ERROR_COLOR
80KILOBYTES = 102468KILOBYTES = 1024
81NO_OP = lambda *a, **kw: None69NO_OP = lambda *a, **kw: None
70FILE_URI_PREFIX = 'file://'
8271
8372
84def error_handler(*args, **kwargs):73def error_handler(*args, **kwargs):
@@ -109,7 +98,7 @@
109@log_call(logger.debug)98@log_call(logger.debug)
110def uri_hook(button, uri, *args, **kwargs):99def uri_hook(button, uri, *args, **kwargs):
111 """Open an URI or do nothing if URI is not an URL."""100 """Open an URI or do nothing if URI is not an URL."""
112 if uri.startswith('http'):101 if uri.startswith('http') or uri.startswith(FILE_URI_PREFIX):
113 gtk.show_uri(None, uri, gtk.gdk.CURRENT_TIME)102 gtk.show_uri(None, uri, gtk.gdk.CURRENT_TIME)
114103
115104
@@ -222,30 +211,27 @@
222211
223 CREDENTIALS_ERROR = _('There was a problem while retrieving the '212 CREDENTIALS_ERROR = _('There was a problem while retrieving the '
224 'credentials.')213 'credentials.')
225 AUTHORIZATION_DENIED = _('The authentication was cancelled. Please either '214 AUTHORIZATION_DENIED = _('The authentication was cancelled.')
226 'click join or connect to continue.')
227 NETWORK_OFFLINE = _('An internet connection is required to join or sign '215 NETWORK_OFFLINE = _('An internet connection is required to join or sign '
228 'in to %(app_name)s.')216 'in to %(app_name)s.')
229 NETWORK_UNKNOWN = _('The internet connection state couldn\'t be '217 NETWORK_UNKNOWN = _('The internet connection state couldn\'t be '
230 'discovered. Maybe NetworkManager is not running?')218 'discovered. Maybe NetworkManager is not running?')
231219
232 CONNECT = _('Connect to Ubuntu One')220 CONNECT = _('Connect to Ubuntu One')
233 BULLET = '<span foreground="%s">✔</span>' % ORANGE
234221
235 def __init__(self, main_window, messages=None):222 def __init__(self, main_window):
236 GreyableBin.__init__(self)223 GreyableBin.__init__(self)
237 ControlPanelMixin.__init__(self, filename='overview.ui')224 ControlPanelMixin.__init__(self, filename='overview.ui')
238 self.add(self.itself)225 self.add(self.itself)
239 self.warning_label.set_text('')226 self.warning_label.set_text('')
240 self.warning_label.set_property('xalign', 0.5)227 self.warning_label.set_property('xalign', 0.5)
241 self.connect('size-allocate', on_size_allocate, self.warning_label)228
229 self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))
242230
243 self.connect_button.set_uri(self.CONNECT)231 self.connect_button.set_uri(self.CONNECT)
244232
245 self.main_window = main_window233 self.main_window = main_window
246 self._credentials_are_new = False234 self._credentials_are_new = False
247 self._messages = messages
248 self._build_messages()
249 self.show()235 self.show()
250236
251 bus = dbus.SessionBus()237 bus = dbus.SessionBus()
@@ -274,21 +260,6 @@
274 self.network_manager_state = networkstate.NetworkManagerState(**kw)260 self.network_manager_state = networkstate.NetworkManagerState(**kw)
275 self.network_manager_state.find_online_state()261 self.network_manager_state.find_online_state()
276262
277 def _build_messages(self):
278 """List messages in a itemized list."""
279 if self._messages is None:
280 self._messages = OVERVIEW_MSGS
281
282 for msg in self._messages:
283 label = gtk.Label()
284 label.set_markup(self.BULLET + ' ' + msg)
285 label.set_property('xalign', 0)
286 label.set_property('wrap', True)
287 self.messages.connect('size-allocate', on_size_allocate, label)
288
289 self.messages.pack_start(label)
290 self.messages.show_all()
291
292 def _set_warning(self, message, label=None):263 def _set_warning(self, message, label=None):
293 """Set 'message' as global warning."""264 """Set 'message' as global warning."""
294 ControlPanelMixin._set_warning(self, message,265 ControlPanelMixin._set_warning(self, message,
@@ -419,18 +390,22 @@
419class VolumesPanel(UbuntuOneBin, ControlPanelMixin):390class VolumesPanel(UbuntuOneBin, ControlPanelMixin):
420 """The volumes panel."""391 """The volumes panel."""
421392
422 TITLE = _('Select the folders from your cloud that you want synchronized '393 TITLE = _('Select the folders from your personal cloud that you want '
423 'in this device.')394 'synchronized in this device.')
424 MY_FOLDERS = _('My folders')395 MY_FOLDERS = _('My folders')
425 ALWAYS_SUBSCRIBED = _('Always in sync!')396 ALWAYS_SUBSCRIBED = _('Always in sync!')
426 FREE_SPACE = _('%(free_space)s available storage')397 FREE_SPACE = _('%(free_space)s available storage')
398 NO_VOLUMES = _('No folders to show.')
399 NAME_NOT_SET = _('[unknown user name]')
400
401 MAX_COLS = 8
402
427 CONTACT_ICON_NAME = 'system-users'403 CONTACT_ICON_NAME = 'system-users'
428 FOLDER_ICON_NAME = 'folder'404 FOLDER_ICON_NAME = 'folder'
429 SHARE_ICON_NAME = 'folder-remote'405 SHARE_ICON_NAME = 'folder-remote'
430 ROW_HEADER = '<span font_size="large"><b>%s</b></span> ' \406 ROW_HEADER = '<span font_size="large"><b>%s</b></span> ' \
431 '<span foreground="grey">%s</span>'407 '<span foreground="grey">%s</span>'
432 ROOT = '%s - <span foreground="%s" font_size="small">%s</span>'408 ROOT = '%s - <span foreground="%s" font_size="small">%s</span>'
433 NO_VOLUMES = _('No folders to show.')
434409
435 def __init__(self, main_window=None):410 def __init__(self, main_window=None):
436 UbuntuOneBin.__init__(self)411 UbuntuOneBin.__init__(self)
@@ -438,6 +413,11 @@
438 self.add(self.itself)413 self.add(self.itself)
439 self.show_all()414 self.show_all()
440415
416 # name, subscribed, icon name, show toggle, sensitive, icon size,
417 # id, path
418 self._empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU,
419 None, None)
420
441 self.backend.connect_to_signal('VolumesInfoReady',421 self.backend.connect_to_signal('VolumesInfoReady',
442 self.on_volumes_info_ready)422 self.on_volumes_info_ready)
443 self.backend.connect_to_signal('VolumesInfoError',423 self.backend.connect_to_signal('VolumesInfoError',
@@ -462,14 +442,16 @@
462 else:442 else:
463 self.on_success()443 self.on_success()
464444
465 # pylint: disable=W0612
466 # name, subscribed, icon name, show toggle, sensitive, icon size, id
467 empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU, None)
468
469 for name, free_bytes, volumes in info:445 for name, free_bytes, volumes in info:
446 if backend.ControlBackend.NAME_NOT_SET in name:
447 name = self.NAME_NOT_SET
448
470 if name:449 if name:
471 name = name + "'s"450 name = name + "'s"
472 icon_name = self.SHARE_ICON_NAME451 icon_name = self.SHARE_ICON_NAME
452
453 # we already added user folders, let's add an empty row
454 treeiter = self.volumes_store.append(None, self._empty_row)
473 else:455 else:
474 name = self.MY_FOLDERS456 name = self.MY_FOLDERS
475 icon_name = self.FOLDER_ICON_NAME457 icon_name = self.FOLDER_ICON_NAME
@@ -477,30 +459,33 @@
477 free_bytes_args = {'free_space': self.humanize(int(free_bytes))}459 free_bytes_args = {'free_space': self.humanize(int(free_bytes))}
478 row = (self.ROW_HEADER % (name, self.FREE_SPACE % free_bytes_args),460 row = (self.ROW_HEADER % (name, self.FREE_SPACE % free_bytes_args),
479 True, self.CONTACT_ICON_NAME, False, False,461 True, self.CONTACT_ICON_NAME, False, False,
480 gtk.ICON_SIZE_LARGE_TOOLBAR, None)462 gtk.ICON_SIZE_LARGE_TOOLBAR, None, None)
481 treeiter = self.volumes_store.append(None, row)463 treeiter = self.volumes_store.append(None, row)
482464
483 volumes.sort(key=operator.itemgetter('path'))465 volumes.sort(key=operator.itemgetter('path'))
484 for volume in volumes:466 for volume in volumes:
485 sensitive = True467 sensitive = True
486 path = self._process_path(volume['path'])468 name = self._process_path(volume[u'path'])
487 if volume['type'] == u'ROOT':469
470 is_root = volume[u'type'] == backend.ControlBackend.ROOT_TYPE
471 is_share = volume[u'type'] == backend.ControlBackend.SHARE_TYPE
472
473 if is_root:
488 sensitive = False474 sensitive = False
489 path = self.ROOT % (path, ORANGE, self.ALWAYS_SUBSCRIBED)475 name = self.ROOT % (name, ORANGE, self.ALWAYS_SUBSCRIBED)
490476 elif is_share:
491 row = (path, bool(volume['subscribed']), icon_name, True,477 name = volume[u'name']
492 sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'])478
493479 row = (name, bool(volume[u'subscribed']), icon_name, True,
494 if volume['type'] == u'ROOT': # root should go first!480 sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'],
481 volume[u'path'])
482
483 if is_root: # root should go first!
495 self.volumes_store.prepend(treeiter, row)484 self.volumes_store.prepend(treeiter, row)
496 else:485 else:
497 self.volumes_store.append(treeiter, row)486 self.volumes_store.append(treeiter, row)
498487
499 # When we display shares info, we'll need to smartly add488 self.volumes_view.expand_all()
500 # an empty row to the tree view to separate volume groups
501 #treeiter = self.volumes_store.append(None, empty_row)
502
503 self.volumes_view.expand_row(0, True)
504 self.volumes_view.show_all()489 self.volumes_view.show_all()
505490
506 self.is_processing = False491 self.is_processing = False
@@ -534,6 +519,12 @@
534519
535 self.is_processing = True520 self.is_processing = True
536521
522 def on_volumes_view_row_activated(self, widget, path, *args, **kwargs):
523 """The user double clicked on a row."""
524 treeiter = self.volumes_store.get_iter(path)
525 volume_path = self.volumes_store.get_value(treeiter, 7)
526 uri_hook(None, FILE_URI_PREFIX + volume_path)
527
537 def load(self):528 def load(self):
538 """Load the volume list."""529 """Load the volume list."""
539 self.backend.volumes_info(reply_handler=NO_OP,530 self.backend.volumes_info(reply_handler=NO_OP,
@@ -739,7 +730,8 @@
739 gobject.TYPE_NONE, ()),730 gobject.TYPE_NONE, ()),
740 }731 }
741732
742 TITLE = _('The devices synced with your personal cloud are listed below.')733 TITLE = _('The devices connected with your personal cloud are listed '
734 'below.')
743 NO_DEVICES = _('No devices to show.')735 NO_DEVICES = _('No devices to show.')
744 CONFIRM_REMOVE = _('Are you sure you want to remove this device '736 CONFIRM_REMOVE = _('Are you sure you want to remove this device '
745 'from Ubuntu One?')737 'from Ubuntu One?')
@@ -1270,6 +1262,8 @@
1270 }1262 }
12711263
1272 QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)')1264 QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)')
1265 DASHBOARD_BUTTON_NAME = 'Account'
1266 DEVICES_BUTTON_NAME = 'Devices'
12731267
1274 def __init__(self, main_window=None):1268 def __init__(self, main_window=None):
1275 gtk.VBox.__init__(self)1269 gtk.VBox.__init__(self)
@@ -1287,6 +1281,7 @@
1287 self.quota_progressbar.set_sensitive(False)1281 self.quota_progressbar.set_sensitive(False)
1288 self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)1282 self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
1289 self.quota_box.pack_start(self.quota_label, expand=False)1283 self.quota_box.pack_start(self.quota_label, expand=False)
1284 self.quota_box.reorder_child(self.quota_label, 0)
12901285
1291 self.status_label = FileSyncStatus()1286 self.status_label = FileSyncStatus()
1292 self.status_box.pack_end(self.status_label, expand=False)1287 self.status_box.pack_end(self.status_label, expand=False)
@@ -1309,9 +1304,13 @@
1309 gtk.gdk.Color(DEFAULT_FG))1304 gtk.gdk.Color(DEFAULT_FG))
1310 self.notebook.insert_page(getattr(self, tab), position=page_num)1305 self.notebook.insert_page(getattr(self, tab), position=page_num)
13111306
1307 self.dashboard_button.set_name(self.DASHBOARD_BUTTON_NAME)
1308
1312 self.volumes_button.connect('clicked', lambda b: self.volumes.load())1309 self.volumes_button.connect('clicked', lambda b: self.volumes.load())
1310 self.services_button.connect('clicked', lambda b: self.services.load())
1311
1312 self.devices_button.set_name(self.DEVICES_BUTTON_NAME)
1313 self.devices_button.connect('clicked', lambda b: self.devices.load())1313 self.devices_button.connect('clicked', lambda b: self.devices.load())
1314 self.services_button.connect('clicked', lambda b: self.services.load())
1315 self.devices.connect('local-device-removed',1314 self.devices.connect('local-device-removed',
1316 lambda widget: self.emit('local-device-removed'))1315 lambda widget: self.emit('local-device-removed'))
13171316
@@ -1397,7 +1396,7 @@
1397class ControlPanelWindow(gtk.Window):1396class ControlPanelWindow(gtk.Window):
1398 """The main window for the Ubuntu One control panel."""1397 """The main window for the Ubuntu One control panel."""
13991398
1400 TITLE = _('%(app_name)s Dashboard')1399 TITLE = _('%(app_name)s Control Panel')
14011400
1402 def __init__(self, switch_to=None):1401 def __init__(self, switch_to=None):
1403 super(ControlPanelWindow, self).__init__()1402 super(ControlPanelWindow, self).__init__()
14041403
=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
--- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-02-12 03:22:55 +0000
@@ -45,11 +45,11 @@
45]45]
4646
47FAKE_SHARES_INFO = [47FAKE_SHARES_INFO = [
48 {u'volume_id': u'1234',48 {u'volume_id': u'1234', u'name': u'do',
49 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',49 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
50 u'subscribed': u'', u'type': u'SHARE'},50 u'subscribed': u'', u'type': u'SHARE'},
5151
52 {u'volume_id': u'5678',52 {u'volume_id': u'5678', u'name': u're',
53 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',53 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
54 u'subscribed': u'True', u'type': u'SHARE'},54 u'subscribed': u'True', u'type': u'SHARE'},
55]55]
5656
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-02-12 03:22:55 +0000
@@ -27,7 +27,7 @@
27from ubuntuone.controlpanel.gtk import gui27from ubuntuone.controlpanel.gtk import gui
28from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,28from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
29 FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,29 FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
30 FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, USER_HOME,30 FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, ROOT, USER_HOME,
31 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,31 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
32 FakedPackageManager, FakedConfirmDialog,32 FakedPackageManager, FakedConfirmDialog,
33)33)
@@ -82,21 +82,6 @@
82 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))82 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
83 self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())83 self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
8484
85 def assert_messages_equal(self, widget, msgs):
86 """Check that 'widget' has all the entries in 'msgs'."""
87 children = list(reversed(widget.get_children()))
88 self.assertEqual(len(children), len(msgs))
89 for label, msg in zip(reversed(children), msgs):
90 full_msg = self.ui.BULLET + ' ' + msg
91 self.assertTrue(label.get_visible())
92 self.assertEqual(label.get_property('xalign'), 0)
93 self.assertEqual(label.get_property('wrap'), True)
94 self.assertEqual(label.get_label(), full_msg)
95
96 expected = gui.gtk.Label()
97 expected.set_markup(full_msg)
98 self.assertEqual(label.get_text(), expected.get_text())
99
100 def assert_backend_called(self, method_name, args, backend=None):85 def assert_backend_called(self, method_name, args, backend=None):
101 """Check that the control panel backend 'method_name' was called."""86 """Check that the control panel backend 'method_name' was called."""
102 if backend is None:87 if backend is None:
@@ -392,7 +377,7 @@
392 """The test suite for the overview panel."""377 """The test suite for the overview panel."""
393378
394 klass = gui.OverviewPanel379 klass = gui.OverviewPanel
395 kwargs = {'messages': None, 'main_window': gui.gtk.Window()}380 kwargs = {'main_window': gui.gtk.Window()}
396 ui_filename = 'overview.ui'381 ui_filename = 'overview.ui'
397382
398 def test_is_a_greyable_bin(self):383 def test_is_a_greyable_bin(self):
@@ -561,10 +546,7 @@
561class OverwiewPanelNoCredsTestCase(OverwiewPanelTestCase):546class OverwiewPanelNoCredsTestCase(OverwiewPanelTestCase):
562 """The test suite for the overview panel when no credentials are found."""547 """The test suite for the overview panel when no credentials are found."""
563548
564 messages = None
565
566 def setUp(self):549 def setUp(self):
567 self.kwargs['messages'] = self.messages
568 super(OverwiewPanelNoCredsTestCase, self).setUp()550 super(OverwiewPanelNoCredsTestCase, self).setUp()
569 self.ui.on_credentials_not_found(gui.U1_APP_NAME)551 self.ui.on_credentials_not_found(gui.U1_APP_NAME)
570552
@@ -581,10 +563,6 @@
581 """There is an image attribute and is correct."""563 """There is an image attribute and is correct."""
582 self.assert_image_equal(self.ui.image, 'overview.png')564 self.assert_image_equal(self.ui.image, 'overview.png')
583565
584 def test_messages(self):
585 """If no messages are passed, the default list is used."""
586 self.assert_messages_equal(self.ui.messages, gui.OVERVIEW_MSGS)
587
588 def test_join_now_is_default_widget(self):566 def test_join_now_is_default_widget(self):
589 """The join now button is the default widget."""567 """The join now button is the default widget."""
590 self.assertTrue(self.ui.join_now_button.get_property('can_default'))568 self.assertTrue(self.ui.join_now_button.get_property('can_default'))
@@ -683,18 +661,6 @@
683 self.assertTrue(self.ui.connect_button.is_sensitive())661 self.assertTrue(self.ui.connect_button.is_sensitive())
684662
685663
686class OverwiewPanelMessagesTestCase(OverwiewPanelTestCase):
687 """The test suite for the overview panel when messages are set."""
688
689 messages = ['Test me', 'A little bit more']
690
691
692class OverwiewPanelMarkupMessagesTestCase(OverwiewPanelTestCase):
693 """The test suite for the overview panel when markup messages are set."""
694
695 messages = ['<small>Test me</small>', 'A <b>little</b> bit more']
696
697
698class DashboardTestCase(ControlPanelMixinTestCase):664class DashboardTestCase(ControlPanelMixinTestCase):
699 """The test suite for the dashboard panel."""665 """The test suite for the dashboard panel."""
700666
@@ -835,7 +801,8 @@
835 """The volumes info is processed when ready."""801 """The volumes info is processed when ready."""
836 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)802 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
837803
838 self.assertEqual(len(FAKE_VOLUMES_INFO), len(self.ui.volumes_store))804 self.assertEqual(len(FAKE_VOLUMES_INFO) + 1, # count the empty row
805 len(self.ui.volumes_store))
839 treeiter = self.ui.volumes_store.get_iter_root()806 treeiter = self.ui.volumes_store.get_iter_root()
840 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:807 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:
841 name = "%s's" % name if name else self.ui.MY_FOLDERS808 name = "%s's" % name if name else self.ui.MY_FOLDERS
@@ -843,7 +810,8 @@
843 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})810 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})
844811
845 # check parent row812 # check parent row
846 row = self.ui.volumes_store.get(treeiter, *xrange(7))813 row = self.ui.volumes_store.get(treeiter,
814 *xrange(self.ui.MAX_COLS))
847815
848 self.assertEqual(row[0], self.ui.ROW_HEADER % header)816 self.assertEqual(row[0], self.ui.ROW_HEADER % header)
849 self.assertTrue(row[1], 'parent will always be subscribed')817 self.assertTrue(row[1], 'parent will always be subscribed')
@@ -852,6 +820,7 @@
852 self.assertFalse(row[4], 'toggle should be non sensitive.')820 self.assertFalse(row[4], 'toggle should be non sensitive.')
853 self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR)821 self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR)
854 self.assertEqual(row[6], None)822 self.assertEqual(row[6], None)
823 self.assertEqual(row[7], None)
855824
856 # check children825 # check children
857 self.assertEqual(len(volumes),826 self.assertEqual(len(volumes),
@@ -860,7 +829,8 @@
860829
861 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))830 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
862 for volume in sorted_vols:831 for volume in sorted_vols:
863 row = self.ui.volumes_store.get(childiter, *xrange(7))832 row = self.ui.volumes_store.get(childiter,
833 *xrange(self.ui.MAX_COLS))
864834
865 sensitive = True835 sensitive = True
866 path = volume['path'].replace(USER_HOME + '/', '')836 path = volume['path'].replace(USER_HOME + '/', '')
@@ -868,6 +838,8 @@
868 sensitive = False838 sensitive = False
869 path = self.ui.ROOT % (path, gui.ORANGE,839 path = self.ui.ROOT % (path, gui.ORANGE,
870 self.ui.ALWAYS_SUBSCRIBED)840 self.ui.ALWAYS_SUBSCRIBED)
841 elif volume['type'] == 'SHARE':
842 path = volume['name']
871843
872 self.assertEqual(row[0], path)844 self.assertEqual(row[0], path)
873 self.assertEqual(row[1], bool(volume['subscribed']))845 self.assertEqual(row[1], bool(volume['subscribed']))
@@ -879,17 +851,28 @@
879 self.assertEqual(row[4], sensitive)851 self.assertEqual(row[4], sensitive)
880 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)852 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)
881 self.assertEqual(row[6], volume['volume_id'])853 self.assertEqual(row[6], volume['volume_id'])
854 self.assertEqual(row[7], volume['path'])
882855
883 childiter = self.ui.volumes_store.iter_next(childiter)856 childiter = self.ui.volumes_store.iter_next(childiter)
884857
885 treeiter = self.ui.volumes_store.iter_next(treeiter)858 treeiter = self.ui.volumes_store.iter_next(treeiter)
886859
860 if treeiter is not None:
861 # skip the empty row
862 row = self.ui.volumes_store.get(treeiter,
863 *xrange(self.ui.MAX_COLS))
864 self.assertEqual(row, self.ui._empty_row)
865
866 # grab next non-empty row
867 treeiter = self.ui.volumes_store.iter_next(treeiter)
868
887 def test_on_volumes_info_ready_clears_the_list(self):869 def test_on_volumes_info_ready_clears_the_list(self):
888 """The old volumes info is cleared before updated."""870 """The old volumes info is cleared before updated."""
889 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)871 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
890 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)872 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
891873
892 self.assertEqual(len(self.ui.volumes_store), len(FAKE_VOLUMES_INFO))874 self.assertEqual(len(FAKE_VOLUMES_INFO) + 1,
875 len(self.ui.volumes_store))
893876
894 def test_on_volumes_info_ready_with_no_volumes(self):877 def test_on_volumes_info_ready_with_no_volumes(self):
895 """When there are no volumes, a notification is shown."""878 """When there are no volumes, a notification is shown."""
@@ -917,7 +900,9 @@
917 """Clicking on 'subscribed' updates the folder subscription."""900 """Clicking on 'subscribed' updates the folder subscription."""
918 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)901 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
919902
920 for parent, (_, _, volumes) in enumerate(FAKE_VOLUMES_INFO):903 real_rows = len(FAKE_VOLUMES_INFO)
904 data = zip(range(real_rows)[::2], FAKE_VOLUMES_INFO) # skip emtpy rows
905 for parent, (_, _, volumes) in data:
921906
922 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))907 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
923 for child, volume in enumerate(sorted_vols):908 for child, volume in enumerate(sorted_vols):
@@ -964,6 +949,17 @@
964 # reload folders list to sanitize the info in volumes_store949 # reload folders list to sanitize the info in volumes_store
965 self.assertTrue(self._called, ((), {}))950 self.assertTrue(self._called, ((), {}))
966951
952 def test_clicking_on_row_opens_folder(self):
953 """The folder activated is opened."""
954 self.patch(gui, 'uri_hook', self._set_called)
955 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
956
957 self.ui.volumes_view.row_activated('0:0',
958 self.ui.volumes_view.get_column(0))
959
960 self.assertEqual(self._called,
961 ((None, gui.FILE_URI_PREFIX + ROOT['path']), {}))
962
967963
968class DeviceTestCase(ControlPanelMixinTestCase):964class DeviceTestCase(ControlPanelMixinTestCase):
969 """The test suite for the device widget."""965 """The test suite for the device widget."""
@@ -2358,3 +2354,13 @@
2358 self.ui.devices.emit('local-device-removed')2354 self.ui.devices.emit('local-device-removed')
23592355
2360 self.assertEqual(self._called, ((self.ui,), {}))2356 self.assertEqual(self._called, ((self.ui,), {}))
2357
2358 def test_dashboard_button_name(self):
2359 """The dashboard_button widget has the proper name."""
2360 self.assertEqual(self.ui.dashboard_button.get_name(),
2361 self.ui.DASHBOARD_BUTTON_NAME)
2362
2363 def test_devices_button_name(self):
2364 """The devices_button widget has the proper name."""
2365 self.assertEqual(self.ui.devices_button.get_name(),
2366 self.ui.DEVICES_BUTTON_NAME)
23612367
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2011-02-12 03:22:55 +0000
@@ -19,9 +19,11 @@
1919
20"""Tests for the DBus service when accessing SyncDaemon."""20"""Tests for the DBus service when accessing SyncDaemon."""
2121
22import uuid
23
22import dbus24import dbus
2325
24from twisted.internet.defer import inlineCallbacks26from twisted.internet.defer import inlineCallbacks, returnValue
2527
26from ubuntuone.controlpanel import dbus_client28from ubuntuone.controlpanel import dbus_client
27from ubuntuone.controlpanel.dbus_client import sd_dbus_iface29from ubuntuone.controlpanel.dbus_client import sd_dbus_iface
@@ -34,6 +36,8 @@
34 "download": "838",36 "download": "838",
35}37}
3638
39SHARES = {}
40
37# pylint, you have to go to decorator's school41# pylint, you have to go to decorator's school
38# pylint: disable=C032242# pylint: disable=C0322
3943
@@ -302,7 +306,7 @@
302306
303307
304class FoldersTestCase(DBusClientTestCase):308class FoldersTestCase(DBusClientTestCase):
305 """Test for the volumes dbus client methods."""309 """Test for the shares dbus client methods."""
306310
307 def setUp(self):311 def setUp(self):
308 super(FoldersTestCase, self).setUp()312 super(FoldersTestCase, self).setUp()
@@ -379,7 +383,7 @@
379 try:383 try:
380 yield dbus_client.subscribe_folder(fid)384 yield dbus_client.subscribe_folder(fid)
381 except dbus_client.VolumesError, e:385 except dbus_client.VolumesError, e:
382 self.assertEqual(e[0], {'id': fid})386 self.assertEqual(e[0], fid)
383 else:387 else:
384 self.fail('dbus_client.subscribe_folder should be errbacking')388 self.fail('dbus_client.subscribe_folder should be errbacking')
385389
@@ -405,11 +409,159 @@
405 try:409 try:
406 yield dbus_client.unsubscribe_folder(fid)410 yield dbus_client.unsubscribe_folder(fid)
407 except dbus_client.VolumesError, e:411 except dbus_client.VolumesError, e:
408 self.assertEqual(e[0], {'id': fid})412 self.assertEqual(e[0], fid)
409 else:413 else:
410 self.fail('dbus_client.unsubscribe_folder should be errbacking')414 self.fail('dbus_client.unsubscribe_folder should be errbacking')
411415
412416
417class FakedSyncDaemonTool(object):
418 """Fake the FakedSyncDaemonTool."""
419
420 def __init__(self, bus):
421 """New instance."""
422
423 def _set_share_attr(self, share_id, attr, value):
424 """Set values to shares."""
425 if share_id not in SHARES:
426 raise dbus.DBusException('share_id not in SHARES.')
427
428 value = sd_dbus_iface.bool_str(value)
429 SHARES[share_id][attr] = value
430 return share_id
431
432 @inlineCallbacks
433 def accept_share(self, share_id):
434 """Accept the share with id: share_id."""
435 yield self._set_share_attr(share_id, u'accepted', True)
436
437 @inlineCallbacks
438 def reject_share(self, share_id):
439 """Reject the share with id: share_id."""
440 yield self._set_share_attr(share_id, u'accepted', False)
441
442 @inlineCallbacks
443 def subscribe_share(self, share_id):
444 """Subscribe to a share given its id."""
445 yield self._set_share_attr(share_id, u'subscribed', True)
446
447 @inlineCallbacks
448 def unsubscribe_share(self, share_id):
449 """Unsubscribe from a share given its id."""
450 yield self._set_share_attr(share_id, u'subscribed', False)
451
452 @inlineCallbacks
453 def get_shares(self):
454 """Get the list of shares (accepted or not)."""
455 result = yield SHARES.values()
456 returnValue(sorted(result))
457
458 @inlineCallbacks
459 def refresh_shares(self):
460 """Call refresh_shares method via DBus."""
461 yield
462
463 @classmethod
464 def create_share(cls, name, accepted=False, subscribed=False):
465 """Add a new share (fake)."""
466 sid = unicode(uuid.uuid4())
467 path = u'/home/tester/.hidden/shares/%s from The Othr User' % name
468 share = {
469 u'name': name, u'subscribed': sd_dbus_iface.bool_str(subscribed),
470 u'accepted': sd_dbus_iface.bool_str(accepted),
471 u'generation': u'36', u'type': u'Share',
472 u'node_id': u'ca3a1cec-09d2-485e-9685-1a5180bd6441',
473 u'volume_id': sid, u'access_level': u'View',
474 u'other_username': u'https://login.ubuntu.com/+id/nHRnYmz',
475 u'other_visible_name': u'The Other User',
476 u'free_bytes': u'2146703403', u'path': path,
477 }
478 SHARES[sid] = share
479 return share
480
481
482class SharesTestCase(DBusClientTestCase):
483 """Test for the shares dbus client methods."""
484
485 def setUp(self):
486 super(SharesTestCase, self).setUp()
487 self.patch(dbus_client, 'SyncDaemonTool', FakedSyncDaemonTool)
488 SHARES.clear()
489
490 @inlineCallbacks
491 def test_get_shares(self):
492 """Retrieve shares info list."""
493 share1 = FakedSyncDaemonTool.create_share(name='first, test me!')
494 share2 = FakedSyncDaemonTool.create_share(name='and test me more!',
495 accepted=True)
496 share3 = FakedSyncDaemonTool.create_share(name='last but not least',
497 accepted=True, subscribed=True)
498
499 result = yield dbus_client.get_shares()
500
501 self.assertEqual(result, sorted([share1, share2, share3]))
502
503 @inlineCallbacks
504 def test_get_shares_error(self):
505 """Handle error when retrieving current syncdaemon status."""
506 self.patch(FakedSyncDaemonTool, 'get_shares', self.fail)
507 try:
508 yield dbus_client.get_shares()
509 except: # pylint: disable=W0702
510 pass # test passes!
511 else:
512 self.fail('dbus_client.get_shares should be errbacking')
513
514 @inlineCallbacks
515 def test_subscribe_share(self):
516 """Subscribe to a share."""
517 share = FakedSyncDaemonTool.create_share(name='to be subscribed',
518 accepted=True, subscribed=False)
519 sid = share['volume_id']
520
521 yield dbus_client.subscribe_share(sid)
522
523 result = yield dbus_client.get_shares()
524 expected, = filter(lambda share: share['volume_id'] == sid, result)
525 self.assertEqual(expected['subscribed'], 'True')
526
527 @inlineCallbacks
528 def test_subscribe_share_error(self):
529 """Subscribe to a share."""
530 sid = u'does not exist'
531 try:
532 yield dbus_client.subscribe_share(sid)
533 except dbus_client.VolumesError, e:
534 self.assertEqual(e[0], sid)
535 else:
536 self.fail('dbus_client.subscribe_share should be errbacking')
537
538 @inlineCallbacks
539 def test_unsubscribe_share(self):
540 """Unsubscribe to a share."""
541 share = FakedSyncDaemonTool.create_share(name='to be unsubscribed',
542 accepted=True, subscribed=True)
543 sid = share['volume_id']
544 yield dbus_client.subscribe_share(sid)
545 # share is subscribed
546
547 yield dbus_client.unsubscribe_share(sid)
548
549 result = yield dbus_client.get_shares()
550 expected, = filter(lambda share: share['volume_id'] == sid, result)
551 self.assertEqual(expected['subscribed'], '')
552
553 @inlineCallbacks
554 def test_unsubscribe_share_error(self):
555 """Unsubscribe to a share."""
556 sid = u'does not exist'
557 try:
558 yield dbus_client.unsubscribe_share(sid)
559 except dbus_client.VolumesError, e:
560 self.assertEqual(e[0], sid)
561 else:
562 self.fail('dbus_client.unsubscribe_share should be errbacking')
563
564
413class StatusMockDBusSyncDaemon(dbus.service.Object):565class StatusMockDBusSyncDaemon(dbus.service.Object):
414 """A mock object that mimicks syncdaemon regarding the Status iface."""566 """A mock object that mimicks syncdaemon regarding the Status iface."""
415567
@@ -549,6 +701,8 @@
549 """A mock object that mimicks syncdaemon."""701 """A mock object that mimicks syncdaemon."""
550702
551 ROOT_DIR = u'/yadda/yoda/Test me'703 ROOT_DIR = u'/yadda/yoda/Test me'
704 SHARES_DIR = u'/yadda/yoda/.hidden/ugly/dir/name/shares'
705 SHARES_DIR_LINK = u'/yadda/yoda/Test me/Shared With Me'
552706
553 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,707 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
554 in_signature='', out_signature='s')708 in_signature='', out_signature='s')
@@ -556,6 +710,18 @@
556 """Return the root dir/mount point."""710 """Return the root dir/mount point."""
557 return self.ROOT_DIR711 return self.ROOT_DIR
558712
713 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
714 in_signature='', out_signature='s')
715 def get_sharesdir(self):
716 """Return the shares dir/mount point."""
717 return self.SHARES_DIR
718
719 @dbus.service.method(sd_dbus_iface.DBUS_IFACE_SYNC_NAME,
720 in_signature='', out_signature='s')
721 def get_sharesdir_link(self):
722 """Return the root dir/mount point."""
723 return self.SHARES_DIR_LINK
724
559725
560class BasicTestCase(DBusClientTestCase):726class BasicTestCase(DBusClientTestCase):
561 """Test for the basic dbus client methods."""727 """Test for the basic dbus client methods."""
@@ -571,3 +737,17 @@
571 root = yield dbus_client.get_root_dir()737 root = yield dbus_client.get_root_dir()
572738
573 self.assertEqual(MockDBusSyncDaemon.ROOT_DIR, root)739 self.assertEqual(MockDBusSyncDaemon.ROOT_DIR, root)
740
741 @inlineCallbacks
742 def test_get_shares_dir(self):
743 """Retrieve current syncdaemon shares dir."""
744 result = yield dbus_client.get_shares_dir()
745
746 self.assertEqual(MockDBusSyncDaemon.SHARES_DIR, result)
747
748 @inlineCallbacks
749 def test_get_shares_dir_link(self):
750 """Retrieve current syncdaemon shares dir."""
751 result = yield dbus_client.get_shares_dir_link()
752
753 self.assertEqual(MockDBusSyncDaemon.SHARES_DIR_LINK, result)
574754
=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
--- ubuntuone/controlpanel/tests/__init__.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/tests/__init__.py 2011-02-12 03:22:55 +0000
@@ -161,8 +161,14 @@
161 u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},161 u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
162]162]
163163
164ROOT_PATH = u'/home/tester/Ubuntu One'
165SHARES_PATH = u'/home/tester/.local/share/ubuntuone/shares'
166SHARES_PATH_LINK = ROOT_PATH + u'/Shared With Me'
167
164SAMPLE_SHARES = [168SAMPLE_SHARES = [
169
165 {u'accepted': u'True',170 {u'accepted': u'True',
171 u'subscribed': u'True',
166 u'access_level': u'View',172 u'access_level': u'View',
167 u'free_bytes': u'39892622746',173 u'free_bytes': u'39892622746',
168 u'generation': u'2704',174 u'generation': u'2704',
@@ -170,11 +176,12 @@
170 u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',176 u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
171 u'other_username': u'otheruser',177 u'other_username': u'otheruser',
172 u'other_visible_name': u'Other User',178 u'other_visible_name': u'Other User',
173 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',179 u'path': SHARES_PATH + u'/re from Other User',
174 u'type': u'Share',180 u'type': u'Share',
175 u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},181 u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
176182
177 {u'accepted': u'True',183 {u'accepted': u'True',
184 u'subscribed': u'True',
178 u'access_level': u'Modify',185 u'access_level': u'Modify',
179 u'free_bytes': u'39892622746',186 u'free_bytes': u'39892622746',
180 u'generation': u'2704',187 u'generation': u'2704',
@@ -182,9 +189,78 @@
182 u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',189 u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
183 u'other_username': u'otheruser',190 u'other_username': u'otheruser',
184 u'other_visible_name': u'Other User',191 u'other_visible_name': u'Other User',
185 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',192 u'path': SHARES_PATH + u'/do from Other User',
186 u'type': u'Share',193 u'type': u'Share',
187 u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},194 u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
195
196 {u'name': u'yadda',
197 u'subscribed': u'True',
198 u'generation': u'36',
199 u'other_username': u'https://login.ubuntu.com/+id/nHRnYmz',
200 u'other_visible_name': u'',
201 u'access_level': u'View',
202 u'node_id': u'ca3a1cec-09d2-485e-9685-1a5180bd6441',
203 u'volume_id': u'f1f1741f-ba49-46ef-b682-816c97e6e3d6',
204 u'free_bytes': u'2146703403',
205 u'path': SHARES_PATH + u'/yadda from ',
206 u'accepted': u'True',
207 u'type': u'Share'},
208
209 {u'name': u'images',
210 u'subscribed': u'True',
211 u'generation': u'36',
212 u'other_username':
213 u'https://login.ubuntu.com/+id/nHRnYmz',
214 u'other_visible_name': u'',
215 u'access_level': u'Modify',
216 u'node_id':
217 u'b932f0d3-6998-423f-9225-7683a4adbd6f',
218 u'volume_id':
219 u'a73f889d-ffd3-4447-b351-f0d8130d1e1a',
220 u'free_bytes': u'2146703403',
221 u'path': SHARES_PATH + u'/images from ',
222 u'accepted': u'True',
223 u'type': u'Share'},
224
225 {u'name': u'read-only',
226 u'subscribed': u'True',
227 u'generation': u'315',
228 u'other_username': u'sharing-user',
229 u'other_visible_name': u'A Sharing User',
230 u'access_level': u'View',
231 u'node_id': u'fd944823-e4c2-45a4-8c95-05997698bdbc',
232 u'volume_id':
233 u'64b43c96-6c7c-4135-994f-03a1a3774512',
234 u'free_bytes': u'108786811673',
235 u'path': SHARES_PATH + u'/read-only from A Sharing User',
236 u'accepted': u'True',
237 u'type': u'Share'},
238
239 {u'name': u'read-write',
240 u'subscribed': u'True',
241 u'generation': u'314',
242 u'other_username': u'sharing-user',
243 u'other_visible_name': u'A Sharing User',
244 u'access_level': u'Modify',
245 u'node_id': u'e95662f7-9979-4745-8c21-8edaf893f143',
246 u'volume_id': u'8896e8f8-57a3-4bf9-8558-fc54b7a3a777',
247 u'free_bytes': u'108786811673',
248 u'path': SHARES_PATH + u'/read-write from A Sharing User',
249 u'accepted': u'True',
250 u'type': u'Share'},
251
252 {u'accepted': u'',
253 u'access_level': u'View',
254 u'free_bytes': u'',
255 u'generation': u'',
256 u'name': u'unaccepted',
257 u'node_id': u'67b61c92-855c-49d8-8e09-6d201d15c999',
258 u'other_username': u'bad guy',
259 u'other_visible_name': u'',
260 u'path': SHARES_PATH + u'/unaccepted from ',
261 u'subscribed': u'',
262 u'type': u'Share',
263 u'volume_id': u'19963c95-c684-48db-8668-ebe6d820d5c3'},
188]264]
189265
190SAMPLE_SHARED = [266SAMPLE_SHARED = [
@@ -196,7 +272,7 @@
196 u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',272 u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
197 u'other_username': u'otheruser',273 u'other_username': u'otheruser',
198 u'other_visible_name': u'Other User',274 u'other_visible_name': u'Other User',
199 u'path': u'/home/tester/Ubuntu One/bar',275 u'path': ROOT_PATH + u'/bar',
200 u'type': u'Shared',276 u'type': u'Shared',
201 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},277 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
202]278]
203279
=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
--- ubuntuone/controlpanel/tests/test_backend.py 2011-01-25 19:08:59 +0000
+++ ubuntuone/controlpanel/tests/test_backend.py 2011-02-12 03:22:55 +0000
@@ -19,6 +19,8 @@
1919
20"""Tests for the control panel backend."""20"""Tests for the control panel backend."""
2121
22from collections import defaultdict
23
22import simplejson24import simplejson
2325
24from twisted.internet import defer26from twisted.internet import defer
@@ -42,6 +44,7 @@
42 EXPECTED_ACCOUNT_INFO,44 EXPECTED_ACCOUNT_INFO,
43 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,45 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
44 EXPECTED_DEVICES_INFO,46 EXPECTED_DEVICES_INFO,
47 ROOT_PATH,
45 SAMPLE_ACCOUNT_NO_CURRENT_PLAN,48 SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
46 SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,49 SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
47 SAMPLE_DEVICES_JSON,50 SAMPLE_DEVICES_JSON,
@@ -49,6 +52,8 @@
49 SAMPLE_QUOTA_JSON,52 SAMPLE_QUOTA_JSON,
50 SAMPLE_SHARED,53 SAMPLE_SHARED,
51 SAMPLE_SHARES,54 SAMPLE_SHARES,
55 SHARES_PATH,
56 SHARES_PATH_LINK,
52 TOKEN,57 TOKEN,
53)58)
54from ubuntuone.controlpanel.webclient import WebClientError59from ubuntuone.controlpanel.webclient import WebClientError
@@ -86,8 +91,8 @@
86 }91 }
87 status_changed_handler = None92 status_changed_handler = None
88 subscribed_folders = []93 subscribed_folders = []
94 subscribed_shares = []
89 actions = []95 actions = []
90 root_dir = u'/home/tester/Something/Pepe Mosquito'
9196
92 def get_credentials(self):97 def get_credentials(self):
93 """Return the mock credentials."""98 """Return the mock credentials."""
@@ -145,7 +150,15 @@
145150
146 def get_root_dir(self):151 def get_root_dir(self):
147 """Grab the root dir."""152 """Grab the root dir."""
148 return self.root_dir153 return ROOT_PATH
154
155 def get_shares_dir(self):
156 """Grab the shares dir."""
157 return SHARES_PATH
158
159 def get_shares_dir_link(self):
160 """Grab the shares dir."""
161 return SHARES_PATH_LINK
149162
150 def get_folders(self):163 def get_folders(self):
151 """Grab list of folders."""164 """Grab list of folders."""
@@ -153,11 +166,23 @@
153166
154 def subscribe_folder(self, volume_id):167 def subscribe_folder(self, volume_id):
155 """Subcribe to 'volume_id'."""168 """Subcribe to 'volume_id'."""
156 self.subscribed_folders.append(volume_id)169 MockDBusClient.subscribed_folders.append(volume_id)
157170
158 def unsubscribe_folder(self, volume_id):171 def unsubscribe_folder(self, volume_id):
159 """Unsubcribe from 'volume_id'."""172 """Unsubcribe from 'volume_id'."""
160 self.subscribed_folders.remove(volume_id)173 MockDBusClient.subscribed_folders.remove(volume_id)
174
175 def get_shares(self):
176 """Grab list of shares."""
177 return SAMPLE_SHARES
178
179 def subscribe_share(self, volume_id):
180 """Subcribe to 'volume_id'."""
181 MockDBusClient.subscribed_shares.append(volume_id)
182
183 def unsubscribe_share(self, volume_id):
184 """Unsubcribe from 'volume_id'."""
185 MockDBusClient.subscribed_shares.remove(volume_id)
161186
162 def get_current_status(self):187 def get_current_status(self):
163 """Grab syncdaemon status."""188 """Grab syncdaemon status."""
@@ -167,10 +192,6 @@
167 """Connect a handler for tracking syncdaemon status changes."""192 """Connect a handler for tracking syncdaemon status changes."""
168 self.status_changed_handler = handler193 self.status_changed_handler = handler
169194
170 def get_shares(self):
171 """Grab list of shares (shares from others to the user)."""
172 return SAMPLE_SHARES
173
174 def get_shared(self):195 def get_shared(self):
175 """Grab list of shared (shares from the user to others)."""196 """Grab list of shared (shares from the user to others)."""
176 return SAMPLE_SHARED197 return SAMPLE_SHARED
@@ -370,40 +391,106 @@
370class BackendVolumesTestCase(BackendBasicTestCase):391class BackendVolumesTestCase(BackendBasicTestCase):
371 """Volumes tests for the backend."""392 """Volumes tests for the backend."""
372393
394 def setUp(self):
395 super(BackendVolumesTestCase, self).setUp()
396 # fake quota info and calculate free bytes
397 # pylint: disable=E1101
398 self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN
399 self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
400
373 @inlineCallbacks401 @inlineCallbacks
374 def test_volumes_info(self):402 def test_volumes_info(self):
375 """The volumes_info method exercises its callback."""403 """The volumes_info method exercises its callback."""
376 # fake quota info and calculate free bytes
377 # pylint: disable=E1101
378 self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN
379 self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON
380 result = yield self.be.account_info()404 result = yield self.be.account_info()
381 free_bytes = int(result['quota_total']) - int(result['quota_used'])405 free_bytes = int(result['quota_total']) - int(result['quota_used'])
382406
383 # get root dir info407 # get root dir info
384 root_dir = MockDBusClient.root_dir408 root_volume = {u'volume_id': u'', u'path': ROOT_PATH,
385 root_volume = {u'volume_id': u'', u'path': root_dir,409 u'subscribed': 'True', u'type': self.be.ROOT_TYPE}
386 u'subscribed': 'True', u'type': u'ROOT'}410
411 # get shares and group by sharing user
412 shares = defaultdict(list)
413 for share in SAMPLE_SHARES:
414 # filter out non accepted values
415 if not bool(share['accepted']):
416 continue
417
418 share = share.copy()
419
420 nicer_path = share[u'path'].replace(SHARES_PATH, SHARES_PATH_LINK)
421 share[u'path'] = nicer_path
422 share[u'type'] = self.be.SHARE_TYPE
423
424 username = share['other_visible_name']
425 if not username:
426 username = share['other_username'] + \
427 ' (%s)' % self.be.NAME_NOT_SET
428
429 shares[username].append(share)
430
431 folders = []
432 for folder in SAMPLE_FOLDERS:
433 folder = folder.copy()
434 folder[u'type'] = self.be.FOLDER_TYPE
435 folders.append(folder)
436
437 expected = [(u'', unicode(free_bytes), [root_volume] + folders)]
438 for other_user, data in shares.iteritems():
439 expected.append((other_user, data[0][u'free_bytes'], data))
387440
388 result = yield self.be.volumes_info()441 result = yield self.be.volumes_info()
389
390 expected = [('', unicode(free_bytes), [root_volume] + SAMPLE_FOLDERS)]
391 # later, we should unify folders and shares info
392 self.assertEqual(result, expected)442 self.assertEqual(result, expected)
393443
394 @inlineCallbacks444 # pylint: disable=W0212
395 def test_subscribe_volume(self):445
446 def test_cached_volumes_are_initially_empty(self):
447 """The cached volume list is empty."""
448 self.assertEqual(self.be._volumes, {})
449
450 @inlineCallbacks
451 def test_volumes_are_cached(self):
452 """The volume list is cached."""
453 # get root dir info
454 root_volume = {u'volume_id': u'', u'path': ROOT_PATH,
455 u'subscribed': 'True', u'type': self.be.ROOT_TYPE}
456 expected = {u'': root_volume}
457 for volume in SAMPLE_SHARES + SAMPLE_FOLDERS:
458 if volume[u'type'] == u'UDF':
459 volume[u'type'] = self.be.FOLDER_TYPE
460 else:
461 volume[u'type'] = self.be.SHARE_TYPE
462
463 sid = volume['volume_id']
464 assert sid not in expected
465 expected[sid] = volume
466
467 _ = yield self.be.volumes_info()
468
469 self.assertEqual(self.be._volumes, expected)
470
471 @inlineCallbacks
472 def test_cached_volumes_are_updated_with_volume_info(self):
473 """The cached volume list is updated."""
474 yield self.test_volumes_are_cached()
475 yield self.test_volumes_are_cached()
476
477 @inlineCallbacks
478 def test_subscribe_volume_folder(self):
396 """The subscribe_volume method subscribes a folder."""479 """The subscribe_volume method subscribes a folder."""
397 fid = '0123-4567'480 fid = '0123-4567'
481 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
482
398 yield self.be.subscribe_volume(volume_id=fid)483 yield self.be.subscribe_volume(volume_id=fid)
399 self.addCleanup(lambda: MockDBusClient.subscribed_folders.remove(fid))484 self.addCleanup(lambda: MockDBusClient.subscribed_folders.remove(fid))
400485
401 self.assertEqual(MockDBusClient.subscribed_folders, [fid])486 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
402487
403 @inlineCallbacks488 @inlineCallbacks
404 def test_unsubscribe_volume(self):489 def test_unsubscribe_volume_folder(self):
405 """The unsubscribe_volume method unsubscribes a folder."""490 """The unsubscribe_volume method unsubscribes a folder."""
406 fid = '0123-4567'491 fid = '0123-4567'
492 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
493
407 yield self.be.subscribe_volume(volume_id=fid)494 yield self.be.subscribe_volume(volume_id=fid)
408 self.assertEqual(MockDBusClient.subscribed_folders, [fid])495 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
409496
@@ -412,9 +499,34 @@
412 self.assertEqual(MockDBusClient.subscribed_folders, [])499 self.assertEqual(MockDBusClient.subscribed_folders, [])
413500
414 @inlineCallbacks501 @inlineCallbacks
415 def test_change_volume_settings(self):502 def test_subscribe_volume_share(self):
503 """The subscribe_volume method subscribes a share."""
504 sid = '0123-4567'
505 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
506
507 yield self.be.subscribe_volume(volume_id=sid)
508 self.addCleanup(lambda: MockDBusClient.subscribed_shares.remove(sid))
509
510 self.assertEqual(MockDBusClient.subscribed_shares, [sid])
511
512 @inlineCallbacks
513 def test_unsubscribe_volume_share(self):
514 """The unsubscribe_volume method unsubscribes a share."""
515 sid = '0123-4567'
516 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
517
518 yield self.be.subscribe_volume(volume_id=sid)
519 self.assertEqual(MockDBusClient.subscribed_shares, [sid])
520
521 yield self.be.unsubscribe_volume(volume_id=sid)
522
523 self.assertEqual(MockDBusClient.subscribed_shares, [])
524
525 @inlineCallbacks
526 def test_change_volume_settings_folder(self):
416 """The volume settings can be changed."""527 """The volume settings can be changed."""
417 fid = '0123-4567'528 fid = '0123-4567'
529 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
418530
419 yield self.be.change_volume_settings(fid, {'subscribed': 'True'})531 yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
420 self.assertEqual(MockDBusClient.subscribed_folders, [fid])532 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
@@ -423,10 +535,23 @@
423 self.assertEqual(MockDBusClient.subscribed_folders, [])535 self.assertEqual(MockDBusClient.subscribed_folders, [])
424536
425 @inlineCallbacks537 @inlineCallbacks
538 def test_change_volume_settings_share(self):
539 """The volume settings can be changed."""
540 sid = '0123-4567'
541 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
542
543 yield self.be.change_volume_settings(sid, {'subscribed': 'True'})
544 self.assertEqual(MockDBusClient.subscribed_shares, [sid])
545
546 yield self.be.change_volume_settings(sid, {'subscribed': ''})
547 self.assertEqual(MockDBusClient.subscribed_shares, [])
548
549 @inlineCallbacks
426 def test_change_volume_settings_no_setting(self):550 def test_change_volume_settings_no_setting(self):
427 """The change volume settings does not fail on empty settings."""551 """The change volume settings does not fail on empty settings."""
428 yield self.be.change_volume_settings('test', {})552 yield self.be.change_volume_settings('test', {})
429 self.assertEqual(MockDBusClient.subscribed_folders, [])553 self.assertEqual(MockDBusClient.subscribed_folders, [])
554 self.assertEqual(MockDBusClient.subscribed_shares, [])
430555
431556
432class BackendSyncStatusTestCase(BackendBasicTestCase):557class BackendSyncStatusTestCase(BackendBasicTestCase):

Subscribers

People subscribed via source and target branches

to all changes: