Merge lp:~nataliabidart/ubuntuone-control-panel/better-folder-mgmt into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: dobey
Approved revision: 174
Merged at revision: 166
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/better-folder-mgmt
Merge into: lp:ubuntuone-control-panel
Diff against target: 1452 lines (+641/-246)
25 files modified
data/qt/controlpanel.ui (+3/-3)
data/qt/device.ui (+1/-1)
data/qt/devices.ui (+5/-12)
data/qt/filesyncstatus.ui (+1/-1)
data/qt/folders.ui (+1/-1)
data/qt/preferences.ui (+82/-88)
data/qt/profile.ui (+3/-3)
data/qt/services.ui (+3/-26)
data/qt/ubuntuonebin.ui (+64/-0)
ubuntuone/controlpanel/gui/__init__.py (+5/-0)
ubuntuone/controlpanel/gui/qt/devices.py (+22/-8)
ubuntuone/controlpanel/gui/qt/folders.py (+32/-14)
ubuntuone/controlpanel/gui/qt/main/linux.py (+1/-1)
ubuntuone/controlpanel/gui/qt/preferences.py (+10/-17)
ubuntuone/controlpanel/gui/qt/profile.py (+10/-6)
ubuntuone/controlpanel/gui/qt/services.py (+13/-6)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+3/-4)
ubuntuone/controlpanel/gui/qt/tests/test_devices.py (+26/-5)
ubuntuone/controlpanel/gui/qt/tests/test_folders.py (+120/-31)
ubuntuone/controlpanel/gui/qt/tests/test_preferences.py (+19/-9)
ubuntuone/controlpanel/gui/qt/tests/test_profile.py (+19/-5)
ubuntuone/controlpanel/gui/qt/tests/test_services.py (+19/-5)
ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py (+92/-0)
ubuntuone/controlpanel/gui/qt/ubuntuonebin.py (+85/-0)
ubuntuone/controlpanel/sd_client/linux.py (+2/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/better-folder-mgmt
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+65281@code.launchpad.net

Commit message

- Implementing a common ancestor for Control Panel's tabs to gracefully handle info processing and delays from the backend.

- Cloud folder creation process is now improved: new UDFs can not be nested, but can be a prefix of an existent folder (LP: #798964).

- Going back to using the system's SDTool, seems like DBus is not longer segfaulting (finger cross!).

Description of the change

DISCLAIMER: please ignore all the warnings that look like:

QLayout: Attempting to add QLayout "" to SomePanel "Form", which already has a layout

I know what casues them, I'm not sure how to fix them, I need our QT guru to teach me how to nest Ui_Forms better.

To test IRL, run:

DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-qt

and go to the Cloud Folders tab. You should your REAL folder info (be careful when playing around!). You should try to add new folders inside an UDF, and also trying with a folder which has in its name the same prefix as an existent UDF (this last operation should succeed).

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

- Adding folder A/B/C and then adding folder A/B/C/D fails as it should and gives a nice error dialog.
- Adding folder A/B/C/D and then adding folder A/B/C fails, and shows this on the command line:

Failure: ubuntuone.platform.linux.tools.ErrorSignal: ('FolderCreateError', (dbus.Dictionary({dbus.String(u'path'): dbus.String(u'/home/eric/ogg/dominik_eulberg')}, signature=dbus.Signature('ss')), dbus.String(u'UDFs can not be nested')))

But after this everything on the Files tab remains greyed out for a long time, and then becomes reponsive again with no changes.

We should at least show a similar error message. (For a future version we could think about just adding the new UDF and removing all previous once that it contains, but that may be harder to explain than just disallowing it.)

I will approve this branch, (unless I find other problems) since it is quite big already, so the other case can be solved in a new one.

I've tested with folders sharing a prefix, but I don't see that being very useful, since all UDFs share a common prefix already (/home/$USER/), so I would assume this has always worked? Maybe I misunderstand the instruction.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

+ 'Please choose a folder outside the existent cloud '
467 + 'folder "%(existent_folder_path)s".'

s/existent/existing/

review: Needs Fixing
Revision history for this message
Eric Casteleijn (thisfred) wrote :

in FoldersPanel.on_add_folder_button_clicked, this seems unnecessary and dangerous:

folder = unicode(folder)

This will take any 8-bit ascii string and make it into a unicode string with the same content, or return unmodified anything that is already a unicode string. Anything that is an encoded string with non-ascii characters will break it though.

Of course showing a path with an unknown encoding to the user will present challenges, but at least we should not error. So maybe use: unicode(folder, 'utf-8', 'replace'), but I have no idea if utf-8 is actually the default on Windows.

review: Needs Fixing
Revision history for this message
Eric Casteleijn (thisfred) wrote :

I see you test for unicode paths, but the trick is to test for encoded paths instead (or at least I think so, does the QT ui do the encoding for you? In that case, the unicode() call is still unnecessary.)

Revision history for this message
Eric Casteleijn (thisfred) :
review: Approve
Revision history for this message
Eric Casteleijn (thisfred) wrote :

I will set this to approve, to not hold this up in case I'm not around to re-review, but please fix the last two concerns (or explain why I'm wrong about them ;)

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> I see you test for unicode paths, but the trick is to test for encoded paths
> instead (or at least I think so, does the QT ui do the encoding for you? In
> that case, the unicode() call is still unnecessary.)

Qt returns a Qstring, which does not support any of the string/unicode methods from the python standard lib. So we need to cast explicitly to unicode() in order to operate with them.

But since you pointed this out, I went to the doc again and I realized I should do a better string management (http://doc.qt.nokia.com/latest/qstring.html#converting-between-8-bit-strings-and-unicode-strings). I'll fix.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> + 'Please choose a folder outside the existent cloud '
> 467 + 'folder "%(existent_folder_path)s".'
>
> s/existent/existing/

Fixed!

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> - Adding folder A/B/C and then adding folder A/B/C/D fails as it should and
> gives a nice error dialog.
> - Adding folder A/B/C/D and then adding folder A/B/C fails, and shows this on
> the command line:
>
> Failure: ubuntuone.platform.linux.tools.ErrorSignal: ('FolderCreateError',
> (dbus.Dictionary({dbus.String(u'path'):
> dbus.String(u'/home/eric/ogg/dominik_eulberg')},
> signature=dbus.Signature('ss')), dbus.String(u'UDFs can not be nested')))
>
> But after this everything on the Files tab remains greyed out for a long time,
> and then becomes reponsive again with no changes.
>
> We should at least show a similar error message. (For a future version we
> could think about just adding the new UDF and removing all previous once that
> it contains, but that may be harder to explain than just disallowing it.)
>
> I will approve this branch, (unless I find other problems) since it is quite
> big already, so the other case can be solved in a new one.

Thanks. I opened bug #800161 to track this.

> I've tested with folders sharing a prefix, but I don't see that being very
> useful, since all UDFs share a common prefix already (/home/$USER/), so I
> would assume this has always worked? Maybe I misunderstand the instruction.

Is very common to make the mistake of not adding the path separator to a path when checking if one oath is prefix of the other, so what I meant was:

having a UDF called A/B, try to create a new one in A/B1. The first version of this branch will no allow it ;-) (but this final version does, and has tests for that).

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> > I see you test for unicode paths, but the trick is to test for encoded paths
> > instead (or at least I think so, does the QT ui do the encoding for you? In
> > that case, the unicode() call is still unnecessary.)
>
> Qt returns a Qstring, which does not support any of the string/unicode methods
> from the python standard lib. So we need to cast explicitly to unicode() in
> order to operate with them.
>
> But since you pointed this out, I went to the doc again and I realized I
> should do a better string management
> (http://doc.qt.nokia.com/latest/qstring.html#converting-between-8-bit-strings-
> and-unicode-strings). I'll fix.

Reading the doc in detail, seems like a QString is already handling all the needed encoding/decoding. As you can see from a run:

PyQt4.QtCore.QString(u'/home/nessita/\xf1o\xf1o \xf1and\xfa')

but I do need to extract the unicode inside the QString, since that is what the backend expects. Does it make sense?

Revision history for this message
Eric Casteleijn (thisfred) wrote :

It does, but here you're passing in a unicode string, what happens if you pass in an encoded string (which I'm pretty sure you can get from the filesystem.)

PyQt4.QtCore.QString(u'/home/nessita/\xf1o\xf1o \xf1and\xfa'.encode('utf-8'))

Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (90.4 KiB)

The attempt to merge lp:~nataliabidart/ubuntuone-control-panel/better-folder-mgmt into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.

running build
Compiled data/qt/device.ui into ubuntuone/controlpanel/gui/qt/ui/device_ui.py
Compiled data/qt/mainwindow.ui into ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py
Compiled data/qt/filesyncstatus.ui into ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py
Compiled data/qt/folders.ui into ubuntuone/controlpanel/gui/qt/ui/folders_ui.py
Compiled data/qt/devices.ui into ubuntuone/controlpanel/gui/qt/ui/devices_ui.py
Compiled data/qt/preferences.ui into ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py
compiled data/qt/images.qrc into ubuntuone/controlpanel/gui/qt/ui/images_rc.py
Compiled data/qt/ubuntuonebin.ui into ubuntuone/controlpanel/gui/qt/ui/ubuntuonebin_ui.py
Compiled data/qt/services.ui into ubuntuone/controlpanel/gui/qt/ui/services_ui.py
Compiled data/qt/controlpanel.ui into ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py
Compiled data/qt/profile.ui into ubuntuone/controlpanel/gui/qt/ui/profile_ui.py
running build_py
creating build
creating build/lib.linux-i686-2.7
creating build/lib.linux-i686-2.7/ubuntuone
copying ubuntuone/__init__.py -> build/lib.linux-i686-2.7/ubuntuone
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/replication_client.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/utils.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/login_client.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/dbus_service.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/backend.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/logger.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui
copying ubuntuone/controlpanel/gui/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/package_manager.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/widgets.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/gui.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/ubuntuonebin.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/gui.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/controlpanel.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/co...

174. By Natalia Bidart

