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