Merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4 into lp:ubuntuone-control-panel/stable-3-0

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 249
Merged at revision: 248
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4
Merge into: lp:ubuntuone-control-panel/stable-3-0
Diff against target: 2307 lines (+540/-1126)
30 files modified
run-tests (+20/-15)
run-tests.bat (+25/-3)
setup.py (+1/-1)
ubuntuone/controlpanel/backend.py (+46/-27)
ubuntuone/controlpanel/gui/__init__.py (+33/-50)
ubuntuone/controlpanel/gui/gtk/gui.py (+6/-4)
ubuntuone/controlpanel/gui/gtk/tests/__init__.py (+1/-10)
ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py (+7/-8)
ubuntuone/controlpanel/gui/qt/folders.py (+2/-7)
ubuntuone/controlpanel/gui/qt/gotoweb.py (+2/-12)
ubuntuone/controlpanel/gui/qt/gui.py (+3/-3)
ubuntuone/controlpanel/gui/qt/main/__init__.py (+5/-14)
ubuntuone/controlpanel/gui/qt/main/linux.py (+11/-3)
ubuntuone/controlpanel/gui/qt/main/windows.py (+21/-0)
ubuntuone/controlpanel/gui/qt/systray.py (+4/-5)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+29/-21)
ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py (+5/-52)
ubuntuone/controlpanel/gui/qt/tests/test_signin.py (+2/-5)
ubuntuone/controlpanel/gui/qt/tests/test_start.py (+89/-91)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+0/-2)
ubuntuone/controlpanel/gui/tests/test_url_sign.py (+0/-102)
ubuntuone/controlpanel/logger.py (+2/-2)
ubuntuone/controlpanel/tests/test_backend.py (+66/-1)
ubuntuone/controlpanel/tests/test_web_client.py (+107/-84)
ubuntuone/controlpanel/web_client.py (+53/-61)
ubuntuone/controlpanel/web_client/libsoup.py (+0/-133)
ubuntuone/controlpanel/web_client/tests/__init__.py (+0/-19)
ubuntuone/controlpanel/web_client/tests/test_libsoup.py (+0/-106)
ubuntuone/controlpanel/web_client/tests/test_txwebclient.py (+0/-177)
ubuntuone/controlpanel/web_client/txwebclient.py (+0/-108)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+93090@code.launchpad.net

Commit message

- Updating from trunk up to revno 259:

[ Natalia B. Bidart <email address hidden> ]
  - Install the uniqueapp module (LP: #930269).
  - Replaced custom webclient with the one from ubuntu-sso-client
    (LP: #926311).
  - Removed the dependency on qt4reactor for Linux implementation.
  - Avoid TypeError when fetching credentials in the Gtk OverviewPanel
     (LP: #927743).
  - Run the whole test suite with a single command (LP: #927770).
  - Do proper cleanup when dealing with UIs (LP: #925617).

[ Manuel de la Pena <email address hidden> ]
  - Forward extra params to u1trial (LP: #924384).

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'run-tests'
2--- run-tests 2012-01-18 17:40:33 +0000
3+++ run-tests 2012-02-14 22:15:21 +0000
4@@ -18,17 +18,11 @@
5
6 QT_TESTS_PATH=ubuntuone/controlpanel/gui/qt/tests
7 GTK_TESTS_PATH=ubuntuone/controlpanel/gui/gtk/tests
8+DBUS_TESTS_PATH=ubuntuone/controlpanel/dbustests
9 WINDOWS_TESTS=test_windows.py
10
11 set -e
12
13-if [ "$1" == "-qt" ]; then
14- USE_QT=1
15- shift
16-else
17- USE_QT=0
18-fi
19-
20 if [ $# -ne 0 ]; then
21 # run specific module given by the caller
22 MODULE="$@"
23@@ -45,16 +39,27 @@
24 echo "Please install the 'pep8' package."
25 fi
26 }
27-
28 unset GTK_MODULES
29
30+XVFB_CMDLINE=""
31+XVFB=$(which xvfb-run)
32+if [ $XVFB ]; then
33+ XVFB_CMDLINE="$XVFB -a"
34+fi
35+
36+echo "*** Running test suite for ""$MODULE"" ***"
37+u1trial --reactor=gi -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH, $GTK_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE"
38+
39+echo "*** Running DBus test suite ***"
40+u1trial --reactor=glib "$DBUS_TESTS_PATH"
41+
42+echo "*** Running GTK test suite ***"
43+$XVFB_CMDLINE u1trial --reactor=glib "$GTK_TESTS_PATH"
44+
45+echo "*** Running QT test suite for ""$MODULE"" ***"
46 ./setup.py build
47-echo "Running test suite for ""$MODULE"
48-if [ "$USE_QT" -eq 0 ]; then
49- `which xvfb-run` u1trial --ignore-paths "$QT_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE"
50-else
51- `which xvfb-run` u1trial --ignore-paths "$GTK_TESTS_PATH" -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE"
52-fi
53-style_check
54+$XVFB_CMDLINE u1trial -p "$GTK_TESTS_PATH" -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE"
55 rm -rf _trial_temp
56 rm -rf build
57+
58+style_check
59
60=== modified file 'run-tests.bat'
61--- run-tests.bat 2012-01-19 15:34:44 +0000
62+++ run-tests.bat 2012-02-14 22:15:21 +0000
63@@ -21,14 +21,36 @@
64 SET IGNORE_PATHS="ubuntuone\controlpanel\gui\gtk, ubuntuone\controlpanel\dbustests"
65 SET IGNORE_MODULES="test_linux.py, test_libsoup.py"
66
67+:: throw the first parameter away if is /skip-lint,
68+:: the way we do this is to ensure that /skip-lint
69+:: is the first parameter and copy all the rest in a loop
70+:: the main reason for that is that %* is not affected
71+:: by SHIFT, that is, it allways have all passed parameters
72+
73+SET PARAMS=%*
74 SET SKIPLINT=0
75-:: lets check if we pass the /skip-lint param, if we did we remember and use shift
76-if "%1" == "/skip-lint" SET SKIPLINT=1
77+IF "%1" == "/skip-lint" (
78+ SET SKIPLINT=1
79+ GOTO :CLEANPARAMS
80+)ELSE (
81+ GOTO :CONTINUEBATCH)
82+:CLEANPARAMS
83+
84+SHIFT
85+SET PARAMS=%1
86+:GETREST
87+SHIFT
88+if [%1]==[] (
89+ GOTO CONTINUEBATCH)
90+SET PARAMS=%PARAMS% %1
91+GOTO GETREST
92+:CONTINUEBATCH
93+
94
95 "%PYTHONEXEPATH%\python.exe" setup.py build
96 ECHO Running tests
97 :: execute the tests with a number of ignored linux only modules
98-"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=qt4 --gui -p %IGNORE_PATHS% -i %IGNORE_MODULES% %MODULE%
99+"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=qt4 --gui -p %IGNORE_PATHS% -i %IGNORE_MODULES% %PARAMS% %MODULE%
100 :: Clean the build from the setupt.py
101 ECHO Cleaning the generated code
102 "%PYTHONEXEPATH%\python.exe" setup.py clean
103
104=== modified file 'setup.py'
105--- setup.py 2012-02-02 15:25:58 +0000
106+++ setup.py 2012-02-14 22:15:21 +0000
107@@ -245,9 +245,9 @@
108 'ubuntuone.controlpanel.gui.qt',
109 'ubuntuone.controlpanel.gui.qt.main',
110 'ubuntuone.controlpanel.gui.qt.ui',
111+ 'ubuntuone.controlpanel.gui.qt.uniqueapp',
112 'ubuntuone.controlpanel.sd_client',
113 'ubuntuone.controlpanel.utils',
114- 'ubuntuone.controlpanel.web_client',
115 ],
116 extra_path='ubuntuone-control-panel',
117 data_files=[
118
119=== modified file 'ubuntuone/controlpanel/backend.py'
120--- ubuntuone/controlpanel/backend.py 2012-01-25 14:49:59 +0000
121+++ ubuntuone/controlpanel/backend.py 2012-02-14 22:15:21 +0000
122@@ -34,41 +34,47 @@
123
124 from ubuntuone.controlpanel import sd_client, replication_client
125 from ubuntuone.controlpanel.logger import setup_logging, log_call
126-# pylint: disable=W0611
127-from ubuntuone.controlpanel.web_client import (UnauthorizedError,
128- web_client_factory, WebClientError)
129-# pylint: enable=W0611
130+from ubuntuone.controlpanel.web_client import (
131+ UnauthorizedError,
132+ WebClient,
133+ WebClientError,
134+)
135
136 logger = setup_logging('backend')
137
138-ACCOUNT_API = "account/"
139-QUOTA_API = "quota/"
140-DEVICES_API = "1.0/devices/"
141-DEVICE_REMOVE_API = "1.0/devices/remove/%s/%s"
142-DEVICE_TYPE_PHONE = "Phone"
143-DEVICE_TYPE_COMPUTER = "Computer"
144-AUTOCONNECT_KEY = 'autoconnect'
145-SHOW_ALL_NOTIFICATIONS_KEY = 'show_all_notifications'
146-SHARE_AUTOSUBSCRIBE_KEY = 'share_autosubscribe'
147-UDF_AUTOSUBSCRIBE_KEY = 'udf_autosubscribe'
148-LIMIT_BW_KEY = 'limit_bandwidth'
149-UPLOAD_KEY = "max_upload_speed"
150-DOWNLOAD_KEY = "max_download_speed"
151-
152-FILE_SYNC_DISABLED = 'file-sync-disabled'
153-FILE_SYNC_DISCONNECTED = 'file-sync-disconnected'
154-FILE_SYNC_ERROR = 'file-sync-error'
155-FILE_SYNC_IDLE = 'file-sync-idle'
156-FILE_SYNC_STARTING = 'file-sync-starting'
157-FILE_SYNC_STOPPED = 'file-sync-stopped'
158-FILE_SYNC_SYNCING = 'file-sync-syncing'
159-FILE_SYNC_UNKNOWN = 'file-sync-unknown'
160+ACCOUNT_API = u"account/"
161+DEVICES_API = u"1.0/devices/"
162+DEVICE_REMOVE_API = u"1.0/devices/remove/%s/%s"
163+QUOTA_API = u"quota/"
164+
165+DEVICE_TYPE_PHONE = u"Phone"
166+DEVICE_TYPE_COMPUTER = u"Computer"
167+
168+AUTOCONNECT_KEY = u'autoconnect'
169+SHOW_ALL_NOTIFICATIONS_KEY = u'show_all_notifications'
170+SHARE_AUTOSUBSCRIBE_KEY = u'share_autosubscribe'
171+UDF_AUTOSUBSCRIBE_KEY = u'udf_autosubscribe'
172+LIMIT_BW_KEY = u'limit_bandwidth'
173+UPLOAD_KEY = u'max_upload_speed'
174+DOWNLOAD_KEY = u'max_download_speed'
175+
176+FILE_SYNC_DISABLED = u'file-sync-disabled'
177+FILE_SYNC_DISCONNECTED = u'file-sync-disconnected'
178+FILE_SYNC_ERROR = u'file-sync-error'
179+FILE_SYNC_IDLE = u'file-sync-idle'
180+FILE_SYNC_STARTING = u'file-sync-starting'
181+FILE_SYNC_STOPPED = u'file-sync-stopped'
182+FILE_SYNC_SYNCING = u'file-sync-syncing'
183+FILE_SYNC_UNKNOWN = u'file-sync-unknown'
184
185 MSG_KEY = 'message'
186 STATUS_KEY = 'status'
187
188 CONTACTS_PKG = 'thunderbird-couchdb'
189
190+UBUNTUONE_FROM_OAUTH = u'https://one.ubuntu.com/api/1.0/from_oauth/'
191+UBUNTUONE_LINK = u'https://one.ubuntu.com/'
192+
193
194 def append_path_sep(path):
195 """If 'path' does not end with the path separator, append it."""
196@@ -135,7 +141,7 @@
197
198 self.login_client = CredentialsManagementTool()
199 self.sd_client = sd_client.SyncDaemonClient()
200- self.wc = web_client_factory(self.get_credentials)
201+ self.wc = WebClient(self.get_credentials)
202
203 logger.info('ControlBackend: instance started.')
204
205@@ -304,6 +310,19 @@
206 returnValue(path)
207
208 @inlineCallbacks
209+ def build_signed_iri(self, iri):
210+ """Returned an OAuth signed iri."""
211+ credentials = None
212+ if iri.startswith(UBUNTUONE_LINK):
213+ credentials = yield self.get_credentials()
214+
215+ if credentials:
216+ parameters = {'next': iri}
217+ iri = yield self.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH,
218+ parameters)
219+ returnValue(iri)
220+
221+ @inlineCallbacks
222 def get_credentials(self):
223 """Find credentials."""
224 if not self._credentials:
225
226=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
227--- ubuntuone/controlpanel/gui/__init__.py 2011-10-06 20:38:39 +0000
228+++ ubuntuone/controlpanel/gui/__init__.py 2012-02-14 22:15:21 +0000
229@@ -21,56 +21,55 @@
230
231 import gettext
232
233-from oauth import oauth
234+from ubuntuone.controlpanel.backend import UBUNTUONE_LINK
235+
236
237 _ = gettext.gettext
238
239
240-ERROR_COLOR = 'red'
241+ERROR_COLOR = u'red'
242 KILOBYTES = 1024
243 NO_OP = lambda *a, **kw: None
244 # http://design.canonical.com/the-toolkit/ubuntu-logo-and-circle-of-friends/
245-ORANGE = '#DD4814'
246+ORANGE = u'#DD4814'
247 QUOTA_THRESHOLD = 0.95
248 SHARES_MIN_SIZE_FULL = 1048576
249-SUCCESS_COLOR = 'green'
250+SUCCESS_COLOR = u'green'
251
252 ERROR_ICON = u'✘'
253 SYNCING_ICON = u'⇅'
254 IDLE_ICON = u'✔'
255
256-CONTACT_ICON_NAME = 'avatar-default'
257-FOLDER_ICON_NAME = 'folder'
258-SHARE_ICON_NAME = 'folder-remote'
259-MUSIC_ICON_NAME = 'audio-x-generic'
260-
261-CONTACTS_ICON = 'contacts.png'
262-FACEBOOK_LOGO = 'facebook.png'
263-FILES_ICON = 'files.png'
264-OVERVIEW_BANNER = 'overview.png'
265-TWITTER_LOGO = 'twitter.png'
266-MUSIC_STORE_ICON = 'music-store.png'
267-MUSIC_STREAM_ICON = 'music-stream.png'
268-NOTES_ICON = 'notes.png'
269-SERVICES_CONTACTS_ICON = 'services-contacts.png'
270-SERVICES_FILES_EXAMPLE = 'services-files-example.png'
271-SERVICES_FILES_ICON = 'services-files.png'
272-
273-FILE_URI_PREFIX = 'file://'
274-UBUNTUONE_FROM_OAUTH = 'https://one.ubuntu.com/api/1.0/from_oauth/'
275-UBUNTUONE_LINK = 'https://one.ubuntu.com/'
276+CONTACT_ICON_NAME = u'avatar-default'
277+FOLDER_ICON_NAME = u'folder'
278+SHARE_ICON_NAME = u'folder-remote'
279+MUSIC_ICON_NAME = u'audio-x-generic'
280+
281+CONTACTS_ICON = u'contacts.png'
282+FACEBOOK_LOGO = u'facebook.png'
283+FILES_ICON = u'files.png'
284+OVERVIEW_BANNER = u'overview.png'
285+TWITTER_LOGO = u'twitter.png'
286+MUSIC_STORE_ICON = u'music-store.png'
287+MUSIC_STREAM_ICON = u'music-stream.png'
288+NOTES_ICON = u'notes.png'
289+SERVICES_CONTACTS_ICON = u'services-contacts.png'
290+SERVICES_FILES_EXAMPLE = u'services-files-example.png'
291+SERVICES_FILES_ICON = u'services-files.png'
292+
293+FILE_URI_PREFIX = u'file://'
294
295 CONTACTS_LINK = UBUNTUONE_LINK
296-EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + 'account/'
297-EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + 'machines/'
298-EDIT_PROFILE_LINK = 'https://login.ubuntu.com/'
299-EDIT_SERVICES_LINK = UBUNTUONE_LINK + 'services'
300-FACEBOOK_LINK = 'http://www.facebook.com/ubuntuone/'
301-GET_SUPPORT_LINK = UBUNTUONE_LINK + 'support/'
302+EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + u'account/'
303+EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + u'machines/'
304+EDIT_PROFILE_LINK = u'https://login.ubuntu.com/'
305+EDIT_SERVICES_LINK = UBUNTUONE_LINK + u'services'
306+FACEBOOK_LINK = u'http://www.facebook.com/ubuntuone/'
307+GET_SUPPORT_LINK = UBUNTUONE_LINK + u'support/'
308 LEARN_MORE_LINK = UBUNTUONE_LINK
309-MANAGE_FILES_LINK = UBUNTUONE_LINK + 'files/'
310-RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + '+forgot_password'
311-TWITTER_LINK = 'http://twitter.com/ubuntuone/'
312+MANAGE_FILES_LINK = UBUNTUONE_LINK + u'files/'
313+RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + u'+forgot_password'
314+TWITTER_LINK = u'http://twitter.com/ubuntuone/'
315
316 ALWAYS_SUBSCRIBED = _('Always in sync')
317 CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One')
318@@ -86,7 +85,7 @@
319 'previous values were restored.')
320 DEVICE_CONFIRM_REMOVE = _('Are you sure you want to remove this device '
321 'from Ubuntu One?')
322-DEVICE_REMOVABLE_PREFIX = 'Ubuntu One @ '
323+DEVICE_REMOVABLE_PREFIX = u'Ubuntu One @ '
324 DEVICE_REMOVAL_ERROR = _('The device could not be removed.')
325 DEVICES_BUTTON_TOOLTIP = _('Manage devices registered with your personal '
326 'cloud')
327@@ -185,19 +184,3 @@
328 str_bytes = str_bytes.rstrip('0')
329 str_bytes = str_bytes.rstrip('.')
330 return '%s %s' % (str_bytes, units[unit])
331-
332-
333-def sign_url(url, credentials, timestamp=None):
334- """Sign the URL using the currently available credentials."""
335- consumer = oauth.OAuthConsumer(credentials["consumer_key"],
336- credentials["consumer_secret"])
337- token = oauth.OAuthToken(credentials["token"],
338- credentials["token_secret"])
339- parameters = {'next': url, 'oauth_timestamp': timestamp}
340- request = oauth.OAuthRequest.from_consumer_and_token(
341- http_url=UBUNTUONE_FROM_OAUTH, http_method='GET',
342- oauth_consumer=consumer, token=token,
343- parameters=parameters)
344- sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
345- request.sign_request(sig_method, consumer, token)
346- return request.to_url()
347
348=== modified file 'ubuntuone/controlpanel/gui/gtk/gui.py'
349--- ubuntuone/controlpanel/gui/gtk/gui.py 2012-01-23 20:15:09 +0000
350+++ ubuntuone/controlpanel/gui/gtk/gui.py 2012-02-14 22:15:21 +0000
351@@ -178,6 +178,9 @@
352 label.set_markup(WARNING_MARKUP % message)
353 label.show()
354
355+ def destroy(self):
356+ """Cleanup."""
357+
358
359 class UbuntuOneBin(gtk.VBox):
360 """A Ubuntu One bin."""
361@@ -248,7 +251,7 @@
362
363 __gsignals__ = {
364 'credentials-found': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
365- (gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT)),
366+ (gobject.TYPE_BOOLEAN,)),
367 }
368
369 def __init__(self, main_window):
370@@ -345,7 +348,7 @@
371 def on_credentials_found(self, credentials):
372 """Credentials backend notifies of credentials found."""
373 self.set_property('greyed', False)
374- self.emit('credentials-found', self._credentials_are_new, credentials)
375+ self.emit('credentials-found', self._credentials_are_new)
376
377 @log_call(logger.info)
378 def on_credentials_not_found(self):
379@@ -1568,8 +1571,7 @@
380 """Show the overview panel."""
381 self.set_current_page(0)
382
383- def on_show_management_panel(self, widget=None,
384- credentials_are_new=False, token=None):
385+ def on_show_management_panel(self, widget=None, credentials_are_new=False):
386 """Show the notebook (main panel)."""
387 if self.get_current_page() == 0:
388 self.management.load()
389
390=== modified file 'ubuntuone/controlpanel/gui/gtk/tests/__init__.py'
391--- ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2011-10-24 21:48:27 +0000
392+++ ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2012-02-14 22:15:21 +0000
393@@ -202,21 +202,12 @@
394
395 if self.klass is not None:
396 self.ui = self.klass(**self.kwargs)
397+ self.addCleanup(self.ui.destroy)
398
399 self.memento = MementoHandler()
400 self.memento.setLevel(logging.DEBUG)
401 gui.logger.addHandler(self.memento)
402
403- @defer.inlineCallbacks
404- def tearDown(self):
405- try:
406- self.ui.hide()
407- del self.ui
408- self.ui = None
409- except AttributeError:
410- pass
411- yield super(BaseTestCase, self).tearDown()
412-
413 def assert_image_equal(self, image, filename):
414 """Check that expected and actual represent the same image."""
415 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
416
417=== modified file 'ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py'
418--- ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2011-11-21 13:32:44 +0000
419+++ ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2012-02-14 22:15:21 +0000
420@@ -278,7 +278,7 @@
421
422 """
423 self.patch(self.ui.management, 'load', self._set_called)
424- self.ui.overview.emit('credentials-found', False, object())
425+ self.ui.overview.emit('credentials-found', False)
426
427 self.assert_current_tab_correct(self.ui.management)
428 self.assertEqual(self.ui.management.notebook.get_current_page(),
429@@ -291,8 +291,7 @@
430 If first signal parameter is True, visible tab should be services.
431
432 """
433- a_token = object()
434- self.ui.overview.emit('credentials-found', True, a_token)
435+ self.ui.overview.emit('credentials-found', True)
436
437 self.assert_current_tab_correct(self.ui.management)
438 self.assertEqual(self.ui.management.notebook.get_current_page(),
439@@ -301,19 +300,19 @@
440 def test_credentials_found_connects_syncdaemon(self):
441 """On 'credentials-found' signal, ask syncdaemon to connect."""
442 # credentials are new
443- self.ui.overview.emit('credentials-found', True, object())
444+ self.ui.overview.emit('credentials-found', True)
445 self.assert_backend_called('connect_files')
446
447 def test_local_device_removed_shows_overview_panel(self):
448 """On 'local-device-removed' signal, the overview panel is shown."""
449- self.ui.overview.emit('credentials-found', True, object())
450+ self.ui.overview.emit('credentials-found', True)
451 self.ui.management.emit('local-device-removed')
452
453 self.assert_current_tab_correct(self.ui.overview)
454
455 def test_unauthorized_shows_overview_panel(self):
456 """On 'unauthorized' signal, the overview panel is shown."""
457- self.ui.overview.emit('credentials-found', True, object())
458+ self.ui.overview.emit('credentials-found', True)
459 self.ui.management.emit('unauthorized')
460
461 self.assert_current_tab_correct(self.ui.overview)
462@@ -628,7 +627,7 @@
463
464 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
465 # assume credentials were in local keyring
466- self.assertEqual(self._called, ((self.ui, False, TOKEN), {}))
467+ self.assertEqual(self._called, ((self.ui, False), {}))
468
469 def test_on_credentials_found_when_creds_are_not_new(self):
470 """Callback 'on_credentials_found' distinguish if creds are new."""
471@@ -641,7 +640,7 @@
472
473 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
474 # assume credentials were not in local keyring
475- self.assertEqual(self._called, ((self.ui, True, TOKEN), {}))
476+ self.assertEqual(self._called, ((self.ui, True), {}))
477
478 def test_on_credentials_not_found(self):
479 """Callback 'on_credentials_not_found' is correct."""
480
481=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
482--- ubuntuone/controlpanel/gui/qt/folders.py 2012-01-18 17:40:33 +0000
483+++ ubuntuone/controlpanel/gui/qt/folders.py 2012-02-14 22:15:21 +0000
484@@ -1,8 +1,6 @@
485 # -*- coding: utf-8 -*-
486-
487-# Authors: Alejandro J. Cura <alecu@canonical.com>
488 #
489-# Copyright 2011 Canonical Ltd.
490+# Copyright 2011-2012 Canonical Ltd.
491 #
492 # This program is free software: you can redistribute it and/or modify it
493 # under the terms of the GNU General Public License version 3, as published
494@@ -85,10 +83,7 @@
495 def on_folder_created(self, new_folder):
496 """Reload folder info after folder creation."""
497 self.is_processing = True
498- # hack to ensure that syncdaemon updates the folder list.
499- # pylint: disable=W0404, E1101
500- from twisted.internet import reactor
501- reactor.callLater(2, self.load)
502+ self.load()
503
504 # pylint: disable=E0202
505 @defer.inlineCallbacks
506
507=== modified file 'ubuntuone/controlpanel/gui/qt/gotoweb.py'
508--- ubuntuone/controlpanel/gui/qt/gotoweb.py 2011-10-05 23:12:23 +0000
509+++ ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-02-14 22:15:21 +0000
510@@ -23,8 +23,7 @@
511 from twisted.internet import defer
512
513 from ubuntuone.controlpanel import cache
514-from ubuntuone.controlpanel.gui import qt, sign_url, UBUNTUONE_LINK
515-from ubuntuone.controlpanel.web_client.txwebclient import timestamp_checker
516+from ubuntuone.controlpanel.gui import qt
517
518
519 class GoToWebButton(cache.Cache, QtGui.QPushButton):
520@@ -44,14 +43,5 @@
521 def on_clicked(self):
522 """Open self.uri if not None, do nothing otherwise."""
523 if self.uri is not None:
524-
525- credentials = None
526- if self.uri.startswith(UBUNTUONE_LINK):
527- credentials = yield self.backend.get_credentials()
528-
529- uri = self.uri
530- if credentials:
531- timestamp = yield timestamp_checker.get_faithful_time()
532- uri = yield sign_url(uri, credentials, timestamp)
533-
534+ uri = yield self.backend.build_signed_iri(self.uri)
535 qt.uri_hook(uri)
536
537=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
538--- ubuntuone/controlpanel/gui/qt/gui.py 2011-09-15 14:42:24 +0000
539+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-02-14 22:15:21 +0000
540@@ -52,20 +52,20 @@
541 # pylint: enable=C0103
542
543
544-def start(stop, minimized=False, with_icon=False):
545+def start(close_callback, minimized=False, with_icon=False):
546 """Show the UI elements."""
547 # pylint: disable=W0404, F0401
548 if not minimized:
549 if with_icon or minimized:
550 window = MainWindow()
551 else:
552- window = MainWindow(close_callback=stop)
553+ window = MainWindow(close_callback=close_callback)
554 window.show()
555 else:
556 window = None
557 if with_icon or minimized:
558 QtGui.QApplication.instance().setQuitOnLastWindowClosed(False)
559- icon = TrayIcon(window=window)
560+ icon = TrayIcon(window=window, close_callback=close_callback)
561 else:
562 icon = None
563 return icon, window
564
565=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
566--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-01-19 19:00:54 +0000
567+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-02-14 22:15:21 +0000
568@@ -14,7 +14,7 @@
569 # You should have received a copy of the GNU General Public License along
570 # with this program. If not, see <http://www.gnu.org/licenses/>.
571
572-"""Provide the correct reactor and ui integration."""
573+"""Provide the correct ui main module."""
574
575 import sys
576
577@@ -37,32 +37,23 @@
578
579
580 def main(switch_to='', alert=False, minimized=False, with_icon=False):
581- """Start the Qt reactor and open the main window."""
582+ """Start the Qt mainloop and open the main window."""
583 # The following cannot be imported outside this function
584 # because u1trial already provides a reactor.
585
586- # The main loop MUST be initialized before importing the reactor
587 app = UniqueApplication(sys.argv, "ubuntuone-control-panel")
588 source.main(app)
589+
590 qss = QtCore.QResource(":/ubuntuone.qss")
591 app.setStyleSheet(qss.data())
592
593- # Reimport 'qt4reactor', 'reactor', 'start', pylint: disable=W0404, F0401
594- import qt4reactor
595- qt4reactor.install()
596-
597- from twisted.internet import reactor
598 from ubuntuone.controlpanel.gui.qt.gui import start
599- # pylint: enable=W0404, F0401
600-
601- # Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101
602
603 # Unused variable 'window', 'icon', pylint: disable=W0612
604- icon, window = start(reactor.stop,
605+ icon, window = start(lambda: source.main_quit(app),
606 minimized=minimized, with_icon=with_icon)
607 # pylint: enable=W0612
608 if icon:
609 app.new_instance.connect(icon.restore_window)
610
611- reactor.run()
612- # pylint: enable=E1101
613+ source.main_start(app)
614
615=== modified file 'ubuntuone/controlpanel/gui/qt/main/linux.py'
616--- ubuntuone/controlpanel/gui/qt/main/linux.py 2012-01-26 12:41:08 +0000
617+++ ubuntuone/controlpanel/gui/qt/main/linux.py 2012-02-14 22:15:21 +0000
618@@ -16,10 +16,18 @@
619
620 """Main method to be used on linux."""
621
622-from dbus.mainloop.qt import DBusQtMainLoop
623-
624
625 def main(app):
626 """Apply style sheet."""
627- # The DBus main loop MUST be initialized before importing the reactor
628+ from dbus.mainloop.qt import DBusQtMainLoop
629 DBusQtMainLoop(set_as_default=True)
630+
631+
632+def main_start(app):
633+ """Start the mainloop."""
634+ app.exec_()
635+
636+
637+def main_quit(app):
638+ """Stop the mainloop."""
639+ app.exit()
640
641=== modified file 'ubuntuone/controlpanel/gui/qt/main/windows.py'
642--- ubuntuone/controlpanel/gui/qt/main/windows.py 2012-01-26 12:41:08 +0000
643+++ ubuntuone/controlpanel/gui/qt/main/windows.py 2012-02-14 22:15:21 +0000
644@@ -24,3 +24,24 @@
645 # Apply font to the entire application
646 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')
647 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')
648+
649+ import qt4reactor
650+ qt4reactor.install()
651+
652+
653+# Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101
654+
655+
656+def main_start(app):
657+ """Start the mainloop."""
658+ from twisted.internet import reactor
659+ reactor.run()
660+
661+
662+def main_quit(app):
663+ """Stop the mainloop."""
664+ from twisted.internet import reactor
665+ reactor.stop()
666+
667+
668+# pylint: enable=E1101
669
670=== modified file 'ubuntuone/controlpanel/gui/qt/systray.py'
671--- ubuntuone/controlpanel/gui/qt/systray.py 2011-12-27 13:54:49 +0000
672+++ ubuntuone/controlpanel/gui/qt/systray.py 2012-02-14 22:15:21 +0000
673@@ -25,7 +25,7 @@
674
675 """System notification icon."""
676
677- def __init__(self, window=None):
678+ def __init__(self, window=None, close_callback=lambda: None):
679 super(TrayIcon, self).__init__(None)
680 self.setIcon(QtGui.QIcon(":/u1icon.png"))
681 self.setVisible(True)
682@@ -41,6 +41,8 @@
683 self.context_menu.addAction(self.quit)
684 self.setContextMenu(self.context_menu)
685
686+ self.close_callback = close_callback
687+
688 def on_activated(self, reason):
689 """The user activated the icon."""
690 if reason == self.Trigger: # Left-click
691@@ -73,7 +75,4 @@
692 except:
693 # Maybe it was not running?
694 pass
695- # pylint: disable=W0404
696- from twisted.internet import reactor
697- # pylint: enable=W0404
698- reactor.stop()
699+ self.close_callback()
700
701=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
702--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-01-18 19:52:25 +0000
703+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-02-14 22:15:21 +0000
704@@ -20,7 +20,6 @@
705
706 import logging
707 import os
708-import urllib
709
710 from PyQt4 import QtGui, QtCore
711 from twisted.internet import defer
712@@ -28,8 +27,7 @@
713
714 from ubuntuone.controlpanel import backend, cache
715 from ubuntuone.controlpanel.tests import TestCase, EXPECTED_ACCOUNT_INFO, TOKEN
716-from ubuntuone.controlpanel.gui import qt, UBUNTUONE_FROM_OAUTH
717-from ubuntuone.controlpanel.gui.qt import gotoweb
718+from ubuntuone.controlpanel.gui import qt
719 from ubuntuone.controlpanel.gui.tests import FakedObject, USER_HOME
720
721 # Attribute 'yyy' defined outside __init__, access to a protected member
722@@ -107,19 +105,31 @@
723
724 next_result = []
725 exposed_methods = [
726- 'account_info', # account
727- 'devices_info', 'device_names_info', # devices
728- 'change_device_settings', 'remove_device',
729- 'volumes_info', 'change_volume_settings', # volumes
730- 'create_folder', 'validate_path_for_folder',
731- 'replications_info', 'change_replication_settings', # replications
732- 'file_sync_status', 'enable_files', 'disable_files', # files
733- 'connect_files', 'disconnect_files',
734- 'restart_files', 'start_files', 'stop_files',
735+ 'account_info',
736+ 'build_signed_iri',
737+ 'change_device_settings',
738+ 'change_file_sync_settings',
739+ 'change_replication_settings',
740+ 'change_volume_settings',
741+ 'connect_files',
742+ 'create_folder',
743+ 'device_names_info',
744+ 'devices_info',
745+ 'disable_files',
746+ 'disconnect_files',
747+ 'enable_files',
748 'file_sync_settings_info',
749- 'change_file_sync_settings',
750+ 'file_sync_status',
751+ 'login',
752+ 'remove_device',
753+ 'replications_info',
754+ 'restart_files',
755 'restore_file_sync_settings',
756- 'shutdown', 'login',
757+ 'shutdown',
758+ 'start_files',
759+ 'stop_files',
760+ 'validate_path_for_folder',
761+ 'volumes_info',
762 ]
763
764 def get_credentials(self):
765@@ -131,6 +141,9 @@
766 """Fake home return."""
767 return USER_HOME
768
769+ def build_signed_iri(self, iri):
770+ """Fake iri signing."""
771+
772
773 class CrashyBackendException(Exception):
774 """A faked backend crash."""
775@@ -284,19 +297,14 @@
776
777 def assert_uri_hook_called(self, button, url):
778 """Check that uri_hook was called with 'url' when clicking 'button'."""
779- fake_time = 12345678
780+ self.patch(self.ui.backend, 'next_result', url)
781 self.patch(qt, 'uri_hook', self._set_called)
782- self.patch(gotoweb.timestamp_checker, "get_faithful_time",
783- lambda: defer.succeed(fake_time))
784 button.click()
785
786 self.assertEqual(len(self._called), 2, 'uri_hook must be called.')
787 self.assertEqual(len(self._called[0]), 1, 'uri_hook must be called.')
788 actual_url = self._called[0][0]
789- if actual_url.startswith(UBUNTUONE_FROM_OAUTH):
790- self.assertIn(urllib.urlencode({'next': url}), actual_url)
791- else:
792- self.assertEqual(actual_url, url)
793+ self.assertEqual(actual_url, url)
794
795 def test_init_loads_ui(self, expected_setup_ui=None):
796 """The __init__ method loads the ui."""
797
798=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py'
799--- ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2011-10-06 20:33:42 +0000
800+++ ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-02-14 22:15:21 +0000
801@@ -20,7 +20,7 @@
802
803 from twisted.internet import defer
804
805-from ubuntuone.controlpanel.gui import qt, UBUNTUONE_LINK
806+from ubuntuone.controlpanel.gui import qt
807 from ubuntuone.controlpanel.gui.qt import gotoweb as gui
808 from ubuntuone.controlpanel.gui.qt.tests import (
809 BaseTestCase,
810@@ -53,11 +53,14 @@
811
812 def test_open_uri_when_clicked(self):
813 """When clicking the button, the uri is opened."""
814+ expected_iri = 'foo-bar-baz'
815+ self.patch(self.ui.backend, 'build_signed_iri', lambda i: expected_iri)
816 self.patch(qt, 'uri_hook', self._set_called)
817 self.ui.uri = 'yadda-yadda-yoo'
818+
819 self.ui.click()
820
821- self.assertEqual(self._called, ((self.ui.uri,), {}))
822+ self.assertEqual(self._called, ((expected_iri,), {}))
823
824 def test_do_nothing_on_clicked_if_uri_is_none(self):
825 """When clicking the button, if the uri is None, do nothing."""
826@@ -66,53 +69,3 @@
827 self.ui.click()
828
829 self.assertEqual(self._called, False)
830-
831-
832-class SignUrlTestCase(GoToWebButtonTestCase):
833- """The test suite for the sign url management."""
834-
835- @defer.inlineCallbacks
836- def setUp(self):
837- yield super(SignUrlTestCase, self).setUp()
838- self.patch(qt, 'uri_hook', lambda url: None)
839- self.patch(gui, 'sign_url', self._set_called)
840- self.creds = yield self.ui.backend.get_credentials()
841- assert len(self.creds) > 0
842-
843- def test_without_ubuntuone_prefix(self):
844- """If given url is not an ubuntuone url, don't sign it."""
845- self.ui.uri = 'bad_prefix' + UBUNTUONE_LINK
846- self.ui.click()
847-
848- self.assertFalse(self._called)
849-
850- def test_with_ubuntuone_prefix(self):
851- """If given url is an ubuntuone url, sign it."""
852- fake_time = 12345
853- self.ui.uri = UBUNTUONE_LINK + 'foo'
854- self.patch(gui.timestamp_checker, "get_faithful_time",
855- lambda: defer.succeed(fake_time))
856- self.ui.click()
857-
858- expected_call = ((self.ui.uri, self.creds, fake_time), {})
859- self.assertEqual(self._called, expected_call)
860-
861-
862-class SignUrlNoCredsTestCase(SignUrlTestCase):
863- """The test suite for the sign url management when there are no creds."""
864-
865- @defer.inlineCallbacks
866- def setUp(self):
867- yield super(SignUrlNoCredsTestCase, self).setUp()
868- self.patch(self.ui.backend, 'get_credentials', lambda: {})
869-
870- def test_with_ubuntuone_prefix(self):
871- """If given url is an ubuntuone url, don't sign it.
872-
873- Since we have no credentials, the url should not be signed.
874-
875- """
876- self.ui.uri = UBUNTUONE_LINK + 'foo'
877- self.ui.click()
878-
879- self.assertFalse(self._called)
880
881=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_signin.py'
882--- ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2011-09-07 17:56:17 +0000
883+++ ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2012-02-14 22:15:21 +0000
884@@ -20,7 +20,6 @@
885
886 from twisted.internet import defer
887
888-from ubuntuone.controlpanel.gui import qt
889 from ubuntuone.controlpanel.gui.qt import signin as gui
890 from ubuntuone.controlpanel.gui.qt.tests import (
891 CrashyBackend,
892@@ -107,10 +106,8 @@
893
894 def test_forgot_password_button(self):
895 """When clicking the forgot passsword btn, the proper url is opened."""
896- self.patch(qt, 'uri_hook', self._set_called)
897- self.ui.ui.forgot_password_button.click()
898-
899- self.assertEqual(self._called, ((gui.RESET_PASSWORD_LINK,), {}))
900+ self.assert_uri_hook_called(self.ui.ui.forgot_password_button,
901+ gui.RESET_PASSWORD_LINK)
902
903
904 class SignInButtonPanelTestCase(BaseSignInPanelTestCase):
905
906=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_start.py'
907--- ubuntuone/controlpanel/gui/qt/tests/test_start.py 2011-09-15 15:37:04 +0000
908+++ ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-02-14 22:15:21 +0000
909@@ -1,91 +1,89 @@
910-# -*- coding: utf-8 -*-
911-
912-# Author: Roberto Alsina <roberto.alsina@canonical.com>
913-#
914-# Copyright 2011 Canonical Ltd.
915-#
916-# This program is free software: you can redistribute it and/or modify it
917-# under the terms of the GNU General Public License version 3, as published
918-# by the Free Software Foundation.
919-#
920-# This program is distributed in the hope that it will be useful, but
921-# WITHOUT ANY WARRANTY; without even the implied warranties of
922-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
923-# PURPOSE. See the GNU General Public License for more details.
924-#
925-# You should have received a copy of the GNU General Public License along
926-# with this program. If not, see <http://www.gnu.org/licenses/>.
927-
928-"""Tests for the start function."""
929-
930-from twisted.internet import defer
931-
932-from ubuntuone.controlpanel.gui.qt import gui
933-from ubuntuone.controlpanel.gui.qt.tests import NO_OP
934-from ubuntuone.controlpanel.tests import TestCase
935-
936-
937-class FakeThing(object):
938-
939- """A fake thing."""
940-
941- def __init__(self):
942- self.args = []
943- self.shown = False
944-
945- def __call__(self, *args, **kwargs):
946- self.args.append((args, kwargs))
947- return self
948-
949- def show(self):
950- """Show."""
951- self.shown = True
952-
953-
954-class FakeReactor(object):
955- """A fake reactor."""
956-
957- def run(self):
958- """Start."""
959-
960- def stop(self):
961- """Stop."""
962-
963-
964-class StartTestCase(TestCase):
965- """Test the qt control panel."""
966-
967- @defer.inlineCallbacks
968- def setUp(self):
969- yield super(StartTestCase, self).setUp()
970- self.main_window = FakeThing()
971- self.tray_icon = FakeThing()
972- self.patch(gui, "MainWindow", self.main_window)
973- self.patch(gui, "TrayIcon", self.tray_icon)
974-
975- def test_minimized(self):
976- """Test behaviour with minimized=True."""
977- gui.start(NO_OP, minimized=True, with_icon=True)
978- self.assertEqual(self.tray_icon.args, [((), {'window': None})])
979- self.assertEqual(self.main_window.args, [])
980-
981- def test_with_icon(self):
982- """Test behaviour with with_icon=True."""
983- gui.start(NO_OP, with_icon=True, minimized=False)
984- self.assertEqual(self.main_window.args, [((), {})])
985- self.assertEqual(self.tray_icon.args, [((),
986- {'window': self.main_window})])
987-
988- def test_both_false(self):
989- """Test behaviour when with_icon and minimized are False."""
990- gui.start(NO_OP, with_icon=False, minimized=False)
991- # Should be called
992- self.assertNotEqual(self.main_window.args, [])
993- # Should not be called
994- self.assertEqual(self.tray_icon.args, [])
995-
996- def test_both_true(self):
997- """Test behaviour when with_icon and minimized are True."""
998- gui.start(NO_OP, with_icon=True, minimized=True)
999- self.assertEqual(self.tray_icon.args, [((), {'window': None})])
1000- self.assertEqual(self.main_window.args, [])
1001+# -*- coding: utf-8 -*-
1002+
1003+# Author: Roberto Alsina <roberto.alsina@canonical.com>
1004+#
1005+# Copyright 2011 Canonical Ltd.
1006+#
1007+# This program is free software: you can redistribute it and/or modify it
1008+# under the terms of the GNU General Public License version 3, as published
1009+# by the Free Software Foundation.
1010+#
1011+# This program is distributed in the hope that it will be useful, but
1012+# WITHOUT ANY WARRANTY; without even the implied warranties of
1013+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1014+# PURPOSE. See the GNU General Public License for more details.
1015+#
1016+# You should have received a copy of the GNU General Public License along
1017+# with this program. If not, see <http://www.gnu.org/licenses/>.
1018+
1019+"""Tests for the start function."""
1020+
1021+from twisted.internet import defer
1022+
1023+from ubuntuone.controlpanel.gui.qt import gui
1024+from ubuntuone.controlpanel.tests import TestCase
1025+
1026+
1027+class FakeThing(object):
1028+
1029+ """A fake thing."""
1030+
1031+ def __init__(self):
1032+ self.args = []
1033+ self.shown = False
1034+
1035+ def __call__(self, *args, **kwargs):
1036+ self.args.append((args, kwargs))
1037+ return self
1038+
1039+ def show(self):
1040+ """Show."""
1041+ self.shown = True
1042+
1043+
1044+class StartTestCase(TestCase):
1045+ """Test the qt control panel."""
1046+
1047+ @defer.inlineCallbacks
1048+ def setUp(self):
1049+ yield super(StartTestCase, self).setUp()
1050+ self.main_window = FakeThing()
1051+ self.tray_icon = FakeThing()
1052+ self.patch(gui, "MainWindow", self.main_window)
1053+ self.patch(gui, "TrayIcon", self.tray_icon)
1054+
1055+ def close_cb(self):
1056+ """A dummy close callback."""
1057+
1058+ def test_minimized(self):
1059+ """Test behaviour with minimized=True."""
1060+ gui.start(close_callback=self.close_cb,
1061+ minimized=True, with_icon=True)
1062+ kwargs = {'close_callback': self.close_cb, 'window': None}
1063+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
1064+ self.assertEqual(self.main_window.args, [])
1065+
1066+ def test_with_icon(self):
1067+ """Test behaviour with with_icon=True."""
1068+ gui.start(close_callback=self.close_cb,
1069+ with_icon=True, minimized=False)
1070+ kwargs = {'close_callback': self.close_cb, 'window': self.main_window}
1071+ self.assertEqual(self.main_window.args, [((), {})])
1072+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
1073+
1074+ def test_both_false(self):
1075+ """Test behaviour when with_icon and minimized are False."""
1076+ gui.start(close_callback=self.close_cb,
1077+ with_icon=False, minimized=False)
1078+ # Should be called
1079+ self.assertNotEqual(self.main_window.args, [])
1080+ # Should not be called
1081+ self.assertEqual(self.tray_icon.args, [])
1082+
1083+ def test_both_true(self):
1084+ """Test behaviour when with_icon and minimized are True."""
1085+ gui.start(close_callback=self.close_cb,
1086+ with_icon=True, minimized=True)
1087+ kwargs = {'close_callback': self.close_cb, 'window': None}
1088+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
1089+ self.assertEqual(self.main_window.args, [])
1090
1091=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
1092--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-01-02 19:38:14 +0000
1093+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-02-14 22:15:21 +0000
1094@@ -19,7 +19,6 @@
1095 """Tests for the notification area icon."""
1096
1097 from PyQt4 import QtGui
1098-from twisted.internet import reactor
1099 from twisted.internet.defer import inlineCallbacks
1100
1101 from ubuntuone.controlpanel.gui.qt import systray
1102@@ -65,7 +64,6 @@
1103 """Quit should call SyncDaemonTool.quit()."""
1104 st = FakeSDTool()
1105 self.patch(systray, "SyncDaemonTool", lambda: st)
1106- self.patch(reactor, "stop", lambda: None)
1107 tray = systray.TrayIcon()
1108 yield tray.stop()
1109 self.assertTrue(st.called)
1110
1111=== removed file 'ubuntuone/controlpanel/gui/tests/test_url_sign.py'
1112--- ubuntuone/controlpanel/gui/tests/test_url_sign.py 2011-10-05 23:12:23 +0000
1113+++ ubuntuone/controlpanel/gui/tests/test_url_sign.py 1970-01-01 00:00:00 +0000
1114@@ -1,102 +0,0 @@
1115-# -*- coding: utf-8 -*-
1116-
1117-# Authors: Roberto Alsina <roberto.alsina@canonical.com>
1118-# Authors: Alejandro J. Cura <alecu@canonical.com>
1119-#
1120-# Copyright 2011 Canonical Ltd.
1121-#
1122-# This program is free software: you can redistribute it and/or modify it
1123-# under the terms of the GNU General Public License version 3, as published
1124-# by the Free Software Foundation.
1125-#
1126-# This program is distributed in the hope that it will be useful, but
1127-# WITHOUT ANY WARRANTY; without even the implied warranties of
1128-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1129-# PURPOSE. See the GNU General Public License for more details.
1130-#
1131-# You should have received a copy of the GNU General Public License along
1132-# with this program. If not, see <http://www.gnu.org/licenses/>.
1133-
1134-"""Tests for the url signing function."""
1135-
1136-from urlparse import urlparse, parse_qs
1137-
1138-from ubuntuone.controlpanel.tests import TestCase
1139-from ubuntuone.controlpanel.gui import (
1140- sign_url,
1141- UBUNTUONE_FROM_OAUTH,
1142-)
1143-
1144-TOKEN = {u'consumer_key': u'consumer_key',
1145- u'consumer_secret': u'consumer_secret',
1146- u'token_name': u'test_token',
1147- u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
1148- u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
1149-
1150-SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \
1151- '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \
1152- 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \
1153- 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \
1154- 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D'
1155-
1156-#pylint: disable=E1101
1157-
1158-
1159-class SignURLTestCase(TestCase):
1160-
1161- """Test cases for the URL signing function."""
1162-
1163- def test_is_correct_domain(self):
1164- """Test that we are using the right domain."""
1165- signed = sign_url("/blah", TOKEN)
1166- parsed_signed = urlparse(signed)
1167- parsed_sample = urlparse(SAMPLE_SIGNED)
1168- self.assertEqual(parsed_signed.netloc, parsed_sample.netloc)
1169-
1170- def test_is_correct_path(self):
1171- """Test that we are using the right path in the URL."""
1172- signed = sign_url("/blah", TOKEN)
1173- parsed_signed = urlparse(signed)
1174- parsed_sample = urlparse(SAMPLE_SIGNED)
1175- self.assertEqual(parsed_signed.path, parsed_sample.path)
1176-
1177- def test_is_correct_scheme(self):
1178- """Test that we are using the right scheme."""
1179- signed = sign_url("/blah", TOKEN)
1180- parsed_signed = urlparse(signed)
1181- parsed_sample = urlparse(SAMPLE_SIGNED)
1182-
1183- self.assertEqual(parsed_signed.scheme, parsed_sample.scheme)
1184-
1185- def test_correct_query(self):
1186- """Test the invariant parts of the signed URL."""
1187- signed = sign_url("/blah", TOKEN)
1188- parsed_signed = urlparse(signed)
1189- parsed_sample = urlparse(SAMPLE_SIGNED)
1190- signed_query = parse_qs(parsed_signed.query)
1191- sample_query = parse_qs(parsed_sample.query)
1192-
1193- for key in ('next',
1194- 'oauth_consumer_key',
1195- 'oauth_signature_method',
1196- 'oauth_token',
1197- 'oauth_version'):
1198- self.assertEqual("%s=%s" % (key, signed_query[key]),
1199- "%s=%s" % (key, sample_query[key]))
1200-
1201- def test_url_with_query(self):
1202- """Test that we are using the right scheme."""
1203- signed = sign_url("/blah?foo=bar", TOKEN)
1204- parsed_signed = urlparse(signed)
1205- signed_query = parse_qs(parsed_signed.query)
1206-
1207- self.assertEqual(signed_query['next'], ['/blah?foo=bar'])
1208-
1209- def test_uses_timestamper(self):
1210- """Test that the signed url is using the server-relative timestamp."""
1211- timestamp = 999
1212- signed = sign_url("/blah?foo=bar", TOKEN, timestamp)
1213- parsed_signed = urlparse(signed)
1214- signed_query = parse_qs(parsed_signed.query)
1215-
1216- self.assertEqual(signed_query['oauth_timestamp'], [str(timestamp)])
1217
1218=== modified file 'ubuntuone/controlpanel/logger.py'
1219--- ubuntuone/controlpanel/logger.py 2011-08-30 16:10:36 +0000
1220+++ ubuntuone/controlpanel/logger.py 2012-02-14 22:15:21 +0000
1221@@ -34,7 +34,7 @@
1222 LOG_LEVEL = logging.DEBUG
1223 else:
1224 # Only log this level and above
1225- LOG_LEVEL = logging.DEBUG # before final release, switch to INFO
1226+ LOG_LEVEL = logging.INFO
1227
1228 FILE_NAME = os.path.join(ubuntuone_log_dir, 'controlpanel.log')
1229 MAIN_HANDLER = RotatingFileHandler(FILE_NAME,
1230@@ -53,7 +53,7 @@
1231 logger = logging.getLogger('ubuntuone.controlpanel.%s' % log_domain)
1232 logger.setLevel(LOG_LEVEL)
1233 logger.addHandler(MAIN_HANDLER)
1234- if os.environ.get('DEBUG'):
1235+ if os.environ.get('U1_DEBUG'):
1236 debug_handler = logging.StreamHandler(sys.stderr)
1237 debug_handler.setFormatter(basic_formatter)
1238 logger.addHandler(debug_handler)
1239
1240=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
1241--- ubuntuone/controlpanel/tests/test_backend.py 2012-01-26 12:44:10 +0000
1242+++ ubuntuone/controlpanel/tests/test_backend.py 2012-02-14 22:15:21 +0000
1243@@ -43,6 +43,8 @@
1244 FILE_SYNC_SYNCING,
1245 FILE_SYNC_UNKNOWN,
1246 MSG_KEY, STATUS_KEY,
1247+ UBUNTUONE_FROM_OAUTH,
1248+ UBUNTUONE_LINK,
1249 )
1250 from ubuntuone.controlpanel.tests import (TestCase,
1251 EMPTY_DESCRIPTION_JSON,
1252@@ -65,6 +67,12 @@
1253 USER_HOME,
1254 )
1255
1256+SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \
1257+ '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \
1258+ 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \
1259+ 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \
1260+ 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D'
1261+
1262
1263 # pylint: disable=E1101, W0201, W0212
1264
1265@@ -104,6 +112,13 @@
1266 result = simplejson.loads(self.results[method])
1267 return defer.succeed(result)
1268
1269+ @defer.inlineCallbacks
1270+ def build_signed_iri(self, iri, params):
1271+ """Fake the IRI signing."""
1272+ creds = yield self.get_credentials()
1273+ result = u'%s-%s-%s' % (iri, unicode(creds), unicode(params))
1274+ defer.returnValue(result)
1275+
1276
1277 class MockLoginClient(CallRecorder):
1278 """A mock login_client module."""
1279@@ -347,7 +362,7 @@
1280 @defer.inlineCallbacks
1281 def setUp(self):
1282 yield super(BackendBasicTestCase, self).setUp()
1283- self.patch(backend, "web_client_factory", MockWebClient)
1284+ self.patch(backend, "WebClient", MockWebClient)
1285 self.patch(backend, "CredentialsManagementTool", MockLoginClient)
1286 self.patch(backend.sd_client, "SyncDaemonClient", MockSDClient)
1287 self.patch(backend, "replication_client", MockReplicationClient())
1288@@ -368,12 +383,14 @@
1289 @inlineCallbacks
1290 def test_get_token(self):
1291 """The get_token method returns the right token."""
1292+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1293 token = yield self.be.get_token()
1294 self.assertEqual(token, TOKEN["token"])
1295
1296 @inlineCallbacks
1297 def test_device_is_local(self):
1298 """The device_is_local returns the right result."""
1299+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1300 result = yield self.be.device_is_local(self.local_token)
1301 self.assertTrue(result)
1302
1303@@ -408,6 +425,54 @@
1304 self.assertIs(self.be.sd_client, self.be.sd_client)
1305
1306
1307+class SignIriTestCase(BackendBasicTestCase):
1308+ """Test cases for the IRI signing function."""
1309+
1310+ @defer.inlineCallbacks
1311+ def setUp(self):
1312+ yield super(SignIriTestCase, self).setUp()
1313+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1314+
1315+ @inlineCallbacks
1316+ def test_without_ubuntuone_prefix(self):
1317+ """If given url is not an ubuntuone url, don't sign it."""
1318+ iri = 'bad_prefix' + UBUNTUONE_LINK
1319+ result = yield self.be.build_signed_iri(iri)
1320+
1321+ self.assertEqual(result, iri)
1322+
1323+ @inlineCallbacks
1324+ def test_with_ubuntuone_prefix(self):
1325+ """If given url is an ubuntuone url, sign it."""
1326+ iri = UBUNTUONE_LINK + 'foo'
1327+ result = yield self.be.build_signed_iri(iri)
1328+
1329+ expected = yield self.be.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH,
1330+ {'next': iri})
1331+ self.assertEqual(expected, result)
1332+
1333+
1334+class SignIriNoCredsTestCase(SignIriTestCase):
1335+ """The test suite for the sign url management when there are no creds."""
1336+
1337+ @defer.inlineCallbacks
1338+ def setUp(self):
1339+ yield super(SignIriNoCredsTestCase, self).setUp()
1340+ self.patch(self.be, 'get_credentials', lambda: defer.succeed({}))
1341+
1342+ @inlineCallbacks
1343+ def test_with_ubuntuone_prefix(self):
1344+ """If given url is an ubuntuone url, don't sign it.
1345+
1346+ Since we have no credentials, the url should not be signed.
1347+
1348+ """
1349+ iri = UBUNTUONE_LINK + 'foo'
1350+ result = yield self.be.build_signed_iri(iri)
1351+
1352+ self.assertEqual(result, iri)
1353+
1354+
1355 class BackendCredentialsTestCase(BackendBasicTestCase):
1356 """Credentials tests for the backend."""
1357
1358
1359=== modified file 'ubuntuone/controlpanel/tests/test_web_client.py'
1360--- ubuntuone/controlpanel/tests/test_web_client.py 2011-10-07 14:36:14 +0000
1361+++ ubuntuone/controlpanel/tests/test_web_client.py 2012-02-14 22:15:21 +0000
1362@@ -1,9 +1,6 @@
1363 # -*- coding: utf-8 -*-
1364-
1365-# Authors: Alejandro J. Cura <alecu@canonical.com>
1366-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1367 #
1368-# Copyright 2010 Canonical Ltd.
1369+# Copyright 2011-2012 Canonical Ltd.
1370 #
1371 # This program is free software: you can redistribute it and/or modify it
1372 # under the terms of the GNU General Public License version 3, as published
1373@@ -17,26 +14,38 @@
1374 # You should have received a copy of the GNU General Public License along
1375 # with this program. If not, see <http://www.gnu.org/licenses/>.
1376
1377-"""Integration tests for the control panel backend webservice client."""
1378-
1379-from twisted.application import internet, service
1380+"""Integration tests for the webclient."""
1381+
1382+from urlparse import urlparse, parse_qs
1383+
1384 from twisted.internet import defer
1385-from twisted.internet.defer import inlineCallbacks
1386-from twisted.web import server, resource
1387-
1388-from ubuntuone.controlpanel import web_client
1389+from twisted.web import resource
1390+
1391+from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
1392+
1393 from ubuntuone.controlpanel.tests import TestCase
1394+from ubuntuone.controlpanel.web_client import (
1395+ UnauthorizedError,
1396+ WebClient,
1397+ WebClientError,
1398+)
1399
1400
1401 SAMPLE_KEY = "result"
1402 SAMPLE_VALUE = "sample result"
1403 SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
1404 SAMPLE_CREDENTIALS = dict(
1405- consumer_key="consumer key",
1406- consumer_secret="consumer secret",
1407- token="the real token",
1408- token_secret="the token secret",
1409+ consumer_key="consumer_key",
1410+ consumer_secret="consumer_secret",
1411+ token="the_real_token",
1412+ token_secret="the_token_secret",
1413 )
1414+SAMPLE_TARGET = u'http://example.com/'
1415+SAMPLE_SIGNED = SAMPLE_TARGET + u'?oauth_nonce=36886134&' \
1416+ 'oauth_timestamp=1328544832&oauth_consumer_key=%s&' \
1417+ 'oauth_signature_method=HMAC-SHA1&next=%%2Fblah&oauth_version=1.0&' \
1418+ 'oauth_token=%s&oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo3D' % \
1419+ (SAMPLE_CREDENTIALS['consumer_key'], SAMPLE_CREDENTIALS['token'])
1420
1421
1422 def sample_get_credentials():
1423@@ -63,11 +72,11 @@
1424 return self.contents
1425
1426
1427-class MockWebService(object):
1428- """A mock webservice for testing"""
1429+class MockWebServer(BaseMockWebServer):
1430+ """A mock webserver for the webclient tests."""
1431
1432- def __init__(self):
1433- """Start up this instance."""
1434+ def get_root_resource(self):
1435+ """Get the root resource with all the children."""
1436 root = resource.Resource()
1437 devices_resource = MockResource()
1438 devices_resource.contents = SAMPLE_RESOURCE
1439@@ -77,91 +86,105 @@
1440 "Unauthrorized", "Unauthrorized")
1441 root.putChild("unauthorized", unauthorized)
1442
1443- site = server.Site(root)
1444- application = service.Application('web')
1445- self.service_collection = service.IServiceCollection(application)
1446- #pylint: disable=E1101
1447- self.tcpserver = internet.TCPServer(0, site)
1448- self.tcpserver.setServiceParent(self.service_collection)
1449- self.service_collection.startService()
1450-
1451- def get_url(self):
1452- """Build the url for this mock server."""
1453- #pylint: disable=W0212
1454- port_num = self.tcpserver._port.getHost().port
1455- return "http://localhost:%d/" % port_num
1456-
1457- def stop(self):
1458- """Shut it down."""
1459- #pylint: disable=E1101
1460- return self.service_collection.stopService()
1461+ return root
1462
1463
1464 class WebClientTestCase(TestCase):
1465 """Test for the webservice client."""
1466
1467- timeout = 8
1468+ timeout = 5
1469
1470- @inlineCallbacks
1471+ @defer.inlineCallbacks
1472 def setUp(self):
1473 yield super(WebClientTestCase, self).setUp()
1474- self.ws = MockWebService()
1475- test_base_url = self.ws.get_url()
1476- self.wc = web_client.web_client_factory(sample_get_credentials,
1477- test_base_url)
1478+ self.ws = MockWebServer()
1479+ self.addCleanup(self.ws.stop)
1480+ self.base_iri = self.ws.get_iri()
1481+
1482+ self.wc = WebClient(sample_get_credentials, base_url=self.base_iri)
1483 self.addCleanup(self.wc.shutdown)
1484- self.addCleanup(self.ws.stop)
1485- web_module = web_client.web_client_module()
1486- if getattr(web_module, "timestamp_checker", None):
1487- fake_timestamp = 12345678
1488- self.patch(web_module.timestamp_checker, "get_faithful_time",
1489- lambda: defer.succeed(fake_timestamp))
1490
1491- @inlineCallbacks
1492+ @defer.inlineCallbacks
1493 def test_get_url(self):
1494 """A method is successfully called in the mock webservice."""
1495 result = yield self.wc.call_api("devices")
1496 self.assertIn(SAMPLE_KEY, result)
1497 self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY])
1498
1499- @inlineCallbacks
1500+ @defer.inlineCallbacks
1501 def test_get_url_error(self):
1502 """The errback is called when there's some error."""
1503 yield self.assertFailure(self.wc.call_api("throwerror"),
1504- web_client.WebClientError)
1505+ WebClientError)
1506
1507- @inlineCallbacks
1508+ @defer.inlineCallbacks
1509 def test_unauthorized(self):
1510 """Detect when a request failed with UNAUTHORIZED."""
1511 yield self.assertFailure(self.wc.call_api("unauthorized"),
1512- web_client.UnauthorizedError)
1513-
1514-
1515-class OAuthTestCase(TestCase):
1516- """Test for the oauth signing code."""
1517-
1518- def test_build_oauth_headers(self):
1519- """Build the oauth headers for a sample request."""
1520-
1521- sample_method = "GET"
1522- sample_url = "http://one.ubuntu.com/"
1523- timestamp = 1
1524- headers = web_client.build_oauth_headers(sample_method, sample_url,
1525- SAMPLE_CREDENTIALS, timestamp)
1526- self.assertIn("Authorization", headers)
1527-
1528- def test_add_oauth_headers(self):
1529- """Add the OAuth headers to a request."""
1530-
1531- def sample_build_headers(*a):
1532- """Build some sample headers."""
1533- return {"header1": "h1", "header2": "h2"}
1534-
1535- self.patch(web_client, "build_oauth_headers", sample_build_headers)
1536- test_request_headers = {}
1537- append_method = test_request_headers.__setitem__
1538- timestamp = 12345
1539- web_client.add_oauth_headers(append_method, "GET", "http://this", {},
1540- timestamp)
1541- self.assertIn("header1", test_request_headers)
1542- self.assertIn("header2", test_request_headers)
1543+ UnauthorizedError)
1544+
1545+
1546+class WebClientBuildSignedIriTestCase(WebClientTestCase):
1547+ """Test for the webservice client when signing iris."""
1548+
1549+ # Instance of 'ParseResult' has no 'foo' member
1550+ # pylint: disable=E1101
1551+
1552+ @defer.inlineCallbacks
1553+ def test_is_correct_domain(self):
1554+ """Test that we are using the right domain."""
1555+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1556+ parsed_signed = urlparse(signed)
1557+ parsed_sample = urlparse(SAMPLE_SIGNED)
1558+ self.assertEqual(parsed_signed.netloc, parsed_sample.netloc)
1559+
1560+ @defer.inlineCallbacks
1561+ def test_is_correct_path(self):
1562+ """Test that we are using the right path in the URL."""
1563+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1564+ parsed_signed = urlparse(signed)
1565+ parsed_sample = urlparse(SAMPLE_SIGNED)
1566+ self.assertEqual(parsed_signed.path, parsed_sample.path)
1567+
1568+ @defer.inlineCallbacks
1569+ def test_is_correct_scheme(self):
1570+ """Test that we are using the right scheme."""
1571+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1572+ parsed_signed = urlparse(signed)
1573+ parsed_sample = urlparse(SAMPLE_SIGNED)
1574+
1575+ self.assertEqual(parsed_signed.scheme, parsed_sample.scheme)
1576+
1577+ @defer.inlineCallbacks
1578+ def test_correct_query(self):
1579+ """Test the invariant parts of the signed URL."""
1580+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET,
1581+ params={'next': u'/blah'})
1582+ parsed_signed = urlparse(signed)
1583+ parsed_sample = urlparse(SAMPLE_SIGNED)
1584+ signed_query = parse_qs(parsed_signed.query)
1585+ sample_query = parse_qs(parsed_sample.query)
1586+
1587+ for key in ('next', 'oauth_consumer_key', 'oauth_version',
1588+ 'oauth_signature_method', 'oauth_token'):
1589+ self.assertEqual(map(unicode, signed_query[key]),
1590+ sample_query[key])
1591+
1592+ @defer.inlineCallbacks
1593+ def test_url_with_query(self):
1594+ """Test that we are using the right scheme."""
1595+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET,
1596+ params={'next': u'/blah?foo=bar'})
1597+ parsed_signed = urlparse(signed)
1598+ signed_query = parse_qs(parsed_signed.query)
1599+
1600+ self.assertEqual(signed_query['next'], [u'/blah?foo=bar'])
1601+
1602+ @defer.inlineCallbacks
1603+ def test_uses_timestamper(self):
1604+ """Test that the signed url is using the serverrelative timestamp."""
1605+ signed = yield self.wc.build_signed_iri(u'/blah?foo=bar')
1606+ parsed_signed = urlparse(signed)
1607+ signed_query = parse_qs(parsed_signed.query)
1608+
1609+ self.assertTrue(signed_query['oauth_timestamp'] is not None)
1610
1611=== removed directory 'ubuntuone/controlpanel/web_client'
1612=== renamed file 'ubuntuone/controlpanel/web_client/__init__.py' => 'ubuntuone/controlpanel/web_client.py'
1613--- ubuntuone/controlpanel/web_client/__init__.py 2011-10-07 14:36:14 +0000
1614+++ ubuntuone/controlpanel/web_client.py 2012-02-14 22:15:21 +0000
1615@@ -1,9 +1,6 @@
1616 # -*- coding: utf-8 -*-
1617-
1618-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
1619-# Alejandro J. Cura <alecu@canonical.com>
1620 #
1621-# Copyright 2011 Canonical Ltd.
1622+# Copyright 2011-2012 Canonical Ltd.
1623 #
1624 # This program is free software: you can redistribute it and/or modify it
1625 # under the terms of the GNU General Public License version 3, as published
1626@@ -19,60 +16,55 @@
1627
1628 """The web client."""
1629
1630-from oauth import oauth
1631-
1632-
1633-# pylint: disable=W0401, W0614
1634-
1635-
1636-class WebClientError(Exception):
1637- """An http error happened while calling the webservice."""
1638-
1639-
1640-class UnauthorizedError(WebClientError):
1641- """The request ended with bad_request, unauthorized or forbidden."""
1642-
1643-
1644-def build_oauth_headers(method, url, credentials, timestamp):
1645- """Build an oauth request given some credentials."""
1646- consumer = oauth.OAuthConsumer(credentials["consumer_key"],
1647- credentials["consumer_secret"])
1648- token = oauth.OAuthToken(credentials["token"],
1649- credentials["token_secret"])
1650- parameters = {}
1651- if timestamp:
1652- parameters["oauth_timestamp"] = timestamp
1653- request = oauth.OAuthRequest.from_consumer_and_token(
1654- http_url=url,
1655- http_method=method,
1656- parameters=parameters,
1657- oauth_consumer=consumer,
1658- token=token)
1659- sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
1660- request.sign_request(sig_method, consumer, token)
1661- return request.to_header()
1662-
1663-
1664-def add_oauth_headers(append_method, method, url, credentials, timestamp=None):
1665- """Sign a libsoup message with oauth headers."""
1666- headers = build_oauth_headers(method, url, credentials, timestamp)
1667- for key, value in headers.items():
1668- append_method(key, value)
1669-
1670-
1671-def web_client_module():
1672- """Choose the module of the web client."""
1673- # the reactor can only be imported after Qt is initialized
1674- # pylint: disable=W0404
1675- from twisted.internet import reactor
1676- if getattr(reactor, "qApp", None):
1677- from ubuntuone.controlpanel.web_client import txwebclient as web_module
1678- else:
1679- from ubuntuone.controlpanel.web_client import libsoup as web_module
1680- return web_module
1681-
1682-
1683-def web_client_factory(*args, **kwargs):
1684- """Choose the type of the web client dynamically."""
1685- web_module = web_client_module()
1686- return web_module.WebClient(*args, **kwargs)
1687+import simplejson
1688+
1689+from twisted.internet import defer
1690+# need to export the exceptions to avoid API breakage
1691+# pylint: disable=W0611
1692+from ubuntu_sso.utils.webclient import (
1693+ UnauthorizedError,
1694+ WebClientError,
1695+ webclient_factory,
1696+)
1697+# pylint: enable=W0611
1698+
1699+from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
1700+from ubuntuone.controlpanel.logger import setup_logging
1701+
1702+
1703+logger = setup_logging('webclient')
1704+
1705+
1706+class WebClient(object):
1707+ """A client for the u1 webservice."""
1708+
1709+ def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
1710+ """Initialize the webclient."""
1711+ self.base_url = base_url
1712+ self.get_credentials = get_credentials
1713+ self.wc = webclient_factory()
1714+ logger.debug("WebClient created: base_url is %r, inner client is %r.",
1715+ self.base_url, self.wc)
1716+
1717+ @defer.inlineCallbacks
1718+ def call_api(self, api_name, extra_headers=None):
1719+ """Call the webservice."""
1720+ # this may log device ID's, but only for removals, which is OK
1721+ logger.debug("calling api: %r", api_name)
1722+ iri = self.base_url + api_name
1723+ credentials = yield self.get_credentials()
1724+ response = yield self.wc.request(iri, extra_headers=extra_headers,
1725+ oauth_credentials=credentials)
1726+ result = simplejson.loads(response.content)
1727+ defer.returnValue(result)
1728+
1729+ @defer.inlineCallbacks
1730+ def build_signed_iri(self, iri, params=None):
1731+ """Build an OAuth iri."""
1732+ credentials = yield self.get_credentials()
1733+ result = yield self.wc.build_signed_iri(iri, credentials, params)
1734+ defer.returnValue(result)
1735+
1736+ def shutdown(self):
1737+ """Shutdown and cleanup."""
1738+ return self.wc.shutdown()
1739
1740=== removed file 'ubuntuone/controlpanel/web_client/libsoup.py'
1741--- ubuntuone/controlpanel/web_client/libsoup.py 2011-10-06 19:56:38 +0000
1742+++ ubuntuone/controlpanel/web_client/libsoup.py 1970-01-01 00:00:00 +0000
1743@@ -1,133 +0,0 @@
1744-# -*- coding: utf-8 -*-
1745-
1746-# Authors: Alejandro J. Cura <alecu@canonical.com>
1747-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1748-#
1749-# Copyright 2010 Canonical Ltd.
1750-#
1751-# This program is free software: you can redistribute it and/or modify it
1752-# under the terms of the GNU General Public License version 3, as published
1753-# by the Free Software Foundation.
1754-#
1755-# This program is distributed in the hope that it will be useful, but
1756-# WITHOUT ANY WARRANTY; without even the implied warranties of
1757-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1758-# PURPOSE. See the GNU General Public License for more details.
1759-#
1760-# You should have received a copy of the GNU General Public License along
1761-# with this program. If not, see <http://www.gnu.org/licenses/>.
1762-
1763-"""The control panel backend webservice client."""
1764-
1765-import simplejson
1766-
1767-# pylint: disable=E0611
1768-from gi.repository import Soup, SoupGNOME
1769-from twisted.internet import defer
1770-
1771-from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
1772-from ubuntuone.controlpanel.web_client import (add_oauth_headers,
1773- WebClientError,
1774- UnauthorizedError)
1775-from ubuntuone.controlpanel.logger import setup_logging
1776-from ubuntuone.storageprotocol.utils import BaseTimestampChecker
1777-
1778-logger = setup_logging('webclient')
1779-
1780-# full list of status codes
1781-# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html
1782-
1783-
1784-class LibsoupTimestampChecker(BaseTimestampChecker):
1785- """Specialized TimestampChecker using libsoup."""
1786-
1787- def __init__(self):
1788- """Initialize this instance."""
1789- super(LibsoupTimestampChecker, self).__init__()
1790- self.session = Soup.SessionAsync()
1791- self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
1792-
1793- def _handler(self, session, msg, d):
1794- """Handle the result of an http message."""
1795- logger.debug("got http response %d for uri %r",
1796- msg.status_code, msg.get_uri().to_string(False))
1797- if msg.status_code == 200:
1798- date = msg.response_headers.get("Date")
1799- d.callback(date)
1800- else:
1801- e = WebClientError(msg.status_code, "")
1802- d.errback(e)
1803-
1804- def get_server_date_header(self, server_url):
1805- """Get the server date using twisted webclient."""
1806- method = "HEAD"
1807- msg = Soup.Message.new(method, server_url)
1808- msg.request_headers.append("Cache-Control", "no-cache")
1809- d = defer.Deferred()
1810- self.session.queue_message(msg, self._handler, d)
1811- return d
1812-
1813- def shutdown(self):
1814- """End the soup session for this webclient."""
1815- self.session.abort()
1816-
1817-
1818-# pylint: disable=C0103
1819-timestamp_checker = LibsoupTimestampChecker()
1820-
1821-
1822-class WebClient(object):
1823- """A client for the u1 webservice."""
1824-
1825- def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
1826- """Initialize the webclient."""
1827- self.base_url = base_url
1828- self.session = Soup.SessionAsync()
1829- self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
1830- self.get_credentials = get_credentials
1831-
1832- def _handler(self, session, msg, d):
1833- """Handle the result of an http message."""
1834- logger.debug("got http response %d for uri %r",
1835- msg.status_code, msg.get_uri().to_string(False))
1836- data = msg.response_body.data
1837- if msg.status_code == 200:
1838- result = simplejson.loads(data)
1839- d.callback(result)
1840- else:
1841- if msg.status_code in (401,):
1842- e = UnauthorizedError(msg.status_code, data)
1843- else:
1844- e = WebClientError(msg.status_code, data)
1845- d.errback(e)
1846-
1847- def _call_api_inner(self, credentials, api_name, timestamp):
1848- """Call the webservice with credentials and timestamp."""
1849- url = (self.base_url + api_name).encode('utf-8')
1850- method = "GET"
1851- logger.debug("getting url: %s, %s", method, url)
1852- msg = Soup.Message.new(method, url)
1853- add_oauth_headers(msg.request_headers.append, method, url,
1854- credentials, timestamp)
1855- d = defer.Deferred()
1856- self.session.queue_message(msg, self._handler, d)
1857- return d
1858-
1859- @defer.inlineCallbacks
1860- def _call_api_add_timestamp(self, credentials, api_name):
1861- """Add the timestamp to the api call."""
1862- timestamp = yield timestamp_checker.get_faithful_time()
1863- result = yield self._call_api_inner(credentials, api_name, timestamp)
1864- defer.returnValue(result)
1865-
1866- def call_api(self, api_name):
1867- """Call the webservice."""
1868- # this may log device ID's, but only for removals, which is OK
1869- logger.debug("calling api: %s", api_name)
1870- d = self.get_credentials()
1871- d.addCallback(self._call_api_add_timestamp, api_name)
1872- return d
1873-
1874- def shutdown(self):
1875- """End the soup session for this webclient."""
1876- self.session.abort()
1877
1878=== removed directory 'ubuntuone/controlpanel/web_client/tests'
1879=== removed file 'ubuntuone/controlpanel/web_client/tests/__init__.py'
1880--- ubuntuone/controlpanel/web_client/tests/__init__.py 2011-09-08 23:52:27 +0000
1881+++ ubuntuone/controlpanel/web_client/tests/__init__.py 1970-01-01 00:00:00 +0000
1882@@ -1,19 +0,0 @@
1883-# -*- coding: utf-8 -*-
1884-
1885-# Authors: Alejandro J. Cura <alecu@canonical.com>
1886-#
1887-# Copyright 2011 Canonical Ltd.
1888-#
1889-# This program is free software: you can redistribute it and/or modify it
1890-# under the terms of the GNU General Public License version 3, as published
1891-# by the Free Software Foundation.
1892-#
1893-# This program is distributed in the hope that it will be useful, but
1894-# WITHOUT ANY WARRANTY; without even the implied warranties of
1895-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1896-# PURPOSE. See the GNU General Public License for more details.
1897-#
1898-# You should have received a copy of the GNU General Public License along
1899-# with this program. If not, see <http://www.gnu.org/licenses/>.
1900-
1901-"""Unit tests for the control panel backend webservice clients."""
1902
1903=== removed file 'ubuntuone/controlpanel/web_client/tests/test_libsoup.py'
1904--- ubuntuone/controlpanel/web_client/tests/test_libsoup.py 2011-10-06 22:27:57 +0000
1905+++ ubuntuone/controlpanel/web_client/tests/test_libsoup.py 1970-01-01 00:00:00 +0000
1906@@ -1,106 +0,0 @@
1907-# -*- coding: utf-8 -*-
1908-
1909-# Authors: Alejandro J. Cura <alecu@canonical.com>
1910-#
1911-# Copyright 2011 Canonical Ltd.
1912-#
1913-# This program is free software: you can redistribute it and/or modify it
1914-# under the terms of the GNU General Public License version 3, as published
1915-# by the Free Software Foundation.
1916-#
1917-# This program is distributed in the hope that it will be useful, but
1918-# WITHOUT ANY WARRANTY; without even the implied warranties of
1919-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1920-# PURPOSE. See the GNU General Public License for more details.
1921-#
1922-# You should have received a copy of the GNU General Public License along
1923-# with this program. If not, see <http://www.gnu.org/licenses/>.
1924-
1925-"""Integration tests for the libsoup based webservice client."""
1926-
1927-import time
1928-
1929-from twisted.application import internet, service
1930-from twisted.internet import defer
1931-from twisted.web import http, resource, server
1932-
1933-from ubuntuone.controlpanel.tests import TestCase
1934-from ubuntuone.controlpanel.web_client.libsoup import LibsoupTimestampChecker
1935-
1936-
1937-class MockResource(resource.Resource):
1938- """A mock resource."""
1939-
1940- isLeaf = True
1941-
1942- def __init__(self):
1943- """Initialize this mock instance."""
1944- resource.Resource.__init__(self)
1945- self.request_headers = []
1946-
1947- # pylint: disable=C0103
1948- def render_GET(self, request):
1949- """Render some content."""
1950- self.request_headers.append(request.requestHeaders)
1951- return "hello!"
1952-
1953-
1954-class MockWebService(object):
1955- """A mock webservice for testing"""
1956-
1957- def __init__(self):
1958- """Start up this instance."""
1959- self.root = MockResource()
1960- site = server.Site(self.root)
1961- application = service.Application('web')
1962- self.service_collection = service.IServiceCollection(application)
1963- #pylint: disable=E1101
1964- self.tcpserver = internet.TCPServer(0, site)
1965- self.tcpserver.setServiceParent(self.service_collection)
1966- self.service_collection.startService()
1967-
1968- def get_url(self):
1969- """Build the url for this mock server."""
1970- #pylint: disable=W0212
1971- port_num = self.tcpserver._port.getHost().port
1972- return "http://localhost:%d/" % port_num
1973-
1974- def stop(self):
1975- """Shut down the service."""
1976- #pylint: disable=E1101
1977- self.service_collection.stopService()
1978-
1979-
1980-class LibsoupTimestampCheckerTestCase(TestCase):
1981- """Tests for LibsoupTimestampChecker."""
1982-
1983- timeout = 3
1984-
1985- @defer.inlineCallbacks
1986- def setUp(self):
1987- yield super(LibsoupTimestampCheckerTestCase, self).setUp()
1988- self.ws = MockWebService()
1989- self.addCleanup(self.ws.stop)
1990-
1991- @defer.inlineCallbacks
1992- def test_gets_server_date(self):
1993- """The server date is gotten right."""
1994- fake_time = 1
1995- self.patch(time, "time", lambda: fake_time)
1996- checker = LibsoupTimestampChecker()
1997- self.addCleanup(checker.shutdown)
1998- d = checker.get_server_date_header(self.ws.get_url())
1999- result = yield d
2000- result_time = http.stringToDatetime(result)
2001- self.assertEqual(result_time, fake_time)
2002-
2003- @defer.inlineCallbacks
2004- def test_server_date_sends_nocache_headers(self):
2005- """Getting the server date sends the no-cache headers."""
2006- checker = LibsoupTimestampChecker()
2007- self.addCleanup(checker.shutdown)
2008- yield checker.get_server_date_header(self.ws.get_url())
2009- self.assertEqual(len(self.ws.root.request_headers), 1)
2010- headers = self.ws.root.request_headers[0]
2011- result = headers.getRawHeaders("Cache-Control")
2012- self.assertEqual(result, ["no-cache"])
2013
2014=== removed file 'ubuntuone/controlpanel/web_client/tests/test_txwebclient.py'
2015--- ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 2011-11-21 13:32:44 +0000
2016+++ ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 1970-01-01 00:00:00 +0000
2017@@ -1,177 +0,0 @@
2018-# -*- coding: utf-8 -*-
2019-
2020-# Authors: Alejandro J. Cura <alecu@canonical.com>
2021-#
2022-# Copyright 2011 Canonical Ltd.
2023-#
2024-# This program is free software: you can redistribute it and/or modify it
2025-# under the terms of the GNU General Public License version 3, as published
2026-# by the Free Software Foundation.
2027-#
2028-# This program is distributed in the hope that it will be useful, but
2029-# WITHOUT ANY WARRANTY; without even the implied warranties of
2030-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2031-# PURPOSE. See the GNU General Public License for more details.
2032-#
2033-# You should have received a copy of the GNU General Public License along
2034-# with this program. If not, see <http://www.gnu.org/licenses/>.
2035-
2036-"""Unit tests for the control panel backend twisted webservice client."""
2037-
2038-import time
2039-
2040-from twisted.application import internet, service
2041-from twisted.internet import defer, reactor
2042-from twisted.internet.defer import inlineCallbacks
2043-from twisted.web import server, resource
2044-from ubuntuone.devtools.testcases import skipIfOS
2045-
2046-from ubuntuone.controlpanel.web_client import txwebclient
2047-from ubuntuone.controlpanel.tests import TestCase
2048-
2049-
2050-SAMPLE_KEY = "result"
2051-SAMPLE_VALUE = "sample result"
2052-SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
2053-SAMPLE_CREDENTIALS = dict(
2054- consumer_key="consumer key",
2055- consumer_secret="consumer secret",
2056- token="the real token",
2057- token_secret="the token secret",
2058-)
2059-
2060-
2061-def sample_get_credentials():
2062- """Will return the sample credentials right now."""
2063- return defer.succeed(SAMPLE_CREDENTIALS)
2064-
2065-
2066-class MockResource(resource.Resource):
2067- """A simple web resource."""
2068- isLeaf = True
2069- contents = ""
2070-
2071- # pylint: disable=C0103
2072- # t.w.resource methods have freeform cased names
2073-
2074- def getChild(self, name, request):
2075- """Get a given child resource."""
2076- if name == '':
2077- return self
2078- return resource.Resource.getChild(self, name, request)
2079-
2080- def render_GET(self, request):
2081- """Make a bit of html out of these resource's content."""
2082- return self.contents
2083-
2084-
2085-class MockWebService(object):
2086- """A mock webservice for testing"""
2087-
2088- def __init__(self):
2089- """Start up this instance."""
2090- root = resource.Resource()
2091- devices_resource = MockResource()
2092- devices_resource.contents = SAMPLE_RESOURCE
2093- root.putChild("devices", devices_resource)
2094- root.putChild("throwerror", resource.NoResource())
2095- unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED,
2096- "Unauthrorized", "Unauthrorized")
2097- root.putChild("unauthorized", unauthorized)
2098-
2099- site = server.Site(root)
2100- application = service.Application('web')
2101- self.service_collection = service.IServiceCollection(application)
2102- #pylint: disable=E1101
2103- self.tcpserver = internet.TCPServer(0, site)
2104- self.tcpserver.setServiceParent(self.service_collection)
2105- self.service_collection.startService()
2106-
2107- def get_url(self):
2108- """Build the url for this mock server."""
2109- #pylint: disable=W0212
2110- port_num = self.tcpserver._port.getHost().port
2111- return "http://localhost:%d/" % port_num
2112-
2113- def stop(self):
2114- """Shut it down."""
2115- #pylint: disable=E1101
2116- return self.service_collection.stopService()
2117-
2118-
2119-class FakeAsyncTimestamper(object):
2120- """A fake timestamp."""
2121-
2122- def __init__(self):
2123- """Initialize this instance."""
2124- self.called = False
2125-
2126- def get_faithful_time(self):
2127- """Return the server checked timestamp."""
2128- self.called = True
2129- return defer.succeed(time.time())
2130-
2131-
2132-class WebClientTestCase(TestCase):
2133- """Test for the webservice client."""
2134-
2135- timeout = 8
2136-
2137- @defer.inlineCallbacks
2138- def setUp(self):
2139- yield super(WebClientTestCase, self).setUp()
2140- self.ws = MockWebService()
2141- test_base_url = self.ws.get_url()
2142- self.wc = txwebclient.WebClient(sample_get_credentials, test_base_url)
2143- self.timestamper = FakeAsyncTimestamper()
2144- self.patch(txwebclient, "timestamp_checker", self.timestamper)
2145- self.addCleanup(self.wc.shutdown)
2146- self.addCleanup(self.ws.stop)
2147-
2148- @inlineCallbacks
2149- def test_get_url(self):
2150- """A method is successfully called in the mock webservice."""
2151- result = yield self.wc.call_api("devices")
2152- self.assertIn(SAMPLE_KEY, result)
2153- self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY])
2154-
2155- @inlineCallbacks
2156- def test_call_api_uses_timestamp(self):
2157- """Check that call_api uses the timestamp."""
2158- yield self.wc.call_api("devices")
2159- self.assertTrue(self.timestamper.called,
2160- "The timestamper must be used.")
2161-
2162- @inlineCallbacks
2163- def test_get_url_error(self):
2164- """The errback is called when there's some error."""
2165- yield self.assertFailure(self.wc.call_api("throwerror"),
2166- txwebclient.WebClientError)
2167-
2168- @inlineCallbacks
2169- def test_unauthorized(self):
2170- """Detect when a request failed with UNAUTHORIZED."""
2171- yield self.assertFailure(self.wc.call_api("unauthorized"),
2172- txwebclient.UnauthorizedError)
2173-
2174-
2175-class WebClientShutdownTestCase(TestCase):
2176- """The webclient behaviour during shutdown."""
2177-
2178- @skipIfOS('win32', 'Failing on windows, see LP: #851158.')
2179- @inlineCallbacks
2180- def test_shutdown(self):
2181- """The webclient behaves well during shutdown."""
2182- self.patch(txwebclient, "timestamp_checker", FakeAsyncTimestamper())
2183- d3 = defer.Deferred()
2184- # pylint: disable=E1101
2185- reactor.callLater(1, d3.callback, None)
2186- ws = MockWebService()
2187- test_base_url = ws.get_url()
2188- wc = txwebclient.WebClient(sample_get_credentials, test_base_url)
2189- d1 = wc.call_api("throwerror")
2190- d2 = ws.stop()
2191- wc.shutdown()
2192- yield d2
2193- yield defer.DeferredList([d1, d3], fireOnOneCallback=True,
2194- fireOnOneErrback=True)
2195
2196=== removed file 'ubuntuone/controlpanel/web_client/txwebclient.py'
2197--- ubuntuone/controlpanel/web_client/txwebclient.py 2011-10-05 23:12:23 +0000
2198+++ ubuntuone/controlpanel/web_client/txwebclient.py 1970-01-01 00:00:00 +0000
2199@@ -1,108 +0,0 @@
2200-# -*- coding: utf-8 -*-
2201-
2202-# Authors: Alejandro J. Cura <alecu@canonical.com>
2203-#
2204-# Copyright 2011 Canonical Ltd.
2205-#
2206-# This program is free software: you can redistribute it and/or modify it
2207-# under the terms of the GNU General Public License version 3, as published
2208-# by the Free Software Foundation.
2209-#
2210-# This program is distributed in the hope that it will be useful, but
2211-# WITHOUT ANY WARRANTY; without even the implied warranties of
2212-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2213-# PURPOSE. See the GNU General Public License for more details.
2214-#
2215-# You should have received a copy of the GNU General Public License along
2216-# with this program. If not, see <http://www.gnu.org/licenses/>.
2217-
2218-"""The control panel backend webservice client using twisted.web."""
2219-
2220-import simplejson
2221-
2222-from twisted.internet import defer, reactor
2223-from twisted.web import client, error, http
2224-
2225-from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
2226-from ubuntuone.controlpanel.web_client import (add_oauth_headers,
2227- WebClientError,
2228- UnauthorizedError)
2229-
2230-from ubuntuone.controlpanel.logger import setup_logging
2231-from ubuntuone.storageprotocol.client import TwistedTimestampChecker
2232-
2233-logger = setup_logging('webclient')
2234-# pylint: disable=C0103
2235-timestamp_checker = TwistedTimestampChecker()
2236-
2237-
2238-class WebClient(object):
2239- """A client for the u1 webservice."""
2240-
2241- def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
2242- """Initialize the webclient."""
2243- self.base_url = base_url
2244- self.get_credentials = get_credentials
2245- self.running = True
2246- # pylint: disable=E1101
2247- self.trigger_id = reactor.addSystemEventTrigger("before", "shutdown",
2248- self.shutdown)
2249-
2250- def _handle_response(self, result):
2251- """Handle the response of the webservice call."""
2252- return simplejson.loads(result)
2253-
2254- def _handle_error(self, failure):
2255- """Handle an error while calling the webservice."""
2256- if failure.type == error.Error:
2257- exception = failure.value
2258- if exception.status == str(http.UNAUTHORIZED):
2259- raise UnauthorizedError(exception.status, exception.response)
2260- else:
2261- raise WebClientError(exception.status, exception.response)
2262- else:
2263- raise WebClientError(-1, failure)
2264-
2265- def _call_api_inner(self, credentials, api_name, timestamp):
2266- """Call the webservice with credentials and timestamp."""
2267- url = (self.base_url + api_name).encode('utf-8')
2268- method = "GET"
2269- logger.debug("getting url: %s, %s", method, url)
2270- headers = {}
2271- add_oauth_headers(headers.__setitem__, method, url, credentials,
2272- timestamp)
2273- d = client.getPage(url, headers=headers)
2274- d.addCallback(self._handle_response)
2275- d.addErrback(self._handle_error)
2276- return d
2277-
2278- @defer.inlineCallbacks
2279- def _call_api_add_timestamp(self, credentials, api_name):
2280- """Add the timestamp to the api call."""
2281- timestamp = yield timestamp_checker.get_faithful_time()
2282- result = yield self._call_api_inner(credentials, api_name, timestamp)
2283- defer.returnValue(result)
2284-
2285- def call_api(self, api_name):
2286- """Call the webservice."""
2287- # this may log device ID's, but only for removals, which is OK
2288- logger.debug("calling api: %s", api_name)
2289- d = self.get_credentials()
2290- d.addErrback(self._handle_error)
2291- d.addCallback(self._call_api_add_timestamp, api_name)
2292- d2 = defer.Deferred()
2293- d.addCallback(d2.callback)
2294-
2295- def mask_errors_on_shutdown(failure):
2296- """Do not fire the errbacks if we are shutting down."""
2297- if self.running:
2298- d2.errback(failure)
2299-
2300- d.addErrback(mask_errors_on_shutdown)
2301- return d2
2302-
2303- def shutdown(self):
2304- """End the pending webclient calls."""
2305- self.running = False
2306- # pylint: disable=E1101
2307- reactor.removeSystemEventTrigger(self.trigger_id)

Subscribers

People subscribed via source and target branches