Merge lp:~dobey/ubuntuone-control-panel/update-4-0 into lp:ubuntuone-control-panel/stable-4-0

Proposed by dobey
Status: Merged
Approved by: dobey
Approved revision: no longer in the source branch.
Merged at revision: 335
Proposed branch: lp:~dobey/ubuntuone-control-panel/update-4-0
Merge into: lp:ubuntuone-control-panel/stable-4-0
Diff against target: 2345 lines (+1516/-167)
28 files modified
data/qt/controlpanel.ui (+13/-2)
data/qt/share_links.ui (+133/-0)
docs/ubuntuone-control-panel-qt.1 (+1/-1)
run-tests (+4/-8)
run-tests.bat (+1/-1)
ubuntuone/controlpanel/backend.py (+14/-0)
ubuntuone/controlpanel/gui/__init__.py (+21/-0)
ubuntuone/controlpanel/gui/qt/__init__.py (+78/-0)
ubuntuone/controlpanel/gui/qt/controlpanel.py (+4/-0)
ubuntuone/controlpanel/gui/qt/filesyncstatus.py (+4/-77)
ubuntuone/controlpanel/gui/qt/folders.py (+14/-4)
ubuntuone/controlpanel/gui/qt/gui.py (+1/-1)
ubuntuone/controlpanel/gui/qt/main/__init__.py (+16/-2)
ubuntuone/controlpanel/gui/qt/main/tests/test_main.py (+35/-0)
ubuntuone/controlpanel/gui/qt/share_links.py (+42/-0)
ubuntuone/controlpanel/gui/qt/systray.py (+316/-23)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+16/-1)
ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py (+30/-29)
ubuntuone/controlpanel/gui/qt/tests/test_folders.py (+44/-0)
ubuntuone/controlpanel/gui/qt/tests/test_gui.py (+5/-0)
ubuntuone/controlpanel/gui/qt/tests/test_share_links.py (+37/-0)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+481/-18)
ubuntuone/controlpanel/sd_client/__init__.py (+4/-0)
ubuntuone/controlpanel/tests/test_backend.py (+12/-0)
ubuntuone/controlpanel/utils/__init__.py (+10/-0)
ubuntuone/controlpanel/utils/darwin.py (+91/-0)
ubuntuone/controlpanel/utils/tests/test_darwin.py (+78/-0)
ubuntuone/controlpanel/utils/tests/test_utils.py (+11/-0)
To merge this branch: bzr merge lp:~dobey/ubuntuone-control-panel/update-4-0
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) trivial Approve
Review via email: mp+120874@code.launchpad.net

Commit message

