Merge lp:~nataliabidart/ubuntuone-control-panel/computer-to-cloud-page into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Roberto Alsina
Approved revision: 296
Merged at revision: 290
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/computer-to-cloud-page
Merge into: lp:ubuntuone-control-panel
Prerequisite: lp:~nataliabidart/ubuntuone-control-panel/cloud-to-computer-page
Diff against target: 1319 lines (+848/-56)
16 files modified
data/qt/controlpanel.ui (+7/-2)
data/qt/local_folders.ui (+179/-0)
ubuntuone/controlpanel/gui/__init__.py (+10/-0)
ubuntuone/controlpanel/gui/qt/addfolder.py (+9/-5)
ubuntuone/controlpanel/gui/qt/controlpanel.py (+4/-2)
ubuntuone/controlpanel/gui/qt/folders.py (+298/-1)
ubuntuone/controlpanel/gui/qt/gotoweb.py (+17/-1)
ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py (+16/-7)
ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py (+1/-1)
ubuntuone/controlpanel/gui/qt/tests/test_folders.py (+206/-0)
ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py (+24/-5)
ubuntuone/controlpanel/gui/qt/tests/test_wizard.py (+33/-15)
ubuntuone/controlpanel/gui/qt/wizard.py (+33/-7)
ubuntuone/controlpanel/tests/__init__.py (+9/-0)
ubuntuone/controlpanel/tests/test_login_client.py (+1/-5)
ubuntuone/controlpanel/tests/test_sd_client.py (+1/-5)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/computer-to-cloud-page
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+98286@code.launchpad.net

Commit message