Why lint why!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/qt/controlpanel.ui'
2--- data/qt/controlpanel.ui 2011-06-02 15:49:20 +0000
3+++ data/qt/controlpanel.ui 2011-06-21 16:31:15 +0000
4@@ -6,8 +6,8 @@
5 <rect>
6 <x>0</x>
7 <y>0</y>
8- <width>646</width>
9- <height>487</height>
10+ <width>374</width>
11+ <height>172</height>
12 </rect>
13 </property>
14 <property name="sizePolicy">
15@@ -17,7 +17,7 @@
16 </sizepolicy>
17 </property>
18 <property name="windowTitle">
19- <string>Form</string>
20+ <string notr="true">Form</string>
21 </property>
22 <layout class="QVBoxLayout" name="verticalLayout">
23 <item>
24
25=== modified file 'data/qt/device.ui'
26--- data/qt/device.ui 2011-06-02 15:48:25 +0000
27+++ data/qt/device.ui 2011-06-21 16:31:15 +0000
28@@ -11,7 +11,7 @@
29 </rect>
30 </property>
31 <property name="windowTitle">
32- <string>Form</string>
33+ <string notr="true">Form</string>
34 </property>
35 <layout class="QHBoxLayout" name="horizontalLayout">
36 <item>
37
38=== modified file 'data/qt/devices.ui'
39--- data/qt/devices.ui 2011-06-02 15:30:51 +0000
40+++ data/qt/devices.ui 2011-06-21 16:31:15 +0000
41@@ -6,22 +6,15 @@
42 <rect>
43 <x>0</x>
44 <y>0</y>
45- <width>702</width>
46- <height>434</height>
47+ <width>558</width>
48+ <height>156</height>
49 </rect>
50 </property>
51 <property name="windowTitle">
52- <string>Form</string>
53+ <string notr="true">Form</string>
54 </property>
55 <layout class="QVBoxLayout" name="verticalLayout">
56 <item>
57- <widget class="QLabel" name="tab_header_label">
58- <property name="text">
59- <string>All cloud connected devices</string>
60- </property>
61- </widget>
62- </item>
63- <item>
64 <widget class="QScrollArea" name="scrollArea">
65 <property name="verticalScrollBarPolicy">
66 <enum>Qt::ScrollBarAlwaysOn</enum>
67@@ -37,8 +30,8 @@
68 <rect>
69 <x>0</x>
70 <y>0</y>
71- <width>669</width>
72- <height>391</height>
73+ <width>535</width>
74+ <height>136</height>
75 </rect>
76 </property>
77 <layout class="QGridLayout" name="gridLayout_2">
78
79=== modified file 'data/qt/filesyncstatus.ui'
80--- data/qt/filesyncstatus.ui 2011-05-26 22:18:04 +0000
81+++ data/qt/filesyncstatus.ui 2011-06-21 16:31:15 +0000
82@@ -11,7 +11,7 @@
83 </rect>
84 </property>
85 <property name="windowTitle">
86- <string>Form</string>
87+ <string notr="true">Form</string>
88 </property>
89 <layout class="QHBoxLayout" name="horizontalLayout_2">
90 <item>
91
92=== modified file 'data/qt/folders.ui'
93--- data/qt/folders.ui 2011-06-01 18:10:22 +0000
94+++ data/qt/folders.ui 2011-06-21 16:31:15 +0000
95@@ -11,7 +11,7 @@
96 </rect>
97 </property>
98 <property name="windowTitle">
99- <string>Form</string>
100+ <string notr="true">Form</string>
101 </property>
102 <layout class="QVBoxLayout" name="verticalLayout">
103 <item>
104
105=== modified file 'data/qt/preferences.ui'
106--- data/qt/preferences.ui 2011-06-16 18:18:23 +0000
107+++ data/qt/preferences.ui 2011-06-21 16:31:15 +0000
108@@ -6,98 +6,92 @@
109 <rect>
110 <x>0</x>
111 <y>0</y>
112- <width>737</width>
113- <height>481</height>
114+ <width>469</width>
115+ <height>352</height>
116 </rect>
117 </property>
118 <property name="windowTitle">
119- <string>Form</string>
120+ <string notr="true">Form</string>
121 </property>
122- <layout class="QVBoxLayout" name="verticalLayout_2">
123+ <layout class="QVBoxLayout" name="verticalLayout">
124 <item>
125 <widget class="QGroupBox" name="verticalGroupBox">
126- <layout class="QVBoxLayout" name="verticalLayout_4">
127- <item>
128- <widget class="QGroupBox" name="groupBox">
129- <property name="title">
130- <string>Bandwidth settings</string>
131- </property>
132- <layout class="QGridLayout" name="gridLayout">
133- <item row="0" column="0">
134- <widget class="QCheckBox" name="limit_uploads_checkbox">
135- <property name="text">
136- <string>Limit upload speed to</string>
137- </property>
138- </widget>
139- </item>
140- <item row="0" column="1">
141- <widget class="QSpinBox" name="upload_speed_spinbox">
142- <property name="minimum">
143- <number>-1</number>
144- </property>
145- <property name="maximum">
146- <number>999999999</number>
147- </property>
148- </widget>
149- </item>
150- <item row="0" column="2">
151- <widget class="QLabel" name="kbps_label_1">
152- <property name="text">
153- <string>Kilobits per second</string>
154- </property>
155- </widget>
156- </item>
157- <item row="2" column="0">
158- <widget class="QCheckBox" name="limit_downloads_checkbox">
159- <property name="text">
160- <string>Limit download speed to</string>
161- </property>
162- </widget>
163- </item>
164- <item row="2" column="1">
165- <widget class="QSpinBox" name="download_speed_spinbox">
166- <property name="minimum">
167- <number>-1</number>
168- </property>
169- <property name="maximum">
170- <number>999999999</number>
171- </property>
172- </widget>
173- </item>
174- <item row="2" column="2">
175- <widget class="QLabel" name="kbps_label_2">
176- <property name="text">
177- <string>Kilobits per second</string>
178- </property>
179- </widget>
180- </item>
181- <item row="0" column="3">
182- <spacer name="horizontalSpacer_2">
183- <property name="orientation">
184- <enum>Qt::Horizontal</enum>
185- </property>
186- <property name="sizeHint" stdset="0">
187- <size>
188- <width>40</width>
189- <height>20</height>
190- </size>
191- </property>
192- </spacer>
193- </item>
194- <item row="3" column="0" colspan="3">
195- <widget class="QLabel" name="label_2">
196- <property name="text">
197- <string>Please note that your files will not sync if you set bandwidth to 0</string>
198- </property>
199- <property name="scaledContents">
200- <bool>false</bool>
201- </property>
202- <property name="wordWrap">
203- <bool>false</bool>
204- </property>
205- </widget>
206- </item>
207- </layout>
208+ <property name="title">
209+ <string>Bandwidth settings</string>
210+ </property>
211+ <layout class="QGridLayout" name="gridLayout">
212+ <item row="0" column="0">
213+ <widget class="QCheckBox" name="limit_uploads_checkbox">
214+ <property name="text">
215+ <string>Limit upload speed to</string>
216+ </property>
217+ </widget>
218+ </item>
219+ <item row="0" column="1">
220+ <widget class="QSpinBox" name="upload_speed_spinbox">
221+ <property name="minimum">
222+ <number>-1</number>
223+ </property>
224+ <property name="maximum">
225+ <number>999999999</number>
226+ </property>
227+ </widget>
228+ </item>
229+ <item row="0" column="2">
230+ <widget class="QLabel" name="kbps_label_1">
231+ <property name="text">
232+ <string>Kilobits per second</string>
233+ </property>
234+ </widget>
235+ </item>
236+ <item row="2" column="0">
237+ <widget class="QCheckBox" name="limit_downloads_checkbox">
238+ <property name="text">
239+ <string>Limit download speed to</string>
240+ </property>
241+ </widget>
242+ </item>
243+ <item row="2" column="1">
244+ <widget class="QSpinBox" name="download_speed_spinbox">
245+ <property name="minimum">
246+ <number>-1</number>
247+ </property>
248+ <property name="maximum">
249+ <number>999999999</number>
250+ </property>
251+ </widget>
252+ </item>
253+ <item row="2" column="2">
254+ <widget class="QLabel" name="kbps_label_2">
255+ <property name="text">
256+ <string>Kilobits per second</string>
257+ </property>
258+ </widget>
259+ </item>
260+ <item row="0" column="3">
261+ <spacer name="horizontalSpacer_2">
262+ <property name="orientation">
263+ <enum>Qt::Horizontal</enum>
264+ </property>
265+ <property name="sizeHint" stdset="0">
266+ <size>
267+ <width>40</width>
268+ <height>20</height>
269+ </size>
270+ </property>
271+ </spacer>
272+ </item>
273+ <item row="3" column="0" colspan="3">
274+ <widget class="QLabel" name="label_2">
275+ <property name="text">
276+ <string>Please note that your files will not sync if you set bandwidth to 0</string>
277+ </property>
278+ <property name="scaledContents">
279+ <bool>false</bool>
280+ </property>
281+ <property name="wordWrap">
282+ <bool>false</bool>
283+ </property>
284 </widget>
285 </item>
286 </layout>
287@@ -108,7 +102,7 @@
288 <property name="title">
289 <string>File Sync Settings</string>
290 </property>
291- <layout class="QVBoxLayout" name="verticalLayout">
292+ <layout class="QVBoxLayout" name="verticalLayout_1">
293 <item>
294 <widget class="QCheckBox" name="autoconnect_checkbox">
295 <property name="text">
296@@ -179,7 +173,7 @@
297 <property name="sizeHint" stdset="0">
298 <size>
299 <width>20</width>
300- <height>152</height>
301+ <height>40</height>
302 </size>
303 </property>
304 </spacer>
305
306=== modified file 'data/qt/profile.ui'
307--- data/qt/profile.ui 2011-06-16 19:49:43 +0000
308+++ data/qt/profile.ui 2011-06-21 16:31:15 +0000
309@@ -6,12 +6,12 @@
310 <rect>
311 <x>0</x>
312 <y>0</y>
313- <width>704</width>
314- <height>449</height>
315+ <width>456</width>
316+ <height>189</height>
317 </rect>
318 </property>
319 <property name="windowTitle">
320- <string>Form</string>
321+ <string notr="true">Form</string>
322 </property>
323 <layout class="QVBoxLayout" name="verticalLayout">
324 <item>
325
326=== modified file 'data/qt/services.ui'
327--- data/qt/services.ui 2011-05-28 06:16:06 +0000
328+++ data/qt/services.ui 2011-06-21 16:31:15 +0000
329@@ -6,22 +6,15 @@
330 <rect>
331 <x>0</x>
332 <y>0</y>
333- <width>636</width>
334- <height>478</height>
335+ <width>341</width>
336+ <height>248</height>
337 </rect>
338 </property>
339 <property name="windowTitle">
340- <string>Form</string>
341+ <string notr="true">Form</string>
342 </property>
343 <layout class="QVBoxLayout" name="verticalLayout">
344 <item>
345- <widget class="QLabel" name="tab_header_label">
346- <property name="text">
347- <string>Selected plans and storage</string>
348- </property>
349- </widget>
350- </item>
351- <item>
352 <layout class="QVBoxLayout" name="mobile_plan_verticallayout">
353 <item>
354 <layout class="QHBoxLayout" name="horizontalLayout">
355@@ -108,22 +101,6 @@
356 </layout>
357 </item>
358 <item>
359- <spacer name="verticalSpacer">
360- <property name="orientation">
361- <enum>Qt::Vertical</enum>
362- </property>
363- <property name="sizeType">
364- <enum>QSizePolicy::Preferred</enum>
365- </property>
366- <property name="sizeHint" stdset="0">
367- <size>
368- <width>20</width>
369- <height>20</height>
370- </size>
371- </property>
372- </spacer>
373- </item>
374- <item>
375 <layout class="QVBoxLayout" name="storage_plan_verticallayout">
376 <item>
377 <layout class="QHBoxLayout" name="horizontalLayout_3">
378
379=== added file 'data/qt/ubuntuonebin.ui'
380--- data/qt/ubuntuonebin.ui 1970-01-01 00:00:00 +0000
381+++ data/qt/ubuntuonebin.ui 2011-06-21 16:31:15 +0000
382@@ -0,0 +1,64 @@
383+<?xml version="1.0" encoding="UTF-8"?>
384+<ui version="4.0">
385+ <class>Form</class>
386+ <widget class="QWidget" name="Form">
387+ <property name="geometry">
388+ <rect>
389+ <x>0</x>
390+ <y>0</y>
391+ <width>248</width>
392+ <height>59</height>
393+ </rect>
394+ </property>
395+ <property name="windowTitle">
396+ <string notr="true">Form</string>
397+ </property>
398+ <layout class="QVBoxLayout" name="verticalLayout">
399+ <item>
400+ <layout class="QHBoxLayout" name="horizontalLayout">
401+ <item>
402+ <widget class="QLabel" name="title_label">
403+ <property name="text">
404+ <string notr="true">The Panel Title</string>
405+ </property>
406+ </widget>
407+ </item>
408+ <item>
409+ <spacer name="horizontalSpacer">
410+ <property name="orientation">
411+ <enum>Qt::Horizontal</enum>
412+ </property>
413+ <property name="sizeHint" stdset="0">
414+ <size>
415+ <width>40</width>
416+ <height>20</height>
417+ </size>
418+ </property>
419+ </spacer>
420+ </item>
421+ <item>
422+ <widget class="QProgressBar" name="spinner">
423+ <property name="sizePolicy">
424+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
425+ <horstretch>0</horstretch>
426+ <verstretch>0</verstretch>
427+ </sizepolicy>
428+ </property>
429+ <property name="maximum">
430+ <number>0</number>
431+ </property>
432+ <property name="value">
433+ <number>-1</number>
434+ </property>
435+ </widget>
436+ </item>
437+ </layout>
438+ </item>
439+ <item>
440+ <widget class="QWidget" name="container" native="true"/>
441+ </item>
442+ </layout>
443+ </widget>
444+ <resources/>
445+ <connections/>
446+</ui>
447
448=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
449--- ubuntuone/controlpanel/gui/__init__.py 2011-06-17 20:08:02 +0000
450+++ ubuntuone/controlpanel/gui/__init__.py 2011-06-21 16:31:15 +0000
451@@ -59,6 +59,7 @@
452
453 CONTACTS_LINK = UBUNTUONE_LINK
454 EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + 'account/'
455+EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + 'machines/'
456 EDIT_PROFILE_LINK = 'https://login.ubuntu.com/'
457 LEARN_MORE_LINK = UBUNTUONE_LINK
458 MANAGE_FILES_LINK = UBUNTUONE_LINK + 'files/'
459@@ -89,6 +90,10 @@
460 '\n\n'
461 'Please choose a folder inside your "%(home_folder)s" '
462 'directory.')
463+FOLDER_NESTED_PATH = _('The chosen directory "%(folder_path)s" is not valid. '
464+ '\n\n'
465+ 'Please choose a folder outside the existing cloud '
466+ 'folder "%(existent_folder_path)s".')
467 FOLDERS_CONFIRM_MERGE = _('The contents of your cloud folder will be merged '
468 'with your local folder "%(folder_path)s" when '
469 'subscribing.\nDo you want to subscribe to this '
470
471=== modified file 'ubuntuone/controlpanel/gui/qt/devices.py'
472--- ubuntuone/controlpanel/gui/qt/devices.py 2011-06-02 19:55:45 +0000
473+++ ubuntuone/controlpanel/gui/qt/devices.py 2011-06-21 16:31:15 +0000
474@@ -18,20 +18,29 @@
475
476 """The user interface for the control panel for Ubuntu One."""
477
478-from PyQt4 import QtGui
479-
480+from gettext import gettext as _
481+
482+# Unused import QtGui
483+# pylint: disable=W0611
484+from PyQt4 import QtGui, QtCore
485+# pylint: enable=W0611
486+
487+from ubuntuone.controlpanel.gui import EDIT_DEVICES_LINK
488+from ubuntuone.controlpanel.gui.qt import uri_hook
489 from ubuntuone.controlpanel.gui.qt.ui import devices_ui
490+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
491 from ubuntuone.controlpanel.gui.qt.device import DeviceWidget
492
493
494-class DevicesPanel(QtGui.QWidget):
495+class DevicesPanel(UbuntuOneBin):
496 """The DevicesFolders Tab Panel widget"""
497
498- def __init__(self, *args):
499- """Initialize the UI of the widget."""
500- QtGui.QWidget.__init__(self, *args)
501- self.ui = devices_ui.Ui_Form()
502- self.ui.setupUi(self)
503+ title = _('All cloud-connected devices')
504+ ui_class = devices_ui
505+
506+ def load(self):
507+ """Load info."""
508+ # request devices info
509
510 def update_local_device(self, device_info):
511 """Update the info for the local device."""
512@@ -54,3 +63,8 @@
513 """Update the list of devices in this panel."""
514 for device_info in devices_info:
515 self.update_device_info(device_info)
516+
517+ @QtCore.pyqtSlot()
518+ def on_manage_devices_button_clicked(self):
519+ """The storage plan modify button was clicked."""
520+ uri_hook(EDIT_DEVICES_LINK)
521
522=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
523--- ubuntuone/controlpanel/gui/qt/folders.py 2011-06-17 21:11:45 +0000
524+++ ubuntuone/controlpanel/gui/qt/folders.py 2011-06-21 16:31:15 +0000
525@@ -25,7 +25,6 @@
526 from PyQt4 import QtGui, QtCore
527 from twisted.internet import defer
528
529-from ubuntuone.controlpanel import backend
530 from ubuntuone.controlpanel.logger import setup_logging, log_call
531 from ubuntuone.controlpanel.gui import (
532 ALWAYS_SUBSCRIBED,
533@@ -34,6 +33,7 @@
534 FILE_URI_PREFIX,
535 FOLDER_ICON_NAME,
536 FOLDER_INVALID_PATH,
537+ FOLDER_NESTED_PATH,
538 FOLDERS_CONFIRM_MERGE,
539 KILOBYTES,
540 MANAGE_FILES_LINK,
541@@ -47,6 +47,7 @@
542 SHARES_MIN_SIZE_FULL,
543 )
544 from ubuntuone.controlpanel.gui.qt import uri_hook
545+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
546 from ubuntuone.controlpanel.gui.qt.ui import folders_ui
547
548
549@@ -68,17 +69,22 @@
550 YES = QtGui.QMessageBox.Yes
551
552
553-class FoldersPanel(QtGui.QWidget):
554+def append_path_sep(path):
555+ """If 'path' does not end with the path separator, append it."""
556+ if not path.endswith(os.path.sep):
557+ path += os.path.sep
558+ return path
559+
560+
561+class FoldersPanel(UbuntuOneBin):
562 """The Folders Tab Panel widget"""
563
564- def __init__(self, *args):
565+ ui_class = folders_ui
566+
567+ def __init__(self, *args, **kwargs):
568 """Initialize the UI of the widget."""
569- QtGui.QWidget.__init__(self, *args)
570- self.ui = folders_ui.Ui_Form()
571- self.ui.setupUi(self)
572-
573- self.is_processing = False
574- self.backend = backend.ControlBackend()
575+ super(FoldersPanel, self).__init__(*args, **kwargs)
576+ self._backend_folders = None
577 logger.debug('%s: started.', self.__class__.__name__)
578
579 # Invalid name "showEvent"
580@@ -89,9 +95,7 @@
581 headers = self.ui.folders.header()
582 headers.setResizeMode(FOLDER_NAME_COL, headers.Stretch)
583 headers.setResizeMode(SUBSCRIPTION_COL, headers.ResizeToContents)
584-
585- self.load()
586- event.accept()
587+ super(FoldersPanel, self).showEvent(event)
588
589 @defer.inlineCallbacks
590 def load(self):
591@@ -127,6 +131,7 @@
592 def process_folders(self, info):
593 """Load folders info into the tree view."""
594 self.ui.folders.clear()
595+ self._backend_folders = []
596 self.is_processing = False
597
598 if not info:
599@@ -170,6 +175,7 @@
600 child = QtGui.QTreeWidgetItem()
601 child.volume_path = volume['path']
602 child.volume_id = volume['volume_id']
603+ self._backend_folders.append(append_path_sep(volume['path']))
604
605 name = self._process_name(volume[u'display_name'])
606 icon_name = FOLDER_ICON_NAME
607@@ -267,19 +273,31 @@
608 @defer.inlineCallbacks
609 def on_add_folder_button_clicked(self):
610 """The 'Sync another folder' button was clicked."""
611- user_home = os.path.expanduser('~')
612+ user_home = append_path_sep(os.path.expanduser('~'))
613 folder = QtGui.QFileDialog.getExistingDirectory(parent=self,
614 directory=user_home)
615+ folder = unicode(folder)
616 logger.debug('on_add_folder_button_clicked: user requested folder '
617 'creation for path %r', folder)
618 if folder == '':
619 return
620
621+ folder = append_path_sep(folder)
622+
623 # handle folder path not within '~'
624- if not folder.startsWith(user_home): # folder is a QString
625+ if not folder.startswith(user_home):
626 text = FOLDER_INVALID_PATH % {'folder_path': folder,
627 'home_folder': user_home}
628 QtGui.QMessageBox.warning(self, '', text, CLOSE)
629+ return
630+
631+ # handle folder path inside an existent cloud folder
632+ inside_udf = filter(folder.startswith, self._backend_folders)
633+ if inside_udf:
634+ text = FOLDER_NESTED_PATH % {'folder_path': folder,
635+ 'existent_folder_path': inside_udf[0]}
636+ QtGui.QMessageBox.warning(self, '', text, CLOSE)
637+ return
638
639 self.is_processing = True
640 yield self.backend.create_folder(folder_path=folder)
641
642=== modified file 'ubuntuone/controlpanel/gui/qt/main/linux.py'
643--- ubuntuone/controlpanel/gui/qt/main/linux.py 2011-06-20 01:46:29 +0000
644+++ ubuntuone/controlpanel/gui/qt/main/linux.py 2011-06-21 16:31:15 +0000
645@@ -149,7 +149,7 @@
646 subprocess.Popen(('u1sdtool', '--unsubscribe-folder', folder_id))
647
648 from ubuntuone.platform.linux import tools
649- tools.SyncDaemonTool = SDTool
650+ #tools.SyncDaemonTool = SDTool
651
652 # pylint believes that reactor has no run nor stop methods. Silence it.
653 # pylint: disable=E1101
654
655=== modified file 'ubuntuone/controlpanel/gui/qt/preferences.py'
656--- ubuntuone/controlpanel/gui/qt/preferences.py 2011-06-17 20:08:02 +0000
657+++ ubuntuone/controlpanel/gui/qt/preferences.py 2011-06-21 16:31:15 +0000
658@@ -18,14 +18,19 @@
659
660 """The user interface for the control panel for Ubuntu One."""
661
662+# Unused import QtGui
663+# pylint: disable=W0611
664 from PyQt4 import QtGui, QtCore
665+# pylint: enable=W0611
666 from twisted.internet import defer
667
668 from ubuntuone.controlpanel import backend
669 from ubuntuone.controlpanel.logger import setup_logging, log_call
670 from ubuntuone.controlpanel.gui import KILOBYTES
671+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
672 from ubuntuone.controlpanel.gui.qt.ui import preferences_ui
673
674+
675 logger = setup_logging('qt.preferences')
676
677 CHECKED = QtCore.Qt.Checked
678@@ -37,25 +42,10 @@
679 return CHECKED if checked else UNCHECKED
680
681
682-class PreferencesPanel(QtGui.QWidget):
683+class PreferencesPanel(UbuntuOneBin):
684 """The Preferences Tab Panel widget"""
685
686- def __init__(self, *args):
687- """Initialize the UI of the widget."""
688- QtGui.QWidget.__init__(self, *args)
689- self.ui = preferences_ui.Ui_Form()
690- self.ui.setupUi(self)
691-
692- self.backend = backend.ControlBackend()
693- logger.debug('%s: started.', self.__class__.__name__)
694-
695- # Invalid name "showEvent"
696- # pylint: disable=C0103
697-
698- def showEvent(self, event):
699- """Load info."""
700- self.load()
701- event.accept()
702+ ui_class = preferences_ui
703
704 @defer.inlineCallbacks
705 def load(self):
706@@ -93,6 +83,9 @@
707 else:
708 checkbox.setCheckState(CHECKED)
709
710+ # Invalid name "on_{down, up}load_speed_spinbox_valueChanged"
711+ # pylint: disable=C0103
712+
713 @QtCore.pyqtSlot(int)
714 def on_download_speed_spinbox_valueChanged(self, new_value):
715 """A new download speed was set."""
716
717=== modified file 'ubuntuone/controlpanel/gui/qt/profile.py'
718--- ubuntuone/controlpanel/gui/qt/profile.py 2011-06-16 19:49:43 +0000
719+++ ubuntuone/controlpanel/gui/qt/profile.py 2011-06-21 16:31:15 +0000
720@@ -18,21 +18,25 @@
721
722 """The user interface for the control panel for Ubuntu One."""
723
724+# Unused import QtGui
725+# pylint: disable=W0611
726 from PyQt4 import QtGui, QtCore
727+# pylint: enable=W0611
728
729 from ubuntuone.controlpanel.gui import EDIT_PROFILE_LINK
730 from ubuntuone.controlpanel.gui.qt import uri_hook
731+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
732 from ubuntuone.controlpanel.gui.qt.ui import profile_ui
733
734
735-class ProfilePanel(QtGui.QWidget):
736+class ProfilePanel(UbuntuOneBin):
737 """The Profile Tab Panel widget"""
738
739- def __init__(self, *args):
740- """Initialize the UI of the widget."""
741- QtGui.QWidget.__init__(self, *args)
742- self.ui = profile_ui.Ui_Form()
743- self.ui.setupUi(self)
744+ ui_class = profile_ui
745+
746+ def load(self):
747+ """Load info."""
748+ # request account info
749
750 def update_account_info(self, account_info):
751 """Update the widgets with the account info."""
752
753=== modified file 'ubuntuone/controlpanel/gui/qt/services.py'
754--- ubuntuone/controlpanel/gui/qt/services.py 2011-06-16 19:49:43 +0000
755+++ ubuntuone/controlpanel/gui/qt/services.py 2011-06-21 16:31:15 +0000
756@@ -18,21 +18,28 @@
757
758 """The user interface for the control panel for Ubuntu One."""
759
760+from gettext import gettext as _
761+
762+# Unused import QtGui
763+# pylint: disable=W0611
764 from PyQt4 import QtGui, QtCore
765+# pylint: enable=W0611
766
767 from ubuntuone.controlpanel.gui import EDIT_ACCOUNT_LINK
768 from ubuntuone.controlpanel.gui.qt import uri_hook
769+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
770 from ubuntuone.controlpanel.gui.qt.ui import services_ui
771
772
773-class ServicesPanel(QtGui.QWidget):
774+class ServicesPanel(UbuntuOneBin):
775 """The ServicesFolders Tab Panel widget"""
776
777- def __init__(self, *args):
778- """Initialize the UI of the widget."""
779- QtGui.QWidget.__init__(self, *args)
780- self.ui = services_ui.Ui_Form()
781- self.ui.setupUi(self)
782+ title = _('Selected plans and storage')
783+ ui_class = services_ui
784+
785+ def load(self):
786+ """Load info."""
787+ # request account info
788
789 def update_account_info(self, account_info):
790 """Update the widgets with the account info."""
791
792=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
793--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2011-06-17 20:08:02 +0000
794+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2011-06-21 16:31:15 +0000
795@@ -67,9 +67,7 @@
796
797 def inner(instance):
798 """Skip a test if is an abstract class."""
799- abstract = any(i is None for i in (instance.innerclass_ui,
800- instance.innerclass_name,
801- instance.class_ui))
802+ abstract = instance.class_ui is None
803 result = None
804 if not abstract:
805 result = test(instance)
806@@ -172,9 +170,10 @@
807 self.assertIn(method_name, self.ui.backend._called)
808 self.assertEqual(self.ui.backend._called[method_name], (args, kwargs))
809
810- @skip_if_abstract_class
811 def test_init_loads_ui(self):
812 """The __init__ method loads the ui."""
813+ if self.innerclass_ui is None:
814+ return
815 self.patch(self.innerclass_ui, self.innerclass_name, FakeUi)
816 # pylint: disable=E1102
817 self.ui = self.class_ui(**self.kwargs)
818
819=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_devices.py'
820--- ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2011-06-02 19:55:45 +0000
821+++ ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2011-06-21 16:31:15 +0000
822@@ -19,17 +19,31 @@
823 """Tests for the devices tab."""
824
825 from ubuntuone.controlpanel.gui.qt import devices as gui
826-from ubuntuone.controlpanel.gui.qt.tests import (BaseTestCase,
827- SAMPLE_DEVICES_INFO)
828-
829-
830-class DevicesPanelTestCase(BaseTestCase):
831+from ubuntuone.controlpanel.gui.qt.tests import (
832+ SAMPLE_DEVICES_INFO,
833+)
834+from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import (
835+ UbuntuOneBinTestCase,
836+)
837+
838+
839+class DevicesPanelTestCase(UbuntuOneBinTestCase):
840 """Test the qt control panel."""
841
842 innerclass_ui = gui.devices_ui
843 innerclass_name = "Ui_Form"
844 class_ui = gui.DevicesPanel
845
846+ def test_is_processing_while_asking_info(self):
847+ """The ui is processing while the contents are loaded."""
848+ def check():
849+ """The ui must be is_processing."""
850+ self.assertTrue(self.ui.is_processing, 'ui must be processing')
851+
852+ self.patch(self.ui.backend, 'devices_info', check)
853+
854+ return self.ui.load() # trigger the info request
855+
856 def test_update_devices_info(self):
857 """The widget is updated with the info."""
858 container = self.ui.ui.devices_list_verticallayout
859@@ -38,3 +52,10 @@
860 expected_name = SAMPLE_DEVICES_INFO[0]["name"]
861 self.assertEqual(self.ui.ui.device_name_label.text(), expected_name)
862 self.assertEqual(container.count(), len(SAMPLE_DEVICES_INFO) - 1)
863+
864+ def test_manage_devices_button(self):
865+ """Clicking the manage devices button opens the proper url."""
866+ self.patch(gui, 'uri_hook', self._set_called)
867+ self.ui.ui.manage_devices_button.click()
868+
869+ self.assertEqual(self._called, ((gui.EDIT_DEVICES_LINK,), {}))
870
871=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_folders.py'
872--- ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2011-06-17 21:11:45 +0000
873+++ ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2011-06-21 16:31:15 +0000
874@@ -25,59 +25,63 @@
875 from twisted.internet import defer
876 from ubuntuone.devtools.handlers import MementoHandler
877
878-from ubuntuone.controlpanel.gui.qt import folders as gui
879-from ubuntuone.controlpanel.gui.qt.tests import (
880- BaseTestCase, FakedConfirmDialog, FakedFileDialog,
881-)
882 from ubuntuone.controlpanel.gui.tests import (
883 FAKE_VOLUMES_INFO,
884 FAKE_VOLUMES_MINIMAL_INFO,
885 FAKE_VOLUMES_NO_FREE_SPACE_INFO,
886 MUSIC_FOLDER, ROOT, USER_HOME,
887 )
888+from ubuntuone.controlpanel.gui.qt import folders as gui
889+from ubuntuone.controlpanel.gui.qt.tests import (
890+ FakedConfirmDialog, FakedFileDialog,
891+)
892+from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import (
893+ UbuntuOneBinTestCase,
894+)
895
896
897 # Access to a protected member
898-# pylint: disable=W0212
899-
900-
901-class FoldersPanelTestCase(BaseTestCase):
902+# Instance of 'ControlBackend' has no '_called' member
903+# pylint: disable=W0212, E1103
904+
905+
906+class FoldersPanelTestCase(UbuntuOneBinTestCase):
907 """Test the qt cloud folders tab."""
908
909 innerclass_ui = gui.folders_ui
910 innerclass_name = "Ui_Form"
911 class_ui = gui.FoldersPanel
912
913+ @defer.inlineCallbacks
914 def setUp(self):
915- super(FoldersPanelTestCase, self).setUp()
916+ yield super(FoldersPanelTestCase, self).setUp()
917 self.memento = MementoHandler()
918 self.memento.setLevel(logging.DEBUG)
919 gui.logger.addHandler(self.memento)
920
921- def test_backend(self):
922- """Backend is created."""
923- self.assertIsInstance(self.ui.backend,
924- gui.backend.ControlBackend)
925+ old_home = os.environ['HOME']
926+ os.environ['HOME'] = USER_HOME
927+ self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home))
928
929
930 class FoldersPanelVolumesInfoTestCase(FoldersPanelTestCase):
931 """Test the qt cloud folders tab."""
932
933+ @defer.inlineCallbacks
934 def setUp(self):
935- super(FoldersPanelVolumesInfoTestCase, self).setUp()
936- return self.ui.load()
937+ yield super(FoldersPanelVolumesInfoTestCase, self).setUp()
938+ yield self.ui.load()
939
940 @defer.inlineCallbacks
941- def test_is_processing_before_asking_volume_info(self):
942- """The ui is processing when contents are load."""
943+ def test_is_processing_while_asking_info(self):
944+ """The ui is processing while the contents are loaded."""
945 def check():
946 """The ui must be is_processing."""
947- # the ui is processing
948 self.assertTrue(self.ui.is_processing, 'ui must be processing')
949
950 self.patch(self.ui.backend, 'volumes_info', check)
951
952- yield self.ui.load() # trigger the volumes_info request
953+ yield self.ui.load() # trigger the info request
954
955 def test_is_not_processing_after_info_ready(self):
956 """The ui is not processing when contents are load."""
957@@ -312,11 +316,12 @@
958 self.assertEqual(self._called, ((gui.MANAGE_FILES_LINK,), {}))
959
960
961-class FoldersPanelFolderCreateTestCase(FoldersPanelTestCase):
962+class FoldersPanelAddFolderTestCase(FoldersPanelTestCase):
963 """The test suite for the folder creation from a local dir."""
964
965+ @defer.inlineCallbacks
966 def setUp(self):
967- super(FoldersPanelFolderCreateTestCase, self).setUp()
968+ yield super(FoldersPanelAddFolderTestCase, self).setUp()
969 # simulate volumes info properly processed
970 self.ui.process_folders([])
971 # reset backend state
972@@ -343,9 +348,9 @@
973 yield self.ui.ui.add_folder_button.click()
974
975 self.assertEqual(FakedFileDialog.args, ())
976+ user_home = gui.append_path_sep(os.path.expanduser('~'))
977 self.assertEqual(FakedFileDialog.kwargs,
978- {'parent': self.ui,
979- 'directory': os.path.expanduser('~')})
980+ {'parent': self.ui, 'directory': user_home})
981
982 @defer.inlineCallbacks
983 def test_does_nothing_if_user_closes_file_chooser(self):
984@@ -367,11 +372,90 @@
985 FakedFileDialog.response = gui.QtCore.QString(outside_home)
986 yield self.ui.ui.add_folder_button.click()
987
988- msg = gui.FOLDER_INVALID_PATH % {'folder_path': outside_home,
989- 'home_folder': user_home}
990- self.assertEqual(FakedConfirmDialog.args,
991- (self.ui, '', msg, gui.CLOSE))
992- self.assertEqual(FakedConfirmDialog.kwargs, {})
993+ args = {'folder_path': gui.append_path_sep(outside_home),
994+ 'home_folder': gui.append_path_sep(user_home)}
995+ msg = gui.FOLDER_INVALID_PATH % args
996+ self.assertEqual(FakedConfirmDialog.args,
997+ (self.ui, '', msg, gui.CLOSE))
998+ self.assertEqual(FakedConfirmDialog.kwargs, {})
999+ # the backend was not called
1000+ self.assertEqual(self.ui.backend._called, {})
1001+
1002+ @defer.inlineCallbacks
1003+ def test_opens_warning_if_folder_inside_root(self):
1004+ """If the user chooses a folder inside the root, show a warning."""
1005+ self.ui.process_folders(FAKE_VOLUMES_MINIMAL_INFO)
1006+ root_path = ROOT['path']
1007+ # create a valid path inside the root
1008+ inside_root = os.path.abspath(os.path.join(root_path, 'test'))
1009+ FakedFileDialog.response = gui.QtCore.QString(inside_root)
1010+ yield self.ui.ui.add_folder_button.click()
1011+
1012+ args = {'folder_path': gui.append_path_sep(inside_root),
1013+ 'existent_folder_path': gui.append_path_sep(root_path)}
1014+ msg = gui.FOLDER_NESTED_PATH % args
1015+ self.assertEqual(FakedConfirmDialog.args,
1016+ (self.ui, '', msg, gui.CLOSE))
1017+ self.assertEqual(FakedConfirmDialog.kwargs, {})
1018+ # the backend was not called
1019+ self.assertEqual(self.ui.backend._called, {})
1020+
1021+ @defer.inlineCallbacks
1022+ def test_opens_warning_if_folder_inside_an_udf(self):
1023+ """If the user chooses a folder inside an UDF, show a warning."""
1024+ self.ui.process_folders(FAKE_VOLUMES_MINIMAL_INFO)
1025+ udf_path = MUSIC_FOLDER['path']
1026+ # create a valid path inside an existing UDF
1027+ inside_udf = os.path.abspath(os.path.join(udf_path, 'test'))
1028+ FakedFileDialog.response = gui.QtCore.QString(inside_udf)
1029+ yield self.ui.ui.add_folder_button.click()
1030+
1031+ args = {'folder_path': gui.append_path_sep(inside_udf),
1032+ 'existent_folder_path': gui.append_path_sep(udf_path)}
1033+ msg = gui.FOLDER_NESTED_PATH % args
1034+ self.assertEqual(FakedConfirmDialog.args,
1035+ (self.ui, '', msg, gui.CLOSE))
1036+ self.assertEqual(FakedConfirmDialog.kwargs, {})
1037+ # the backend was not called
1038+ self.assertEqual(self.ui.backend._called, {})
1039+
1040+ @defer.inlineCallbacks
1041+ def test_no_warning_if_folder_is_prefix_from_a_udf(self):
1042+ """Test proper displaying of warning messages.
1043+
1044+ If the user chooses a folder with the same prefix as an UDF, but
1045+ outside every UDF, do not show a warning and call backend.
1046+
1047+ """
1048+ self.ui.process_folders(FAKE_VOLUMES_MINIMAL_INFO)
1049+ tricky_path = ROOT['path']
1050+ assert not tricky_path.endswith(os.path.sep)
1051+ tricky_path += ' Suffix'
1052+ assert tricky_path.startswith(ROOT['path'])
1053+
1054+ FakedFileDialog.response = gui.QtCore.QString(tricky_path)
1055+ yield self.ui.ui.add_folder_button.click()
1056+
1057+ # no warning
1058+ self.assertEqual(FakedConfirmDialog.args, None)
1059+ self.assertEqual(FakedConfirmDialog.kwargs, None)
1060+ # backend called
1061+ tricky_path = gui.append_path_sep(tricky_path)
1062+ self.assert_backend_called('create_folder', folder_path=tricky_path)
1063+
1064+ @defer.inlineCallbacks
1065+ def test_handles_unicode_paths(self):
1066+ """Unicode paths are properly handled."""
1067+ folder = os.path.join(os.path.expanduser('~'), u'ñoño ñandú ❤')
1068+ FakedFileDialog.response = gui.QtCore.QString(folder)
1069+ yield self.ui.ui.add_folder_button.click()
1070+
1071+ # no warning
1072+ self.assertEqual(FakedConfirmDialog.args, None)
1073+ self.assertEqual(FakedConfirmDialog.kwargs, None)
1074+ # backend called
1075+ folder = gui.append_path_sep(folder)
1076+ self.assert_backend_called('create_folder', folder_path=folder)
1077
1078 @defer.inlineCallbacks
1079 def test_calls_backend(self):
1080@@ -380,6 +464,11 @@
1081 FakedFileDialog.response = gui.QtCore.QString(folder)
1082 yield self.ui.ui.add_folder_button.click()
1083
1084+ # no warning
1085+ self.assertEqual(FakedConfirmDialog.args, None)
1086+ self.assertEqual(FakedConfirmDialog.kwargs, None)
1087+ # backend called
1088+ folder = gui.append_path_sep(folder)
1089 self.assert_backend_called('create_folder', folder_path=folder)
1090
1091 @defer.inlineCallbacks
1092@@ -398,7 +487,7 @@
1093 self.assertFalse(self.ui.is_processing, 'ui must not be processing')
1094
1095 @defer.inlineCallbacks
1096- def test_reload_volumes_info(self):
1097+ def test_reload_volumes_info_on_success(self):
1098 """Once the folder is created, the volumes info is reloaded."""
1099 self.patch(self.ui, 'load', self._set_called)
1100
1101@@ -412,8 +501,9 @@
1102 class FoldersPanelSubscriptionTestCase(FoldersPanelTestCase):
1103 """The test suite for the folder subscription."""
1104
1105+ @defer.inlineCallbacks
1106 def setUp(self):
1107- super(FoldersPanelSubscriptionTestCase, self).setUp()
1108+ yield super(FoldersPanelSubscriptionTestCase, self).setUp()
1109 self.patch(gui.os.path, 'exists', lambda path: True)
1110 FakedConfirmDialog.response = gui.YES
1111 FakedConfirmDialog.args = None
1112@@ -450,7 +540,6 @@
1113 """Clicking on 'subscribed' sets is_processing flag until done."""
1114 def check(volume_id, settings):
1115 """The ui must be is_processing when a change was requested."""
1116- # the ui is processing
1117 self.assertTrue(self.ui.is_processing, 'ui must be processing')
1118
1119 self.patch(self.ui.backend, 'change_volume_settings', check)
1120
1121=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_preferences.py'
1122--- ubuntuone/controlpanel/gui/qt/tests/test_preferences.py 2011-06-17 20:08:02 +0000
1123+++ ubuntuone/controlpanel/gui/qt/tests/test_preferences.py 2011-06-21 16:31:15 +0000
1124@@ -20,28 +20,38 @@
1125
1126 from __future__ import division
1127
1128+from twisted.internet import defer
1129+
1130 from ubuntuone.controlpanel.gui.qt import preferences as gui
1131-from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1132+from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import (
1133+ UbuntuOneBinTestCase,
1134+)
1135
1136 # Access to a protected member
1137 # pylint: disable=W0212
1138
1139
1140-class PreferencesPanelTestCase(BaseTestCase):
1141+class PreferencesPanelTestCase(UbuntuOneBinTestCase):
1142 """Test the qt cloud preferences tab."""
1143
1144 innerclass_ui = gui.preferences_ui
1145 innerclass_name = "Ui_Form"
1146 class_ui = gui.PreferencesPanel
1147
1148+ @defer.inlineCallbacks
1149 def setUp(self):
1150- super(PreferencesPanelTestCase, self).setUp()
1151- return self.ui.load()
1152-
1153- def test_backend(self):
1154- """Backend is created."""
1155- self.assertIsInstance(self.ui.backend,
1156- gui.backend.ControlBackend)
1157+ yield super(PreferencesPanelTestCase, self).setUp()
1158+ yield self.ui.load()
1159+
1160+ def test_is_processing_while_asking_info(self):
1161+ """The ui is processing while the contents are loaded."""
1162+ def check():
1163+ """The ui must be is_processing."""
1164+ self.assertTrue(self.ui.is_processing, 'ui must be processing')
1165+
1166+ self.patch(self.ui.backend, 'file_sync_settings_info', check)
1167+
1168+ yield self.ui.load() # trigger the info request
1169
1170 def test_file_sync_settings_info_is_requested(self):
1171 """The file_sync_settings info is requested to the backend."""
1172
1173=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_profile.py'
1174--- ubuntuone/controlpanel/gui/qt/tests/test_profile.py 2011-06-16 20:33:39 +0000
1175+++ ubuntuone/controlpanel/gui/qt/tests/test_profile.py 2011-06-21 16:31:15 +0000
1176@@ -19,17 +19,31 @@
1177 """Tests for the profile tab."""
1178
1179 from ubuntuone.controlpanel.gui.qt import profile as gui
1180-from ubuntuone.controlpanel.gui.qt.tests import (BaseTestCase,
1181- SAMPLE_ACCOUNT_INFO, SAMPLE_EMAIL, SAMPLE_NAME)
1182-
1183-
1184-class ProfilePanelTestCase(BaseTestCase):
1185+from ubuntuone.controlpanel.gui.qt.tests import (
1186+ SAMPLE_ACCOUNT_INFO, SAMPLE_EMAIL, SAMPLE_NAME,
1187+)
1188+from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import (
1189+ UbuntuOneBinTestCase,
1190+)
1191+
1192+
1193+class ProfilePanelTestCase(UbuntuOneBinTestCase):
1194 """Test the qt control panel."""
1195
1196 innerclass_ui = gui.profile_ui
1197 innerclass_name = "Ui_Form"
1198 class_ui = gui.ProfilePanel
1199
1200+ def test_is_processing_while_asking_info(self):
1201+ """The ui is processing while the contents are loaded."""
1202+ def check():
1203+ """The ui must be is_processing."""
1204+ self.assertTrue(self.ui.is_processing, 'ui must be processing')
1205+
1206+ self.patch(self.ui.backend, 'account_info', check)
1207+
1208+ return self.ui.load() # trigger the info request
1209+
1210 def test_update_account_info(self):
1211 """Backend's file sync status changed callback is connected."""
1212 self.ui.update_account_info(SAMPLE_ACCOUNT_INFO)
1213
1214=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_services.py'
1215--- ubuntuone/controlpanel/gui/qt/tests/test_services.py 2011-06-16 20:33:39 +0000
1216+++ ubuntuone/controlpanel/gui/qt/tests/test_services.py 2011-06-21 16:31:15 +0000
1217@@ -19,17 +19,31 @@
1218 """Tests for the services tab."""
1219
1220 from ubuntuone.controlpanel.gui.qt import services as gui
1221-from ubuntuone.controlpanel.gui.qt.tests import (BaseTestCase,
1222- SAMPLE_ACCOUNT_INFO, SAMPLE_PLAN)
1223-
1224-
1225-class ServicesPanelTestCase(BaseTestCase):
1226+from ubuntuone.controlpanel.gui.qt.tests import (
1227+ SAMPLE_ACCOUNT_INFO, SAMPLE_PLAN,
1228+)
1229+from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import (
1230+ UbuntuOneBinTestCase,
1231+)
1232+
1233+
1234+class ServicesPanelTestCase(UbuntuOneBinTestCase):
1235 """Test the qt control panel."""
1236
1237 innerclass_ui = gui.services_ui
1238 innerclass_name = "Ui_Form"
1239 class_ui = gui.ServicesPanel
1240
1241+ def test_is_processing_while_asking_info(self):
1242+ """The ui is processing while the contents are loaded."""
1243+ def check():
1244+ """The ui must be is_processing."""
1245+ self.assertTrue(self.ui.is_processing, 'ui must be processing')
1246+
1247+ self.patch(self.ui.backend, 'account_info', check)
1248+
1249+ return self.ui.load() # trigger the info request
1250+
1251 def test_update_account_info(self):
1252 """Backend's file sync status changed callback is connected."""
1253 self.ui.update_account_info(SAMPLE_ACCOUNT_INFO)
1254
1255=== added file 'ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py'
1256--- ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py 1970-01-01 00:00:00 +0000
1257+++ ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py 2011-06-21 16:31:15 +0000
1258@@ -0,0 +1,92 @@
1259+# -*- coding: utf-8 -*-
1260+
1261+# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
1262+#
1263+# Copyright 2011 Canonical Ltd.
1264+#
1265+# This program is free software: you can redistribute it and/or modify it
1266+# under the terms of the GNU General Public License version 3, as published
1267+# by the Free Software Foundation.
1268+#
1269+# This program is distributed in the hope that it will be useful, but
1270+# WITHOUT ANY WARRANTY; without even the implied warranties of
1271+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1272+# PURPOSE. See the GNU General Public License for more details.
1273+#
1274+# You should have received a copy of the GNU General Public License along
1275+# with this program. If not, see <http://www.gnu.org/licenses/>.
1276+
1277+"""Tests for the Ubuntu One Bin."""
1278+
1279+from ubuntuone.controlpanel.gui.qt import ubuntuonebin as gui
1280+from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase, FakeUi
1281+
1282+# Attribute 'yyy' defined outside __init__, access to a protected member
1283+# pylint: disable=W0201, W0212
1284+
1285+
1286+class UbuntuOneBinTestCase(BaseTestCase):
1287+ """Test the qt cloud folders tab."""
1288+
1289+ class_ui = gui.UbuntuOneBin
1290+
1291+ def setUp(self):
1292+ super(UbuntuOneBinTestCase, self).setUp()
1293+ self.ui.show() # need to show to test widgets visibility
1294+
1295+ def test_backend(self):
1296+ """Backend is created."""
1297+ self.assertIsInstance(self.ui.backend,
1298+ gui.backend.ControlBackend)
1299+
1300+ def test_init_loads_ui(self):
1301+ """The __init__ method loads the ui."""
1302+ if self.innerclass_ui is None:
1303+ return
1304+ self.patch(self.innerclass_ui, self.innerclass_name, FakeUi)
1305+ # pylint: disable=E1102
1306+ self.ui = self.class_ui(**self.kwargs)
1307+ # pylint: disable=E1101
1308+ self.assertEqual(self.ui.ui._called,
1309+ {'setupUi': ((self.ui.bin_ui.container,), {})})
1310+
1311+ def test_is_not_processing_after_creation(self):
1312+ """After creation, is not processing."""
1313+ self.assertFalse(self.ui.is_processing)
1314+
1315+ def test_spinner_is_active(self):
1316+ """The spinner is active."""
1317+ self.assertEqual(self.ui.bin_ui.spinner.minimum(), 0)
1318+ self.assertEqual(self.ui.bin_ui.spinner.maximum(), 0)
1319+
1320+ def test_is_enabled_if_not_processing(self):
1321+ """If not processing, the UI is enabled."""
1322+ self.ui.is_processing = False
1323+ self.assertTrue(self.ui.isEnabled())
1324+ self.assertFalse(self.ui.bin_ui.spinner.isVisible())
1325+
1326+ def test_is_not_enabled_if_processing(self):
1327+ """If processing, the UI is disabled."""
1328+ self.ui.is_processing = True
1329+ self.assertFalse(self.ui.isEnabled())
1330+ self.assertTrue(self.ui.bin_ui.spinner.isVisible())
1331+
1332+ def test_title_when_none(self):
1333+ """The title is the empty string when class title is None."""
1334+
1335+ class MyBin(gui.UbuntuOneBin):
1336+ """Inherit to set a class title."""
1337+ title = None
1338+
1339+ ui = MyBin()
1340+ self.assertEqual(ui.bin_ui.title_label.text(), '')
1341+
1342+ def test_title_when_not_none(self):
1343+ """The title is the empty string when class title is None."""
1344+
1345+ class MyBin(gui.UbuntuOneBin):
1346+ """Inherit to set a class title."""
1347+ title = 'Hello World!'
1348+
1349+ ui = MyBin()
1350+ self.assertEqual(ui.bin_ui.title_label.text(), MyBin.title)
1351
1352=== added file 'ubuntuone/controlpanel/gui/qt/ubuntuonebin.py'
1353--- ubuntuone/controlpanel/gui/qt/ubuntuonebin.py 1970-01-01 00:00:00 +0000
1354+++ ubuntuone/controlpanel/gui/qt/ubuntuonebin.py 2011-06-21 16:31:15 +0000
1355@@ -0,0 +1,85 @@
1356+# -*- coding: utf-8 -*-
1357+
1358+# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
1359+#
1360+# Copyright 2011 Canonical Ltd.
1361+#
1362+# This program is free software: you can redistribute it and/or modify it
1363+# under the terms of the GNU General Public License version 3, as published
1364+# by the Free Software Foundation.
1365+#
1366+# This program is distributed in the hope that it will be useful, but
1367+# WITHOUT ANY WARRANTY; without even the implied warranties of
1368+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1369+# PURPOSE. See the GNU General Public License for more details.
1370+#
1371+# You should have received a copy of the GNU General Public License along
1372+# with this program. If not, see <http://www.gnu.org/licenses/>.
1373+
1374+"""The base widget for the Control Panel tabs."""
1375+
1376+from PyQt4 import QtGui, QtCore
1377+
1378+from ubuntuone.controlpanel import backend
1379+from ubuntuone.controlpanel.gui.qt.ui import ubuntuonebin_ui
1380+
1381+
1382+class UbuntuOneBin(QtGui.QWidget):
1383+ """The base widget for the Control Panel's tabs."""
1384+
1385+ title = None
1386+ ui_class = None
1387+
1388+ def __init__(self, parent=None):
1389+ """Initialize the UI of the widget."""
1390+ QtGui.QWidget.__init__(self, parent)
1391+
1392+ self.bin_ui = ubuntuonebin_ui.Ui_Form()
1393+ self.bin_ui.setupUi(self)
1394+
1395+ if self.title is None:
1396+ self.bin_ui.title_label.setText('')
1397+ else:
1398+ self.bin_ui.title_label.setText(self.title)
1399+
1400+ self.ui = None
1401+ if self.ui_class is not None:
1402+ # self.ui_class is not callable
1403+ # pylint: disable=E1102
1404+ self.ui = self.ui_class.Ui_Form()
1405+ self.ui.setupUi(self.bin_ui.container)
1406+ # connect all signals to this instance
1407+ QtCore.QMetaObject.connectSlotsByName(self)
1408+
1409+ self._is_processing = None
1410+ self.is_processing = False
1411+
1412+ self.backend = backend.ControlBackend()
1413+
1414+ def _get_is_processing(self):
1415+ """Get the value of is_processing."""
1416+ return self._is_processing
1417+
1418+ def _set_is_processing(self, new_value):
1419+ """Set the value of is_processing.
1420+
1421+ If is_processing, disable the UI and show a spinner.
1422+ If not is_processing, enable the UI and hide the spinner.
1423+
1424+ """
1425+ self.bin_ui.spinner.setVisible(new_value)
1426+ self.setEnabled(not new_value)
1427+ self._is_processing = new_value
1428+
1429+ is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
1430+
1431+ # Invalid name "showEvent"
1432+ # pylint: disable=C0103
1433+
1434+ def showEvent(self, event):
1435+ """Load info."""
1436+ self.load()
1437+ event.accept()
1438+
1439+ def load(self):
1440+ """Load the widget with specific info."""
1441
1442=== modified file 'ubuntuone/controlpanel/sd_client/linux.py'
1443--- ubuntuone/controlpanel/sd_client/linux.py 2011-06-14 21:44:32 +0000
1444+++ ubuntuone/controlpanel/sd_client/linux.py 2011-06-21 16:31:15 +0000
1445@@ -25,6 +25,8 @@
1446
1447 from ubuntuone.controlpanel.logger import setup_logging
1448
1449+# pylint: disable=E1101
1450+
1451
1452 logger = setup_logging('sd_client')
1453

Subscribers

People subscribed via source and target branches