[Diego Sarmentero]

    - Adding Share Links Tab UI (LP: #1039142).
    - Show the not accepted shares (LP: #1034542).
    - Show current and recent transfers on the transfers menu (LP: #1034542).
    - Adding open (folder, program, url) actions to the menu (LP: #1034542)
    - Adding status actions to the system tray menu (LP: #1034542).

[Mike McCracken]

    - Remove use of env vars for u1lint and u1trial from old buildout. (LP: #1037904)
    - Add darwin-only function to perform first-run "install" steps (LP: #1024623)

[Roberto Alsina]

     - Give an error if the user subscribes a UDF and the local path is not a folder (LP:1033488)
     - Prevented loading of the scrollbar overlay (LP:1007421).

To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks just like trunk. +1.

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

The attempt to merge lp:~dobey/ubuntuone-control-panel/update-4-0 into lp:ubuntuone-control-panel/stable-4-0 failed. Below is the output from the failed tests.

*** Running DBus test suite ***
ubuntuone.controlpanel.dbustests.test_dbus_service
  BaseTestCase
    runTest ... [OK]
  DBusServiceMainTestCase
    test_dbus_service_cant_register ... Control panel backend already running.
                                   [OK]
    test_dbus_service_main ... [OK]
  DBusServiceTestCase
    test_cant_register_twice ... [SKIPPED]
    test_dbus_busname_created ... [OK]
    test_error_handler_default ... [OK]
    test_error_handler_with_exception ... [OK]
    test_error_handler_with_failure ... [OK]
    test_error_handler_with_non_string_dict ... [OK]
    test_error_handler_with_string_dict ... [OK]
    test_register_service ... [OK]
  FileSyncTestCase
    test_file_sync_status_changed ... [OK]
    test_file_sync_status_disabled ... [OK]
    test_file_sync_status_disconnected ... [OK]
    test_file_sync_status_error ... [OK]
    test_file_sync_status_idle ... [OK]
    test_file_sync_status_starting ... [OK]
    test_file_sync_status_stopped ... [OK]
    test_file_sync_status_syncing ... [OK]
    test_file_sync_status_unknown ... [OK]
    test_status_changed_handler ... [OK]
    test_status_changed_handler_after_status_requested ... [OK]
    test_status_changed_handler_after_status_requested_twice ... [OK]
  OperationsAuthErrorTestCase
    test_account_info_returned ... [OK]
    test_change_device_settings ... [OK]
    test_change_replication_settings ... [OK]
    test_change_volume_settings ... [OK]
    test_connect_files ... [OK]
    test_devices_info_returned ... [OK]
    test_disable_files ... [OK]
    test_disconnect_files ... [OK]
    test_enable_files ... [OK]
    test_remove_device ... [OK]
    test_replications_info ... [OK]
    test_restart_files ... [OK]
    t...

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

The attempt to merge lp:~dobey/ubuntuone-control-panel/update-4-0 into lp:ubuntuone-control-panel/stable-4-0 failed. Below is the output from the failed tests.

*** Running DBus test suite ***
ubuntuone.controlpanel.dbustests.test_dbus_service
  BaseTestCase
    runTest ... [OK]
  DBusServiceMainTestCase
    test_dbus_service_cant_register ... Control panel backend already running.
                                   [OK]
    test_dbus_service_main ... [OK]
  DBusServiceTestCase
    test_cant_register_twice ... [SKIPPED]
    test_dbus_busname_created ... [OK]
    test_error_handler_default ... [OK]
    test_error_handler_with_exception ... [OK]
    test_error_handler_with_failure ... [OK]
    test_error_handler_with_non_string_dict ... [OK]
    test_error_handler_with_string_dict ... [OK]
    test_register_service ... [OK]
  FileSyncTestCase
    test_file_sync_status_changed ... [OK]
    test_file_sync_status_disabled ... [OK]
    test_file_sync_status_disconnected ... [OK]
    test_file_sync_status_error ... [OK]
    test_file_sync_status_idle ... [OK]
    test_file_sync_status_starting ... [OK]
    test_file_sync_status_stopped ... [OK]
    test_file_sync_status_syncing ... [OK]
    test_file_sync_status_unknown ... [OK]
    test_status_changed_handler ... [OK]
    test_status_changed_handler_after_status_requested ... [OK]
    test_status_changed_handler_after_status_requested_twice ... [OK]
  OperationsAuthErrorTestCase
    test_account_info_returned ... [OK]
    test_change_device_settings ... [OK]
    test_change_replication_settings ... [OK]
    test_change_volume_settings ... [OK]
    test_connect_files ... [OK]
    test_devices_info_returned ... [OK]
    test_disable_files ... [OK]
    test_disconnect_files ... [OK]
    test_enable_files ... [OK]
    test_remove_device ... [OK]
    test_replications_info ... [OK]
    test_restart_files ... [OK]
    t...

335. By Roberto Alsina

[Diego Sarmentero]

    - Adding Share Links Tab UI (LP: #1039142).
    - Show the not accepted shares (LP: #1034542).
    - Show current and recent transfers on the transfers menu (LP: #1034542).
    - Adding open (folder, program, url) actions to the menu (LP: #1034542)
    - Adding status actions to the system tray menu (LP: #1034542).

[Mike McCracken]

    - Remove use of env vars for u1lint and u1trial from old buildout. (LP: #1037904)
    - Add darwin-only function to perform first-run "install" steps (LP: #1024623)

[Roberto Alsina]

     - Give an error if the user subscribes a UDF and the local path is not a folder (LP:1033488)
     - Prevented loading of the scrollbar overlay (LP:1007421).

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-20 13:05:20 +0000
3+++ data/qt/controlpanel.ui 2012-08-22 21:22:18 +0000
4@@ -26,7 +26,7 @@
5 <item>
6 <widget class="QStackedWidget" name="switcher">
7 <property name="currentIndex">
8- <number>1</number>
9+ <number>0</number>
10 </property>
11 <widget class="QWidget" name="management">
12 <property name="sizePolicy">
13@@ -229,13 +229,18 @@
14 </sizepolicy>
15 </property>
16 <property name="currentIndex">
17- <number>0</number>
18+ <number>1</number>
19 </property>
20 <widget class="FoldersPanel" name="folders_tab">
21 <attribute name="title">
22 <string notr="true">Folders</string>
23 </attribute>
24 </widget>
25+ <widget class="ShareLinksPanel" name="share_links_tab">
26+ <attribute name="title">
27+ <string>Share Links</string>
28+ </attribute>
29+ </widget>
30 <widget class="DevicesPanel" name="devices_tab">
31 <attribute name="title">
32 <string notr="true">Devices</string>
33@@ -416,6 +421,12 @@
34 <header>ubuntuone.controlpanel.gui.qt.wizard</header>
35 <container>1</container>
36 </customwidget>
37+ <customwidget>
38+ <class>ShareLinksPanel</class>
39+ <extends>QWidget</extends>
40+ <header>ubuntuone.controlpanel.gui.qt.share_links</header>
41+ <container>1</container>
42+ </customwidget>
43 </customwidgets>
44 <tabstops>
45 <tabstop>help_button</tabstop>
46
47=== added file 'data/qt/share_links.ui'
48--- data/qt/share_links.ui 1970-01-01 00:00:00 +0000
49+++ data/qt/share_links.ui 2012-08-22 21:22:18 +0000
50@@ -0,0 +1,133 @@
51+<?xml version="1.0" encoding="UTF-8"?>
52+<ui version="4.0">
53+ <class>Form</class>
54+ <widget class="QWidget" name="Form">
55+ <property name="geometry">
56+ <rect>
57+ <x>0</x>
58+ <y>0</y>
59+ <width>532</width>
60+ <height>335</height>
61+ </rect>
62+ </property>
63+ <property name="windowTitle">
64+ <string>Form</string>
65+ </property>
66+ <layout class="QVBoxLayout" name="verticalLayout">
67+ <property name="spacing">
68+ <number>15</number>
69+ </property>
70+ <property name="leftMargin">
71+ <number>0</number>
72+ </property>
73+ <property name="topMargin">
74+ <number>10</number>
75+ </property>
76+ <property name="rightMargin">
77+ <number>0</number>
78+ </property>
79+ <property name="bottomMargin">
80+ <number>0</number>
81+ </property>
82+ <item>
83+ <layout class="QVBoxLayout" name="verticalLayout_2">
84+ <property name="spacing">
85+ <number>0</number>
86+ </property>
87+ <property name="sizeConstraint">
88+ <enum>QLayout::SetDefaultConstraint</enum>
89+ </property>
90+ <property name="leftMargin">
91+ <number>10</number>
92+ </property>
93+ <item>
94+ <widget class="QLabel" name="search_files_lbl">
95+ <property name="sizePolicy">
96+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
97+ <horstretch>0</horstretch>
98+ <verstretch>0</verstretch>
99+ </sizepolicy>
100+ </property>
101+ <property name="font">
102+ <font>
103+ <pointsize>10</pointsize>
104+ <weight>75</weight>
105+ <bold>true</bold>
106+ </font>
107+ </property>
108+ <property name="text">
109+ <string>Search files</string>
110+ </property>
111+ </widget>
112+ </item>
113+ <item>
114+ <widget class="QLineEdit" name="lineEdit">
115+ <property name="sizePolicy">
116+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
117+ <horstretch>0</horstretch>
118+ <verstretch>0</verstretch>
119+ </sizepolicy>
120+ </property>
121+ <property name="maximumSize">
122+ <size>
123+ <width>400</width>
124+ <height>16777215</height>
125+ </size>
126+ </property>
127+ <property name="frame">
128+ <bool>true</bool>
129+ </property>
130+ </widget>
131+ </item>
132+ </layout>
133+ </item>
134+ <item>
135+ <widget class="QStackedWidget" name="stackedWidget">
136+ <widget class="QWidget" name="page">
137+ <layout class="QVBoxLayout" name="verticalLayout_4">
138+ <property name="spacing">
139+ <number>0</number>
140+ </property>
141+ <property name="margin">
142+ <number>0</number>
143+ </property>
144+ <item>
145+ <widget class="QGroupBox" name="shared_group">
146+ <property name="title">
147+ <string> Shared files</string>
148+ </property>
149+ <property name="flat">
150+ <bool>false</bool>
151+ </property>
152+ <layout class="QVBoxLayout" name="verticalLayout_3">
153+ <property name="spacing">
154+ <number>0</number>
155+ </property>
156+ <property name="margin">
157+ <number>0</number>
158+ </property>
159+ <item>
160+ <widget class="QTreeWidget" name="treeWidget">
161+ <attribute name="headerVisible">
162+ <bool>false</bool>
163+ </attribute>
164+ <column>
165+ <property name="text">
166+ <string notr="true">1</string>
167+ </property>
168+ </column>
169+ </widget>
170+ </item>
171+ </layout>
172+ </widget>
173+ </item>
174+ </layout>
175+ </widget>
176+ <widget class="QWidget" name="page_2"/>
177+ </widget>
178+ </item>
179+ </layout>
180+ </widget>
181+ <resources/>
182+ <connections/>
183+</ui>
184
185=== modified file 'docs/ubuntuone-control-panel-qt.1'
186--- docs/ubuntuone-control-panel-qt.1 2012-03-08 16:40:50 +0000
187+++ docs/ubuntuone-control-panel-qt.1 2012-08-22 21:22:18 +0000
188@@ -12,7 +12,7 @@
189 .TP
190 \fB\-\-switch\-to\fR PANEL_NAME
191 Start Ubuntu One in the PANEL_NAME tab. Possible
192-values are: folders, devices, settings, account
193+values are: folders, share_links, devices, settings, account
194 .TP
195 \fB\-\-minimized\fR
196 Start Ubuntu One only in the notification area, with
197
198=== modified file 'run-tests'
199--- run-tests 2012-06-07 23:00:22 +0000
200+++ run-tests 2012-08-22 21:22:18 +0000
201@@ -32,7 +32,7 @@
202 fi
203
204 style_check() {
205- $u1lint --ignore ubuntuone/controlpanel/gui/qt/ui
206+ u1lint --ignore ubuntuone/controlpanel/gui/qt/ui
207 if [ -x `which pep8` ]; then
208 pep8 --exclude '.svn,CVS,.bzr,.hg,.git,*_ui.py,*_rc.py' --repeat . bin/*
209 else
210@@ -52,8 +52,6 @@
211 SYSNAME=`uname -s`
212
213 if [ "$SYSNAME" == "Darwin" ]; then
214- u1trial="python $u1trial"
215- u1lint="python $u1lint"
216 IGNORE_FILES="$WINDOWS_TESTS, $LINUX_TESTS"
217 IGNORE_DBUS="-p $DBUS_TESTS_PATH"
218 echo "*** Skipping DBus test suite ***"
219@@ -64,21 +62,19 @@
220 if [ "x$XVFB_CMDLINE" == "x" ]; then
221 echo "WARNING: Could not find xvfb-run, prepare for visual spam."
222 fi
223- u1trial=u1trial
224- u1lint=u1lint
225 IGNORE_FILES="$WINDOWS_TESTS, $DARWIN_TESTS"
226 IGNORE_DBUS=""
227 echo "*** Running DBus test suite ***"
228- $u1trial --reactor=glib "$DBUS_TESTS_PATH"
229+ u1trial --reactor=glib "$DBUS_TESTS_PATH"
230 REACTOR=gi
231 fi
232
233 echo "*** Running test suite for ""$MODULE"" ***"
234-$u1trial --reactor=$REACTOR -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH" -i "$IGNORE_FILES" "$MODULE"
235+u1trial --reactor=$REACTOR -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH" -i "$IGNORE_FILES" "$MODULE"
236
237 echo "*** Running QT test suite for ""$MODULE"" ***"
238 ./setup.py build
239-$XVFB_CMDLINE $u1trial $IGNORE_DBUS -i "$IGNORE_FILES" --reactor=qt4 --gui "$MODULE"
240+$XVFB_CMDLINE u1trial $IGNORE_DBUS -i "$IGNORE_FILES" --reactor=qt4 --gui "$MODULE"
241 rm -rf _trial_temp
242 rm -rf build
243
244
245=== modified file 'run-tests.bat'
246--- run-tests.bat 2012-04-20 14:45:56 +0000
247+++ run-tests.bat 2012-08-22 21:22:18 +0000
248@@ -19,7 +19,7 @@
249 SET MODULE="ubuntuone"
250 SET PYTHONEXEPATH=""
251 SET IGNORE_PATHS="ubuntuone\controlpanel\dbustests"
252-SET IGNORE_MODULES="test_linux.py, test_libsoup.py"
253+SET IGNORE_MODULES="test_linux.py, test_libsoup.py, test_darwin.py"
254 SET PYTHONPATH=%PYTHONPATH%;..\ubuntu-sso-client;..\ubuntuone-client;.
255
256 ECHO Checking for Python on the path
257
258=== modified file 'ubuntuone/controlpanel/backend.py'
259--- ubuntuone/controlpanel/backend.py 2012-06-22 14:42:29 +0000
260+++ ubuntuone/controlpanel/backend.py 2012-08-22 21:22:18 +0000
261@@ -821,6 +821,20 @@
262 yield self.change_file_sync_settings(self.DEFAULT_FILE_SYNC_SETTINGS)
263
264 @log_call(logger.info)
265+ @inlineCallbacks
266+ def sync_menu(self):
267+ """Obtain the data to create the sync menu."""
268+ result = yield self.sd_client.sync_menu()
269+ returnValue(result)
270+
271+ @log_call(logger.info)
272+ @inlineCallbacks
273+ def get_shares(self):
274+ """Obtain the data to create the sync menu."""
275+ result = yield self.sd_client.get_shares()
276+ returnValue(result)
277+
278+ @log_call(logger.info)
279 def shutdown(self):
280 """Stop this service."""
281 # do any other needed cleanup
282
283=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
284--- ubuntuone/controlpanel/gui/__init__.py 2012-05-29 14:36:17 +0000
285+++ ubuntuone/controlpanel/gui/__init__.py 2012-08-22 21:22:18 +0000
286@@ -42,6 +42,10 @@
287 SHARES_MIN_SIZE_FULL = 1048576
288 SUCCESS_COLOR = u'green'
289
290+# Sync Menu:
291+RECENTTRANSFERS = 'recent-transfers'
292+UPLOADING = 'uploading'
293+
294 ERROR_ICON = u'✘'
295 SYNCING_ICON = u'⇅'
296 IDLE_ICON = u'✔'
297@@ -65,7 +69,9 @@
298
299 FILE_URI_PREFIX = u'file://'
300
301+ACCEPT_SHARES = 'https://one.ubuntu.com/files/shareoffer/%s/'
302 CONTACTS_LINK = UBUNTUONE_LINK
303+DASHBOARD = UBUNTUONE_LINK + u'dashboard/'
304 EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + u'account/'
305 EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + u'machines/'
306 EDIT_PROFILE_LINK = u'https://login.ubuntu.com/'
307@@ -140,6 +146,9 @@
308 'with your local folder "%(folder_path)s" when '
309 'subscribing.\nDo you want to subscribe to this '
310 'cloud folder?')
311+FOLDER_EXISTS_AS_FILE = _('Unable to subscribe because '
312+ '"%(folder_path)s" already exists on your '
313+ 'device and is not a folder.')
314 FOLDERS_MANAGE_LABEL = _('Go to the web for public and private sharing '
315 'options')
316 FOLDERS_TITLE = _('Select which folders from your cloud you want to sync with '
317@@ -173,6 +182,9 @@
318 GET_HELP_ONLINE = _('Get help online')
319 GET_MORE_STORAGE = _('Get more storage')
320 GREETING = _('Hi %(user_display_name)s')
321+GO_TO_WEB = _('Go to the Website')
322+IN_PROGRESS = _('In Progress')
323+IN_PROGRESS_FILE = u'%s – %d%%'
324 INSTALL = _('Install')
325 INSTALL_PACKAGE = _('You need to install the package <i>%(package_name)s'
326 '</i> in order to enable more sync services.')
327@@ -202,6 +214,7 @@
328 MAIN_DEVICES_TAB = _('Devices')
329 MAIN_FOLDERS_TAB = _('Folders')
330 MAIN_PREFERENCES_TAB = _('Settings')
331+MAIN_SHARE_LINKS_TAB = _('Share links')
332 MAIN_WINDOW_TITLE = _('%(app_name)s Control Panel')
333 MUSIC_DISPLAY_NAME = _('Purchased Music')
334 MUSIC_REAL_PATH = u'.ubuntuone/Purchased from Ubuntu One'
335@@ -209,15 +222,20 @@
336 NAME_NOT_SET = _('[unknown user name]')
337 NETWORK_OFFLINE = _('An internet connection is required to join or sign '
338 'in to %(app_name)s.')
339+NEW_SHARE_BY = _('New Share by %s')
340 NO_DEVICES = _('No devices to show.')
341 NO_FOLDERS = _('No folders to show.')
342 NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
343+OPEN_UBUNTU_ONE = _('Open Ubuntu One')
344+OPEN_UBUNTU_ONE_FOLDER = _('Open the Ubuntu One Folder')
345 PERCENTAGE_LABEL = _('%(percentage)s used')
346 PLEASE_WAIT = _('Please wait')
347 PROFILE_LABEL = _('Personal details')
348 QUOTA_LABEL = _('Using %(used)s of %(total)s (%(percentage).0f%%)')
349 REMOVE_BUTTON = _('Remove')
350+RECENT_TRANSFERS = _('Recent Transfers')
351 RESTORE_LABEL = _('Restore')
352+SEARCH_FILES = _('Search files')
353 SELECT_FOLDERS = _('Select sync folders')
354 SERVICES_BUTTON_TOOLTIP = _('Manage the sync services')
355 SERVICES_TITLE = _('Enable the sync services for this computer.')
356@@ -236,12 +254,15 @@
357 'to this computer')
358 SETTINGS_SYNC_ALL_SHARES = _('Automatically sync all folders shared with me '
359 'to this computer')
360+SHARE_A_FILE = _('Share a File')
361+SHARED_FILES = _('Shared files')
362 SHARES_BUTTON_TOOLTIP = _('Manage the shares offered to others')
363 SHARES_TITLE = _('Manage permissions for shares made to other users.')
364 SIGN_IN = _('Sign in')
365 SUCCESS_INSTALL = _('<i>%(package_name)s</i> was successfully installed')
366 SYNC_STREAM_SHARE = _('Sync, stream, share')
367 TALK_TO_US = _('Talk to us')
368+TRANSFERS = _('Current and Recent Transfers')
369 VALUE_ERROR = _('Value could not be retrieved.')
370 UNKNOWN_ERROR = _('Unknown error')
371 USAGE_LABEL = _('%(used)s of %(total)s')
372
373=== modified file 'ubuntuone/controlpanel/gui/qt/__init__.py'
374--- ubuntuone/controlpanel/gui/qt/__init__.py 2012-04-17 18:08:45 +0000
375+++ ubuntuone/controlpanel/gui/qt/__init__.py 2012-08-22 21:22:18 +0000
376@@ -24,13 +24,91 @@
377 from PyQt4 import QtGui, QtCore
378 from twisted.internet import defer
379
380+from ubuntuone.controlpanel import backend
381 from ubuntuone.controlpanel.gui import (
382+ ERROR_COLOR,
383+ FILE_SYNC_CONNECT,
384+ FILE_SYNC_CONNECT_TOOLTIP,
385+ FILE_SYNC_DISABLED,
386+ FILE_SYNC_DISCONNECT,
387+ FILE_SYNC_DISCONNECT_TOOLTIP,
388+ FILE_SYNC_DISCONNECTED,
389+ FILE_SYNC_ENABLE,
390+ FILE_SYNC_ENABLE_TOOLTIP,
391+ FILE_SYNC_ERROR,
392+ FILE_SYNC_IDLE,
393+ FILE_SYNC_RESTART,
394+ FILE_SYNC_RESTART_TOOLTIP,
395+ FILE_SYNC_START,
396+ FILE_SYNC_START_TOOLTIP,
397+ FILE_SYNC_STARTING,
398+ FILE_SYNC_STOP,
399+ FILE_SYNC_STOP_TOOLTIP,
400+ FILE_SYNC_STOPPED,
401+ FILE_SYNC_SYNCING,
402 FILE_URI_PREFIX,
403 GENERAL_ERROR_TITLE,
404 GENERAL_ERROR_MSG,
405 )
406
407
408+WARNING_MARKUP = '<font color="%s"><b>%%s</b></font>' % ERROR_COLOR
409+DISCONNECTED_ICON = 'sync_status_disconnected'
410+ERROR_ICON = 'sync_status_alert'
411+IDLE_ICON = 'sync_status_sync_done'
412+SYNCING_ICON = 'sync_status_syncing'
413+
414+
415+FILE_SYNC_STATUS = {
416+ backend.FILE_SYNC_DISABLED:
417+ {'msg': FILE_SYNC_DISABLED, 'action': FILE_SYNC_ENABLE,
418+ 'backend_method': 'enable_files',
419+ 'icon': ERROR_ICON,
420+ 'tooltip': FILE_SYNC_ENABLE_TOOLTIP},
421+ backend.FILE_SYNC_DISCONNECTED:
422+ {'msg': FILE_SYNC_DISCONNECTED, 'action': FILE_SYNC_CONNECT,
423+ 'backend_method': 'connect_files',
424+ 'icon': DISCONNECTED_ICON,
425+ 'tooltip': FILE_SYNC_CONNECT_TOOLTIP},
426+ backend.FILE_SYNC_ERROR:
427+ {'msg': WARNING_MARKUP % FILE_SYNC_ERROR, 'action': FILE_SYNC_RESTART,
428+ 'backend_method': 'restart_files',
429+ 'icon': ERROR_ICON,
430+ 'tooltip': FILE_SYNC_RESTART_TOOLTIP},
431+ backend.FILE_SYNC_IDLE:
432+ {'msg': FILE_SYNC_IDLE, 'action': FILE_SYNC_DISCONNECT,
433+ 'backend_method': 'disconnect_files',
434+ 'icon': IDLE_ICON,
435+ 'tooltip': FILE_SYNC_DISCONNECT_TOOLTIP},
436+ backend.FILE_SYNC_STARTING:
437+ {'msg': FILE_SYNC_STARTING, 'action': FILE_SYNC_STOP,
438+ 'backend_method': 'stop_files',
439+ 'icon': SYNCING_ICON,
440+ 'tooltip': FILE_SYNC_STOP_TOOLTIP},
441+ backend.FILE_SYNC_STOPPED:
442+ {'msg': FILE_SYNC_STOPPED, 'action': FILE_SYNC_START,
443+ 'backend_method': 'start_files',
444+ 'icon': ERROR_ICON,
445+ 'tooltip': FILE_SYNC_START_TOOLTIP},
446+ backend.FILE_SYNC_SYNCING:
447+ {'msg': FILE_SYNC_SYNCING, 'action': FILE_SYNC_DISCONNECT,
448+ 'backend_method': 'disconnect_files',
449+ 'icon': SYNCING_ICON,
450+ 'tooltip': FILE_SYNC_DISCONNECT_TOOLTIP},
451+ backend.FILE_SYNC_UNKNOWN:
452+ {'msg': WARNING_MARKUP % FILE_SYNC_ERROR, 'action': FILE_SYNC_RESTART,
453+ 'backend_method': 'restart_files',
454+ 'icon': ERROR_ICON,
455+ 'tooltip': FILE_SYNC_RESTART_TOOLTIP},
456+}
457+
458+
459+def icon_name_from_status(status_key):
460+ """Get the icon name for the status."""
461+ icon_name = FILE_SYNC_STATUS[status_key]['icon']
462+ return icon_name
463+
464+
465 def force_wordwrap(widget, size, text):
466 """Set the text to the widget after word wrapping it."""
467 font_metrics = QtGui.QFontMetrics(widget.font())
468
469=== modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py'
470--- ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-04-11 11:47:19 +0000
471+++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2012-08-22 21:22:18 +0000
472@@ -35,6 +35,7 @@
473 MAIN_DEVICES_TAB,
474 MAIN_FOLDERS_TAB,
475 MAIN_PREFERENCES_TAB,
476+ MAIN_SHARE_LINKS_TAB,
477 PERCENTAGE_LABEL,
478 show_quota_warning,
479 TALK_TO_US,
480@@ -76,6 +77,9 @@
481 self.ui.tab_widget.setTabText(
482 self.ui.tab_widget.indexOf(self.ui.folders_tab), MAIN_FOLDERS_TAB)
483 self.ui.tab_widget.setTabText(
484+ self.ui.tab_widget.indexOf(self.ui.share_links_tab),
485+ MAIN_SHARE_LINKS_TAB)
486+ self.ui.tab_widget.setTabText(
487 self.ui.tab_widget.indexOf(self.ui.devices_tab), MAIN_DEVICES_TAB)
488 self.ui.tab_widget.setTabText(
489 self.ui.tab_widget.indexOf(self.ui.preferences_tab),
490
491=== modified file 'ubuntuone/controlpanel/gui/qt/filesyncstatus.py'
492--- ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2012-05-10 21:01:30 +0000
493+++ ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2012-08-22 21:22:18 +0000
494@@ -22,92 +22,19 @@
495 from ubuntuone.controlpanel import backend, cache
496 from ubuntuone.controlpanel.logger import setup_logging, log_call
497 from ubuntuone.controlpanel.gui import (
498- ERROR_COLOR,
499- FILE_SYNC_CONNECT,
500- FILE_SYNC_CONNECT_TOOLTIP,
501- FILE_SYNC_DISABLED,
502- FILE_SYNC_DISCONNECT,
503- FILE_SYNC_DISCONNECT_TOOLTIP,
504- FILE_SYNC_DISCONNECTED,
505- FILE_SYNC_ENABLE,
506- FILE_SYNC_ENABLE_TOOLTIP,
507- FILE_SYNC_ERROR,
508- FILE_SYNC_IDLE,
509- FILE_SYNC_RESTART,
510- FILE_SYNC_RESTART_TOOLTIP,
511- FILE_SYNC_START,
512- FILE_SYNC_START_TOOLTIP,
513- FILE_SYNC_STARTING,
514- FILE_SYNC_STOP,
515- FILE_SYNC_STOP_TOOLTIP,
516- FILE_SYNC_STOPPED,
517- FILE_SYNC_SYNCING,
518 LOADING,
519 PLEASE_WAIT,
520 )
521-from ubuntuone.controlpanel.gui.qt import pixmap_from_name
522+from ubuntuone.controlpanel.gui.qt import (
523+ FILE_SYNC_STATUS,
524+ pixmap_from_name,
525+)
526 from ubuntuone.controlpanel.gui.qt.ui import filesyncstatus_ui
527
528
529 logger = setup_logging('qt.filesyncstatus')
530
531
532-WARNING_MARKUP = '<font color="%s"><b>%%s</b></font>' % ERROR_COLOR
533-DISCONNECTED_ICON = 'sync_status_disconnected'
534-ERROR_ICON = 'sync_status_alert'
535-IDLE_ICON = 'sync_status_sync_done'
536-SYNCING_ICON = 'sync_status_syncing'
537-
538-FILE_SYNC_STATUS = {
539- backend.FILE_SYNC_DISABLED:
540- {'msg': FILE_SYNC_DISABLED, 'action': FILE_SYNC_ENABLE,
541- 'backend_method': 'enable_files',
542- 'icon': ERROR_ICON,
543- 'tooltip': FILE_SYNC_ENABLE_TOOLTIP},
544- backend.FILE_SYNC_DISCONNECTED:
545- {'msg': FILE_SYNC_DISCONNECTED, 'action': FILE_SYNC_CONNECT,
546- 'backend_method': 'connect_files',
547- 'icon': DISCONNECTED_ICON,
548- 'tooltip': FILE_SYNC_CONNECT_TOOLTIP},
549- backend.FILE_SYNC_ERROR:
550- {'msg': WARNING_MARKUP % FILE_SYNC_ERROR, 'action': FILE_SYNC_RESTART,
551- 'backend_method': 'restart_files',
552- 'icon': ERROR_ICON,
553- 'tooltip': FILE_SYNC_RESTART_TOOLTIP},
554- backend.FILE_SYNC_IDLE:
555- {'msg': FILE_SYNC_IDLE, 'action': FILE_SYNC_DISCONNECT,
556- 'backend_method': 'disconnect_files',
557- 'icon': IDLE_ICON,
558- 'tooltip': FILE_SYNC_DISCONNECT_TOOLTIP},
559- backend.FILE_SYNC_STARTING:
560- {'msg': FILE_SYNC_STARTING, 'action': FILE_SYNC_STOP,
561- 'backend_method': 'stop_files',
562- 'icon': SYNCING_ICON,
563- 'tooltip': FILE_SYNC_STOP_TOOLTIP},
564- backend.FILE_SYNC_STOPPED:
565- {'msg': FILE_SYNC_STOPPED, 'action': FILE_SYNC_START,
566- 'backend_method': 'start_files',
567- 'icon': ERROR_ICON,
568- 'tooltip': FILE_SYNC_START_TOOLTIP},
569- backend.FILE_SYNC_SYNCING:
570- {'msg': FILE_SYNC_SYNCING, 'action': FILE_SYNC_DISCONNECT,
571- 'backend_method': 'disconnect_files',
572- 'icon': SYNCING_ICON,
573- 'tooltip': FILE_SYNC_DISCONNECT_TOOLTIP},
574- backend.FILE_SYNC_UNKNOWN:
575- {'msg': WARNING_MARKUP % FILE_SYNC_ERROR, 'action': FILE_SYNC_RESTART,
576- 'backend_method': 'restart_files',
577- 'icon': ERROR_ICON,
578- 'tooltip': FILE_SYNC_RESTART_TOOLTIP},
579-}
580-
581-
582-def icon_name_from_status(status_key):
583- """Get the icon name for the status."""
584- icon_name = FILE_SYNC_STATUS[status_key]['icon']
585- return icon_name
586-
587-
588 class FileSyncStatus(cache.Cache, QtGui.QWidget):
589 """The FileSyncStatus widget"""
590
591
592=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
593--- ubuntuone/controlpanel/gui/qt/folders.py 2012-06-04 16:25:39 +0000
594+++ ubuntuone/controlpanel/gui/qt/folders.py 2012-08-22 21:22:18 +0000
595@@ -26,6 +26,8 @@
596 from PyQt4 import QtGui, QtCore
597 from twisted.internet import defer
598
599+from ubuntuone.platform import is_link
600+
601 from ubuntuone.controlpanel.utils import default_folders
602 from ubuntuone.controlpanel.logger import setup_logging, log_call
603 from ubuntuone.controlpanel.gui import (
604@@ -38,6 +40,7 @@
605 FOLDERS_COLUMN_NAME,
606 FOLDERS_COLUMN_SYNC_LOCALLY,
607 FOLDERS_CONFIRM_MERGE,
608+ FOLDER_EXISTS_AS_FILE,
609 FOLDERS_MANAGE_LABEL,
610 GET_MORE_STORAGE,
611 humanize,
612@@ -328,16 +331,23 @@
613 os.path.exists(volume_path))
614 response = YES
615 if subscribed and os.path.exists(volume_path):
616- text = FOLDERS_CONFIRM_MERGE % {'folder_path': volume_path}
617- buttons = YES | NO | CANCEL
618- response = QtGui.QMessageBox.warning(self, '', text, buttons, YES)
619+ if os.path.isdir(volume_path) and not is_link(volume_path):
620+ text = FOLDERS_CONFIRM_MERGE % {'folder_path': volume_path}
621+ buttons = YES | NO | CANCEL
622+ response = QtGui.QMessageBox.warning(self, '', text, buttons,
623+ YES)
624+ else:
625+ text = FOLDER_EXISTS_AS_FILE % {'folder_path': volume_path}
626+ buttons = CLOSE
627+ QtGui.QMessageBox.warning(self, '', text, buttons)
628+ response = NO
629
630 self.is_processing = True
631
632 if response == YES:
633 # user accepted, merge the folder content
634 yield self.backend.change_volume_settings(volume_id,
635- {'subscribed': subscribed})
636+ {'subscribed': subscribed})
637 self.load()
638 else:
639 # restore old value
640
641=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
642--- ubuntuone/controlpanel/gui/qt/gui.py 2012-07-05 16:43:00 +0000
643+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-08-22 21:22:18 +0000
644@@ -67,7 +67,7 @@
645
646 def switch_to(self, tabname="folders"):
647 """Switch control panel to the required tab."""
648- tabnames = ["folders", "devices", "settings", "account"]
649+ tabnames = ["folders", "share_links", "devices", "settings", "account"]
650 if tabname in tabnames:
651 self.ui.control_panel.ui.tab_widget.setCurrentIndex(
652 tabnames.index(tabname))
653
654=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
655--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-06-28 04:25:16 +0000
656+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-08-22 21:22:18 +0000
657@@ -17,6 +17,7 @@
658 """Provide the correct ui main module."""
659
660 import argparse
661+import os
662 import sys
663
664 from PyQt4 import QtCore
665@@ -37,6 +38,8 @@
666 source = dbus_main
667 # pylint: enable=C0103
668
669+from ubuntuone.controlpanel.utils import install_config_and_daemons
670+
671
672 def parser_options():
673 """Parse command line parameters."""
674@@ -44,8 +47,8 @@
675 result.add_argument("--switch-to", dest="switch_to",
676 metavar="PANEL_NAME", default="",
677 help="Start Ubuntu One in the "
678- "PANEL_NAME tab. Possible values are: "
679- "folders, devices, settings, account")
680+ "PANEL_NAME tab. Possible values are: "
681+ "folders, share_links, devices, settings, account")
682 result.add_argument("--minimized", dest="minimized", action="store_true",
683 default=False,
684 help="Start Ubuntu One "
685@@ -69,6 +72,15 @@
686
687 def main(args, install_reactor_darwin=False):
688 """Start the Qt mainloop and open the main window."""
689+
690+ # Disable the overlay-scrollbar GTK module that was
691+ # added in Ubuntu 12.10 because it breaks Qt (LP:1007421)
692+ gtk_mod = os.getenv('GTK_MODULES')
693+ if gtk_mod is not None:
694+ gtk_mod = ':'.join([mod for mod in
695+ gtk_mod.split(':') if mod != 'overlay-scrollbar'])
696+ os.environ['GTK_MODULES'] = gtk_mod
697+
698 # The following cannot be imported outside this function
699 # because u1trial already provides a reactor.
700
701@@ -109,6 +121,8 @@
702 data.append(unicode(qss.data()))
703 app.setStyleSheet('\n'.join(data))
704
705+ install_config_and_daemons()
706+
707 # Unused variable 'window', 'icon', pylint: disable=W0612
708 icon, window = start(lambda: source.main_quit(app),
709 minimized=minimized, with_icon=with_icon,
710
711=== modified file 'ubuntuone/controlpanel/gui/qt/main/tests/test_main.py'
712--- ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-06-28 04:14:08 +0000
713+++ ubuntuone/controlpanel/gui/qt/main/tests/test_main.py 2012-08-22 21:22:18 +0000
714@@ -16,6 +16,7 @@
715
716 """Tests for the control panel's Qt main."""
717
718+import os
719 import sys
720
721 from PyQt4 import QtCore
722@@ -146,6 +147,32 @@
723 (['ubuntuone-installer', sys.argv[0]],
724 ('ubuntuone-control-panel',), {}))
725
726+ def _test_gtk_module_cleanup(self, modules, expected):
727+ """Check if the module cleanup works."""
728+ old_modules = os.environ.get('GTK_MODULES', '')
729+
730+ def clean_env():
731+ """Reset the environment variable."""
732+ os.environ['GTK_MODULES'] = old_modules
733+
734+ self.addCleanup(clean_env)
735+ os.environ['GTK_MODULES'] = modules
736+ main.main([sys.argv[0]])
737+ self.assertEqual(os.environ['GTK_MODULES'], expected)
738+
739+ def test_gtk_module_cleanup_1(self):
740+ """Test that we deactivate the overlay scrollbar GTK module."""
741+ self._test_gtk_module_cleanup('aaa:overlay-scrollbar:bbb', 'aaa:bbb')
742+
743+ def test_gtk_module_cleanup_2(self):
744+ """Test that we deactivate the overlay scrollbar GTK module."""
745+ self._test_gtk_module_cleanup('overlay-scrollbar', '')
746+
747+ def test_gtk_module_cleanup_3(self):
748+ """Test that we deactivate the overlay scrollbar GTK module."""
749+ self._test_gtk_module_cleanup('overlay-scrollbar:overlay-scrollbars',
750+ 'overlay-scrollbars')
751+
752 def test_title_not_fail(self):
753 """Ensure -title is removed before it gets to argparse."""
754 main.main([sys.argv[0], "-title"])
755@@ -233,3 +260,11 @@
756 self.patch(sys, 'platform', 'not-darwin')
757 main.main([sys.argv[0]], install_reactor_darwin=False)
758 self.assertEqual(self.qt4reactor_installed, False)
759+
760+ def test_install_config(self):
761+ """Test that install_config_and_daemons is called."""
762+ self.patch(main, 'install_config_and_daemons',
763+ self._set_called)
764+
765+ main.main([sys.argv[0]], install_reactor_darwin=False)
766+ self.assertEqual(self._called, ((), {}))
767
768=== added file 'ubuntuone/controlpanel/gui/qt/share_links.py'
769--- ubuntuone/controlpanel/gui/qt/share_links.py 1970-01-01 00:00:00 +0000
770+++ ubuntuone/controlpanel/gui/qt/share_links.py 2012-08-22 21:22:18 +0000
771@@ -0,0 +1,42 @@
772+# -*- coding: utf-8 *-*
773+
774+# Copyright 2012 Canonical Ltd.
775+#
776+# This program is free software: you can redistribute it and/or modify it
777+# under the terms of the GNU General Public License version 3, as published
778+# by the Free Software Foundation.
779+#
780+# This program is distributed in the hope that it will be useful, but
781+# WITHOUT ANY WARRANTY; without even the implied warranties of
782+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
783+# PURPOSE. See the GNU General Public License for more details.
784+#
785+# You should have received a copy of the GNU General Public License along
786+# with this program. If not, see <http://www.gnu.org/licenses/>.
787+
788+"""The user interface for the control panel for Ubuntu One."""
789+
790+from ubuntuone.controlpanel.logger import setup_logging
791+from ubuntuone.controlpanel.gui import (
792+ SEARCH_FILES,
793+ SHARED_FILES,
794+)
795+
796+from ubuntuone.controlpanel.gui.qt.ui import share_links_ui
797+from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
798+
799+
800+logger = setup_logging('qt.share_links')
801+
802+
803+class ShareLinksPanel(UbuntuOneBin):
804+ """The Share Links Tab Panel widget"""
805+
806+ ui_class = share_links_ui
807+ logger = logger
808+
809+ def _setup(self):
810+ """Do some extra setupping for the UI."""
811+ super(ShareLinksPanel, self)._setup()
812+ self.ui.search_files_lbl.setText(SEARCH_FILES)
813+ self.ui.shared_group.setTitle(SHARED_FILES)
814
815=== modified file 'ubuntuone/controlpanel/gui/qt/systray.py'
816--- ubuntuone/controlpanel/gui/qt/systray.py 2012-05-14 20:16:02 +0000
817+++ ubuntuone/controlpanel/gui/qt/systray.py 2012-08-22 21:22:18 +0000
818@@ -15,17 +15,94 @@
819 # with this program. If not, see <http://www.gnu.org/licenses/>.
820 """System notification area icon."""
821
822-from PyQt4 import QtGui
823+import os
824+import sys
825+from operator import itemgetter
826+
827+from PyQt4 import QtGui, QtCore
828 from twisted.internet.defer import inlineCallbacks
829 from ubuntuone.platform.tools import SyncDaemonTool
830
831+from ubuntuone.controlpanel import backend, cache
832+from ubuntuone.controlpanel.logger import setup_logging
833 from ubuntuone.controlpanel.gui import (
834- RESTORE_LABEL,
835- QUIT_LABEL,
836-)
837-
838-
839-class TrayIcon(QtGui.QSystemTrayIcon):
840+ ACCEPT_SHARES,
841+ DASHBOARD,
842+ GET_HELP_ONLINE,
843+ GET_MORE_STORAGE,
844+ GET_STORAGE_LINK,
845+ GET_SUPPORT_LINK,
846+ GO_TO_WEB,
847+ IN_PROGRESS,
848+ IN_PROGRESS_FILE,
849+ LOADING,
850+ NEW_SHARE_BY,
851+ OPEN_UBUNTU_ONE,
852+ OPEN_UBUNTU_ONE_FOLDER,
853+ PLEASE_WAIT,
854+ RECENT_TRANSFERS,
855+ RECENTTRANSFERS,
856+ TRANSFERS,
857+ UPLOADING,
858+)
859+from ubuntuone.controlpanel.gui.qt import (
860+ FILE_SYNC_STATUS,
861+ icon_from_name,
862+)
863+
864+
865+logger = setup_logging('qt.systray')
866+
867+
868+class ProgressBarAction(QtGui.QWidgetAction):
869+
870+ """Menu action that display a progress bar for the uploads."""
871+
872+ def __init__(self, filename, percentage, parent=None):
873+ super(ProgressBarAction, self).__init__(parent)
874+ self._filename = os.path.basename(filename)
875+ self.widget = QtGui.QWidget(None)
876+ text = IN_PROGRESS_FILE % (self._filename, percentage)
877+ self.setText(text)
878+ self.label = QtGui.QLabel(text)
879+ self.progress = QtGui.QProgressBar()
880+ self.progress.setTextVisible(False)
881+ self.progress.setValue(percentage)
882+ self.progress.setFixedHeight(10)
883+ vbox = QtGui.QVBoxLayout()
884+ vbox.setContentsMargins(10, 5, 10, 5)
885+ vbox.addWidget(self.label)
886+ vbox.addWidget(self.progress)
887+ self.widget.setLayout(vbox)
888+
889+ self.setDefaultWidget(self.widget)
890+
891+ def set_value(self, progress):
892+ """Set the value of the progress bar."""
893+ text = IN_PROGRESS_FILE % (self._filename, progress)
894+ self.setText(text)
895+ self.label.setText(text)
896+ self.setText(text)
897+ self.progress.setValue(progress)
898+
899+
900+class SharesAction(QtGui.QAction):
901+
902+ """New Shares action."""
903+
904+ def __init__(self, text, volume_id, parent=None):
905+ super(SharesAction, self).__init__(text, parent)
906+ self._volume_id = volume_id
907+
908+ self.triggered.connect(self.accept_share)
909+
910+ def accept_share(self):
911+ """Open the accept shares page."""
912+ url = ACCEPT_SHARES % self._volume_id
913+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
914+
915+
916+class TrayIcon(QtGui.QSystemTrayIcon, cache.Cache):
917
918 """System notification icon."""
919
920@@ -34,23 +111,162 @@
921 self.setIcon(QtGui.QIcon(":/icon.png"))
922 self.setVisible(True)
923 self.window = window
924- self.activated.connect(self.on_activated)
925+ self.root_path = ''
926+ self.recent_transfers = {}
927+ self.uploading = {}
928+ self._backend_method = None
929+ self._previous_shares = None
930+ self.close_callback = close_callback
931 self.context_menu = QtGui.QMenu()
932- self.restore = QtGui.QAction(RESTORE_LABEL, self,
933- triggered=self.restore_window)
934- self.quit = QtGui.QAction(QUIT_LABEL, self,
935- triggered=self.stop)
936- self.context_menu.addAction(self.restore)
937- self.context_menu.addSeparator()
938- self.context_menu.addAction(self.quit)
939- self.setContextMenu(self.context_menu)
940-
941- self.close_callback = close_callback
942-
943- def on_activated(self, reason):
944- """The user activated the icon."""
945- if reason == self.Trigger: # Left-click
946- self.restore_window()
947+ self.status = None
948+ self.quit = None
949+ self.get_help_online = None
950+ self.open_u1 = None
951+ self.status_action = None
952+ self.go_to_web = None
953+ self.get_more_storage = None
954+ self.transfers = None
955+ self.open_u1_folder = None
956+
957+ self.load_menu()
958+ # Refresh the Shares every five minutes if needed.
959+ self._timer_id = self.startTimer(300000)
960+
961+ # pylint: disable=C0103
962+
963+ def timerEvent(self, event):
964+ """Update the menu on each iteration."""
965+ self.load_menu()
966+
967+ # pylint: enable=C0103
968+
969+ @inlineCallbacks
970+ def load_menu(self):
971+ """Load the content of the menu."""
972+ shares_info = yield self.backend.get_shares()
973+ shares_info = [share for share in shares_info if not share['accepted']]
974+ if shares_info != self._previous_shares:
975+ # Items
976+ self.context_menu.clear()
977+
978+ self.status = self.context_menu.addAction(LOADING)
979+ self.status.setEnabled(False)
980+ self.status_action = self.context_menu.addAction(PLEASE_WAIT)
981+ self.refresh_status()
982+ self.context_menu.addSeparator()
983+
984+ self.open_u1 = self.context_menu.addAction(OPEN_UBUNTU_ONE)
985+ # TODO: Share a file action when the Shares tab is ready in U1-CP
986+ self.open_u1_folder = self.context_menu.addAction(
987+ OPEN_UBUNTU_ONE_FOLDER)
988+ self.go_to_web = self.context_menu.addAction(GO_TO_WEB)
989+ self.context_menu.addSeparator()
990+
991+ # Shares
992+ self._previous_shares = shares_info
993+ max_shares = 0
994+ for share in self._previous_shares:
995+ if max_shares == 3:
996+ break
997+ max_shares += 1
998+ text = NEW_SHARE_BY % share['other_visible_name']
999+ share_action = SharesAction(text, share['volume_id'],
1000+ self.context_menu)
1001+ self.context_menu.addAction(share_action)
1002+ if self._previous_shares:
1003+ self.context_menu.addSeparator()
1004+
1005+ # Transfers
1006+ self.transfers = TransfersMenu(self)
1007+ self.context_menu.addMenu(self.transfers)
1008+
1009+ self.get_more_storage = self.context_menu.addAction(
1010+ GET_MORE_STORAGE)
1011+ self.get_help_online = self.context_menu.addAction(GET_HELP_ONLINE)
1012+ self.quit = self.context_menu.addAction("Quit")
1013+
1014+ self.setContextMenu(self.context_menu)
1015+
1016+ # Connect Signals
1017+ self.status_action.triggered.connect(self.change_status)
1018+ self.open_u1.triggered.connect(self.restore_window)
1019+ self.open_u1_folder.triggered.connect(self.open_u1_folder_action)
1020+ self.get_more_storage.triggered.connect(
1021+ self.get_more_storage_action)
1022+ self.go_to_web.triggered.connect(self.go_to_web_action)
1023+ self.get_help_online.triggered.connect(self.get_help_action)
1024+ self.quit.triggered.connect(self.stop)
1025+
1026+ self._get_volumes_info()
1027+
1028+ @inlineCallbacks
1029+ def refresh_status(self):
1030+ """Update Ubuntu One status."""
1031+ info = yield self.backend.file_sync_status()
1032+ self._process_status(info)
1033+ self.backend.status_changed_handler = self._process_status
1034+
1035+ def _process_status(self, status):
1036+ """Match status with signals."""
1037+ if status is None:
1038+ return
1039+ try:
1040+ status_key = status[backend.STATUS_KEY]
1041+ data = FILE_SYNC_STATUS[status_key]
1042+ except (KeyError, TypeError):
1043+ logger.exception(
1044+ '_process_status: received unknown status dict %r', status)
1045+ return
1046+
1047+ self.status_action.setEnabled(True)
1048+ icon_name = data.get('icon')
1049+ icon = QtGui.QIcon()
1050+ if icon_name is not None:
1051+ icon = icon_from_name(icon_name)
1052+ text = data.get('msg')
1053+ self.status.setIcon(icon)
1054+ self.status.setText(text)
1055+
1056+ action = data.get('action')
1057+ self._backend_method = getattr(self.backend,
1058+ data['backend_method'], None)
1059+ self.status_action.setText(action)
1060+
1061+ def change_status(self):
1062+ """Change the Syncing status of syncdaemon."""
1063+ if self._backend_method is not None:
1064+ self.status_action.setEnabled(False)
1065+ self._backend_method()
1066+ else:
1067+ logger.error('on_sync_status_button_clicked: backend method is '
1068+ 'None!')
1069+
1070+ @inlineCallbacks
1071+ def _get_volumes_info(self):
1072+ """Get the volumes info."""
1073+ info = yield self.backend.volumes_info()
1074+ self._process_volumes_info(info)
1075+
1076+ def _process_volumes_info(self, info):
1077+ """Process the volumes info."""
1078+ _, _, volumes = info[0]
1079+ self.root_path = u'file://%s' % unicode(volumes[0]['path'])
1080+
1081+ def open_u1_folder_action(self):
1082+ """Open the web to get more storage."""
1083+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.root_path))
1084+
1085+ def get_more_storage_action(self):
1086+ """Open the web to get more storage."""
1087+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(GET_STORAGE_LINK))
1088+
1089+ def go_to_web_action(self):
1090+ """Open the web in the dashboard."""
1091+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(DASHBOARD))
1092+
1093+ def get_help_action(self):
1094+ """Open the web in the dashboard."""
1095+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(GET_SUPPORT_LINK))
1096
1097 def restore_window(self):
1098 """Show the main window."""
1099@@ -81,3 +297,80 @@
1100 # Maybe it was not running?
1101 pass
1102 self.close_callback()
1103+
1104+
1105+class TransfersMenu(QtGui.QMenu):
1106+
1107+ """Menu that shows recent and current transfers."""
1108+
1109+ def __init__(self, parent):
1110+ super(TransfersMenu, self).__init__(TRANSFERS)
1111+ self._parent = parent
1112+ self.uploading = {}
1113+ self.previous_data = {}
1114+
1115+ if sys.platform not in ('win32', 'darwin'):
1116+ self._timer_id = self.startTimer(1000)
1117+
1118+ # pylint: disable=C0103
1119+
1120+ def showEvent(self, event):
1121+ """Executed when the transfers menu is shown."""
1122+ logger.info('This is never executed on Ubuntu')
1123+ super(TransfersMenu, self).showEvent(event)
1124+ self.get_transfers_data()
1125+ self._timer_id = self.startTimer(1000)
1126+
1127+ def hideEvent(self, event):
1128+ """Executed when the transfers menu is hidden."""
1129+ logger.info('This is never executed on Ubuntu')
1130+ super(TransfersMenu, self).hideEvent(event)
1131+ self.killTimer(self._timer_id)
1132+
1133+ def timerEvent(self, event):
1134+ """Update the menu on each iteration."""
1135+ self.get_transfers_data()
1136+
1137+ # pylint: enable=C0103
1138+
1139+ @inlineCallbacks
1140+ def get_transfers_data(self):
1141+ """Get the transfers data to create the submenu."""
1142+ data = yield self._parent.backend.sync_menu()
1143+ self._update_transfers(data)
1144+
1145+ def _update_transfers(self, data):
1146+ """Generate the transfers menu."""
1147+ current_transfers = data[RECENTTRANSFERS]
1148+ current_upload = [
1149+ filename for filename, _, _ in data[UPLOADING]]
1150+ uploading_data = data[UPLOADING]
1151+ uploading_data.sort(key=itemgetter(2))
1152+ uploading_data.reverse()
1153+ if current_transfers != self.previous_data.get('transfers') or \
1154+ current_upload != self.previous_data.get('uploading'):
1155+ self.clear()
1156+ self.previous_data['transfers'] = current_transfers
1157+ self.previous_data['uploading'] = current_upload
1158+ self.uploading.clear()
1159+ recent = self.addAction(RECENT_TRANSFERS)
1160+ recent.setEnabled(False)
1161+ for filename in data[RECENTTRANSFERS]:
1162+ self.addAction(os.path.basename(filename))
1163+ self.addSeparator()
1164+ in_progress = self.addAction(IN_PROGRESS)
1165+ in_progress.setEnabled(False)
1166+ show_max = 0
1167+ for filename, size, written in uploading_data:
1168+ if show_max == 5:
1169+ break
1170+ percentage = written * 100 / size
1171+ self.uploading[filename] = ProgressBarAction(
1172+ filename, percentage)
1173+ self.addAction(self.uploading[filename])
1174+ show_max += 1
1175+ else:
1176+ for filename, size, written in uploading_data:
1177+ percentage = written * 100 / size
1178+ if filename in self.uploading:
1179+ self.uploading[filename].set_value(percentage)
1180
1181=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
1182--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-04-02 11:10:49 +0000
1183+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-08-22 21:22:18 +0000
1184@@ -25,7 +25,11 @@
1185
1186 from ubuntuone.controlpanel import backend, cache
1187 from ubuntuone.controlpanel.tests import TestCase, EXPECTED_ACCOUNT_INFO, TOKEN
1188-from ubuntuone.controlpanel.gui import qt
1189+from ubuntuone.controlpanel.gui import (
1190+ qt,
1191+ RECENTTRANSFERS,
1192+ UPLOADING,
1193+)
1194 from ubuntuone.controlpanel.gui.tests import (
1195 FakedObject,
1196 FAKE_VOLUMES_INFO,
1197@@ -164,6 +168,17 @@
1198 def build_signed_iri(self, iri):
1199 """Fake iri signing."""
1200
1201+ def sync_menu(self):
1202+ """Fake sync_menu."""
1203+ data = {}
1204+ data[RECENTTRANSFERS] = []
1205+ data[UPLOADING] = []
1206+ return data
1207+
1208+ def get_shares(self):
1209+ """Fake get_shares."""
1210+ return []
1211+
1212
1213 class CrashyBackendException(Exception):
1214 """A faked backend crash."""
1215
1216=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py'
1217--- ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-05-10 21:01:30 +0000
1218+++ ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-08-22 21:22:18 +0000
1219@@ -22,6 +22,7 @@
1220 LOADING,
1221 PLEASE_WAIT,
1222 )
1223+from ubuntuone.controlpanel.gui import qt
1224 from ubuntuone.controlpanel.gui.qt import filesyncstatus as gui
1225 from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1226
1227@@ -64,7 +65,7 @@
1228 self.assertEqual(expected_text, actual_text)
1229
1230 actual_icon = self.ui.ui.sync_status_icon.pixmap()
1231- pixmap_name = gui.icon_name_from_status(status_bd)
1232+ pixmap_name = qt.icon_name_from_status(status_bd)
1233 expected_icon = gui.pixmap_from_name(pixmap_name)
1234 self.assertEqualPixmaps(expected_icon, actual_icon)
1235
1236@@ -73,7 +74,7 @@
1237 action)
1238
1239 is_default = self.ui.ui.sync_status_button.isDefault()
1240- expected_default = (action == gui.FILE_SYNC_CONNECT)
1241+ expected_default = (action == qt.FILE_SYNC_CONNECT)
1242 self.assertEqual(expected_default, is_default)
1243
1244 self.ui.ui.sync_status_button.click()
1245@@ -96,17 +97,17 @@
1246 def test_process_info_invalid_status(self):
1247 """File sync status invalid, ignore event."""
1248 status = {backend.STATUS_KEY: backend.FILE_SYNC_STARTING,
1249- backend.MSG_KEY: gui.FILE_SYNC_STARTING,
1250+ backend.MSG_KEY: qt.FILE_SYNC_STARTING,
1251 'icon': backend.FILE_SYNC_STARTING}
1252 self.ui.process_info(status)
1253 self.ui.process_info(3)
1254
1255 actual_icon = self.ui.ui.sync_status_icon.pixmap()
1256- pixmap_name = gui.icon_name_from_status(backend.FILE_SYNC_STARTING)
1257+ pixmap_name = qt.icon_name_from_status(backend.FILE_SYNC_STARTING)
1258 expected_icon = gui.pixmap_from_name(pixmap_name)
1259 self.assertEqualPixmaps(expected_icon, actual_icon)
1260 actual_text = unicode(self.ui.ui.sync_status_label.text())
1261- self.assertEqual(gui.FILE_SYNC_STARTING, actual_text)
1262+ self.assertEqual(qt.FILE_SYNC_STARTING, actual_text)
1263
1264 def test_process_info_changed(self):
1265 """Backend's file sync status changed callback is connected."""
1266@@ -120,70 +121,70 @@
1267 def test_process_info_disabled(self):
1268 """File sync status disabled update the label."""
1269 self.assert_status_correct(status_bd=backend.FILE_SYNC_DISABLED,
1270- status_ui=gui.FILE_SYNC_DISABLED,
1271- action=gui.FILE_SYNC_ENABLE,
1272+ status_ui=qt.FILE_SYNC_DISABLED,
1273+ action=qt.FILE_SYNC_ENABLE,
1274 callback='enable_files',
1275- tooltip=gui.FILE_SYNC_ENABLE_TOOLTIP)
1276+ tooltip=qt.FILE_SYNC_ENABLE_TOOLTIP)
1277
1278 def test_process_info_disconnected(self):
1279 """File sync status disconnected update the label."""
1280 self.assert_status_correct(status_bd=backend.FILE_SYNC_DISCONNECTED,
1281- status_ui=gui.FILE_SYNC_DISCONNECTED,
1282- action=gui.FILE_SYNC_CONNECT,
1283+ status_ui=qt.FILE_SYNC_DISCONNECTED,
1284+ action=qt.FILE_SYNC_CONNECT,
1285 callback='connect_files',
1286- tooltip=gui.FILE_SYNC_CONNECT_TOOLTIP)
1287+ tooltip=qt.FILE_SYNC_CONNECT_TOOLTIP)
1288
1289 def test_process_info_error(self):
1290 """File sync status error update the label."""
1291- msg = gui.WARNING_MARKUP % gui.FILE_SYNC_ERROR
1292+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
1293 self.assert_status_correct(status_bd=backend.FILE_SYNC_ERROR,
1294 status_ui=msg,
1295 msg_bd='what an error!',
1296- action=gui.FILE_SYNC_RESTART,
1297+ action=qt.FILE_SYNC_RESTART,
1298 callback='restart_files',
1299- tooltip=gui.FILE_SYNC_RESTART_TOOLTIP)
1300+ tooltip=qt.FILE_SYNC_RESTART_TOOLTIP)
1301
1302 def test_process_info_idle(self):
1303 """File sync status idle update the label."""
1304 self.assert_status_correct(status_bd=backend.FILE_SYNC_IDLE,
1305- status_ui=gui.FILE_SYNC_IDLE,
1306- action=gui.FILE_SYNC_DISCONNECT,
1307+ status_ui=qt.FILE_SYNC_IDLE,
1308+ action=qt.FILE_SYNC_DISCONNECT,
1309 callback='disconnect_files',
1310- tooltip=gui.FILE_SYNC_DISCONNECT_TOOLTIP)
1311+ tooltip=qt.FILE_SYNC_DISCONNECT_TOOLTIP)
1312
1313 def test_process_info_starting(self):
1314 """File sync status starting update the label."""
1315 self.assert_status_correct(status_bd=backend.FILE_SYNC_STARTING,
1316- status_ui=gui.FILE_SYNC_STARTING,
1317- action=gui.FILE_SYNC_STOP,
1318+ status_ui=qt.FILE_SYNC_STARTING,
1319+ action=qt.FILE_SYNC_STOP,
1320 callback='stop_files',
1321- tooltip=gui.FILE_SYNC_STOP_TOOLTIP)
1322+ tooltip=qt.FILE_SYNC_STOP_TOOLTIP)
1323
1324 def test_process_info_stopped(self):
1325 """File sync status stopped update the label."""
1326 self.assert_status_correct(status_bd=backend.FILE_SYNC_STOPPED,
1327- status_ui=gui.FILE_SYNC_STOPPED,
1328- action=gui.FILE_SYNC_START,
1329+ status_ui=qt.FILE_SYNC_STOPPED,
1330+ action=qt.FILE_SYNC_START,
1331 callback='start_files',
1332- tooltip=gui.FILE_SYNC_START_TOOLTIP)
1333+ tooltip=qt.FILE_SYNC_START_TOOLTIP)
1334
1335 def test_process_info_syncing(self):
1336 """File sync status syncing update the label."""
1337 self.assert_status_correct(status_bd=backend.FILE_SYNC_SYNCING,
1338- status_ui=gui.FILE_SYNC_SYNCING,
1339- action=gui.FILE_SYNC_DISCONNECT,
1340+ status_ui=qt.FILE_SYNC_SYNCING,
1341+ action=qt.FILE_SYNC_DISCONNECT,
1342 callback='disconnect_files',
1343- tooltip=gui.FILE_SYNC_DISCONNECT_TOOLTIP)
1344+ tooltip=qt.FILE_SYNC_DISCONNECT_TOOLTIP)
1345
1346 def test_process_info_unknown(self):
1347 """File sync status unknown update the label."""
1348- msg = gui.WARNING_MARKUP % gui.FILE_SYNC_ERROR
1349+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
1350 self.assert_status_correct(status_bd=backend.FILE_SYNC_UNKNOWN,
1351 status_ui=msg,
1352 msg_bd='yadda oops',
1353- action=gui.FILE_SYNC_RESTART,
1354+ action=qt.FILE_SYNC_RESTART,
1355 callback='restart_files',
1356- tooltip=gui.FILE_SYNC_RESTART_TOOLTIP)
1357+ tooltip=qt.FILE_SYNC_RESTART_TOOLTIP)
1358
1359 def test_on_sync_status_button_clicked(self):
1360 """Check the labels and icon when the button is pressed."""
1361
1362=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_folders.py'
1363--- ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-06-22 14:42:29 +0000
1364+++ ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2012-08-22 21:22:18 +0000
1365@@ -591,6 +591,8 @@
1366 def test_confirm_dialog_if_path_exists(self):
1367 """The confirmation dialog is correct."""
1368 self.patch(gui.os.path, 'exists', lambda path: True)
1369+ self.patch(gui.os.path, 'isdir', lambda path: True)
1370+ self.patch(gui, 'is_link', lambda path: False)
1371
1372 # make sure the item is subscribed
1373 self.ui.is_processing = True
1374@@ -607,6 +609,48 @@
1375 self.assertEqual(FakedDialog.kwargs, {})
1376
1377 @defer.inlineCallbacks
1378+ def test_error_if_path_exists_as_file(self):
1379+ """The error dialog is correct."""
1380+ self.patch(gui.os.path, 'exists', lambda path: True)
1381+ self.patch(gui.os.path, 'isdir', lambda path: False)
1382+ self.patch(gui, 'is_link', lambda path: False)
1383+
1384+ # make sure the item is subscribed
1385+ self.ui.is_processing = True
1386+ self.set_item_checked()
1387+ self.ui.is_processing = False
1388+
1389+ yield self.ui.on_folders_itemChanged(self.item)
1390+
1391+ volume_path = self.item.volume_path
1392+ msg = gui.FOLDER_EXISTS_AS_FILE % {'folder_path': volume_path}
1393+ buttons = gui.CLOSE
1394+ self.assertEqual(FakedDialog.args,
1395+ (self.ui, '', msg, buttons))
1396+ self.assertEqual(FakedDialog.kwargs, {})
1397+
1398+ @defer.inlineCallbacks
1399+ def test_error_if_path_exists_as_valid_link(self):
1400+ """The error dialog is correct."""
1401+ self.patch(gui.os.path, 'exists', lambda path: True)
1402+ self.patch(gui.os.path, 'isdir', lambda path: True)
1403+ self.patch(gui, 'is_link', lambda path: True)
1404+
1405+ # make sure the item is subscribed
1406+ self.ui.is_processing = True
1407+ self.set_item_checked()
1408+ self.ui.is_processing = False
1409+
1410+ yield self.ui.on_folders_itemChanged(self.item)
1411+
1412+ volume_path = self.item.volume_path
1413+ msg = gui.FOLDER_EXISTS_AS_FILE % {'folder_path': volume_path}
1414+ buttons = gui.CLOSE
1415+ self.assertEqual(FakedDialog.args,
1416+ (self.ui, '', msg, buttons))
1417+ self.assertEqual(FakedDialog.kwargs, {})
1418+
1419+ @defer.inlineCallbacks
1420 def test_confirm_dialog_if_path_does_not_exist(self):
1421 """The confirmation dialog is not created if not needed."""
1422 self.patch(gui.os.path, 'exists', lambda path: False)
1423
1424=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gui.py'
1425--- ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-04-05 14:52:55 +0000
1426+++ ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-08-22 21:22:18 +0000
1427@@ -81,6 +81,11 @@
1428 self.ui.ui.control_panel.ui.tab_widget.currentIndex(),
1429 self.ui.ui.control_panel.ui.tab_widget.indexOf(
1430 self.ui.ui.control_panel.ui.folders_tab))
1431+ self.ui.switch_to("share_links")
1432+ self.assertEqual(
1433+ self.ui.ui.control_panel.ui.tab_widget.currentIndex(),
1434+ self.ui.ui.control_panel.ui.tab_widget.indexOf(
1435+ self.ui.ui.control_panel.ui.share_links_tab))
1436 self.ui.switch_to("devices")
1437 self.assertEqual(
1438 self.ui.ui.control_panel.ui.tab_widget.currentIndex(),
1439
1440=== added file 'ubuntuone/controlpanel/gui/qt/tests/test_share_links.py'
1441--- ubuntuone/controlpanel/gui/qt/tests/test_share_links.py 1970-01-01 00:00:00 +0000
1442+++ ubuntuone/controlpanel/gui/qt/tests/test_share_links.py 2012-08-22 21:22:18 +0000
1443@@ -0,0 +1,37 @@
1444+# -*- coding: utf-8 -*-
1445+
1446+# Copyright 2012 Canonical Ltd.
1447+#
1448+# This program is free software: you can redistribute it and/or modify it
1449+# under the terms of the GNU General Public License version 3, as published
1450+# by the Free Software Foundation.
1451+#
1452+# This program is distributed in the hope that it will be useful, but
1453+# WITHOUT ANY WARRANTY; without even the implied warranties of
1454+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1455+# PURPOSE. See the GNU General Public License for more details.
1456+#
1457+# You should have received a copy of the GNU General Public License along
1458+# with this program. If not, see <http://www.gnu.org/licenses/>.
1459+
1460+"""Tests for the account tab."""
1461+
1462+from ubuntuone.controlpanel.gui import (
1463+ SEARCH_FILES,
1464+ SHARED_FILES,
1465+)
1466+from ubuntuone.controlpanel.gui.qt import share_links as gui
1467+from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1468+
1469+
1470+class ShareLinksTestCase(BaseTestCase):
1471+ """Test the qt control panel."""
1472+
1473+ innerclass_ui = gui.share_links_ui
1474+ innerclass_name = "Ui_Form"
1475+ class_ui = gui.ShareLinksPanel
1476+
1477+ def test_setup(self):
1478+ """Check that the strings are properly setted."""
1479+ self.assertEqual(self.ui.ui.search_files_lbl.text(), SEARCH_FILES)
1480+ self.assertEqual(self.ui.ui.shared_group.title(), SHARED_FILES)
1481
1482=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
1483--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-06-22 14:42:29 +0000
1484+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-08-22 21:22:18 +0000
1485@@ -18,12 +18,25 @@
1486
1487 """Tests for the notification area icon."""
1488
1489-from PyQt4 import QtGui
1490+from PyQt4 import QtGui, QtCore
1491 from twisted.internet.defer import inlineCallbacks
1492
1493+import ubuntuone.controlpanel.gui.qt.gui
1494+from ubuntuone.controlpanel.gui import (
1495+ qt,
1496+ IN_PROGRESS,
1497+ IN_PROGRESS_FILE,
1498+ RECENT_TRANSFERS,
1499+ RECENTTRANSFERS,
1500+ UPLOADING,
1501+)
1502 from ubuntuone.controlpanel.gui.qt import systray
1503-from ubuntuone.controlpanel.tests import TestCase
1504-import ubuntuone.controlpanel.gui.qt.gui
1505+from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1506+from ubuntuone.controlpanel.tests import ROOT_PATH
1507+
1508+
1509+# pylint: disable=C0103, W0212
1510+backend = systray.backend
1511
1512
1513 class FakeSDTool(object):
1514@@ -46,14 +59,153 @@
1515 super(FakeMainWindow, self).__init__()
1516
1517
1518-class SystrayTestCase(TestCase):
1519+class FakeDesktopService(object):
1520+
1521+ """Fake QDesktopService."""
1522+
1523+ data = {}
1524+
1525+ @classmethod
1526+ def openUrl(cls, url):
1527+ """Fake openUrl."""
1528+ FakeDesktopService.data['cls'] = cls
1529+ FakeDesktopService.data['url'] = url
1530+
1531+
1532+class SystrayTestCase(BaseTestCase):
1533
1534 """Test the notification area icon."""
1535
1536+ class_ui = systray.TrayIcon
1537+
1538+ @inlineCallbacks
1539+ def setUp(self):
1540+ # We need to patch the startTimer first, to avoid the timer
1541+ # to get started on initialization.
1542+ self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
1543+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1544+ yield super(SystrayTestCase, self).setUp()
1545+ self.patch(QtGui, "QDesktopServices", FakeDesktopService)
1546+
1547+ def assert_status_correct(self, status_bd, status_ui, action,
1548+ callback=None):
1549+ """The shown status is correct."""
1550+ expected_text = status_ui
1551+
1552+ status = {backend.STATUS_KEY: status_bd}
1553+ self.ui._process_status(status)
1554+
1555+ actual_text = unicode(self.ui.status.text())
1556+ self.assertEqual(expected_text, actual_text)
1557+
1558+ self.assertFalse(self.ui.status.isEnabled())
1559+ self.assertEqual(unicode(self.ui.status_action.text()), action)
1560+
1561+ self.ui.status_action.trigger()
1562+ self.assertFalse(self.ui.status_action.isEnabled())
1563+ self.assert_backend_called(callback)
1564+
1565+ def test_process_info_invalid_status(self):
1566+ """File sync status invalid, ignore event."""
1567+ status = {backend.STATUS_KEY: backend.FILE_SYNC_STARTING,
1568+ backend.MSG_KEY: qt.FILE_SYNC_STARTING,
1569+ 'icon': backend.FILE_SYNC_STARTING}
1570+ self.ui._process_status(status)
1571+ self.ui._process_status(3)
1572+
1573+ actual_text = unicode(self.ui.status.text())
1574+ self.assertEqual(qt.FILE_SYNC_STARTING, actual_text)
1575+
1576+ def test_process_info_changed(self):
1577+ """Backend's file sync status changed callback is connected."""
1578+ self.ui.refresh_status()
1579+ self.assertEqual(self.ui.backend.status_changed_handler,
1580+ self.ui._process_status)
1581+
1582+ def test_process_info_disabled(self):
1583+ """File sync status disabled update the label."""
1584+ self.ui.refresh_status()
1585+ self.assert_status_correct(status_bd=backend.FILE_SYNC_DISABLED,
1586+ status_ui=qt.FILE_SYNC_DISABLED,
1587+ action=qt.FILE_SYNC_ENABLE,
1588+ callback='enable_files')
1589+
1590+ def test_process_info_disconnected(self):
1591+ """File sync status disconnected update the label."""
1592+ self.assert_status_correct(status_bd=backend.FILE_SYNC_DISCONNECTED,
1593+ status_ui=qt.FILE_SYNC_DISCONNECTED,
1594+ action=qt.FILE_SYNC_CONNECT,
1595+ callback='connect_files')
1596+
1597+ def test_process_info_error(self):
1598+ """File sync status error update the label."""
1599+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
1600+ self.assert_status_correct(status_bd=backend.FILE_SYNC_ERROR,
1601+ status_ui=msg,
1602+ action=qt.FILE_SYNC_RESTART,
1603+ callback='restart_files')
1604+
1605+ def test_process_info_idle(self):
1606+ """File sync status idle update the label."""
1607+ self.assert_status_correct(status_bd=backend.FILE_SYNC_IDLE,
1608+ status_ui=qt.FILE_SYNC_IDLE,
1609+ action=qt.FILE_SYNC_DISCONNECT,
1610+ callback='disconnect_files')
1611+
1612+ def test_process_info_starting(self):
1613+ """File sync status starting update the label."""
1614+ self.assert_status_correct(status_bd=backend.FILE_SYNC_STARTING,
1615+ status_ui=qt.FILE_SYNC_STARTING,
1616+ action=qt.FILE_SYNC_STOP,
1617+ callback='stop_files')
1618+
1619+ def test_process_info_stopped(self):
1620+ """File sync status stopped update the label."""
1621+ self.assert_status_correct(status_bd=backend.FILE_SYNC_STOPPED,
1622+ status_ui=qt.FILE_SYNC_STOPPED,
1623+ action=qt.FILE_SYNC_START,
1624+ callback='start_files')
1625+
1626+ def test_process_info_syncing(self):
1627+ """File sync status syncing update the label."""
1628+ self.assert_status_correct(status_bd=backend.FILE_SYNC_SYNCING,
1629+ status_ui=qt.FILE_SYNC_SYNCING,
1630+ action=qt.FILE_SYNC_DISCONNECT,
1631+ callback='disconnect_files')
1632+
1633+ def test_process_info_unknown(self):
1634+ """File sync status unknown update the label."""
1635+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
1636+ self.assert_status_correct(status_bd=backend.FILE_SYNC_UNKNOWN,
1637+ status_ui=msg,
1638+ action=qt.FILE_SYNC_RESTART,
1639+ callback='restart_files')
1640+
1641+ def test_backend(self):
1642+ """Backend is created."""
1643+ self.assertIsInstance(self.ui.backend,
1644+ backend.ControlBackend)
1645+
1646+ def test_refresh_status_requested(self):
1647+ """test refresh_status was requested on initialized."""
1648+ data = {}
1649+
1650+ def callback(status):
1651+ """Fake _process_status callback."""
1652+ data['called'] = True
1653+ data['status'] = status
1654+
1655+ self.patch(self.ui, "_process_status", callback)
1656+ self.ui.refresh_status()
1657+ self.assert_backend_called('file_sync_status')
1658+ self.assertTrue(data['called'])
1659+ self.assertEqual(data['status'], [])
1660+
1661 def test_quit(self):
1662 """Test the quit option in the menu."""
1663 # Not done on setup, because if I patch stop
1664 # after instantiation, it doesn't get called.
1665+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1666 self.patch(systray.TrayIcon, "stop", self._set_called)
1667 tray = systray.TrayIcon()
1668 tray.quit.trigger()
1669@@ -63,6 +215,7 @@
1670 def test_stop_sd(self):
1671 """Quit should call SyncDaemonTool.quit()."""
1672 st = FakeSDTool()
1673+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1674 self.patch(systray, "SyncDaemonTool", lambda: st)
1675 tray = systray.TrayIcon()
1676 yield tray.stop()
1677@@ -70,38 +223,31 @@
1678
1679 def test_restore_no_window(self):
1680 """Test the restore window option in the menu, with no window."""
1681+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1682 self.patch(ubuntuone.controlpanel.gui.qt.gui,
1683 "MainWindow", FakeMainWindow)
1684 tray = systray.TrayIcon()
1685 self.assertEqual(tray.window, None)
1686- tray.restore.trigger()
1687+ tray.open_u1.trigger()
1688 self.assertIsInstance(tray.window, FakeMainWindow)
1689 self.assertTrue(tray.window.isVisible())
1690 self.assertEqual(tray.window.args, ((),
1691 {'close_callback': tray.delete_window}))
1692
1693- def test_activate(self):
1694- """Test the icon activation."""
1695- tray = systray.TrayIcon()
1696- window = FakeMainWindow()
1697- tray.window = window
1698- self.assertFalse(tray.window.isVisible())
1699- tray.activated.emit(tray.Trigger)
1700- self.assertEqual(tray.window, window)
1701- self.assertTrue(tray.window.isVisible())
1702-
1703 def test_restore_window(self):
1704 """Test the restore window option in the menu, with a window."""
1705+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1706 tray = systray.TrayIcon()
1707 window = FakeMainWindow()
1708 tray.window = window
1709 self.assertFalse(tray.window.isVisible())
1710- tray.restore.trigger()
1711+ tray.open_u1.trigger()
1712 self.assertEqual(tray.window, window)
1713 self.assertTrue(tray.window.isVisible())
1714
1715 def test_restore_window_minimized(self):
1716 """Test the restore window option in the menu, with a min. window."""
1717+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1718 tray = systray.TrayIcon()
1719 window = FakeMainWindow()
1720 # This cannot be tested with the real activateWindow
1721@@ -109,11 +255,12 @@
1722 # it has a small delay, and it fails.
1723 self.patch(window, "activateWindow", self._set_called)
1724 tray.window = window
1725- tray.restore.trigger()
1726+ tray.open_u1.trigger()
1727 self.assertEqual(self._called, ((), {}))
1728
1729 def test_delete_window(self):
1730 """Test deleting an existing window."""
1731+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1732 tray = systray.TrayIcon()
1733 window = FakeMainWindow()
1734 tray.window = window
1735@@ -123,14 +270,330 @@
1736
1737 def test_delete_no_window(self):
1738 """Test deleting without an existing window."""
1739+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1740 tray = systray.TrayIcon()
1741 tray.delete_window()
1742 self.assertEqual(tray.window, None)
1743
1744+ def test_open_u1_folder_action(self):
1745+ """Test open_u1_folder_action."""
1746+ self.ui.open_u1_folder.trigger()
1747+ self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1748+ expected_url = QtCore.QUrl(u'file://%s' % ROOT_PATH)
1749+ self.assertEqual(FakeDesktopService.data['url'], expected_url)
1750+
1751+ def test_get_more_storage_action(self):
1752+ """Test get_more_storage."""
1753+ self.ui.get_more_storage.trigger()
1754+ self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1755+ expected_url = QtCore.QUrl(systray.GET_STORAGE_LINK)
1756+ self.assertEqual(FakeDesktopService.data['url'], expected_url)
1757+
1758+ def test_go_to_web_action(self):
1759+ """Test go_to_web."""
1760+ self.ui.go_to_web.trigger()
1761+ self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1762+ expected_url = QtCore.QUrl(systray.DASHBOARD)
1763+ self.assertEqual(FakeDesktopService.data['url'], expected_url)
1764+
1765+ def test_get_help_action(self):
1766+ """Test get_help_online."""
1767+ self.ui.get_help_online.trigger()
1768+ self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1769+ expected_url = QtCore.QUrl(systray.GET_SUPPORT_LINK)
1770+ self.assertEqual(FakeDesktopService.data['url'], expected_url)
1771+
1772 def test_initialization(self):
1773 """Test that everything initializes correctly."""
1774+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1775 tray = systray.TrayIcon()
1776 self.assertTrue(tray.isVisible())
1777 self.assertEqual(tray.window, None)
1778+ self.assertNotEqual(tray.icon(), None)
1779+ self.assertEqual(tray.uploading, {})
1780+ self.assertEqual(tray.recent_transfers, {})
1781 self.assertIsInstance(tray.context_menu, QtGui.QMenu)
1782- self.assertNotEqual(tray.icon(), None)
1783+ self.assertIsInstance(tray.status, QtGui.QAction)
1784+ self.assertIsInstance(tray.status_action, QtGui.QAction)
1785+ self.assertIsInstance(tray.open_u1, QtGui.QAction)
1786+ self.assertIsInstance(tray.open_u1_folder, QtGui.QAction)
1787+ self.assertIsInstance(tray.go_to_web, QtGui.QAction)
1788+ self.assertIsInstance(tray.get_more_storage, QtGui.QAction)
1789+ self.assertIsInstance(tray.get_help_online, QtGui.QAction)
1790+ self.assertIsInstance(tray.transfers, QtGui.QMenu)
1791+ # This checks that _get_volumes_info and _process_volumes_info
1792+ # is being called correctly on initialization.
1793+ expected_home = u'file://%s' % ROOT_PATH
1794+ self.assertEqual(tray.root_path, expected_home)
1795+
1796+ def test_get_transfers_data(self):
1797+ """Check that _get_transfers_data return the proper data."""
1798+
1799+ result = []
1800+
1801+ def fake_callback(data):
1802+ """Fake callback to process data."""
1803+ result.append(data)
1804+
1805+ self.patch(self.ui.transfers, "_update_transfers", fake_callback)
1806+ self.ui.transfers.get_transfers_data()
1807+ self.assertEqual(result, [{RECENTTRANSFERS: [], UPLOADING: []}])
1808+
1809+
1810+class TransfersMenuTestCase(BaseTestCase):
1811+
1812+ """Test the info to be display in the transfers menu."""
1813+
1814+ class_ui = systray.TrayIcon
1815+
1816+ @inlineCallbacks
1817+ def setUp(self):
1818+ # We need to patch the startTimer first, to avoid the timer
1819+ # to get started on initialization.
1820+ self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
1821+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1822+ yield super(TransfersMenuTestCase, self).setUp()
1823+ self.patch(QtGui, "QDesktopServices", FakeDesktopService)
1824+ self.patch(self.ui.transfers._parent.backend, "sync_menu",
1825+ self.fake_sync_menu)
1826+ self.recent_transfers = []
1827+ self.uploading = []
1828+
1829+ def fake_sync_menu(self):
1830+ """Fake backend sync_menu."""
1831+ return {RECENTTRANSFERS: self.recent_transfers,
1832+ UPLOADING: self.uploading}
1833+
1834+ def test_load_menu(self):
1835+ """Show the menu with just the labels."""
1836+ self.ui.transfers.get_transfers_data()
1837+ actions = self.ui.transfers.actions()
1838+ self.assertEqual(actions[0].text(), RECENT_TRANSFERS)
1839+ self.assertTrue(actions[1].isSeparator())
1840+ self.assertEqual(actions[2].text(), IN_PROGRESS)
1841+
1842+ def test_load_recent_transfers(self):
1843+ """Show the menu with the recent transfers."""
1844+ self.recent_transfers = ['file1', 'file2']
1845+ self.ui.transfers.get_transfers_data()
1846+ actions = self.ui.transfers.actions()
1847+ self.assertEqual(actions[0].text(), RECENT_TRANSFERS)
1848+ self.assertEqual(actions[1].text(), 'file1')
1849+ self.assertEqual(actions[2].text(), 'file2')
1850+ self.assertTrue(actions[3].isSeparator())
1851+ self.assertEqual(actions[4].text(), IN_PROGRESS)
1852+
1853+ def test_load_in_progress(self):
1854+ """Show the menu with the current progress."""
1855+ in_progress = [
1856+ ('file1', 200, 150),
1857+ ('file2', 400, 100),
1858+ ('file3', 300, 200),
1859+ ]
1860+
1861+ # Return copy of in_progress
1862+ self.uploading = in_progress[:]
1863+ self.ui.transfers.get_transfers_data()
1864+ actions = self.ui.transfers.actions()
1865+ self.assertEqual(actions[0].text(), RECENT_TRANSFERS)
1866+ self.assertTrue(actions[1].isSeparator())
1867+ self.assertEqual(actions[2].text(), IN_PROGRESS)
1868+
1869+ # This also check that the files are ordered based on written
1870+ percentage = in_progress[2][2] * 100 / in_progress[2][1]
1871+ text = IN_PROGRESS_FILE % (in_progress[2][0], percentage)
1872+ self.assertEqual(actions[3].label.text(), text)
1873+ self.assertEqual(actions[3].progress.value(), percentage)
1874+
1875+ percentage = in_progress[0][2] * 100 / in_progress[0][1]
1876+ text = IN_PROGRESS_FILE % (in_progress[0][0], percentage)
1877+ self.assertEqual(actions[4].label.text(), text)
1878+ self.assertEqual(actions[4].progress.value(), percentage)
1879+
1880+ percentage = in_progress[1][2] * 100 / in_progress[1][1]
1881+ text = IN_PROGRESS_FILE % (in_progress[1][0], percentage)
1882+ self.assertEqual(actions[5].label.text(), text)
1883+ self.assertEqual(actions[5].progress.value(), percentage)
1884+
1885+ def test_load_in_progress_refresh(self):
1886+ """Show the menu with the current progress and refresh it."""
1887+ in_progress = [
1888+ ('file1', 200, 150),
1889+ ('file2', 400, 100),
1890+ ('file3', 300, 200),
1891+ ]
1892+ # Return copy of in_progress
1893+ self.uploading = in_progress[:]
1894+ self.ui.transfers.get_transfers_data()
1895+ actions = self.ui.transfers.actions()
1896+ self.assertEqual(actions[0].text(), RECENT_TRANSFERS)
1897+ self.assertTrue(actions[1].isSeparator())
1898+ self.assertEqual(actions[2].text(), IN_PROGRESS)
1899+
1900+ # This also check that the files are ordered based on written
1901+ previous_actions = []
1902+ percentage = in_progress[2][2] * 100 / in_progress[2][1]
1903+ text = IN_PROGRESS_FILE % (in_progress[2][0], percentage)
1904+ self.assertEqual(actions[3].text(), text)
1905+ previous_actions.append(actions[3])
1906+
1907+ percentage = in_progress[0][2] * 100 / in_progress[0][1]
1908+ text = IN_PROGRESS_FILE % (in_progress[0][0], percentage)
1909+ self.assertEqual(actions[4].text(), text)
1910+ previous_actions.append(actions[4])
1911+
1912+ percentage = in_progress[1][2] * 100 / in_progress[1][1]
1913+ text = IN_PROGRESS_FILE % (in_progress[1][0], percentage)
1914+ self.assertEqual(actions[5].text(), text)
1915+ previous_actions.append(actions[5])
1916+
1917+ in_progress = [
1918+ ('file1', 200, 170),
1919+ ('file2', 400, 300),
1920+ ('file3', 300, 210),
1921+ ]
1922+ self.uploading = in_progress[:]
1923+
1924+ self.ui.transfers.get_transfers_data()
1925+ actions = self.ui.transfers.actions()
1926+ current_actions = []
1927+ self.assertEqual(actions[0].text(), RECENT_TRANSFERS)
1928+ self.assertTrue(actions[1].isSeparator())
1929+ self.assertEqual(actions[2].text(), IN_PROGRESS)
1930+
1931+ # This also check that the files are ordered based on written
1932+ percentage = in_progress[2][2] * 100 / in_progress[2][1]
1933+ text = IN_PROGRESS_FILE % (in_progress[2][0], percentage)
1934+ self.assertEqual(actions[3].text(), text)
1935+ current_actions.append(actions[3])
1936+
1937+ percentage = in_progress[0][2] * 100 / in_progress[0][1]
1938+ text = IN_PROGRESS_FILE % (in_progress[0][0], percentage)
1939+ self.assertEqual(actions[4].text(), text)
1940+ current_actions.append(actions[4])
1941+
1942+ percentage = in_progress[1][2] * 100 / in_progress[1][1]
1943+ text = IN_PROGRESS_FILE % (in_progress[1][0], percentage)
1944+ self.assertEqual(actions[5].text(), text)
1945+ current_actions.append(actions[5])
1946+
1947+ self.assertEqual(previous_actions, current_actions)
1948+
1949+ def test_menu_not_reload(self):
1950+ """Show the menu and test that is not reload it on refresh."""
1951+ self.recent_transfers = ['file1', 'file2']
1952+ self.ui.transfers.get_transfers_data()
1953+ previous_actions = self.ui.transfers.actions()
1954+ self.ui.transfers.get_transfers_data()
1955+ current_actions = self.ui.transfers.actions()
1956+ self.assertEqual(previous_actions, current_actions)
1957+
1958+ def test_menu_reload(self):
1959+ """Show the menu and test that is reload it on refresh."""
1960+ self.recent_transfers = ['file1', 'file2']
1961+ self.ui.transfers.get_transfers_data()
1962+ previous_actions = self.ui.transfers.actions()
1963+ self.recent_transfers = ['file1', 'file2', 'file3']
1964+
1965+ self.ui.transfers.get_transfers_data()
1966+ current_actions = self.ui.transfers.actions()
1967+ self.assertNotEqual(previous_actions, current_actions)
1968+
1969+ def test_progress_not_reload(self):
1970+ """Show the menu and test that is not reload it on refresh."""
1971+ in_progress = [
1972+ ('file1', 200, 150),
1973+ ('file2', 400, 100),
1974+ ('file3', 300, 200),
1975+ ]
1976+ # Return copy of in_progress
1977+ self.uploading = in_progress[:]
1978+ self.ui.transfers.get_transfers_data()
1979+ previous_actions = self.ui.transfers.actions()
1980+ in_progress = [
1981+ ('file1', 200, 170),
1982+ ('file2', 400, 300),
1983+ ('file3', 300, 210),
1984+ ]
1985+ self.uploading = in_progress[:]
1986+ self.ui.transfers.get_transfers_data()
1987+ current_actions = self.ui.transfers.actions()
1988+
1989+ self.assertEqual(previous_actions, current_actions)
1990+
1991+ def test_progress_reload(self):
1992+ """Show the menu and test that is reload it on refresh."""
1993+ in_progress = [
1994+ ('file1', 200, 150),
1995+ ('file2', 400, 100),
1996+ ('file3', 300, 200),
1997+ ]
1998+ # Return copy of in_progress
1999+ self.uploading = in_progress[:]
2000+ self.ui.transfers.get_transfers_data()
2001+ previous_actions = self.ui.transfers.actions()
2002+ in_progress = [
2003+ ('file1', 200, 170),
2004+ ('file2', 400, 300),
2005+ ('file3', 300, 210),
2006+ ('file4', 1000, 410),
2007+ ]
2008+ self.uploading = in_progress[:]
2009+ self.ui.transfers.get_transfers_data()
2010+ current_actions = self.ui.transfers.actions()
2011+
2012+ self.assertNotEqual(previous_actions, current_actions)
2013+
2014+
2015+class SharesTestCase(BaseTestCase):
2016+
2017+ """Test the info to be displayed in the shares section."""
2018+
2019+ class_ui = systray.TrayIcon
2020+
2021+ @inlineCallbacks
2022+ def setUp(self):
2023+ # We need to patch the startTimer first, to avoid the timer
2024+ # to get started on initialization.
2025+ self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
2026+ self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
2027+ yield super(SharesTestCase, self).setUp()
2028+ self.patch(QtGui, "QDesktopServices", FakeDesktopService)
2029+ self.patch(self.ui.backend, "get_shares", self.fake_get_backend)
2030+ self._shares_info = []
2031+
2032+ def fake_get_backend(self):
2033+ """Fake get_backend."""
2034+ return self._shares_info
2035+
2036+ def test_shares_refresh_not_reload(self):
2037+ """Check that we get the new info of shares but not reload the menu."""
2038+ actions = self.ui.context_menu.actions()
2039+ self.ui.load_menu()
2040+ post_actions = self.ui.context_menu.actions()
2041+ self.assertEqual(actions, post_actions)
2042+
2043+ def test_shares_refresh_reload(self):
2044+ """Check that we get the new info of shares but not reload the menu."""
2045+ actions = self.ui.context_menu.actions()
2046+ self._shares_info = [
2047+ {'accepted': False, 'other_visible_name': 'name',
2048+ 'volume_id': 'asd123-asd123-asd123-asd123'}
2049+ ]
2050+ self.ui.load_menu()
2051+ post_actions = self.ui.context_menu.actions()
2052+ self.assertNotEqual(actions, post_actions)
2053+
2054+ def test_share_triggered(self):
2055+ """Check that we get the new info of shares but not reload the menu."""
2056+ volume_id = 'asd123-asd123-asd123-asd123'
2057+ self._shares_info = [
2058+ {'accepted': False, 'other_visible_name': 'name',
2059+ 'volume_id': volume_id}
2060+ ]
2061+ self.ui.load_menu()
2062+ actions = self.ui.context_menu.actions()
2063+ share_action = actions[7]
2064+ share_action.trigger()
2065+ expected = QtCore.QUrl(systray.ACCEPT_SHARES % volume_id)
2066+ self.assertEqual(FakeDesktopService.data['url'], expected)
2067
2068=== modified file 'ubuntuone/controlpanel/sd_client/__init__.py'
2069--- ubuntuone/controlpanel/sd_client/__init__.py 2012-03-29 21:37:22 +0000
2070+++ ubuntuone/controlpanel/sd_client/__init__.py 2012-08-22 21:22:18 +0000
2071@@ -205,3 +205,7 @@
2072 def refresh_volumes(self):
2073 """Refresh the volumes information from syncdaemon."""
2074 return self.proxy.refresh_volumes()
2075+
2076+ def sync_menu(self):
2077+ """Get the sync menu data from syncdaemon."""
2078+ return self.proxy.sync_menu()
2079
2080=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
2081--- ubuntuone/controlpanel/tests/test_backend.py 2012-05-11 13:30:34 +0000
2082+++ ubuntuone/controlpanel/tests/test_backend.py 2012-08-22 21:22:18 +0000
2083@@ -167,6 +167,7 @@
2084 self.shares = []
2085 self.folders = []
2086 self.volumes_refreshed = False
2087+ self.menu_data = {'recent-transfers': (), 'uploading': ()}
2088
2089 def get_throttling_limits(self):
2090 """Return the sample speed limits."""
2091@@ -326,6 +327,10 @@
2092 self.volumes_refreshed = True
2093 return defer.succeed(None)
2094
2095+ def sync_menu(self):
2096+ """Return the sync menu data."""
2097+ return self.menu_data
2098+
2099
2100 class MockReplicationClient(CallRecorder):
2101 """A mock replication_client module."""
2102@@ -1697,3 +1702,10 @@
2103
2104 result = yield self.be.file_sync_settings_info()
2105 self.assertEqual(self.default_settings, result)
2106+
2107+ @inlineCallbacks
2108+ def test_sync_menu(self):
2109+ """Check that we get the right data to create the menu."""
2110+ result = yield self.be.sync_menu()
2111+ expected = {'recent-transfers': (), 'uploading': ()}
2112+ self.assertEqual(result, expected)
2113
2114=== modified file 'ubuntuone/controlpanel/utils/__init__.py'
2115--- ubuntuone/controlpanel/utils/__init__.py 2012-04-05 14:52:55 +0000
2116+++ ubuntuone/controlpanel/utils/__init__.py 2012-08-22 21:22:18 +0000
2117@@ -40,13 +40,23 @@
2118 add_to_autostart = windows.add_to_autostart
2119 are_updates_present = windows.are_updates_present
2120 default_folders = windows.default_folders
2121+ install_config_and_daemons = no_op
2122 perform_update = windows.perform_update
2123 uninstall_application = windows.uninstall_application
2124+elif sys.platform == 'darwin':
2125+ from ubuntuone.controlpanel.utils import darwin
2126+ add_to_autostart = no_op
2127+ are_updates_present = no_op
2128+ default_folders = darwin.default_folders
2129+ install_config_and_daemons = darwin.install_config_and_daemons
2130+ perform_update = no_op
2131+ uninstall_application = no_op
2132 else:
2133 from ubuntuone.controlpanel.utils import linux
2134 add_to_autostart = no_op
2135 are_updates_present = no_op
2136 default_folders = linux.default_folders
2137+ install_config_and_daemons = no_op
2138 perform_update = no_op
2139 uninstall_application = no_op
2140
2141
2142=== added file 'ubuntuone/controlpanel/utils/darwin.py'
2143--- ubuntuone/controlpanel/utils/darwin.py 1970-01-01 00:00:00 +0000
2144+++ ubuntuone/controlpanel/utils/darwin.py 2012-08-22 21:22:18 +0000
2145@@ -0,0 +1,91 @@
2146+# -*- coding: utf-8 -*-
2147+#
2148+# Copyright 2012 Canonical Ltd.
2149+#
2150+# This program is free software: you can redistribute it and/or modify it
2151+# under the terms of the GNU General Public License version 3, as published
2152+# by the Free Software Foundation.
2153+#
2154+# This program is distributed in the hope that it will be useful, but
2155+# WITHOUT ANY WARRANTY; without even the implied warranties of
2156+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2157+# PURPOSE. See the GNU General Public License for more details.
2158+#
2159+# You should have received a copy of the GNU General Public License along
2160+# with this program. If not, see <http://www.gnu.org/licenses/>.
2161+
2162+"""Miscelaneous functions and constants for darwin."""
2163+
2164+import os
2165+import shutil
2166+import sys
2167+
2168+from twisted.internet import defer
2169+
2170+from dirspec.basedir import save_config_path
2171+from ubuntuone.controlpanel.logger import setup_logging
2172+
2173+logger = setup_logging('utils.darwin')
2174+AUTOUPDATE_BIN_NAME = 'autoupdate-darwin'
2175+UNINSTALL_BIN_NAME = 'uninstall-darwin'
2176+
2177+
2178+def add_to_autostart():
2179+ """Add syncdaemon to the session's autostart."""
2180+ # TODO
2181+
2182+
2183+@defer.inlineCallbacks
2184+def are_updates_present():
2185+ """Return if there are updates for Ubuntu One."""
2186+ result = False
2187+ # TODO
2188+ defer.returnValue(result)
2189+
2190+
2191+def default_folders(user_home=None):
2192+ """Return a list of the folders to add by default."""
2193+ folders = []
2194+ # TODO
2195+ return folders
2196+
2197+
2198+def install_config_and_daemons():
2199+ """Install required data files and fsevents daemon.
2200+
2201+ This function is a replacement for an installer. As such it is
2202+ required on first-run, but it's also called every time we start
2203+ up, in case anything has moved or been deleted.
2204+ """
2205+
2206+ # Do nothing if we are running from source:
2207+ if getattr(sys, 'frozen', None) is None:
2208+ return
2209+
2210+ main_app_dir = ''.join(__file__.partition('.app')[:-1])
2211+ main_app_resources_dir = os.path.join(main_app_dir,
2212+ 'Contents',
2213+ 'Resources')
2214+
2215+ config_path = save_config_path('ubuntuone')
2216+
2217+ conf_filenames = ['syncdaemon.conf',
2218+ 'logging.conf']
2219+ for conf_filename in conf_filenames:
2220+ src_path = os.path.join(main_app_resources_dir,
2221+ conf_filename)
2222+ dest_path = os.path.join(config_path,
2223+ conf_filename)
2224+
2225+ if not os.path.exists(dest_path):
2226+ shutil.copyfile(src_path, dest_path)
2227+
2228+
2229+def perform_update():
2230+ """Spawn the autoupdate process and call the stop function."""
2231+ # TODO
2232+
2233+
2234+def uninstall_application():
2235+ """Uninstall Ubuntu One."""
2236+ # TODO
2237
2238=== added file 'ubuntuone/controlpanel/utils/tests/test_darwin.py'
2239--- ubuntuone/controlpanel/utils/tests/test_darwin.py 1970-01-01 00:00:00 +0000
2240+++ ubuntuone/controlpanel/utils/tests/test_darwin.py 2012-08-22 21:22:18 +0000
2241@@ -0,0 +1,78 @@
2242+# -*- coding: utf-8 -*-
2243+#
2244+# Copyright 2012 Canonical Ltd.
2245+#
2246+# This program is free software: you can redistribute it and/or modify it
2247+# under the terms of the GNU General Public License version 3, as published
2248+# by the Free Software Foundation.
2249+#
2250+# This program is distributed in the hope that it will be useful, but
2251+# WITHOUT ANY WARRANTY; without even the implied warranties of
2252+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2253+# PURPOSE. See the GNU General Public License for more details.
2254+#
2255+# You should have received a copy of the GNU General Public License along
2256+# with this program. If not, see <http://www.gnu.org/licenses/>.
2257+
2258+"""Test the darwin utils functions."""
2259+
2260+import os
2261+import sys
2262+
2263+from twisted.internet import defer
2264+
2265+from ubuntuone.controlpanel import utils
2266+from ubuntuone.devtools.testcases import TestCase
2267+
2268+# let me use protected methods
2269+# pylint: disable=W0212
2270+
2271+
2272+class InstallConfigTestCase(TestCase):
2273+ """Test install_config_and_daemons."""
2274+
2275+ @defer.inlineCallbacks
2276+ def setUp(self):
2277+ """Set up multi-call checker."""
2278+ yield super(InstallConfigTestCase, self).setUp()
2279+ self._called = []
2280+
2281+ def _set_called(self, *args, **kwargs):
2282+ """Store 'args' and 'kwargs for test assertions."""
2283+ self._called.append((args, kwargs))
2284+
2285+ def test_do_nothing_unfrozen(self):
2286+ """Test that install_config_and_daemons does nothing when unfrozen."""
2287+
2288+ self.patch(utils.darwin, 'save_config_path',
2289+ self._set_called)
2290+ utils.install_config_and_daemons()
2291+ self.assertEqual(self._called, [])
2292+
2293+ def _test_copying_conf_files(self, exists):
2294+ """Call install_config_and_daemons, parameterize os.path.exists."""
2295+ sys.frozen = 'macosx_app'
2296+ self.addCleanup(delattr, sys, 'frozen')
2297+ self.patch(utils.darwin, 'save_config_path', lambda x: "TARGET_PATH")
2298+ self.patch(utils.darwin, '__file__', "/path/to/Main.app/ignore")
2299+ self.patch(os.path, "exists", lambda x: exists)
2300+ self.patch(utils.darwin.shutil, 'copyfile',
2301+ self._set_called)
2302+ utils.install_config_and_daemons()
2303+
2304+ def test_copies_conf_files(self):
2305+ """When frozen, we copy the conf files if they don't exist."""
2306+ self._test_copying_conf_files(False)
2307+ self.assertEqual(self._called,
2308+ [(('/path/to/Main.app/Contents/'
2309+ 'Resources/syncdaemon.conf',
2310+ 'TARGET_PATH/syncdaemon.conf'), {}),
2311+ (('/path/to/Main.app/Contents/'
2312+ 'Resources/logging.conf',
2313+ 'TARGET_PATH/logging.conf'),
2314+ {})])
2315+
2316+ def test_does_not_copy_conf_files(self):
2317+ """When frozen, we do not copy the conf files if they do exist."""
2318+ self._test_copying_conf_files(True)
2319+ self.assertEqual(self._called, [])
2320
2321=== modified file 'ubuntuone/controlpanel/utils/tests/test_utils.py'
2322--- ubuntuone/controlpanel/utils/tests/test_utils.py 2011-11-14 11:33:31 +0000
2323+++ ubuntuone/controlpanel/utils/tests/test_utils.py 2012-08-22 21:22:18 +0000
2324@@ -24,6 +24,7 @@
2325 from twisted.internet import defer
2326
2327 from ubuntuone.devtools.handlers import MementoHandler
2328+from ubuntuone.devtools.testcases import skipIfOS
2329
2330 from ubuntuone.controlpanel import utils
2331 from ubuntuone.controlpanel.tests import TestCase
2332@@ -149,3 +150,13 @@
2333 utils.ERROR_MESSAGE: unicode(exc)}
2334
2335 self.assertEqual(expected, result)
2336+
2337+
2338+class FirstRunInstallTestCase(TestCase):
2339+ """Test cases for darwin-only first-run installations."""
2340+
2341+ @skipIfOS("darwin", "Darwin-only code is tested in test_darwin.")
2342+ def test_no_op_on_win_or_linux(self):
2343+ """Test that install_config_and_daemons does nothing on win/linux."""
2344+ self.assertEqual(utils.install_config_and_daemons,
2345+ utils.no_op)

Subscribers

People subscribed via source and target branches

to all changes: