Merge lp:~ralsina/ubuntuone-windows-installer/fix_800376 into lp:ubuntuone-windows-installer
- fix_800376
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Roberto Alsina |
Approved revision: | 11 |
Merged at revision: | 10 |
Proposed branch: | lp:~ralsina/ubuntuone-windows-installer/fix_800376 |
Merge into: | lp:ubuntuone-windows-installer |
Diff against target: |
649 lines (+450/-22) 4 files modified
data/qt/local_folders.ui (+167/-0) ubuntuone_installer/gui/qt/gui.py (+11/-16) ubuntuone_installer/gui/qt/local_folders.py (+159/-0) ubuntuone_installer/gui/qt/tests/test_gui.py (+113/-6) |
To merge this branch: | bzr merge lp:~ralsina/ubuntuone-windows-installer/fix_800376 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Manuel de la Peña (community) | Approve | ||
Natalia Bidart (community) | Approve | ||
Review via email:
|
Commit message
First branch of the implementation of "Syncing your computer to the cloud" pages.
Description of the change
Initial implementation of a page to select which local folders will be synced to Ubuntu One.
Currently it doesn't do UDF creation or validation, because that functionality should be imported from control panel.
To test IRL (windows only):
You need ubuntuone-client and ubuntu-
PYTHONPATH=
On another terminal, start the SSO windows client.
Then you can start the wizard:
python bin\ubuntuone-
You should be able to click "Agree & Install", then login with a valid user/password, then click "Next" and see this page.
In the page, you should get your "My Documents" folder added by default, and after some seconds,
the space used should be visible on that item and in the header of the list.
I intentionally faked the quota to be very low. Because of that, you should see a widget asking you to buy more space.
If you click "remove" in that row, the item should disappear, and the space used adjust accordingly to 0.
This branch doesn't close the bug, it's just a step towards that.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Roberto Alsina (ralsina) wrote : | # |
> A couple of needs fixing:
>
> * always call super() instead of explicitly calling the parent class
Ok.
>
> * the diff seems to add several (at first) not needed blank lines, can you
> please remove those, if they are not needed? (we usually do not add empty
> lines after the docstrings)
Ok.
> * by convention, setUp and tearDown, if defined, should be the first 2 methods
> after the class declaration
Ok.
> * this docstring:
>
> 506 + """After removing all folders, offer_frame should be hidden.
> 507 +
> 508 + Push the user over quota, it should be visible"""
>
> should be:
>
> 506 + """After removing all folders, offer_frame should be hidden.
> 507 +
> 508 + Push the user over quota, it should be visible.
>
> """
Ok.
All of those hsould be fixed now.
- 9. By Roberto Alsina
-
Implement the page that lets the user choose between "sync now" / "sync later" / "sync selectively"
- 10. By Roberto Alsina
-
Resolve conflicts with trunk
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Natalia Bidart (nataliabidart) : | # |
- 11. By Roberto Alsina
-
PEP 252
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Manuel de la Peña (mandel) : | # |
Preview Diff
1 | === added file 'data/qt/local_folders.ui' | |||
2 | --- data/qt/local_folders.ui 1970-01-01 00:00:00 +0000 | |||
3 | +++ data/qt/local_folders.ui 2011-06-29 15:13:36 +0000 | |||
4 | @@ -0,0 +1,167 @@ | |||
5 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
6 | 2 | <ui version="4.0"> | ||
7 | 3 | <class>Form</class> | ||
8 | 4 | <widget class="QWidget" name="Form"> | ||
9 | 5 | <property name="geometry"> | ||
10 | 6 | <rect> | ||
11 | 7 | <x>0</x> | ||
12 | 8 | <y>0</y> | ||
13 | 9 | <width>460</width> | ||
14 | 10 | <height>536</height> | ||
15 | 11 | </rect> | ||
16 | 12 | </property> | ||
17 | 13 | <property name="windowTitle"> | ||
18 | 14 | <string>Form</string> | ||
19 | 15 | </property> | ||
20 | 16 | <layout class="QVBoxLayout" name="verticalLayout_2"> | ||
21 | 17 | <item> | ||
22 | 18 | <widget class="QLabel" name="label"> | ||
23 | 19 | <property name="text"> | ||
24 | 20 | <string>Ok! Now it's time to choose wich folder sync to the cloud from this computer. | ||
25 | 21 | We started by suggesting a few</string> | ||
26 | 22 | </property> | ||
27 | 23 | <property name="textFormat"> | ||
28 | 24 | <enum>Qt::RichText</enum> | ||
29 | 25 | </property> | ||
30 | 26 | <property name="wordWrap"> | ||
31 | 27 | <bool>true</bool> | ||
32 | 28 | </property> | ||
33 | 29 | </widget> | ||
34 | 30 | </item> | ||
35 | 31 | <item> | ||
36 | 32 | <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||
37 | 33 | <item> | ||
38 | 34 | <widget class="QLabel" name="label_2"> | ||
39 | 35 | <property name="text"> | ||
40 | 36 | <string>Folders on my computer</string> | ||
41 | 37 | </property> | ||
42 | 38 | </widget> | ||
43 | 39 | </item> | ||
44 | 40 | <item> | ||
45 | 41 | <spacer name="horizontalSpacer_3"> | ||
46 | 42 | <property name="orientation"> | ||
47 | 43 | <enum>Qt::Horizontal</enum> | ||
48 | 44 | </property> | ||
49 | 45 | <property name="sizeHint" stdset="0"> | ||
50 | 46 | <size> | ||
51 | 47 | <width>40</width> | ||
52 | 48 | <height>20</height> | ||
53 | 49 | </size> | ||
54 | 50 | </property> | ||
55 | 51 | </spacer> | ||
56 | 52 | </item> | ||
57 | 53 | <item> | ||
58 | 54 | <widget class="QPushButton" name="pushButton_2"> | ||
59 | 55 | <property name="text"> | ||
60 | 56 | <string>Add a folder</string> | ||
61 | 57 | </property> | ||
62 | 58 | </widget> | ||
63 | 59 | </item> | ||
64 | 60 | </layout> | ||
65 | 61 | </item> | ||
66 | 62 | <item> | ||
67 | 63 | <widget class="QTreeWidget" name="folder_list"> | ||
68 | 64 | <attribute name="headerStretchLastSection"> | ||
69 | 65 | <bool>false</bool> | ||
70 | 66 | </attribute> | ||
71 | 67 | <column> | ||
72 | 68 | <property name="text"> | ||
73 | 69 | <string>My Folders</string> | ||
74 | 70 | </property> | ||
75 | 71 | </column> | ||
76 | 72 | <column> | ||
77 | 73 | <property name="text"> | ||
78 | 74 | <string>Space (Total)</string> | ||
79 | 75 | </property> | ||
80 | 76 | </column> | ||
81 | 77 | <column> | ||
82 | 78 | <property name="text"> | ||
83 | 79 | <string>Remove</string> | ||
84 | 80 | </property> | ||
85 | 81 | </column> | ||
86 | 82 | </widget> | ||
87 | 83 | </item> | ||
88 | 84 | <item> | ||
89 | 85 | <widget class="QCheckBox" name="checkBox"> | ||
90 | 86 | <property name="text"> | ||
91 | 87 | <string>Connect automatically when computer starts</string> | ||
92 | 88 | </property> | ||
93 | 89 | </widget> | ||
94 | 90 | </item> | ||
95 | 91 | <item> | ||
96 | 92 | <widget class="QCheckBox" name="checkBox_2"> | ||
97 | 93 | <property name="text"> | ||
98 | 94 | <string>Automatically sync all selected folders from this computer to the cloud</string> | ||
99 | 95 | </property> | ||
100 | 96 | </widget> | ||
101 | 97 | </item> | ||
102 | 98 | <item> | ||
103 | 99 | <widget class="QFrame" name="offer_frame"> | ||
104 | 100 | <property name="frameShape"> | ||
105 | 101 | <enum>QFrame::StyledPanel</enum> | ||
106 | 102 | </property> | ||
107 | 103 | <property name="frameShadow"> | ||
108 | 104 | <enum>QFrame::Raised</enum> | ||
109 | 105 | </property> | ||
110 | 106 | <layout class="QVBoxLayout" name="verticalLayout"> | ||
111 | 107 | <property name="leftMargin"> | ||
112 | 108 | <number>0</number> | ||
113 | 109 | </property> | ||
114 | 110 | <property name="rightMargin"> | ||
115 | 111 | <number>0</number> | ||
116 | 112 | </property> | ||
117 | 113 | <item> | ||
118 | 114 | <widget class="QLabel" name="offer_label"> | ||
119 | 115 | <property name="text"> | ||
120 | 116 | <string>The folders you have selected to sync take over your 2GB space. You can remove some folders or add some extra space</string> | ||
121 | 117 | </property> | ||
122 | 118 | <property name="wordWrap"> | ||
123 | 119 | <bool>true</bool> | ||
124 | 120 | </property> | ||
125 | 121 | </widget> | ||
126 | 122 | </item> | ||
127 | 123 | <item> | ||
128 | 124 | <layout class="QHBoxLayout" name="horizontalLayout"> | ||
129 | 125 | <item> | ||
130 | 126 | <spacer name="horizontalSpacer"> | ||
131 | 127 | <property name="orientation"> | ||
132 | 128 | <enum>Qt::Horizontal</enum> | ||
133 | 129 | </property> | ||
134 | 130 | <property name="sizeHint" stdset="0"> | ||
135 | 131 | <size> | ||
136 | 132 | <width>40</width> | ||
137 | 133 | <height>20</height> | ||
138 | 134 | </size> | ||
139 | 135 | </property> | ||
140 | 136 | </spacer> | ||
141 | 137 | </item> | ||
142 | 138 | <item> | ||
143 | 139 | <widget class="QPushButton" name="pushButton"> | ||
144 | 140 | <property name="text"> | ||
145 | 141 | <string>add more space</string> | ||
146 | 142 | </property> | ||
147 | 143 | </widget> | ||
148 | 144 | </item> | ||
149 | 145 | <item> | ||
150 | 146 | <spacer name="horizontalSpacer_2"> | ||
151 | 147 | <property name="orientation"> | ||
152 | 148 | <enum>Qt::Horizontal</enum> | ||
153 | 149 | </property> | ||
154 | 150 | <property name="sizeHint" stdset="0"> | ||
155 | 151 | <size> | ||
156 | 152 | <width>40</width> | ||
157 | 153 | <height>20</height> | ||
158 | 154 | </size> | ||
159 | 155 | </property> | ||
160 | 156 | </spacer> | ||
161 | 157 | </item> | ||
162 | 158 | </layout> | ||
163 | 159 | </item> | ||
164 | 160 | </layout> | ||
165 | 161 | </widget> | ||
166 | 162 | </item> | ||
167 | 163 | </layout> | ||
168 | 164 | </widget> | ||
169 | 165 | <resources/> | ||
170 | 166 | <connections/> | ||
171 | 167 | </ui> | ||
172 | 0 | 168 | ||
173 | === modified file 'ubuntuone_installer/gui/qt/gui.py' | |||
174 | --- ubuntuone_installer/gui/qt/gui.py 2011-06-28 19:45:45 +0000 | |||
175 | +++ ubuntuone_installer/gui/qt/gui.py 2011-06-29 15:13:36 +0000 | |||
176 | @@ -72,6 +72,7 @@ | |||
177 | 72 | license_ui, | 72 | license_ui, |
178 | 73 | congratulations_ui, | 73 | congratulations_ui, |
179 | 74 | ) | 74 | ) |
180 | 75 | from ubuntuone_installer.gui.qt.local_folders import LocalFoldersPage | ||
181 | 75 | from ubuntuone_installer.gui.qt.sync_now_or_later import SyncNowOrLaterPage | 76 | from ubuntuone_installer.gui.qt.sync_now_or_later import SyncNowOrLaterPage |
182 | 76 | 77 | ||
183 | 77 | _ = gettext.gettext | 78 | _ = gettext.gettext |
184 | @@ -87,7 +88,7 @@ | |||
185 | 87 | """Wizard Page that displays the license info and links to the GPL.""" | 88 | """Wizard Page that displays the license info and links to the GPL.""" |
186 | 88 | 89 | ||
187 | 89 | def __init__(self, parent=None): | 90 | def __init__(self, parent=None): |
189 | 90 | QtGui.QWizardPage.__init__(self, parent) | 91 | super(LicensePage, self).__init__(parent) |
190 | 91 | self.ui = license_ui.Ui_Form() | 92 | self.ui = license_ui.Ui_Form() |
191 | 92 | self.ui.setupUi(self) | 93 | self.ui.setupUi(self) |
192 | 93 | self.setCommitPage(False) | 94 | self.setCommitPage(False) |
193 | @@ -97,7 +98,6 @@ | |||
194 | 97 | 98 | ||
195 | 98 | def initializePage(self): | 99 | def initializePage(self): |
196 | 99 | """Setup UI details.""" | 100 | """Setup UI details.""" |
197 | 100 | |||
198 | 101 | # Set the right texts and connections for buttons | 101 | # Set the right texts and connections for buttons |
199 | 102 | self.setButtonText(QtGui.QWizard.NextButton, _("Agree && Install")) | 102 | self.setButtonText(QtGui.QWizard.NextButton, _("Agree && Install")) |
200 | 103 | self.setButtonText(QtGui.QWizard.CancelButton, | 103 | self.setButtonText(QtGui.QWizard.CancelButton, |
201 | @@ -124,7 +124,6 @@ | |||
202 | 124 | 124 | ||
203 | 125 | def print_document(self, button_id): | 125 | def print_document(self, button_id): |
204 | 126 | """Print the document displayed in textBrowser.""" | 126 | """Print the document displayed in textBrowser.""" |
205 | 127 | |||
206 | 128 | if button_id == QtGui.QWizard.CustomButton1: | 127 | if button_id == QtGui.QWizard.CustomButton1: |
207 | 129 | document = self.ui.textBrowser.document() | 128 | document = self.ui.textBrowser.document() |
208 | 130 | printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) | 129 | printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) |
209 | @@ -143,7 +142,7 @@ | |||
210 | 143 | """Wizard Page that lets the user Sign into Ubuntu One.""" | 142 | """Wizard Page that lets the user Sign into Ubuntu One.""" |
211 | 144 | 143 | ||
212 | 145 | def __init__(self, ui=None, controller=None, parent=None): | 144 | def __init__(self, ui=None, controller=None, parent=None): |
214 | 146 | QtGui.QWizardPage.__init__(self, parent) | 145 | super(SignInPage, self).__init__(parent) |
215 | 147 | self.ui = ui | 146 | self.ui = ui |
216 | 148 | self.ui.setupUi(self) | 147 | self.ui.setupUi(self) |
217 | 149 | self.controller = controller | 148 | self.controller = controller |
218 | @@ -155,7 +154,6 @@ | |||
219 | 155 | 154 | ||
220 | 156 | def initializePage(self): | 155 | def initializePage(self): |
221 | 157 | """Setup UI details.""" | 156 | """Setup UI details.""" |
222 | 158 | |||
223 | 159 | self.setButtonText(QtGui.QWizard.CancelButton, | 157 | self.setButtonText(QtGui.QWizard.CancelButton, |
224 | 160 | _("Close window and setup later")) | 158 | _("Close window and setup later")) |
225 | 161 | # Layout without custom button 1, | 159 | # Layout without custom button 1, |
226 | @@ -175,7 +173,7 @@ | |||
227 | 175 | """Wizard Page that lets a current user Sign into Ubuntu One.""" | 173 | """Wizard Page that lets a current user Sign into Ubuntu One.""" |
228 | 176 | 174 | ||
229 | 177 | def __init__(self, ui=None, controller=None, parent=None): | 175 | def __init__(self, ui=None, controller=None, parent=None): |
231 | 178 | QtGui.QWizardPage.__init__(self, parent) | 176 | super(CurrentUserSignInPage, self).__init__(parent) |
232 | 179 | self.ui = ui | 177 | self.ui = ui |
233 | 180 | self.ui.setupUi(self) | 178 | self.ui.setupUi(self) |
234 | 181 | self.controller = controller | 179 | self.controller = controller |
235 | @@ -185,9 +183,6 @@ | |||
236 | 185 | # Invalid names of Qt-inherited methods | 183 | # Invalid names of Qt-inherited methods |
237 | 186 | # pylint: disable=C0103 | 184 | # pylint: disable=C0103 |
238 | 187 | 185 | ||
239 | 188 | def initializePage(self): | ||
240 | 189 | """Setup UI details.""" | ||
241 | 190 | |||
242 | 191 | def nextId(self): | 186 | def nextId(self): |
243 | 192 | """Provide the next id.""" | 187 | """Provide the next id.""" |
244 | 193 | return self.next | 188 | return self.next |
245 | @@ -197,7 +192,7 @@ | |||
246 | 197 | """Shown after SSO login, before setup.""" | 192 | """Shown after SSO login, before setup.""" |
247 | 198 | 193 | ||
248 | 199 | def __init__(self, ui=None, controller=None, parent=None): | 194 | def __init__(self, ui=None, controller=None, parent=None): |
250 | 200 | QtGui.QWizardPage.__init__(self, parent) | 195 | super(SuccessPage, self).__init__(parent) |
251 | 201 | self.ui = ui | 196 | self.ui = ui |
252 | 202 | self.ui.setupUi(self) | 197 | self.ui.setupUi(self) |
253 | 203 | self.controller = controller | 198 | self.controller = controller |
254 | @@ -225,7 +220,7 @@ | |||
255 | 225 | """Final page of the wizard.""" | 220 | """Final page of the wizard.""" |
256 | 226 | 221 | ||
257 | 227 | def __init__(self, parent=None): | 222 | def __init__(self, parent=None): |
259 | 228 | QtGui.QWizardPage.__init__(self, parent) | 223 | super(CongratulationsPage, self).__init__(parent) |
260 | 229 | self.ui = congratulations_ui.Ui_Form() | 224 | self.ui = congratulations_ui.Ui_Form() |
261 | 230 | self.ui.setupUi(self) | 225 | self.ui.setupUi(self) |
262 | 231 | self.setFinalPage(True) | 226 | self.setFinalPage(True) |
263 | @@ -235,7 +230,6 @@ | |||
264 | 235 | 230 | ||
265 | 236 | def initializePage(self): | 231 | def initializePage(self): |
266 | 237 | """Setup UI details.""" | 232 | """Setup UI details.""" |
267 | 238 | |||
268 | 239 | # We need custom buttons | 233 | # We need custom buttons |
269 | 240 | self.wizard().setButtonText(QtGui.QWizard.FinishButton, | 234 | self.wizard().setButtonText(QtGui.QWizard.FinishButton, |
270 | 241 | _("Go to my Ubuntu One dashboard")) | 235 | _("Go to my Ubuntu One dashboard")) |
271 | @@ -269,7 +263,6 @@ | |||
272 | 269 | 263 | ||
273 | 270 | def __init__(self, close_callback=None): | 264 | def __init__(self, close_callback=None): |
274 | 271 | """Initialize this instance.""" | 265 | """Initialize this instance.""" |
275 | 272 | |||
276 | 273 | # Used to decide the next page dynamically | 266 | # Used to decide the next page dynamically |
277 | 274 | self._next_id = None | 267 | self._next_id = None |
278 | 275 | 268 | ||
279 | @@ -279,7 +272,7 @@ | |||
280 | 279 | self.tc_url = TC_URL | 272 | self.tc_url = TC_URL |
281 | 280 | self.ping_url = PING_URL | 273 | self.ping_url = PING_URL |
282 | 281 | 274 | ||
284 | 282 | QtGui.QWizard.__init__(self) | 275 | super(MainWindow, self).__init__() |
285 | 283 | self.close_callback = close_callback | 276 | self.close_callback = close_callback |
286 | 284 | 277 | ||
287 | 285 | self.creds = Credentials( | 278 | self.creds = Credentials( |
288 | @@ -346,6 +339,8 @@ | |||
289 | 346 | 339 | ||
290 | 347 | # End of SSO pages | 340 | # End of SSO pages |
291 | 348 | 341 | ||
292 | 342 | self.local_folders_page = LocalFoldersPage() | ||
293 | 343 | self.local_folders_page_id = self.addPage(self.local_folders_page) | ||
294 | 349 | self.SYNC_NOW_OR_LATER_PAGE = self.addPage(SyncNowOrLaterPage()) | 344 | self.SYNC_NOW_OR_LATER_PAGE = self.addPage(SyncNowOrLaterPage()) |
295 | 350 | self.CONGRATULATIONS_PAGE = self.addPage(CongratulationsPage()) | 345 | self.CONGRATULATIONS_PAGE = self.addPage(CongratulationsPage()) |
296 | 351 | 346 | ||
297 | @@ -363,13 +358,13 @@ | |||
298 | 363 | """Called on successful login.""" | 358 | """Called on successful login.""" |
299 | 364 | self._next_id = self.SUCCESS_PAGE | 359 | self._next_id = self.SUCCESS_PAGE |
300 | 365 | self.next() | 360 | self.next() |
302 | 366 | self._next_id = self.SYNC_NOW_OR_LATER_PAGE | 361 | self._next_id = self.local_folders_page_id |
303 | 367 | 362 | ||
304 | 368 | def registration_success_slot(self): | 363 | def registration_success_slot(self): |
305 | 369 | """Called on successful registration.""" | 364 | """Called on successful registration.""" |
306 | 370 | self._next_id = self.SUCCESS_PAGE | 365 | self._next_id = self.SUCCESS_PAGE |
307 | 371 | self.next() | 366 | self.next() |
309 | 372 | self._next_id = self.SYNC_NOW_OR_LATER_PAGE | 367 | self._next_id = self.local_folders_page_id |
310 | 373 | 368 | ||
311 | 374 | def done(self, result): | 369 | def done(self, result): |
312 | 375 | """The main window is being closed, call any custom callback.""" | 370 | """The main window is being closed, call any custom callback.""" |
313 | 376 | 371 | ||
314 | === added file 'ubuntuone_installer/gui/qt/local_folders.py' | |||
315 | --- ubuntuone_installer/gui/qt/local_folders.py 1970-01-01 00:00:00 +0000 | |||
316 | +++ ubuntuone_installer/gui/qt/local_folders.py 2011-06-29 15:13:36 +0000 | |||
317 | @@ -0,0 +1,159 @@ | |||
318 | 1 | # -*- coding: utf-8 -*- | ||
319 | 2 | |||
320 | 3 | # Authors: Roberto Alsina <roberto.alsina@canonical.com> | ||
321 | 4 | # | ||
322 | 5 | # Copyright 2011 Canonical Ltd. | ||
323 | 6 | # | ||
324 | 7 | # This program is free software: you can redistribute it and/or modify it | ||
325 | 8 | # under the terms of the GNU General Public License version 3, as published | ||
326 | 9 | # by the Free Software Foundation. | ||
327 | 10 | # | ||
328 | 11 | # This program is distributed in the hope that it will be useful, but | ||
329 | 12 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
330 | 13 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
331 | 14 | # PURPOSE. See the GNU General Public License for more details. | ||
332 | 15 | # | ||
333 | 16 | # You should have received a copy of the GNU General Public License along | ||
334 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
335 | 18 | |||
336 | 19 | """Widget to create UDFs in the Windows Install Wizard.""" | ||
337 | 20 | |||
338 | 21 | import ctypes | ||
339 | 22 | import gettext | ||
340 | 23 | import os | ||
341 | 24 | import threading | ||
342 | 25 | import Queue | ||
343 | 26 | |||
344 | 27 | from PyQt4 import QtCore, QtGui | ||
345 | 28 | |||
346 | 29 | from ubuntuone.controlpanel.gui import humanize | ||
347 | 30 | |||
348 | 31 | from ubuntuone_installer.gui.qt.ui import local_folders_ui | ||
349 | 32 | |||
350 | 33 | _ = gettext.gettext | ||
351 | 34 | |||
352 | 35 | |||
353 | 36 | class FolderItem(QtGui.QTreeWidgetItem): | ||
354 | 37 | """Class representing a folder in the folder list UI.""" | ||
355 | 38 | def __init__(self, strings, path=None, queue=None): | ||
356 | 39 | super(FolderItem, self).__init__(strings) | ||
357 | 40 | self.thread = CalculateSize(path, queue) | ||
358 | 41 | self.thread.start() | ||
359 | 42 | self.size = None | ||
360 | 43 | self.path = path | ||
361 | 44 | |||
362 | 45 | |||
363 | 46 | class CalculateSize(threading.Thread): | ||
364 | 47 | """Class to calculate the size of a folder in the background.""" | ||
365 | 48 | def __init__(self, path_name, queue): | ||
366 | 49 | self.path_name = path_name | ||
367 | 50 | self.queue = queue | ||
368 | 51 | self._stop = False | ||
369 | 52 | super(CalculateSize, self).__init__() | ||
370 | 53 | self.daemon = True | ||
371 | 54 | |||
372 | 55 | def run(self): | ||
373 | 56 | total_size = 0 | ||
374 | 57 | for dirpath, dirnames, filenames in os.walk(self.path_name): | ||
375 | 58 | for f in filenames: | ||
376 | 59 | fp = os.path.join(dirpath, f) | ||
377 | 60 | total_size += os.path.getsize(fp) | ||
378 | 61 | self.queue.put([self.path_name, total_size]) | ||
379 | 62 | |||
380 | 63 | |||
381 | 64 | class LocalFoldersPage(QtGui.QWizardPage): | ||
382 | 65 | """Wizard page to create UDFs in the Windows Installer.""" | ||
383 | 66 | def __init__(self, parent=None): | ||
384 | 67 | super(LocalFoldersPage, self).__init__(parent) | ||
385 | 68 | self.setTitle(_("Syncing your computer with the cloud")) | ||
386 | 69 | self.ui = local_folders_ui.Ui_Form() | ||
387 | 70 | self.ui.setupUi(self) | ||
388 | 71 | |||
389 | 72 | header_view = self.ui.folder_list.header() | ||
390 | 73 | header_view.setResizeMode(0, header_view.Stretch) | ||
391 | 74 | |||
392 | 75 | self.queue = Queue.Queue() | ||
393 | 76 | self.timer = QtCore.QTimer() | ||
394 | 77 | self.items = {} | ||
395 | 78 | for folder_name in self.default_folders(): | ||
396 | 79 | self.add_folder(folder_name) | ||
397 | 80 | self.update_sizes() | ||
398 | 81 | self.timer.start(2000) | ||
399 | 82 | self.timer.timeout.connect(self.update_sizes) | ||
400 | 83 | |||
401 | 84 | def initializePage(self): | ||
402 | 85 | self.wizard()._next_id = None | ||
403 | 86 | |||
404 | 87 | def quota(self): | ||
405 | 88 | """The quota available to the user.""" | ||
406 | 89 | # FIXME: get this from real life | ||
407 | 90 | return 2 * 1024 * 1024 | ||
408 | 91 | |||
409 | 92 | def default_folders(self): | ||
410 | 93 | """Return a list of the folders to add by default.""" | ||
411 | 94 | # Special Folder "My Documents" | ||
412 | 95 | dll = ctypes.windll.shell32 | ||
413 | 96 | buf = ctypes.create_string_buffer(300) | ||
414 | 97 | dll.SHGetSpecialFolderPathA(None, buf, 0x0005, False) | ||
415 | 98 | return [buf.value, ] | ||
416 | 99 | |||
417 | 100 | def add_folder(self, path): | ||
418 | 101 | """Add a folder to the list.""" | ||
419 | 102 | if path in self.items: | ||
420 | 103 | return None | ||
421 | 104 | # FIXME: the path should actually be sent to u1cp to verify as valid | ||
422 | 105 | item = FolderItem([path, "", _("Remove")], path=path, queue=self.queue) | ||
423 | 106 | self.ui.folder_list.addTopLevelItem(item) | ||
424 | 107 | self.items[path] = item | ||
425 | 108 | return item | ||
426 | 109 | |||
427 | 110 | def update_sizes(self): | ||
428 | 111 | """Poll the queue were the threads put the size info.""" | ||
429 | 112 | try: | ||
430 | 113 | path, size = self.queue.get(False) | ||
431 | 114 | item = self.items.get(path) | ||
432 | 115 | if item: | ||
433 | 116 | item.size = size | ||
434 | 117 | item.setText(1, humanize(size)) | ||
435 | 118 | except Queue.Empty: | ||
436 | 119 | pass | ||
437 | 120 | total = 0 | ||
438 | 121 | for path, item in self.items.items(): | ||
439 | 122 | if item.size is None: | ||
440 | 123 | total = _("Calculating") | ||
441 | 124 | break | ||
442 | 125 | total += item.size | ||
443 | 126 | |||
444 | 127 | if isinstance(total, long): | ||
445 | 128 | self.show_hide_offer(total) | ||
446 | 129 | total = humanize(total) | ||
447 | 130 | else: | ||
448 | 131 | self.show_hide_offer(0) | ||
449 | 132 | self.ui.folder_list.headerItem().setText(1, _("Space (%s)" % total)) | ||
450 | 133 | |||
451 | 134 | def show_hide_offer(self, cur_size): | ||
452 | 135 | """Show or hide the offer to buy space according to the total size.""" | ||
453 | 136 | quota = self.quota() | ||
454 | 137 | |||
455 | 138 | if cur_size > quota: | ||
456 | 139 | self.ui.offer_frame.setVisible(True) | ||
457 | 140 | else: | ||
458 | 141 | self.ui.offer_frame.setVisible(False) | ||
459 | 142 | |||
460 | 143 | self.ui.offer_label.setText(_("The folders you have selected to sync " | ||
461 | 144 | "take over your %s space. You can remove some folders or add " | ||
462 | 145 | "some extra space" % humanize(quota))) | ||
463 | 146 | |||
464 | 147 | def stop_threads(self): | ||
465 | 148 | """Stop all pending threads.""" | ||
466 | 149 | for path, item in self.items: | ||
467 | 150 | item.thread._stop = True | ||
468 | 151 | |||
469 | 152 | def on_folder_list_itemClicked(self, item, column): | ||
470 | 153 | """Delete folder from the list.""" | ||
471 | 154 | if column == 2: | ||
472 | 155 | del(self.items[item.path]) | ||
473 | 156 | item.thread._stop = True | ||
474 | 157 | self.ui.folder_list.takeTopLevelItem( | ||
475 | 158 | self.ui.folder_list.indexOfTopLevelItem(item)) | ||
476 | 159 | self.update_sizes() | ||
477 | 0 | 160 | ||
478 | === modified file 'ubuntuone_installer/gui/qt/tests/test_gui.py' | |||
479 | --- ubuntuone_installer/gui/qt/tests/test_gui.py 2011-06-28 19:18:35 +0000 | |||
480 | +++ ubuntuone_installer/gui/qt/tests/test_gui.py 2011-06-29 15:13:36 +0000 | |||
481 | @@ -19,6 +19,11 @@ | |||
482 | 19 | 19 | ||
483 | 20 | """Tests for the Qt UI.""" | 20 | """Tests for the Qt UI.""" |
484 | 21 | 21 | ||
485 | 22 | import os | ||
486 | 23 | import Queue | ||
487 | 24 | import shutil | ||
488 | 25 | import tempfile | ||
489 | 26 | |||
490 | 22 | from PyQt4 import QtCore | 27 | from PyQt4 import QtCore |
491 | 23 | 28 | ||
492 | 24 | from ubuntuone.credentials import ( | 29 | from ubuntuone.credentials import ( |
493 | @@ -32,6 +37,7 @@ | |||
494 | 32 | from ubuntuone_installer.gui.qt import gui | 37 | from ubuntuone_installer.gui.qt import gui |
495 | 33 | from ubuntuone_installer.gui.qt.tests import BaseTestCase | 38 | from ubuntuone_installer.gui.qt.tests import BaseTestCase |
496 | 34 | from ubuntuone_installer.gui.qt.embedded_sso import UbuntuSSOClientGUI | 39 | from ubuntuone_installer.gui.qt.embedded_sso import UbuntuSSOClientGUI |
497 | 40 | from ubuntuone_installer.gui.qt import local_folders | ||
498 | 35 | 41 | ||
499 | 36 | TOKEN = {u'consumer_key': u'xQ7xDAz', | 42 | TOKEN = {u'consumer_key': u'xQ7xDAz', |
500 | 37 | u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy', | 43 | u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy', |
501 | @@ -96,7 +102,8 @@ | |||
502 | 96 | 102 | ||
503 | 97 | def test_start_control_panel_on_finishing(self): | 103 | def test_start_control_panel_on_finishing(self): |
504 | 98 | """If done is called with result=1, the control panel | 104 | """If done is called with result=1, the control panel |
506 | 99 | should be called.""" | 105 | should be called. |
507 | 106 | """ | ||
508 | 100 | self.patch(gui.subprocess, "Popen", self._set_called) | 107 | self.patch(gui.subprocess, "Popen", self._set_called) |
509 | 101 | self.ui.done(result=1) | 108 | self.ui.done(result=1) |
510 | 102 | self.assertEqual(self._called, | 109 | self.assertEqual(self._called, |
511 | @@ -104,7 +111,8 @@ | |||
512 | 104 | 111 | ||
513 | 105 | def test_not_start_control_panel_on_cancel(self): | 112 | def test_not_start_control_panel_on_cancel(self): |
514 | 106 | """If done is called with result=0, the control panel | 113 | """If done is called with result=0, the control panel |
516 | 107 | should NOT be called.""" | 114 | should NOT be called. |
517 | 115 | """ | ||
518 | 108 | self.patch(gui.subprocess, "Popen", self._set_called) | 116 | self.patch(gui.subprocess, "Popen", self._set_called) |
519 | 109 | self.ui.done(result=0) | 117 | self.ui.done(result=0) |
520 | 110 | self.assertEqual(self._called, False) | 118 | self.assertEqual(self._called, False) |
521 | @@ -143,8 +151,7 @@ | |||
522 | 143 | congrats_page.ui.progressContainer.isVisible(), False) | 151 | congrats_page.ui.progressContainer.isVisible(), False) |
523 | 144 | 152 | ||
524 | 145 | def test_credential_parameters(self): | 153 | def test_credential_parameters(self): |
527 | 146 | """Test if the credentials are created the same way | 154 | """Compare credential parameters with control panel's.""" |
526 | 147 | as in control panel.""" | ||
528 | 148 | self.assertEqual(self.ui.creds.kwargs, { | 155 | self.assertEqual(self.ui.creds.kwargs, { |
529 | 149 | 'app_name': APP_NAME, | 156 | 'app_name': APP_NAME, |
530 | 150 | 'ui_module': "ubuntuone_installer.gui.qt.embedded_sso", | 157 | 'ui_module': "ubuntuone_installer.gui.qt.embedded_sso", |
531 | @@ -160,7 +167,6 @@ | |||
532 | 160 | 167 | ||
533 | 161 | def setUp(self): | 168 | def setUp(self): |
534 | 162 | """Initialize this test instance.""" | 169 | """Initialize this test instance.""" |
535 | 163 | |||
536 | 164 | self.kwargs = { | 170 | self.kwargs = { |
537 | 165 | 'app_name': APP_NAME, | 171 | 'app_name': APP_NAME, |
538 | 166 | 'tc_url': TC_URL, | 172 | 'tc_url': TC_URL, |
539 | @@ -171,8 +177,109 @@ | |||
540 | 171 | 177 | ||
541 | 172 | def test_sso_client_gui(self): | 178 | def test_sso_client_gui(self): |
542 | 173 | """Ensure the class stores the right parameters.""" | 179 | """Ensure the class stores the right parameters.""" |
543 | 174 | |||
544 | 175 | self.assertEqual(isinstance( | 180 | self.assertEqual(isinstance( |
545 | 176 | self.ui.controller, | 181 | self.ui.controller, |
546 | 177 | ubuntu_sso.qt.controllers.UbuntuSSOWizardController), True) | 182 | ubuntu_sso.qt.controllers.UbuntuSSOWizardController), True) |
547 | 178 | self.assertEqual(self.ui.view, True) | 183 | self.assertEqual(self.ui.view, True) |
548 | 184 | |||
549 | 185 | |||
550 | 186 | class LocalFoldersTestCase(BaseTestCase): | ||
551 | 187 | """Test the LocalFoldersPage code.""" | ||
552 | 188 | |||
553 | 189 | class_ui = local_folders.LocalFoldersPage | ||
554 | 190 | |||
555 | 191 | def setUp(self): | ||
556 | 192 | """Initialize this test instance.""" | ||
557 | 193 | # Create a test folder with a known size | ||
558 | 194 | self.tmpdir = tempfile.mkdtemp() | ||
559 | 195 | f = open(os.path.join(self.tmpdir, 'test_file'), 'wb') | ||
560 | 196 | f.write(" " * 600) | ||
561 | 197 | f.close() | ||
562 | 198 | os.mkdir(os.path.join(self.tmpdir, 'test_dir')) | ||
563 | 199 | f = open(os.path.join(self.tmpdir, 'test_dir', 'test_file_2'), 'wb') | ||
564 | 200 | f.write(" " * 737) | ||
565 | 201 | f.close() | ||
566 | 202 | super(LocalFoldersTestCase, self).setUp() | ||
567 | 203 | |||
568 | 204 | def tearDown(self): | ||
569 | 205 | """Remove the temporary tree.""" | ||
570 | 206 | shutil.rmtree(self.tmpdir) | ||
571 | 207 | BaseTestCase.tearDown(self) | ||
572 | 208 | |||
573 | 209 | def test_size_calculation(self): | ||
574 | 210 | """Test the recursive folder size calculation.""" | ||
575 | 211 | self.queue = Queue.Queue() | ||
576 | 212 | self.csize = local_folders.CalculateSize(self.tmpdir, self.queue) | ||
577 | 213 | self.csize.run() | ||
578 | 214 | path, size = self.queue.get() | ||
579 | 215 | self.assertEqual(path, self.tmpdir) | ||
580 | 216 | self.assertEqual(size, 1337) | ||
581 | 217 | |||
582 | 218 | def test_item_addition_removal(self): | ||
583 | 219 | """Add an item (plus the default one), then remove them.""" | ||
584 | 220 | self.ui.add_folder(self.tmpdir) | ||
585 | 221 | self.assertEqual(2, self.ui.ui.folder_list.topLevelItemCount()) | ||
586 | 222 | self.ui.on_folder_list_itemClicked( | ||
587 | 223 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
588 | 224 | self.ui.on_folder_list_itemClicked( | ||
589 | 225 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
590 | 226 | self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) | ||
591 | 227 | self.assertEqual({}, self.ui.items) | ||
592 | 228 | |||
593 | 229 | def test_total_size(self): | ||
594 | 230 | """Test that the header reflects the change in item sizes.""" | ||
595 | 231 | while self.ui.ui.folder_list.topLevelItemCount(): | ||
596 | 232 | self.ui.on_folder_list_itemClicked( | ||
597 | 233 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
598 | 234 | self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) | ||
599 | 235 | item = self.ui.add_folder(self.tmpdir) | ||
600 | 236 | item.size = 1337 | ||
601 | 237 | self.ui.update_sizes() | ||
602 | 238 | self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), | ||
603 | 239 | u"Space (1337)") | ||
604 | 240 | |||
605 | 241 | def test_add_twice(self): | ||
606 | 242 | """Behaviour for adding the same folder twice: | ||
607 | 243 | |||
608 | 244 | * It's added only once. | ||
609 | 245 | """ | ||
610 | 246 | while self.ui.ui.folder_list.topLevelItemCount(): | ||
611 | 247 | self.ui.on_folder_list_itemClicked( | ||
612 | 248 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
613 | 249 | self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) | ||
614 | 250 | self.ui.add_folder(self.tmpdir) | ||
615 | 251 | self.ui.add_folder(self.tmpdir) | ||
616 | 252 | self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) | ||
617 | 253 | |||
618 | 254 | def test_add_missing_folder(self): | ||
619 | 255 | """Behaviour for adding a folder that doesn't exist: | ||
620 | 256 | |||
621 | 257 | * It's added. | ||
622 | 258 | * Has size 0. | ||
623 | 259 | """ | ||
624 | 260 | |||
625 | 261 | while self.ui.ui.folder_list.topLevelItemCount(): | ||
626 | 262 | self.ui.on_folder_list_itemClicked( | ||
627 | 263 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
628 | 264 | self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) | ||
629 | 265 | item = self.ui.add_folder(os.path.join("xyzzy", "xyzzy", "xyzzy")) | ||
630 | 266 | self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) | ||
631 | 267 | item.thread.run() | ||
632 | 268 | self.ui.update_sizes() | ||
633 | 269 | self.assertEqual(0, item.size) | ||
634 | 270 | # world did not explode | ||
635 | 271 | |||
636 | 272 | def test_over_quota(self): | ||
637 | 273 | """After removing all folders, offer_frame should be hidden. | ||
638 | 274 | |||
639 | 275 | Push the user over quota, it should be visible. | ||
640 | 276 | """ | ||
641 | 277 | self.patch(self.ui.ui.offer_frame, "setVisible", self._set_called) | ||
642 | 278 | while self.ui.ui.folder_list.topLevelItemCount(): | ||
643 | 279 | self.ui.on_folder_list_itemClicked( | ||
644 | 280 | self.ui.ui.folder_list.topLevelItem(0), 2) | ||
645 | 281 | self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) | ||
646 | 282 | self.ui.update_sizes() | ||
647 | 283 | self.assertEqual(self._called, ((False,), {})) | ||
648 | 284 | self.ui.show_hide_offer(self.ui.quota() + 1) | ||
649 | 285 | self.assertEqual(self._called, ((True,), {})) |
A couple of needs fixing:
* always call super() instead of explicitly calling the parent class
* the diff seems to add several (at first) not needed blank lines, can you please remove those, if they are not needed? (we usually do not add empty lines after the docstrings)
* by convention, setUp and tearDown, if defined, should be the first 2 methods after the class declaration
* this docstring:
506 + """After removing all folders, offer_frame should be hidden.
507 +
508 + Push the user over quota, it should be visible"""
should be:
506 + """After removing all folders, offer_frame should be hidden.
507 +
508 + Push the user over quota, it should be visible.
"""