- Added 'Computer to cloud' page to the wizard (part of LP: #933697).
- Tweaked the title of the aforementioned page (LP: #888521).

Description of the change

To test IRL, please have nightlies installed and up to date. Then, from this branch, please run:

./setup.py clean build; U1_DEBUG=True PYTHONPATH=. bin/ubuntuone-control-panel-qt

Assuming you already have U1 credentials, go to the devices tab and remove the current device. You will be presented with the initial screen, where you can play with:

From the dependency branch, you will get:

- closing from the button in the right bottom corner, you should get a confirmation dialog
- after login/register you should be presented with a screen to choose cloud folders to sync from your cloud to your desktop
- optionally, you can play with settings clicking on the button at the end of the folder listing

From this branch, you will get an additional page at the end to choose local folders to sync to your cloud. The list will offer some 'suggested' folders, and you can also add a custom folder using the 'add folder' button. Please note that changes will get applied *only* when the finish button is clicked.

Known bugs from this branch:

Bug #959447
Bug #959690

To post a comment you must log in.
296. By Natalia Bidart

Merged trunk in.

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

It all works as expected!

Code looks good, but it does use new style and old style formatting: The problem is that while new style formatting has an awesome syntax, it is reportedly much slower than the old style, *and* it may tie us to a newer Python than we want without good reason.

Old style formatting is not scheduled to ever disappear from the language so I feel we should stick with that, at least until the performance problems are solved, and everyone is on a python that supports it, but maybe just forever.

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

+1 excellent work!

review: Approve

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 2012-03-12 20:29:41 +0000
3+++ data/qt/controlpanel.ui 2012-03-19 21:32:18 +0000
4@@ -153,9 +153,9 @@
5 </widget>
6 </item>
7 <item>
8- <widget class="GoToWebButton" name="get_more_space_button">
9+ <widget class="GetStorageButton" name="get_more_space_button">
10 <property name="text">
11- <string notr="true">Get more storage</string>
12+ <string notr="true">foo bar baz</string>
13 </property>
14 <property name="default">
15 <bool>true</bool>
16@@ -362,6 +362,11 @@
17 </widget>
18 <customwidgets>
19 <customwidget>
20+ <class>GetStorageButton</class>
21+ <extends>QPushButton</extends>
22+ <header>ubuntuone.controlpanel.gui.qt.gotoweb</header>
23+ </customwidget>
24+ <customwidget>
25 <class>GoToWebButton</class>
26 <extends>QPushButton</extends>
27 <header>ubuntuone.controlpanel.gui.qt.gotoweb</header>
28
29=== added file 'data/qt/local_folders.ui'
30--- data/qt/local_folders.ui 1970-01-01 00:00:00 +0000
31+++ data/qt/local_folders.ui 2012-03-19 21:32:18 +0000
32@@ -0,0 +1,179 @@
33+<?xml version="1.0" encoding="UTF-8"?>
34+<ui version="4.0">
35+ <class>Form</class>
36+ <widget class="QWidget" name="Form">
37+ <property name="geometry">
38+ <rect>
39+ <x>0</x>
40+ <y>0</y>
41+ <width>274</width>
42+ <height>312</height>
43+ </rect>
44+ </property>
45+ <layout class="QVBoxLayout" name="verticalLayout_2">
46+ <property name="topMargin">
47+ <number>0</number>
48+ </property>
49+ <item>
50+ <widget class="QTreeWidget" name="folders">
51+ <property name="verticalScrollBarPolicy">
52+ <enum>Qt::ScrollBarAlwaysOn</enum>
53+ </property>
54+ <property name="alternatingRowColors">
55+ <bool>true</bool>
56+ </property>
57+ <property name="indentation">
58+ <number>0</number>
59+ </property>
60+ <property name="rootIsDecorated">
61+ <bool>false</bool>
62+ </property>
63+ <property name="uniformRowHeights">
64+ <bool>true</bool>
65+ </property>
66+ <property name="allColumnsShowFocus">
67+ <bool>true</bool>
68+ </property>
69+ <attribute name="headerStretchLastSection">
70+ <bool>false</bool>
71+ </attribute>
72+ <column>
73+ <property name="text">
74+ <string notr="true">Sync these folders on my computer</string>
75+ </property>
76+ </column>
77+ <column>
78+ <property name="text">
79+ <string notr="true">Space (Total)</string>
80+ </property>
81+ </column>
82+ </widget>
83+ </item>
84+ <item>
85+ <layout class="QHBoxLayout" name="horizontalLayout_3">
86+ <item>
87+ <spacer name="horizontalSpacer_4">
88+ <property name="orientation">
89+ <enum>Qt::Horizontal</enum>
90+ </property>
91+ <property name="sizeHint" stdset="0">
92+ <size>
93+ <width>40</width>
94+ <height>20</height>
95+ </size>
96+ </property>
97+ </spacer>
98+ </item>
99+ <item>
100+ <widget class="AddFolderButton" name="add_folder_button">
101+ <property name="text">
102+ <string notr="true">Add a folder</string>
103+ </property>
104+ <property name="default">
105+ <bool>true</bool>
106+ </property>
107+ <property name="DisabledState" stdset="0">
108+ <bool>false</bool>
109+ </property>
110+ </widget>
111+ </item>
112+ <item>
113+ <spacer name="horizontalSpacer_5">
114+ <property name="orientation">
115+ <enum>Qt::Horizontal</enum>
116+ </property>
117+ <property name="sizeHint" stdset="0">
118+ <size>
119+ <width>40</width>
120+ <height>20</height>
121+ </size>
122+ </property>
123+ </spacer>
124+ </item>
125+ </layout>
126+ </item>
127+ <item>
128+ <widget class="QFrame" name="offer_frame">
129+ <layout class="QVBoxLayout" name="verticalLayout">
130+ <property name="leftMargin">
131+ <number>0</number>
132+ </property>
133+ <property name="rightMargin">
134+ <number>0</number>
135+ </property>
136+ <item>
137+ <widget class="QLabel" name="offer_label">
138+ <property name="text">
139+ <string notr="true">overflow message</string>
140+ </property>
141+ <property name="wordWrap">
142+ <bool>true</bool>
143+ </property>
144+ </widget>
145+ </item>
146+ <item>
147+ <layout class="QHBoxLayout" name="horizontalLayout">
148+ <item>
149+ <spacer name="horizontalSpacer">
150+ <property name="orientation">
151+ <enum>Qt::Horizontal</enum>
152+ </property>
153+ <property name="sizeHint" stdset="0">
154+ <size>
155+ <width>40</width>
156+ <height>20</height>
157+ </size>
158+ </property>
159+ </spacer>
160+ </item>
161+ <item>
162+ <widget class="GetStorageButton" name="add_storage_button">
163+ <property name="sizePolicy">
164+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
165+ <horstretch>0</horstretch>
166+ <verstretch>0</verstretch>
167+ </sizepolicy>
168+ </property>
169+ <property name="text">
170+ <string notr="true">add storage</string>
171+ </property>
172+ <property name="default">
173+ <bool>true</bool>
174+ </property>
175+ </widget>
176+ </item>
177+ <item>
178+ <spacer name="horizontalSpacer_2">
179+ <property name="orientation">
180+ <enum>Qt::Horizontal</enum>
181+ </property>
182+ <property name="sizeHint" stdset="0">
183+ <size>
184+ <width>40</width>
185+ <height>20</height>
186+ </size>
187+ </property>
188+ </spacer>
189+ </item>
190+ </layout>
191+ </item>
192+ </layout>
193+ </widget>
194+ </item>
195+ </layout>
196+ </widget>
197+ <customwidgets>
198+ <customwidget>
199+ <class>GetStorageButton</class>
200+ <extends>QPushButton</extends>
201+ <header>ubuntuone.controlpanel.gui.qt.gotoweb</header>
202+ </customwidget>
203+ <customwidget>
204+ <class>AddFolderButton</class>
205+ <extends>QPushButton</extends>
206+ <header>ubuntuone.controlpanel.gui.qt.addfolder</header>
207+ </customwidget>
208+ </customwidgets>
209+ <resources/>
210+ <connections/>
211+</ui>
212
213=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
214--- ubuntuone/controlpanel/gui/__init__.py 2012-03-16 21:18:43 +0000
215+++ ubuntuone/controlpanel/gui/__init__.py 2012-03-19 21:32:18 +0000
216@@ -71,6 +71,7 @@
217 EDIT_PROFILE_LINK = u'https://login.ubuntu.com/'
218 EDIT_SERVICES_LINK = UBUNTUONE_LINK + u'services'
219 FACEBOOK_LINK = u'http://www.facebook.com/ubuntuone/'
220+GET_STORAGE_LINK = UBUNTUONE_LINK + u'services/#storage_panel'
221 GET_SUPPORT_LINK = UBUNTUONE_LINK + u'support/'
222 LEARN_MORE_LINK = UBUNTUONE_LINK
223 MANAGE_FILES_LINK = UBUNTUONE_LINK + u'files/'
224@@ -93,6 +94,9 @@
225 CLOUD_TO_COMPUTER_SUBTITLE = _('These are the folders in your cloud. '
226 'Select the ones you want to sync with this computer.')
227 CLOUD_TO_COMPUTER_TITLE = _('Syncing the cloud to your computer')
228+COMPUTER_TO_CLOUD_SUBTITLE = _('Okay! Now it\'s time to choose which folders '
229+ 'on this computer you would like to sync to the cloud.')
230+COMPUTER_TO_CLOUD_TITLE = _('Syncing your computer with the cloud')
231 CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One')
232 CONTACTS = _('Thunderbird plug-in')
233 CREDENTIALS_ERROR = _('There was a problem while retrieving the credentials.')
234@@ -177,6 +181,12 @@
235 INSTALLING = _('Installation of <i>%(package_name)s</i> in progress')
236 LOADING = _('Loading...')
237 LOADING_OVERLAY = _('Getting information, please wait...')
238+LOCAL_FOLDERS_CALCULATING = _('Calculating...')
239+LOCAL_FOLDERS_OVERFLOW = _('The folders you have selected to sync take '
240+ 'over your {quota_total} space. '
241+ 'You can remove some folders or add some extra storage.')
242+LOCAL_FOLDERS_FOLDER_HEADER = _('Sync these folders on my computer')
243+LOCAL_FOLDERS_SPACE_HEADER = _('Space {space_total}')
244 MAIN_ACCOUNT_TAB = _('Account information')
245 MAIN_DEVICES_TAB = _('Devices')
246 MAIN_FOLDERS_TAB = _('Folders')
247
248=== modified file 'ubuntuone/controlpanel/gui/qt/addfolder.py'
249--- ubuntuone/controlpanel/gui/qt/addfolder.py 2012-03-09 14:16:38 +0000
250+++ ubuntuone/controlpanel/gui/qt/addfolder.py 2012-03-19 21:32:18 +0000
251@@ -56,6 +56,8 @@
252 def __init__(self, *args, **kwargs):
253 """Initialize the UI of the widget."""
254 super(AddFolderButton, self).__init__(*args, **kwargs)
255+ self.add_folder_func = \
256+ lambda *a, **kw: defer.fail(NotImplementedError())
257 self.cloud_folders = []
258 self.clicked.connect(self.on_clicked)
259
260@@ -70,18 +72,20 @@
261 parent=self, directory=home_dir,
262 options=FILE_CHOOSER_OPTIONS)
263 folder = unicode(QtCore.QDir.toNativeSeparators(folder))
264- logger.debug('on_add_folder_button_clicked: user requested folder '
265- 'creation for path %r', folder)
266+ logger.info('on_add_folder_button_clicked: user requested folder '
267+ 'creation for path %r.', folder)
268 if folder == '':
269 self.folderCreationCanceled.emit()
270- return
271+ defer.returnValue(None)
272
273 is_valid = yield self.backend.validate_path_for_folder(folder)
274 if not is_valid:
275+ logger.error('on_add_folder_button_clicked: user requested to '
276+ 'create a folder for an invalid path %r.', folder)
277 text = FOLDER_INVALID_PATH % {'folder_path': folder,
278 'home_folder': home_dir}
279 QtGui.QMessageBox.warning(self, '', text, CLOSE)
280- return
281+ defer.returnValue(None)
282
283- yield self.backend.create_folder(folder_path=folder)
284+ yield self.add_folder_func(folder_path=folder)
285 self.folderCreated.emit(folder)
286
287=== modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py'
288--- ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-16 21:12:20 +0000
289+++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-03-19 21:32:18 +0000
290@@ -24,10 +24,10 @@
291 from ubuntuone.controlpanel.backend import AUTOCONNECT_KEY
292 from ubuntuone.controlpanel.logger import setup_logging, log_call
293 from ubuntuone.controlpanel.gui import (
294- EDIT_SERVICES_LINK,
295 FACEBOOK_LINK,
296 GET_HELP_ONLINE,
297 GET_MORE_STORAGE,
298+ GET_STORAGE_LINK,
299 GET_SUPPORT_LINK,
300 GREETING,
301 humanize,
302@@ -63,7 +63,7 @@
303 def _setup(self):
304 """Do some extra setupping for the UI."""
305 self.ui.get_more_space_button.setText(GET_MORE_STORAGE)
306- self.ui.get_more_space_button.uri = EDIT_SERVICES_LINK
307+ self.ui.get_more_space_button.uri = GET_STORAGE_LINK
308
309 self.ui.help_button.setText(GET_HELP_ONLINE)
310 self.ui.help_button.uri = GET_SUPPORT_LINK
311@@ -100,6 +100,8 @@
312 @log_call(logger.debug)
313 def on_credentials_found(self):
314 """Credentials are not found or were removed."""
315+ folders_tab_idx = self.ui.tab_widget.indexOf(self.ui.folders_tab)
316+ self.ui.tab_widget.setCurrentIndex(folders_tab_idx)
317 self.ui.switcher.setCurrentWidget(self.ui.management)
318 self.connect_file_sync()
319 self.is_processing = False
320
321=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
322--- ubuntuone/controlpanel/gui/qt/folders.py 2012-03-19 13:47:56 +0000
323+++ ubuntuone/controlpanel/gui/qt/folders.py 2012-03-19 21:32:18 +0000
324@@ -19,10 +19,13 @@
325 from __future__ import division
326
327 import os
328+import Queue
329+import threading
330
331 from PyQt4 import QtGui, QtCore
332 from twisted.internet import defer
333
334+from ubuntuone.controlpanel.utils import default_folders
335 from ubuntuone.controlpanel.logger import setup_logging, log_call
336 from ubuntuone.controlpanel.gui import (
337 ALWAYS_SUBSCRIBED,
338@@ -35,6 +38,12 @@
339 FOLDERS_COLUMN_SYNC_LOCALLY,
340 FOLDERS_CONFIRM_MERGE,
341 FOLDERS_MANAGE_LABEL,
342+ GET_MORE_STORAGE,
343+ humanize,
344+ LOCAL_FOLDERS_CALCULATING,
345+ LOCAL_FOLDERS_FOLDER_HEADER,
346+ LOCAL_FOLDERS_OVERFLOW,
347+ LOCAL_FOLDERS_SPACE_HEADER,
348 MANAGE_FILES_LINK,
349 MUSIC_ICON_NAME,
350 MUSIC_DISPLAY_NAME,
351@@ -48,7 +57,7 @@
352 uri_hook,
353 )
354 from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
355-from ubuntuone.controlpanel.gui.qt.ui import folders_ui
356+from ubuntuone.controlpanel.gui.qt.ui import folders_ui, local_folders_ui
357
358
359 logger = setup_logging('qt.folders')
360@@ -57,6 +66,9 @@
361 SUBSCRIPTION_COL = 1
362 EXPLORE_COL = 2
363
364+LOCAL_SUBSCRIPTION_COL = 0
365+LOCAL_SPACE_COL = 1
366+
367 CANCEL = QtGui.QMessageBox.Cancel
368 CHECKED = QtCore.Qt.Checked
369 CLOSE = QtGui.QMessageBox.Close
370@@ -102,6 +114,7 @@
371 super(FoldersPanel, self)._setup()
372 self.ui.add_folder_button.folderCreated.connect(self.on_folder_created)
373 self.ui.add_folder_button.setText(FOLDERS_BUTTON_ADD_FOLDER)
374+ self.ui.add_folder_button.add_folder_func = self.backend.create_folder
375
376 self.ui.share_publish_button.setVisible(not self.remote_folders)
377 self.ui.add_folder_button.setVisible(not self.remote_folders)
378@@ -328,3 +341,287 @@
379 """The Folders Panel that only shows remote cloud folders."""
380
381 remote_folders = True
382+
383+
384+class CalculateSize(threading.Thread):
385+ """A thread that calculates, in the background, the size of a folder."""
386+
387+ def __init__(self, path_name, queue):
388+ self.path_name = path_name
389+ self.queue = queue
390+ self._stop = False
391+
392+ super(CalculateSize, self).__init__()
393+
394+ # http://docs.python.org/library/threading.html#threading.Thread.daemon
395+ # "A boolean value indicating whether this thread is a daemon thread
396+ # (True) or not (False)"
397+ self.daemon = True
398+
399+ def run(self):
400+ """Run this thread."""
401+ logger.debug('size_calculator: about to calculate size for %r.',
402+ self.path_name)
403+ try:
404+ total_size = 0
405+ for dirpath, _, filenames in os.walk(self.path_name):
406+ for f in filenames:
407+ fp = os.path.join(dirpath, f)
408+ if os.path.isfile(fp):
409+ total_size += os.path.getsize(fp)
410+ if self._stop:
411+ logger.warning('size_calculator: stopping due to external '
412+ 'request.')
413+ return
414+ except: # pylint: disable=W0702
415+ logger.exception('size_calculator: failed for %r:', self.path_name)
416+ else:
417+ logger.info('size_calculator: added new size %r for %r.',
418+ self.path_name, total_size)
419+ self.queue.put([self.path_name, total_size])
420+
421+
422+class FolderItem(QtGui.QTreeWidgetItem):
423+ """A folder in the local folder list."""
424+
425+ def __init__(self, values=None, path=None, queue=None, volume_id=None):
426+ super(FolderItem, self).__init__(values)
427+ self.path = path
428+ self.volume_id = volume_id
429+ self.thread = None
430+ self.size = 0
431+
432+ state = UNCHECKED
433+ if path is not None:
434+ if volume_id is not None:
435+ state = CHECKED
436+ elif queue is not None:
437+ # calculate sizes of non-existing folders
438+ self.thread = CalculateSize(path, queue)
439+ self.thread.start()
440+ self.size = None
441+
442+ self.setCheckState(LOCAL_SUBSCRIPTION_COL, state)
443+
444+
445+class LocalFoldersPanel(UbuntuOneBin):
446+ """The panel that only shows local, non-synched folders."""
447+
448+ changesApplied = QtCore.pyqtSignal()
449+ changesCanceled = QtCore.pyqtSignal()
450+ logger = logger
451+ ui_class = local_folders_ui
452+
453+ def __init__(self, *args, **kwargs):
454+ super(LocalFoldersPanel, self).__init__(*args, **kwargs)
455+ self.queue = Queue.Queue()
456+ self.timer = QtCore.QTimer()
457+ self.items = {}
458+ self.user_home = None
459+ self.account_info = None
460+
461+ def _setup(self):
462+ """Do some extra setupping for the UI."""
463+ super(LocalFoldersPanel, self)._setup()
464+ # Start with storage upgrade offer invisible
465+ self.ui.offer_frame.setVisible(False)
466+
467+ headers = self.ui.folders.header()
468+ headers.setResizeMode(LOCAL_SUBSCRIPTION_COL, headers.Stretch)
469+ headers.setResizeMode(LOCAL_SPACE_COL, headers.Custom)
470+ headers.resizeSection(LOCAL_SPACE_COL, 130)
471+
472+ self.ui.folders.headerItem().setText(LOCAL_SUBSCRIPTION_COL,
473+ LOCAL_FOLDERS_FOLDER_HEADER)
474+ self._set_space_header()
475+
476+ self.ui.add_folder_button.folderCreated.connect(self.on_folder_created)
477+ self.ui.add_folder_button.setText(FOLDERS_BUTTON_ADD_FOLDER)
478+ self.ui.add_folder_button.add_folder_func = self.add_folder
479+
480+ self.ui.add_storage_button.setText(GET_MORE_STORAGE)
481+
482+ def _set_space_header(self, total=None):
483+ """Set the folders listing 'space' header."""
484+ if total is None:
485+ total = ''
486+ else:
487+ try:
488+ total = humanize(long(total))
489+ except (TypeError, ValueError):
490+ pass
491+ total = '(%s)' % total
492+
493+ title = LOCAL_FOLDERS_SPACE_HEADER.format(space_total=total)
494+ self.ui.folders.headerItem().setText(LOCAL_SPACE_COL, title)
495+
496+ def _stop(self):
497+ """Stop all pending threads and timers."""
498+ self.timer.stop()
499+ self.timer = None
500+
501+ for item in self.items.itervalues():
502+ if item.thread is None:
503+ logger.warning('LocalFoldersPanel: attempted to stop a thread '
504+ 'for an item with a None thread.')
505+ else:
506+ item.thread._stop = True
507+
508+ # pylint: disable=E0202
509+ @defer.inlineCallbacks
510+ def load(self):
511+ """Load specific tab info."""
512+ self.is_processing = True
513+ self.account_info = yield self.backend.account_info()
514+ self._set_space_header(self.account_info['quota_used'])
515+ volumes_info = yield self.backend.volumes_info(with_storage_info=False)
516+ yield self.process_info(volumes_info)
517+
518+ @defer.inlineCallbacks
519+ @log_call(logger.debug)
520+ def process_info(self, volumes_info):
521+ """Load local folders info into the tree view."""
522+ try:
523+ folders = []
524+ for _, _, volumes in volumes_info:
525+ for volume in volumes:
526+ if (volume[u'type'] == self.backend.FOLDER_TYPE and
527+ bool(volume['subscribed'])):
528+ folders.append((volume['path'], volume['volume_id']))
529+
530+ # add local folders only if they are valid
531+ self.user_home = yield self.backend.get_home_dir()
532+ for folder in default_folders(user_home=self.user_home):
533+ is_valid = yield self.backend.validate_path_for_folder(folder)
534+ if is_valid:
535+ folders.append((folder, None))
536+
537+ # always clear the items dict first, since clearing the folders
538+ # list will trigger "underlying C/C++ object has been deleted"
539+ self.items.clear()
540+ self.ui.folders.clear()
541+
542+ # self.timer can be None if self._stop was called before
543+ # this piece of code was executed
544+ if self.timer is not None:
545+ for path, volume_id in folders:
546+ self.add_folder(folder_path=path, volume_id=volume_id)
547+
548+ self.timer.start(2000)
549+ self.timer.timeout.connect(self.update_sizes)
550+ finally:
551+ self.is_processing = False
552+
553+ @handle_errors(logger=logger)
554+ @defer.inlineCallbacks
555+ def apply_changes(self):
556+ """When moving to next page, create/[un]subscribe UDFs."""
557+ self.is_processing = True
558+ try:
559+ self._stop()
560+
561+ for path, item in self.items.iteritems():
562+ subscribed = item.checkState(LOCAL_SUBSCRIPTION_COL) == CHECKED
563+ if item.volume_id is not None:
564+ logger.info('apply_changes: change settings for %r to %r.',
565+ item.path, dict(subscribed=subscribed))
566+ yield self.backend.change_volume_settings(item.volume_id,
567+ dict(subscribed=subscribed))
568+ else:
569+ if subscribed:
570+ logger.info('apply_changes: create folder for %r.',
571+ path)
572+ yield self.backend.create_folder(path)
573+ finally:
574+ self.is_processing = False
575+
576+ self.changesApplied.emit()
577+
578+ @handle_errors(logger=logger)
579+ def add_folder(self, folder_path, volume_id=None):
580+ """Add a folder to the list."""
581+ if folder_path in self.items:
582+ logger.warning('LocalFoldersPanel: already have an item for %r.',
583+ folder_path)
584+
585+ if self.user_home is None:
586+ logger.warning('LocalFoldersPanel: user home is None! '
587+ 'paths will not be pretty.')
588+ display_name = folder_path
589+ else:
590+ user_home = self.user_home + os.path.sep
591+ display_name = _process_name(folder_path.replace(user_home, ''))
592+
593+ item = FolderItem([display_name, ""], path=folder_path,
594+ queue=self.queue, volume_id=volume_id)
595+ self.ui.folders.addTopLevelItem(item)
596+
597+ if volume_id is None: # new folder
598+ self.items[folder_path] = item
599+
600+ @log_call(logger.info)
601+ def on_folder_created(self, new_folder):
602+ """User clicked on the "Add Folder" button."""
603+ item = self.items.get(unicode(new_folder))
604+ if item is not None:
605+ item.setCheckState(LOCAL_SUBSCRIPTION_COL, CHECKED)
606+ else:
607+ logger.warning('LocalFoldersPanel: on_folder_created was called '
608+ 'for %r which is not tracked in the internal dict',
609+ unicode(new_folder))
610+
611+ @handle_errors(logger=logger)
612+ def update_sizes(self):
613+ """Poll the queue were the threads put the size info.
614+
615+ Every item put in this queue will be a non-synched folder. Thus, the
616+ item's volume_id will be None, so no need to check that.
617+
618+ """
619+ while True:
620+ try:
621+ path, size = self.queue.get(block=False)
622+ except Queue.Empty:
623+ break
624+ else:
625+ item = self.items.get(path)
626+ if item:
627+ item.size = size
628+ item.setText(LOCAL_SPACE_COL, humanize(size))
629+
630+ total = long(self.account_info['quota_used'])
631+ for path, item in self.items.iteritems():
632+ if item.size is None:
633+ total = LOCAL_FOLDERS_CALCULATING
634+ break
635+
636+ subscribed = item.checkState(LOCAL_SUBSCRIPTION_COL) == CHECKED
637+ if subscribed:
638+ total += item.size
639+
640+ if isinstance(total, long):
641+ self.show_hide_offer(total)
642+ else:
643+ self.show_hide_offer(0)
644+
645+ self._set_space_header(total)
646+
647+ def show_hide_offer(self, current_size):
648+ """Show or hide the offer to buy space according to the total size."""
649+ quota = long(self.account_info['quota_total'])
650+ if current_size > quota:
651+ msg = LOCAL_FOLDERS_OVERFLOW.format(quota_total=humanize(quota))
652+ self.ui.offer_label.setText(msg)
653+ self.ui.offer_frame.setVisible(True)
654+ else:
655+ self.ui.offer_frame.setVisible(False)
656+
657+ # pylint: disable=C0103
658+
659+ def on_folders_itemChanged(self, item, column):
660+ """Update the size for the chosen row."""
661+ if column == LOCAL_SUBSCRIPTION_COL:
662+ self.update_sizes()
663+ self.items[item.path] = item
664+
665+ # pylint: enable=C0103
666
667=== modified file 'ubuntuone/controlpanel/gui/qt/gotoweb.py'
668--- ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-02-06 15:23:27 +0000
669+++ ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-03-19 21:32:18 +0000
670@@ -23,16 +23,25 @@
671 from twisted.internet import defer
672
673 from ubuntuone.controlpanel import cache
674+from ubuntuone.controlpanel.gui import (
675+ GET_MORE_STORAGE,
676+ GET_STORAGE_LINK,
677+)
678 from ubuntuone.controlpanel.gui import qt
679
680
681 class GoToWebButton(cache.Cache, QtGui.QPushButton):
682 """The GoToWebButton widget"""
683
684+ uri = None
685+ legend = None
686+
687 def __init__(self, *args, **kwargs):
688 """Initialize the UI of the widget."""
689 super(GoToWebButton, self).__init__(*args, **kwargs)
690- self.uri = None
691+ if self.legend is not None:
692+ self.setText(self.legend)
693+
694 self.setIcon(qt.icon_from_name('external_icon_white'))
695 self.setLayoutDirection(QtCore.Qt.RightToLeft)
696 self.clicked.connect(self.on_clicked)
697@@ -45,3 +54,10 @@
698 if self.uri is not None:
699 uri = yield self.backend.build_signed_iri(self.uri)
700 qt.uri_hook(uri)
701+
702+
703+class GetStorageButton(GoToWebButton):
704+ """A specific GoToWebButton to get more storage."""
705+
706+ legend = GET_MORE_STORAGE
707+ uri = GET_STORAGE_LINK
708
709=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py'
710--- ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2012-03-09 13:44:53 +0000
711+++ ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2012-03-19 21:32:18 +0000
712@@ -18,8 +18,6 @@
713
714 """Tests for the AddFolderButton widget."""
715
716-import os
717-
718 from twisted.internet import defer
719
720 from ubuntuone.controlpanel.gui.tests import (
721@@ -48,11 +46,10 @@
722 @defer.inlineCallbacks
723 def setUp(self):
724 yield super(AddFolderButtonTestCase, self).setUp()
725+ self.created_folders = []
726 self.patch(FakedFileDialog, 'response', gui.QtCore.QString(''))
727 self.patch(self.ui.backend, 'validate_path_for_folder', lambda p: True)
728- old_home = os.environ['HOME']
729- os.environ['HOME'] = USER_HOME
730- self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home))
731+ self.patch(self.ui, 'add_folder_func', self.add_folder)
732
733 @defer.inlineCallbacks
734 def assert_does_nothing(self, method_call):
735@@ -73,6 +70,18 @@
736 # the folderCreated signal was not emitted
737 self.assertEqual(self._called, False)
738
739+ def add_folder(self, folder_path):
740+ """A dummy add folder func."""
741+ self.created_folders.append(folder_path)
742+ return defer.succeed(None)
743+
744+ def test_default_add_folder_func(self):
745+ """The add_folder_func is used."""
746+ folder = set_path_on_file_dialog()
747+ yield self.ui.on_clicked()
748+
749+ self.assertEqual(self.created_folders, [folder])
750+
751 @defer.inlineCallbacks
752 def test_add_folder_button_clicked_opens_file_chooser(self):
753 """When adding a new folder, the proper file chooser is raised."""
754@@ -121,7 +130,7 @@
755 self.assertEqual(FakedDialog.args, None)
756 self.assertEqual(FakedDialog.kwargs, None)
757 # backend called
758- self.assert_backend_called('create_folder', folder_path=folder)
759+ self.assertEqual(self.created_folders, [folder])
760
761 @defer.inlineCallbacks
762 def test_calls_backend(self):
763@@ -133,7 +142,7 @@
764 self.assertEqual(FakedDialog.args, None)
765 self.assertEqual(FakedDialog.kwargs, None)
766 # backend called
767- self.assert_backend_called('create_folder', folder_path=folder)
768+ self.assertEqual(self.created_folders, [folder])
769
770 @defer.inlineCallbacks
771 def test_emit_folder_created_on_success(self):
772
773=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py'
774--- ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2012-03-16 18:54:55 +0000
775+++ ubuntuone/controlpanel/gui/qt/tests/test_controlpanel.py 2012-03-19 21:32:18 +0000
776@@ -193,7 +193,7 @@
777 def test_get_more_space_button(self):
778 """When clicking the get more GB button, the proper url is opened."""
779 self.assert_uri_hook_called(self.ui.ui.get_more_space_button,
780- gui.EDIT_SERVICES_LINK)
781+ gui.GET_STORAGE_LINK)
782
783 def test_help_button(self):
784 """When clicking the help button, the proper url is opened."""
785
786=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_folders.py'
787--- ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-03-19 13:47:56 +0000
788+++ ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-03-19 21:32:18 +0000
789@@ -20,11 +20,14 @@
790 import logging
791 import operator
792 import os
793+import Queue
794+import shutil
795
796 from PyQt4 import QtGui
797 from twisted.internet import defer
798 from ubuntuone.devtools.handlers import MementoHandler
799
800+from ubuntuone.controlpanel.tests import helper_fail
801 from ubuntuone.controlpanel.gui.tests import (
802 FAKE_VOLUMES_INFO,
803 FAKE_VOLUMES_MINIMAL_INFO,
804@@ -413,6 +416,8 @@
805
806 def test_add_folder_button(self):
807 """The 'add_folder_button' is visible by default."""
808+ self.assertEqual(self.ui.ui.add_folder_button.add_folder_func,
809+ self.ui.backend.create_folder)
810 self.assertTrue(self.ui.ui.add_folder_button.isVisible())
811
812 def test_check_settings_button(self):
813@@ -650,3 +655,204 @@
814
815 class_ui = gui.RemoteFoldersPanel
816 faked_volumes = volumes_with_music_unsubscribed()
817+
818+
819+class BaseLocalFoldersTestCase(BaseTestCase):
820+ """Test suite for the class implementing the LocalFolders feature."""
821+
822+ @defer.inlineCallbacks
823+ def setUp(self):
824+ self.path = 'not-existing-dir'
825+ self.expected_size = self.build_test_dir(self.path)
826+ self.queue = Queue.Queue()
827+ yield super(BaseLocalFoldersTestCase, self).setUp()
828+
829+ def build_test_dir(self, dir_path):
830+ """Build a testing directory hierarchy."""
831+ assert not os.path.exists(dir_path)
832+
833+ os.makedirs(dir_path)
834+ self.addCleanup(shutil.rmtree, dir_path)
835+
836+ total_size = 0
837+
838+ a_file = os.path.join(dir_path, 'test_file')
839+ with open(a_file, 'wb') as f:
840+ f.write('z' * 1000000)
841+
842+ total_size += os.path.getsize(a_file)
843+
844+ inner_dir = os.path.join(dir_path, 'test_dir')
845+ os.mkdir(inner_dir)
846+
847+ other_file = os.path.join(dir_path, 'other_test_file')
848+ with open(other_file, 'wb') as f:
849+ f.write(' ' * 99999)
850+
851+ total_size += os.path.getsize(other_file)
852+
853+ empty_dir = os.path.join(dir_path, 'empty')
854+ os.mkdir(empty_dir)
855+
856+ # add a symlink to confirm those are avoided
857+ a_link = os.path.join(dir_path, 'some_link')
858+ os.symlink(a_file, a_link)
859+
860+ return total_size
861+
862+
863+class CalculateSizeTestCase(BaseLocalFoldersTestCase):
864+ """Test suite for the CalculateSize thread implementation."""
865+
866+ @defer.inlineCallbacks
867+ def setUp(self):
868+ yield super(CalculateSizeTestCase, self).setUp()
869+ self.ui = gui.CalculateSize(path_name=self.path, queue=self.queue)
870+ self.patch(self.ui, 'start', lambda: None)
871+
872+ def test_creation(self):
873+ """The created instance is correct."""
874+ self.assertEqual(self.ui.path_name, self.path)
875+ self.assertEqual(self.ui.queue, self.queue)
876+ self.assertTrue(self.ui.daemon)
877+
878+ def test_run(self):
879+ """The run() method calculates the size for the given path."""
880+ self.ui.run()
881+
882+ path, size = self.queue.get(block=True, timeout=0.5)
883+
884+ self.assertEqual(path, self.path)
885+ self.assertEqual(size, self.expected_size)
886+
887+ def test_run_handles_errors(self):
888+ """The run() method handles errors."""
889+ self.patch(gui.os, 'walk', helper_fail)
890+ self.ui.run()
891+
892+ self.assertRaises(Queue.Empty, self.queue.get, block=True, timeout=0.5)
893+
894+
895+class FakedCalculateSize(object):
896+ """A faked CalculateSize thread."""
897+
898+ def __init__(self, *args, **kwargs):
899+ self.started = False
900+
901+ def start(self):
902+ """Fake start."""
903+ self.started = True
904+
905+
906+class FolderItemTestCase(BaseLocalFoldersTestCase):
907+ """Test suite for the FolderItem widget."""
908+
909+ @defer.inlineCallbacks
910+ def setUp(self):
911+ yield super(FolderItemTestCase, self).setUp()
912+ self.calculator = FakedCalculateSize(self.path, self.queue)
913+ self.patch(gui, 'CalculateSize', lambda *a, **kw: self.calculator)
914+ self.values = ['foo', 'bar']
915+
916+ assert not self.calculator.started
917+
918+ def test_no_params(self):
919+ """The creation with no params uses defaults."""
920+ item = gui.FolderItem()
921+
922+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
923+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
924+ self.assertEqual(item.path, None)
925+ self.assertEqual(item.volume_id, None)
926+ self.assertEqual(item.thread, None)
927+ self.assertEqual(item.size, 0)
928+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
929+ gui.UNCHECKED)
930+
931+ def test_values(self):
932+ """The creation with only values."""
933+ item = gui.FolderItem(values=self.values)
934+
935+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), self.values[0])
936+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), self.values[1])
937+ self.assertEqual(item.path, None)
938+ self.assertEqual(item.volume_id, None)
939+ self.assertEqual(item.thread, None)
940+ self.assertEqual(item.size, 0)
941+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
942+ gui.UNCHECKED)
943+
944+ def test_path(self):
945+ """The creation with only a path."""
946+ item = gui.FolderItem(path=self.path)
947+
948+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
949+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
950+ self.assertEqual(item.path, self.path)
951+ self.assertEqual(item.volume_id, None)
952+ self.assertEqual(item.thread, None)
953+ self.assertEqual(item.size, 0)
954+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
955+ gui.UNCHECKED)
956+
957+ def test_queue(self):
958+ """The creation with only a queue."""
959+ item = gui.FolderItem(queue=self.queue)
960+
961+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
962+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
963+ self.assertEqual(item.path, None)
964+ self.assertEqual(item.volume_id, None)
965+ self.assertEqual(item.thread, None)
966+ self.assertEqual(item.size, 0)
967+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
968+ gui.UNCHECKED)
969+
970+ def test_volume_id(self):
971+ """The creation with only a volume_id."""
972+ item = gui.FolderItem(volume_id='yadda')
973+
974+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
975+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
976+ self.assertEqual(item.path, None)
977+ self.assertEqual(item.volume_id, 'yadda')
978+ self.assertEqual(item.thread, None)
979+ self.assertEqual(item.size, 0)
980+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
981+ gui.UNCHECKED)
982+
983+ def test_path_and_volume_id(self):
984+ """The creation with only a volume_id."""
985+ item = gui.FolderItem(path=self.path, volume_id='yadda')
986+
987+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
988+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
989+ self.assertEqual(item.path, self.path)
990+ self.assertEqual(item.volume_id, 'yadda')
991+ self.assertEqual(item.thread, None)
992+ self.assertEqual(item.size, 0)
993+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
994+ gui.CHECKED)
995+
996+ def test_path_and_queue(self):
997+ """The creation with only a volume_id."""
998+ item = gui.FolderItem(path=self.path, queue=self.queue)
999+
1000+ self.assertEqual(item.text(gui.LOCAL_SUBSCRIPTION_COL), '')
1001+ self.assertEqual(item.text(gui.LOCAL_SPACE_COL), '')
1002+ self.assertEqual(item.path, self.path)
1003+ self.assertEqual(item.volume_id, None)
1004+ self.assertEqual(item.thread, self.calculator)
1005+ self.assertEqual(item.size, None)
1006+ self.assertEqual(item.checkState(gui.LOCAL_SUBSCRIPTION_COL),
1007+ gui.UNCHECKED)
1008+
1009+ self.assertTrue(self.calculator.started)
1010+
1011+
1012+class LocalFoldersPanelTestCase(UbuntuOneBinTestCase):
1013+ """Test suite for the LocalFoldersPanel widget."""
1014+
1015+ class_ui = gui.LocalFoldersPanel
1016+
1017+ # TODO: add the test suite (LP: #959690).
1018
1019=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py'
1020--- ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-02-06 15:23:27 +0000
1021+++ ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-03-19 21:32:18 +0000
1022@@ -18,8 +18,6 @@
1023
1024 """Tests for the GoToWebButton widget."""
1025
1026-from twisted.internet import defer
1027-
1028 from ubuntuone.controlpanel.gui import qt
1029 from ubuntuone.controlpanel.gui.qt import gotoweb as gui
1030 from ubuntuone.controlpanel.gui.qt.tests import (
1031@@ -32,9 +30,16 @@
1032
1033 class_ui = gui.GoToWebButton
1034
1035- @defer.inlineCallbacks
1036- def setUp(self):
1037- yield super(GoToWebButtonTestCase, self).setUp()
1038+ def test_uri_default(self):
1039+ """The uri uses the default."""
1040+ self.assertEqual(self.ui.uri, self.class_ui.uri)
1041+
1042+ def test_text_default(self):
1043+ """The text uses the default."""
1044+ if self.class_ui.legend is not None:
1045+ self.assertEqual(self.ui.text(), self.class_ui.legend)
1046+ else:
1047+ self.assertEqual(self.ui.text(), '')
1048
1049 def test_uri_can_be_set(self):
1050 """The uri can be set."""
1051@@ -69,3 +74,17 @@
1052 self.ui.click()
1053
1054 self.assertEqual(self._called, False)
1055+
1056+
1057+class GetStorageButtonTestCase(GoToWebButtonTestCase):
1058+ """The test suite for the GetStorageButton widget."""
1059+
1060+ class_ui = gui.GetStorageButton
1061+
1062+ def test_uri(self):
1063+ """The default uri is correct."""
1064+ self.assertEqual(self.ui.uri, gui.GET_STORAGE_LINK)
1065+
1066+ def test_text(self):
1067+ """The default legend is correct."""
1068+ self.assertEqual(self.ui.text(), gui.GET_MORE_STORAGE)
1069
1070=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_wizard.py'
1071--- ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-16 21:15:09 +0000
1072+++ ubuntuone/controlpanel/gui/qt/tests/test_wizard.py 2012-03-19 21:32:18 +0000
1073@@ -110,10 +110,6 @@
1074 panel_class = gui.RemoteFoldersPanel
1075 sub_title = gui.CLOUD_TO_COMPUTER_SUBTITLE
1076
1077- def test_is_final(self):
1078- """The page is not final."""
1079- self.assertTrue(self.ui.isFinalPage())
1080-
1081 def test_folder_panel_shows_remote_folders_only(self):
1082 """The FolderPanel shows only remote folders."""
1083 self.assertTrue(self.ui.panel.remote_folders)
1084@@ -126,6 +122,19 @@
1085 panel_class = gui.PreferencesPanel
1086
1087
1088+class ComputerToCloudPageTestCase(UbuntuOnePageTestCase):
1089+ """Test the ComputerToCloudPage wizard page."""
1090+
1091+ class_ui = gui.ComputerToCloudPage
1092+ main_title = gui.COMPUTER_TO_CLOUD_TITLE
1093+ panel_class = gui.LocalFoldersPanel
1094+ sub_title = gui.COMPUTER_TO_CLOUD_SUBTITLE
1095+
1096+ def test_is_final(self):
1097+ """The page is not final."""
1098+ self.assertTrue(self.ui.isFinalPage())
1099+
1100+
1101 class UbuntuOneWizardTestCase(BaseTestCase):
1102 """Test the UbuntuOneWizard widget."""
1103
1104@@ -266,16 +275,33 @@
1105 class UbuntuOneWizardCloudToComputerTestCase(UbuntuOneWizardSignInTestCase):
1106 """Test the CloudToComputerPage wizard page."""
1107
1108- buttons = {'FinishButton':
1109- (None, 'finished', (gui.QtGui.QDialog.Accepted,))}
1110+ buttons = {'NextButton': (None, 'currentIdChanged', (3,))}
1111 page_name = 'cloud_folders'
1112 stage_name = 'folders'
1113
1114+
1115+class UbuntuOneWizardSettingsTestCase(UbuntuOneWizardSignInTestCase):
1116+ """Test the CloudToComputerPage wizard page."""
1117+
1118+ buttons = {'BackButton': (None, 'currentIdChanged', (0,))}
1119+ page_name = 'settings'
1120+ stage_name = 'folders'
1121+
1122+
1123+class UbuntuOneWizardComputerToCloudTestCase(UbuntuOneWizardSignInTestCase):
1124+ """Test the CloudToComputerPage wizard page."""
1125+
1126+ buttons = {
1127+ 'FinishButton': (None, 'finished', (gui.QtGui.QDialog.Accepted,)),
1128+ 'BackButton': (None, 'currentIdChanged', (0,)),
1129+ }
1130+ page_name = 'local_folders'
1131+ stage_name = 'sync'
1132+
1133 def test_done_rejected(self):
1134 """When the wizard is closed on the final page, emit rejected."""
1135 self.ui.finished.connect(self._set_called)
1136
1137- self.ui._next_id = self.ui.pages[self.ui.cloud_folders_page]
1138 assert self.ui.page(self.ui.currentId()).isFinalPage()
1139 self.ui.done(gui.QtGui.QDialog.Rejected)
1140
1141@@ -286,14 +312,6 @@
1142 # does not apply to this page
1143
1144
1145-class UbuntuOneWizardSettingsTestCase(UbuntuOneWizardSignInTestCase):
1146- """Test the CloudToComputerPage wizard page."""
1147-
1148- buttons = {'BackButton': (None, 'currentIdChanged', (0,))}
1149- page_name = 'settings'
1150- stage_name = 'folders'
1151-
1152-
1153 class UbuntuOneWizardLoginTestCase(UbuntuOneWizardTestCase):
1154 """Test the login through the wizard."""
1155
1156
1157=== modified file 'ubuntuone/controlpanel/gui/qt/wizard.py'
1158--- ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-16 21:15:09 +0000
1159+++ ubuntuone/controlpanel/gui/qt/wizard.py 2012-03-19 21:32:18 +0000
1160@@ -34,10 +34,13 @@
1161 ARE_YOU_SURE_YES,
1162 CLOUD_TO_COMPUTER_SUBTITLE,
1163 CLOUD_TO_COMPUTER_TITLE,
1164+ COMPUTER_TO_CLOUD_SUBTITLE,
1165+ COMPUTER_TO_CLOUD_TITLE,
1166 UBUNTUONE_LINK,
1167 )
1168 from ubuntuone.controlpanel.gui.qt.folders import (
1169 RemoteFoldersPanel,
1170+ LocalFoldersPanel,
1171 )
1172 from ubuntuone.controlpanel.gui.qt.preferences import PreferencesPanel
1173 from ubuntuone.controlpanel.gui.qt.signin import SignInPanel
1174@@ -109,7 +112,6 @@
1175 main_title = CLOUD_TO_COMPUTER_TITLE
1176 panel_class = RemoteFoldersPanel
1177 sub_title = CLOUD_TO_COMPUTER_SUBTITLE
1178- is_final = True
1179
1180 def __init__(self, *args, **kwargs):
1181 super(CloudToComputerPage, self).__init__(*args, **kwargs)
1182@@ -122,6 +124,15 @@
1183 panel_class = PreferencesPanel
1184
1185
1186+class ComputerToCloudPage(UbuntuOnePage):
1187+ """The page to choose local folders to sync remotly."""
1188+
1189+ is_final = True
1190+ main_title = COMPUTER_TO_CLOUD_TITLE
1191+ panel_class = LocalFoldersPanel
1192+ sub_title = COMPUTER_TO_CLOUD_SUBTITLE
1193+
1194+
1195 class UbuntuOneWizard(cache.Cache, QtGui.QWizard):
1196 """The Ubuntu One wizard."""
1197
1198@@ -160,6 +171,10 @@
1199 self.settings_page = SettingsPage()
1200 self.addPage(self.settings_page)
1201
1202+ # computer to cloud
1203+ self.local_folders_page = ComputerToCloudPage()
1204+ self.addPage(self.local_folders_page)
1205+
1206 self._next_id = self.pages[self.signin_page]
1207 self.next()
1208
1209@@ -187,16 +202,22 @@
1210 stage = self.side_widget.signin_stage
1211 self._next_id = self.pages[self.cloud_folders_page]
1212 elif page is self.cloud_folders_page:
1213- button_layout = [self.Stretch, self.FinishButton]
1214+ button_layout = [self.Stretch, self.NextButton]
1215 button = self.cloud_folders_page.panel.ui.check_settings_button
1216- button_to = self.button(self.FinishButton)
1217+ button_to = self.button(self.NextButton)
1218 stage = self.side_widget.folders_stage
1219+ self._next_id = self.pages[self.local_folders_page]
1220 elif page is self.settings_page:
1221- button_layout = [self.BackButton, self.Stretch]
1222+ button_layout = [self.Stretch, self.BackButton]
1223 button = self.settings_page.panel.ui.apply_changes_button
1224 button_to = self.button(self.BackButton)
1225 stage = self.side_widget.folders_stage
1226 self._next_id = self.pages[self.cloud_folders_page]
1227+ elif page is self.local_folders_page:
1228+ button_layout = [self.Stretch, self.BackButton, self.FinishButton]
1229+ button = self.local_folders_page.panel.ui.add_folder_button
1230+ button_to = self.button(self.BackButton)
1231+ stage = self.side_widget.sync_stage
1232 else:
1233 logger.error('UbuntuOneWizard.initializePage was called with an'
1234 'unknown page: %r (page_id was %r).', page, page_id)
1235@@ -217,7 +238,7 @@
1236 """Called clean up 'page_id' just before the user leaves it."""
1237 page = self.page(page_id)
1238 logger.debug('UbuntuOneWizard.cleanupPage: page is %r.', page)
1239- if page is self.settings_page:
1240+ if page is self.settings_page or page is self.local_folders_page:
1241 self.initializePage(self.pages[self.cloud_folders_page])
1242
1243 def nextId(self):
1244@@ -257,8 +278,13 @@
1245
1246 def done(self, result):
1247 """The main window is being closed, call any custom callback."""
1248- if (result == QtGui.QDialog.Rejected and
1249- not self.page(self.currentId()).isFinalPage()):
1250+ if result == QtGui.QDialog.Accepted:
1251+ parent_done = super(UbuntuOneWizard, self).done
1252+ f = lambda: parent_done(QtGui.QDialog.Accepted)
1253+ self.local_folders_page.panel.changesApplied.connect(f)
1254+ # commit local_folders_page's changes
1255+ self.local_folders_page.panel.apply_changes()
1256+ elif not self.page(self.currentId()).isFinalPage():
1257 response = self.confirm_dialog.exec_()
1258 if response == QtGui.QDialog.Accepted:
1259 logger.warning('UbuntuOneWizard: user canceled setup.')
1260
1261=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
1262--- ubuntuone/controlpanel/tests/__init__.py 2011-11-21 13:32:44 +0000
1263+++ ubuntuone/controlpanel/tests/__init__.py 2012-03-19 21:32:18 +0000
1264@@ -334,6 +334,15 @@
1265 ]
1266
1267
1268+class CustomError(Exception):
1269+ """Custom error for tests."""
1270+
1271+
1272+def helper_fail(*a, **kw):
1273+ """Helper to raise an exception, usually used when monkey-patching."""
1274+ raise CustomError((a, kw))
1275+
1276+
1277 class TestCase(BaseTestCase):
1278 """Basics for testing."""
1279
1280
1281=== modified file 'ubuntuone/controlpanel/tests/test_login_client.py'
1282--- ubuntuone/controlpanel/tests/test_login_client.py 2011-10-24 21:48:27 +0000
1283+++ ubuntuone/controlpanel/tests/test_login_client.py 2012-03-19 21:32:18 +0000
1284@@ -21,11 +21,7 @@
1285 from twisted.internet import defer
1286
1287 from ubuntuone.controlpanel import login_client
1288-from ubuntuone.controlpanel.tests import TestCase, TOKEN
1289-
1290-
1291-class CustomError(Exception):
1292- """Custom error for tests."""
1293+from ubuntuone.controlpanel.tests import CustomError, TestCase, TOKEN
1294
1295
1296 class FakedCredentialsManagementTool(object):
1297
1298=== modified file 'ubuntuone/controlpanel/tests/test_sd_client.py'
1299--- ubuntuone/controlpanel/tests/test_sd_client.py 2012-01-26 13:16:17 +0000
1300+++ ubuntuone/controlpanel/tests/test_sd_client.py 2012-03-19 21:32:18 +0000
1301@@ -32,7 +32,7 @@
1302 from ubuntuone.syncdaemon.interaction_interfaces import bool_str
1303
1304 from ubuntuone.controlpanel import sd_client
1305-from ubuntuone.controlpanel.tests import TestCase
1306+from ubuntuone.controlpanel.tests import CustomError, TestCase
1307
1308 # Instance of 'SyncDaemonTool' has no 'foo' member
1309 # pylint: disable=E1101
1310@@ -41,10 +41,6 @@
1311 SAMPLE_LIMITS = {'upload': 999, 'download': 838}
1312
1313
1314-class CustomError(Exception):
1315- """Custom error for tests."""
1316-
1317-
1318 class FakedSyncDaemonTool(object):
1319 """Fake the SyncDaemonTool."""
1320

Subscribers

People subscribed via source and target branches