Merge lp:~mvo/software-center/recommends into lp:software-center
- recommends
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2638 |
Proposed branch: | lp:~mvo/software-center/recommends |
Merge into: | lp:software-center |
Diff against target: |
1434 lines (+590/-507) 22 files modified
debian/control (+0/-1) softwarecenter/backend/login.py (+0/-7) softwarecenter/backend/oneconfhandler/core.py (+5/-11) softwarecenter/backend/piston/sreclient_pristine.py (+41/-0) softwarecenter/backend/piston/sso_helper.py (+85/-0) softwarecenter/backend/piston/ubuntusso_pristine.py (+22/-0) softwarecenter/backend/recommends.py (+82/-0) softwarecenter/backend/reviews/__init__.py (+3/-5) softwarecenter/backend/reviews/rnr.py (+11/-13) softwarecenter/backend/scagent.py (+32/-27) softwarecenter/backend/spawn_helper.py (+24/-0) softwarecenter/backend/ubuntusso.py (+46/-249) softwarecenter/enums.py (+9/-0) softwarecenter/paths.py (+1/-3) test/gtk3/test_views.py (+2/-0) test/test_reviews.py (+3/-2) test/test_ubuntu_sso_api.py (+4/-4) utils/piston-helpers/piston_generic_helper.py (+215/-0) utils/piston-helpers/piston_get_review_stats_helper.py (+0/-65) utils/piston-helpers/piston_get_scagent_available_apps.py.THIS (+2/-57) utils/piston-helpers/piston_get_useful_votes_helper.py (+0/-46) utils/submit_review_gtk3.py (+3/-17) |
To merge this branch: | bzr merge lp:~mvo/software-center/recommends |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
software-store-developers | Pending | ||
Review via email: mp+87639@code.launchpad.net |
Commit message
Description of the change
This should be merged after the lp:~mvo/software-center/replace-restfulclient-with-piston branch gets merged.
Its adds a basic GObject for the recommender server, nothing more yet.
Gary Lasker (gary-lasker) wrote : | # |
- 2630. By Michael Vogt
-
add "recommend_me" call
Michael Vogt (mvo) wrote : | # |
On Fri, Jan 06, 2012 at 06:18:29AM -0000, Gary Lasker wrote:
> Hey Michael! Please double check one thing, there's a funky rename in the last rev of the branch:
>
> utils/piston-
>
> There are also a few simple pyflakes fixes that we'll want to do before merging.
Thanks! I merged your branch that cleans this up now and moved it into
trunk.
> This is a very nice kickoff for the client-side recommendations work, thanks!!
>
Great! Happy that you like it. Now we just need to add a bit of up :)
Cheers,
Michael
Preview Diff
1 | === added symlink 'data/piston_generic_helper.py' |
2 | === target is u'../utils/piston-helpers/piston_generic_helper.py' |
3 | === added symlink 'data/piston_get_recommends.py' |
4 | === target is u'../utils/piston-helpers/piston_get_recommends.py' |
5 | === removed symlink 'data/piston_get_review_stats_helper.py' |
6 | === target was u'../utils/piston-helpers/piston_get_review_stats_helper.py' |
7 | === removed symlink 'data/piston_get_scagent_available_apps.py' |
8 | === target was u'../utils/piston-helpers/piston_get_scagent_available_apps.py' |
9 | === removed symlink 'data/piston_get_useful_votes_helper.py' |
10 | === target was u'../utils/piston-helpers/piston_get_useful_votes_helper.py' |
11 | === modified file 'debian/control' |
12 | --- debian/control 2011-12-06 14:22:20 +0000 |
13 | +++ debian/control 2012-01-05 15:35:32 +0000 |
14 | @@ -40,7 +40,6 @@ |
15 | policykit-1, |
16 | policykit-1-gnome | policykit-1-kde, |
17 | python-xdg, |
18 | - python-lazr.restfulclient, |
19 | ubuntu-sso-client (>= 0.99.6), |
20 | python-piston-mini-client (>= 0.1+bzr29), |
21 | oneconf (>= 0.2.6), |
22 | |
23 | === modified file 'softwarecenter/backend/login.py' |
24 | --- softwarecenter/backend/login.py 2011-09-15 14:24:14 +0000 |
25 | +++ softwarecenter/backend/login.py 2012-01-05 15:35:32 +0000 |
26 | @@ -49,10 +49,3 @@ |
27 | raise NotImplemented |
28 | def cancel_login(self): |
29 | self.emit("login-canceled") |
30 | - @property |
31 | - def new_account_url(self): |
32 | - return self.NEW_ACCOUNT_URL |
33 | - @property |
34 | - def forgoten_password_url(self): |
35 | - return self.FORGOT_PASSWORD_URL |
36 | - |
37 | |
38 | === modified file 'softwarecenter/backend/oneconfhandler/core.py' |
39 | --- softwarecenter/backend/oneconfhandler/core.py 2011-10-26 12:01:19 +0000 |
40 | +++ softwarecenter/backend/oneconfhandler/core.py 2012-01-05 15:35:32 +0000 |
41 | @@ -22,7 +22,7 @@ |
42 | from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY |
43 | |
44 | from softwarecenter.backend.login_sso import get_sso_backend |
45 | -from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend |
46 | +from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend |
47 | from softwarecenter.utils import clear_token_from_ubuntu_sso |
48 | |
49 | import datetime |
50 | @@ -176,6 +176,8 @@ |
51 | self.ssoapi = get_ubuntu_sso_backend(token) |
52 | self.ssoapi.connect("whoami", self._whoami_done) |
53 | self.ssoapi.connect("error", self._whoami_error) |
54 | + # this will automatically verify the token and retrigger login |
55 | + # if its expired |
56 | self.ssoapi.whoami() |
57 | |
58 | def _whoami_done(self, ssologin, result): |
59 | @@ -184,13 +186,5 @@ |
60 | |
61 | def _whoami_error(self, ssologin, e): |
62 | logging.error("whoami error '%s'" % e) |
63 | - # HACK: clear the token from the keyring assuming that it expired |
64 | - # or got deauthorized by the user on the website |
65 | - # this really should be done by ubuntu-sso-client itself |
66 | - import lazr.restfulclient.errors |
67 | - errortype = lazr.restfulclient.errors.HTTPError |
68 | - if (type(e) == errortype): |
69 | - LOG.warn("authentication error, resetting token and retrying") |
70 | - clear_token_from_ubuntu_sso(self.appname) |
71 | - self._share_inventory(False) |
72 | - return |
73 | + self._share_inventory(False) |
74 | + return |
75 | |
76 | === added file 'softwarecenter/backend/piston/sreclient_pristine.py' |
77 | --- softwarecenter/backend/piston/sreclient_pristine.py 1970-01-01 00:00:00 +0000 |
78 | +++ softwarecenter/backend/piston/sreclient_pristine.py 2012-01-05 15:35:32 +0000 |
79 | @@ -0,0 +1,41 @@ |
80 | +from piston_mini_client import (PistonAPI, PistonResponseObject, |
81 | + returns_list_of, returns_json) |
82 | +from piston_mini_client.validators import (validate_pattern, validate, |
83 | + oauth_protected) |
84 | + |
85 | +# These are factored out as constants for if you need to work against a |
86 | +# server that doesn't support both schemes (like http-only dev servers) |
87 | +PUBLIC_API_SCHEME = 'http' |
88 | +AUTHENTICATED_API_SCHEME = 'https' |
89 | + |
90 | +class SoftwareCenterRecommenderAPI(PistonAPI): |
91 | + default_service_root = 'http://localhost:8000/api/2.0' |
92 | + |
93 | + @returns_json |
94 | + def server_status(self): |
95 | + return self._get('server-status', scheme=PUBLIC_API_SCHEME) |
96 | + |
97 | + @oauth_protected |
98 | + @returns_json |
99 | + def profile(self): |
100 | + return self._get('profile', scheme=PUBLIC_API_SCHEME) |
101 | + |
102 | + @oauth_protected |
103 | + def recommend_me(self): |
104 | + return self._get('recommended_me', scheme=PUBLIC_API_SCHEME) |
105 | + |
106 | + @oauth_protected |
107 | + @validate('pkgname', str) |
108 | + def recommend_app(self, pkgname): |
109 | + return self._get('recommended_app/%s/' % pkgname, |
110 | + scheme=PUBLIC_API_SCHEME) |
111 | + |
112 | + @returns_json |
113 | + def recommend_top(self): |
114 | + return self._get('recommend_top', scheme=PUBLIC_API_SCHEME) |
115 | + |
116 | + @returns_json |
117 | + def feedback(self): |
118 | + return self._get('feedback', scheme=PUBLIC_API_SCHEME) |
119 | + |
120 | + |
121 | |
122 | === added file 'softwarecenter/backend/piston/sso_helper.py' |
123 | --- softwarecenter/backend/piston/sso_helper.py 1970-01-01 00:00:00 +0000 |
124 | +++ softwarecenter/backend/piston/sso_helper.py 2012-01-05 15:35:32 +0000 |
125 | @@ -0,0 +1,85 @@ |
126 | +#!/usr/bin/python |
127 | +# Copyright (C) 2012 Canonical |
128 | +# |
129 | +# Authors: |
130 | +# Michael Vogt |
131 | +# |
132 | +# This program is free software; you can redistribute it and/or modify it under |
133 | +# the terms of the GNU General Public License as published by the Free Software |
134 | +# Foundation; version 3. |
135 | +# |
136 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
137 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
138 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
139 | +# details. |
140 | +# |
141 | +# You should have received a copy of the GNU General Public License along with |
142 | +# this program; if not, write to the Free Software Foundation, Inc., |
143 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
144 | + |
145 | +from gi.repository import GObject |
146 | +from gettext import gettext as _ |
147 | + |
148 | +from softwarecenter.backend.login_sso import get_sso_backend |
149 | +from softwarecenter.backend.restfulclient import UbuntuSSOAPI |
150 | +from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING, |
151 | + SOFTWARE_CENTER_SSO_DESCRIPTION, |
152 | + ) |
153 | +from softwarecenter.utils import clear_token_from_ubuntu_sso |
154 | + |
155 | + |
156 | + |
157 | +class SSOLoginHelper(object): |
158 | + def __init__(self, xid=0): |
159 | + self.oauth = None |
160 | + self.xid = xid |
161 | + self.loop = GObject.MainLoop(GObject.main_context_default()) |
162 | + |
163 | + def _login_successful(self, sso_backend, oauth_result): |
164 | + self.oauth = oauth_result |
165 | + # FIXME: actually verify the token against ubuntu SSO |
166 | + self.loop.quit() |
167 | + |
168 | + def verify_token(self, token): |
169 | + def _whoami_done(sso, me): |
170 | + self._whoami = me |
171 | + self.loop.quit() |
172 | + def _whoami_error(sso, err): |
173 | + #print "ERRR", err |
174 | + self.loop.quit() |
175 | + self._whoami = None |
176 | + sso = UbuntuSSOAPI(token) |
177 | + sso.connect("whoami", _whoami_done) |
178 | + sso.connect("error", _whoami_error) |
179 | + sso.whoami() |
180 | + self.loop.run() |
181 | + # check if the token is valid |
182 | + if self._whoami is None: |
183 | + return False |
184 | + else: |
185 | + return True |
186 | + |
187 | + def clear_token(self): |
188 | + clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING) |
189 | + |
190 | + def get_oauth_token_and_verify_sync(self): |
191 | + token = self.get_oauth_token_sync() |
192 | + # check if the token is valid and reset it if it is not |
193 | + if token and not self.verify_token(token): |
194 | + self.clear_token() |
195 | + # re-trigger login |
196 | + token = self.get_oauth_token_sync() |
197 | + return token |
198 | + |
199 | + def get_oauth_token_sync(self): |
200 | + self.oauth = None |
201 | + sso = get_sso_backend( |
202 | + self.xid, |
203 | + SOFTWARE_CENTER_NAME_KEYRING, |
204 | + _(SOFTWARE_CENTER_SSO_DESCRIPTION)) |
205 | + sso.connect("login-successful", self._login_successful) |
206 | + sso.connect("login-failed", lambda s: self.loop.quit()) |
207 | + sso.connect("login-canceled", lambda s: self.loop.quit()) |
208 | + sso.login_or_register() |
209 | + self.loop.run() |
210 | + return self.oauth |
211 | |
212 | === added file 'softwarecenter/backend/piston/ubuntusso_pristine.py' |
213 | --- softwarecenter/backend/piston/ubuntusso_pristine.py 1970-01-01 00:00:00 +0000 |
214 | +++ softwarecenter/backend/piston/ubuntusso_pristine.py 2012-01-05 15:35:32 +0000 |
215 | @@ -0,0 +1,22 @@ |
216 | +from piston_mini_client import PistonAPI, returns_json |
217 | +from piston_mini_client.validators import oauth_protected |
218 | + |
219 | +# These are factored out as constants for if you need to work against a |
220 | +# server that doesn't support both schemes (like http-only dev servers) |
221 | +PUBLIC_API_SCHEME = 'http' |
222 | +AUTHENTICATED_API_SCHEME = 'https' |
223 | + |
224 | + |
225 | +# this is only here because: |
226 | +# a) ubuntu-sso-client does not support verifying if the credentials |
227 | +# are still valid |
228 | +# b) the restful client interface is not really needed because we just |
229 | +# need this one single call |
230 | +class UbuntuSsoAPI(PistonAPI): |
231 | + default_service_root = 'http://localhost:8000/api/2.0' |
232 | + |
233 | + @oauth_protected |
234 | + @returns_json |
235 | + def whoami(self, id=None): |
236 | + return self._get('accounts?ws.op=me', |
237 | + scheme=AUTHENTICATED_API_SCHEME) |
238 | |
239 | === added file 'softwarecenter/backend/recommends.py' |
240 | --- softwarecenter/backend/recommends.py 1970-01-01 00:00:00 +0000 |
241 | +++ softwarecenter/backend/recommends.py 2012-01-05 15:35:32 +0000 |
242 | @@ -0,0 +1,82 @@ |
243 | +#!/usr/bin/python |
244 | +# -*- coding: utf-8 -*- |
245 | + |
246 | +# Copyright (C) 2012 Canonical |
247 | +# |
248 | +# Authors: |
249 | +# Michael Vogt |
250 | +# |
251 | +# This program is free software; you can redistribute it and/or modify it under |
252 | +# the terms of the GNU General Public License as published by the Free Software |
253 | +# Foundation; version 3. |
254 | +# |
255 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
256 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
257 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
258 | +# details. |
259 | +# |
260 | +# You should have received a copy of the GNU General Public License along with |
261 | +# this program; if not, write to the Free Software Foundation, Inc., |
262 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
263 | + |
264 | +from gi.repository import GObject |
265 | +import logging |
266 | +import os |
267 | + |
268 | +import softwarecenter.paths |
269 | +from softwarecenter.paths import PistonHelpers |
270 | +from spawn_helper import SpawnHelper |
271 | +from softwarecenter.i18n import get_language |
272 | +from softwarecenter.distro import get_distro, get_current_arch |
273 | + |
274 | +LOG = logging.getLogger(__name__) |
275 | + |
276 | +class RecommenderAgent(GObject.GObject): |
277 | + |
278 | + __gsignals__ = { |
279 | + "recommend-top" : (GObject.SIGNAL_RUN_LAST, |
280 | + GObject.TYPE_NONE, |
281 | + (GObject.TYPE_PYOBJECT,), |
282 | + ), |
283 | + "error" : (GObject.SIGNAL_RUN_LAST, |
284 | + GObject.TYPE_NONE, |
285 | + (str,), |
286 | + ), |
287 | + } |
288 | + |
289 | + def __init__(self, xid=None): |
290 | + GObject.GObject.__init__(self) |
291 | + |
292 | + def query_recommend_top(self): |
293 | + # build the command |
294 | + spawner = SpawnHelper() |
295 | + spawner.parent_xid = self.xid |
296 | + spawner.connect("data-available", self._on_recommend_top_data) |
297 | + spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
298 | + spawner.run_generic_piston_helper( |
299 | + "SoftwareCenterRecommenderAPI", "recommend_top") |
300 | + |
301 | + def _on_recommend_top_data(self, spawner, piston_top_apps): |
302 | + self.emit("recommend-top", piston_top_apps) |
303 | + |
304 | + |
305 | +if __name__ == "__main__": |
306 | + from gi.repository import Gtk |
307 | + |
308 | + def _recommend_top(agent, top_apps): |
309 | + print ("_recommend_top: %s" % top_apps) |
310 | + def _error(agent, msg): |
311 | + print ("got a error: %s" % msg) |
312 | + Gtk.main_quit() |
313 | + |
314 | + # test specific stuff |
315 | + logging.basicConfig() |
316 | + softwarecenter.paths.datadir = "./data" |
317 | + |
318 | + agent = RecommenderAgent() |
319 | + agent.connect("recommend-top", _recommend_top) |
320 | + agent.connect("error", _error) |
321 | + agent.query_recommend_top() |
322 | + |
323 | + |
324 | + Gtk.main() |
325 | |
326 | === modified file 'softwarecenter/backend/reviews/__init__.py' |
327 | --- softwarecenter/backend/reviews/__init__.py 2011-11-07 10:04:33 +0000 |
328 | +++ softwarecenter/backend/reviews/__init__.py 2012-01-05 15:35:32 +0000 |
329 | @@ -19,6 +19,7 @@ |
330 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
331 | |
332 | import datetime |
333 | +import json |
334 | import logging |
335 | import operator |
336 | import os |
337 | @@ -107,13 +108,10 @@ |
338 | return False |
339 | |
340 | # run the command and add watcher |
341 | - cmd = [os.path.join( |
342 | - softwarecenter.paths.datadir, PistonHelpers.GET_USEFUL_VOTES), |
343 | - "--username", user, |
344 | - ] |
345 | spawn_helper = SpawnHelper() |
346 | spawn_helper.connect("data-available", self._on_usefulness_data) |
347 | - spawn_helper.run(cmd) |
348 | + spawn_helper.run_generic_piston_helper( |
349 | + "RatingsAndReviewsAPI", "get_usefulness", username=user) |
350 | |
351 | def _on_usefulness_data(self, spawn_helper, results): |
352 | '''called if usefulness retrieved from server''' |
353 | |
354 | === modified file 'softwarecenter/backend/reviews/rnr.py' |
355 | --- softwarecenter/backend/reviews/rnr.py 2011-11-07 08:26:13 +0000 |
356 | +++ softwarecenter/backend/reviews/rnr.py 2012-01-05 15:35:32 +0000 |
357 | @@ -128,22 +128,20 @@ |
358 | except OSError: |
359 | days_delta = 0 |
360 | LOG.debug("refresh with days_delta: %s" % days_delta) |
361 | + # FIXME: the server currently has bug (#757695) so we |
362 | + # can not turn this on just yet and need to use |
363 | + # the old "catch-all" review-stats for now |
364 | #origin = "any" |
365 | #distroseries = self.distro.get_codename() |
366 | - cmd = [os.path.join( |
367 | - softwarecenter.paths.datadir, PistonHelpers.GET_REVIEW_STATS), |
368 | - # FIXME: the server currently has bug (#757695) so we |
369 | - # can not turn this on just yet and need to use |
370 | - # the old "catch-all" review-stats for now |
371 | - #"--origin", origin, |
372 | - #"--distroseries", distroseries, |
373 | - ] |
374 | + spawn_helper = SpawnHelper() |
375 | + spawn_helper.connect("data-available", self._on_review_stats_data, callback) |
376 | if days_delta: |
377 | - cmd += ["--days-delta", str(days_delta)] |
378 | - spawn_helper = SpawnHelper() |
379 | - spawn_helper.connect("data-available", self._on_review_stats_data, callback) |
380 | - spawn_helper.run(cmd) |
381 | - |
382 | + spawn_helper.run_generic_piston_helper( |
383 | + "RatingsAndReviewsAPI", "review_stats", days=days_delta) |
384 | + else: |
385 | + spawn_helper.run_generic_piston_helper( |
386 | + "RatingsAndReviewsAPI", "review_stats") |
387 | + |
388 | def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback): |
389 | """ process stdout from the helper """ |
390 | review_stats = self.REVIEW_STATS_CACHE |
391 | |
392 | === modified file 'softwarecenter/backend/scagent.py' |
393 | --- softwarecenter/backend/scagent.py 2012-01-03 15:20:56 +0000 |
394 | +++ softwarecenter/backend/scagent.py 2012-01-05 15:35:32 +0000 |
395 | @@ -20,6 +20,7 @@ |
396 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
397 | |
398 | from gi.repository import GObject |
399 | +import json |
400 | import logging |
401 | import os |
402 | |
403 | @@ -56,14 +57,7 @@ |
404 | GObject.GObject.__init__(self) |
405 | self.distro = get_distro() |
406 | self.ignore_cache = ignore_cache |
407 | - binary = os.path.join( |
408 | - softwarecenter.paths.datadir, PistonHelpers.SOFTWARE_CENTER_AGENT) |
409 | - self.HELPER_CMD = [binary] |
410 | - if self.ignore_cache: |
411 | - self.HELPER_CMD.append("--ignore-cache") |
412 | - if xid: |
413 | - self.HELPER_CMD.append("--parent-xid") |
414 | - self.HELPER_CMD.append(str(xid)) |
415 | + self.xid = xid |
416 | |
417 | def query_available(self, series_name=None, arch_tag=None): |
418 | self._query_available(series_name, arch_tag, for_qa=False) |
419 | @@ -78,41 +72,52 @@ |
420 | if not arch_tag: |
421 | arch_tag = get_current_arch() |
422 | # build the command |
423 | - cmd = self.HELPER_CMD[:] |
424 | + spawner = SpawnHelper() |
425 | + spawner.parent_xid = self.xid |
426 | + spawner.ignore_cache = self.ignore_cache |
427 | + spawner.connect("data-available", self._on_query_available_data) |
428 | + spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
429 | if for_qa: |
430 | - cmd.append("available_apps_qa") |
431 | + spawner.needs_auth = True |
432 | + spawner.run_generic_piston_helper( |
433 | + "SoftwareCenterAgentAPI", |
434 | + "available_apps_qa", |
435 | + lang=get_langugage(), |
436 | + series=series_name, |
437 | + arch=arch_tag) |
438 | else: |
439 | - cmd.append("available_apps") |
440 | - cmd += [language, |
441 | - series_name, |
442 | - arch_tag, |
443 | - ] |
444 | - spawner = SpawnHelper() |
445 | - spawner.connect("data-available", self._on_query_available_data) |
446 | - spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
447 | - spawner.run(cmd) |
448 | + spawner.run_generic_piston_helper( |
449 | + "SoftwareCenterAgentAPI", |
450 | + "available_apps", |
451 | + lang=get_language(), |
452 | + series=series_name, |
453 | + arch=arch_tag) |
454 | + |
455 | def _on_query_available_data(self, spawner, piston_available): |
456 | self.emit("available", piston_available) |
457 | |
458 | def query_available_for_me(self, oauth_token, openid_identifier): |
459 | - cmd = self.HELPER_CMD[:] |
460 | - cmd.append("subscriptions_for_me") |
461 | spawner = SpawnHelper() |
462 | + spawner.parent_xid = self.xid |
463 | + spawner.ignore_cache = self.ignore_cache |
464 | spawner.connect("data-available", self._on_query_available_for_me_data) |
465 | spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
466 | - spawner.run(cmd) |
467 | + spawner.needs_auth = True |
468 | + spawner.run_generic_piston_helper( |
469 | + "SoftwareCenterAgentAPI", "subscriptions_for_me") |
470 | def _on_query_available_for_me_data(self, spawner, piston_available_for_me): |
471 | self.emit("available-for-me", piston_available_for_me) |
472 | |
473 | def query_exhibits(self): |
474 | - cmd = self.HELPER_CMD[:] |
475 | - cmd.append("exhibits") |
476 | - cmd.append(get_language()) |
477 | - cmd.append(self.distro.get_codename()) |
478 | spawner = SpawnHelper() |
479 | + spawner.parent_xid = self.xid |
480 | + spawner.ignore_cache = self.ignore_cache |
481 | spawner.connect("data-available", self._on_exhibits_data_available) |
482 | spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
483 | - spawner.run(cmd) |
484 | + spawner.run_generic_piston_helper( |
485 | + "SoftwareCenterAgentAPI", "exhiits", |
486 | + lang=get_language(), series=self.distro.get_codename()) |
487 | + |
488 | def _on_exhibits_data_available(self, spawner, exhibits): |
489 | for exhibit in exhibits: |
490 | # special case, if there is no title provided by the server |
491 | |
492 | === modified file 'softwarecenter/backend/spawn_helper.py' |
493 | --- softwarecenter/backend/spawn_helper.py 2011-09-15 14:24:14 +0000 |
494 | +++ softwarecenter/backend/spawn_helper.py 2012-01-05 15:35:32 +0000 |
495 | @@ -30,6 +30,9 @@ |
496 | import os |
497 | import json |
498 | |
499 | +import softwarecenter.paths |
500 | +from softwarecenter.paths import PistonHelpers |
501 | + |
502 | from gi.repository import GObject |
503 | |
504 | LOG = logging.getLogger(__name__) |
505 | @@ -59,6 +62,27 @@ |
506 | self._io_watch = None |
507 | self._child_watch = None |
508 | self._cmd = None |
509 | + self.needs_auth = False |
510 | + self.ignore_cache = False |
511 | + self.parent_xid = None |
512 | + |
513 | + def run_generic_piston_helper(self, klass, func, **kwargs): |
514 | + binary = os.path.join( |
515 | + softwarecenter.paths.datadir, PistonHelpers.GENERIC_HELPER) |
516 | + cmd = [binary] |
517 | + cmd += ["--datadir", softwarecenter.paths.datadir] |
518 | + if self.needs_auth: |
519 | + cmd.append("--needs-auth") |
520 | + if self.ignore_cache: |
521 | + cmd.append("--ignore-cache") |
522 | + if self.parent_xid: |
523 | + cmd.append("--parent-xid") |
524 | + cmd.append(str(xid)) |
525 | + cmd += [klass, func] |
526 | + if kwargs: |
527 | + cmd.append(json.dumps(kwargs)) |
528 | + LOG.debug("run_generic_piston_helper()") |
529 | + self.run(cmd) |
530 | |
531 | def run(self, cmd): |
532 | self._cmd = cmd |
533 | |
534 | === renamed file 'softwarecenter/backend/restfulclient.py' => 'softwarecenter/backend/ubuntusso.py' |
535 | --- softwarecenter/backend/restfulclient.py 2012-01-05 09:53:52 +0000 |
536 | +++ softwarecenter/backend/ubuntusso.py 2012-01-05 15:35:32 +0000 |
537 | @@ -19,133 +19,27 @@ |
538 | # this program; if not, write to the Free Software Foundation, Inc., |
539 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
540 | |
541 | -import os |
542 | |
543 | from gi.repository import GObject |
544 | |
545 | import logging |
546 | -import threading |
547 | - |
548 | -from softwarecenter.enums import BUY_SOMETHING_HOST |
549 | - |
550 | -# possible workaround for bug #599332 is to try to import lazr.restful |
551 | -# import lazr.restful |
552 | -# import lazr.restfulclient |
553 | - |
554 | -from lazr.restfulclient.resource import ServiceRoot |
555 | -from lazr.restfulclient.authorize import BasicHttpAuthorizer |
556 | -from lazr.restfulclient.authorize.oauth import OAuthAuthorizer |
557 | -from oauth.oauth import OAuthToken |
558 | - |
559 | -from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR |
560 | -from Queue import Queue |
561 | +import os |
562 | + |
563 | +import softwarecenter.paths |
564 | +from softwarecenter.paths import PistonHelpers |
565 | + |
566 | +import piston_mini_client.auth |
567 | |
568 | # mostly for testing |
569 | from fake_review_settings import FakeReviewSettings, network_delay |
570 | - |
571 | -from login import LoginBackend |
572 | +from spawn_helper import SpawnHelper |
573 | + |
574 | +from softwarecenter.utils import clear_token_from_ubuntu_sso |
575 | + |
576 | +from gettext import gettext as _ |
577 | |
578 | LOG = logging.getLogger(__name__) |
579 | |
580 | -UBUNTU_SSO_SERVICE = os.environ.get( |
581 | - "USSOC_SERVICE_URL", "https://login.ubuntu.com/api/1.0") |
582 | -UBUNTU_SOFTWARE_CENTER_AGENT_SERVICE = BUY_SOMETHING_HOST+"/api/1.0" |
583 | - |
584 | -class AttributesObject(object): |
585 | - """ convinient object to hold attributes """ |
586 | - MAX_REPR_STRING_SIZE = 30 |
587 | - |
588 | - def __repr__(self): |
589 | - s = "<'%s': " % self.__class__.__name__ |
590 | - for key in vars(self): |
591 | - value = str(getattr(self, key)) |
592 | - if len(value) > self.MAX_REPR_STRING_SIZE: |
593 | - value = "%s..." % value[:self.MAX_REPR_STRING_SIZE] |
594 | - s += "%s='%s';" % (key, value) |
595 | - s += ">" |
596 | - return s |
597 | - |
598 | - |
599 | -def restful_collection_to_real_python(restful_list): |
600 | - """ take a restful and convert it to a python list with real python |
601 | - objects |
602 | - """ |
603 | - l = [] |
604 | - for entry in restful_list: |
605 | - o = AttributesObject() |
606 | - for attr in entry.lp_attributes: |
607 | - setattr(o, attr, getattr(entry, attr)) |
608 | - l.append(o) |
609 | - return l |
610 | - |
611 | -class RestfulClientWorker(threading.Thread): |
612 | - """ a generic worker thread for a lazr.restfulclient """ |
613 | - |
614 | - def __init__(self, authorizer, service_root): |
615 | - """ init the thread """ |
616 | - threading.Thread.__init__(self) |
617 | - self._service_root_url = service_root |
618 | - self._authorizer = authorizer |
619 | - self._pending_requests = Queue() |
620 | - self._shutdown = False |
621 | - self.daemon = True |
622 | - self.error = None |
623 | - self._cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, |
624 | - "restfulclient") |
625 | - |
626 | - def run(self): |
627 | - """ |
628 | - Main thread run interface, logs into launchpad |
629 | - """ |
630 | - LOG.debug("lp worker thread run") |
631 | - try: |
632 | - self.service = ServiceRoot(self._authorizer, |
633 | - self._service_root_url, |
634 | - self._cachedir) |
635 | - except: |
636 | - logging.exception("worker thread can not connect to service root") |
637 | - self.error = "ERROR_SERVICE_ROOT" |
638 | - self._shutdown = True |
639 | - return |
640 | - # loop |
641 | - self._wait_for_commands() |
642 | - |
643 | - def shutdown(self): |
644 | - """Request shutdown""" |
645 | - self._shutdown = True |
646 | - |
647 | - def queue_request(self, func, args, kwargs, result_callback, error_callback): |
648 | - """ |
649 | - queue a (remote) command for execution, the result_callback will |
650 | - call with the result_list when done (that function will be |
651 | - called async) |
652 | - """ |
653 | - self._pending_requests.put((func, args, kwargs, result_callback, error_callback)) |
654 | - |
655 | - def _wait_for_commands(self): |
656 | - """internal helper that waits for commands""" |
657 | - while True: |
658 | - while not self._pending_requests.empty(): |
659 | - LOG.debug("found pending request") |
660 | - (func_str, args, kwargs, result_callback, error_callback) = self._pending_requests.get() |
661 | - # run func async |
662 | - try: |
663 | - func = self.service |
664 | - for part in func_str.split("."): |
665 | - func = getattr(func, part) |
666 | - res = func(*args, **kwargs) |
667 | - except Exception ,e: |
668 | - error_callback(e) |
669 | - else: |
670 | - result_callback(res) |
671 | - self._pending_requests.task_done() |
672 | - # wait a bit |
673 | - import time |
674 | - time.sleep(0.1) |
675 | - if (self._shutdown and |
676 | - self._pending_requests.empty()): |
677 | - return |
678 | - |
679 | class UbuntuSSOAPI(GObject.GObject): |
680 | |
681 | __gsignals__ = { |
682 | @@ -162,46 +56,24 @@ |
683 | |
684 | def __init__(self, token): |
685 | GObject.GObject.__init__(self) |
686 | - self._whoami = None |
687 | - self._error = None |
688 | - self.service = UBUNTU_SSO_SERVICE |
689 | + # FIXME: the token is not currently used as the helper will |
690 | + # query the keyring again |
691 | self.token = token |
692 | - token = OAuthToken(self.token["token"], self.token["token_secret"]) |
693 | - authorizer = OAuthAuthorizer(self.token["consumer_key"], |
694 | - self.token["consumer_secret"], |
695 | - access_token=token) |
696 | - # we need to init the GObject.init_threads() |
697 | - # - if we do it globally s-c will crash on exit (LP: #907568) |
698 | - # - if we don't do it, s-c will hang in worker_thread.start() |
699 | - # (even though threads_init is deprecated and according to the |
700 | - # docs is run automatically nowdays) |
701 | - # - if we do it here some apps will still crash |
702 | - self.worker_thread = RestfulClientWorker(authorizer, self.service) |
703 | - self.worker_thread.start() |
704 | - GObject.timeout_add(200, self._monitor_thread) |
705 | - |
706 | - def _monitor_thread(self): |
707 | - # glib bit of the threading, runs in the main thread |
708 | - if self._whoami is not None: |
709 | - self.emit("whoami", self._whoami) |
710 | - self._whoami = None |
711 | - if self._error is not None: |
712 | - self.emit("error", self._error) |
713 | - self._error = None |
714 | - return True |
715 | - |
716 | - def _thread_whoami_done(self, result): |
717 | - self._whoami = result |
718 | - |
719 | - def _thread_whoami_error(self, e): |
720 | - self._error = e |
721 | + |
722 | + def _on_whoami_data(self, spawner, piston_whoami): |
723 | + self.emit("whoami", piston_whoami) |
724 | |
725 | def whoami(self): |
726 | + """ trigger request for the getting account information, this |
727 | + will also verify if the current token is valid and if not |
728 | + trigger a cleanup/re-authenticate |
729 | + """ |
730 | LOG.debug("whoami called") |
731 | - self.worker_thread.queue_request("accounts.me", (), {}, |
732 | - self._thread_whoami_done, |
733 | - self._thread_whoami_error) |
734 | - |
735 | + spawner = SpawnHelper() |
736 | + spawner.connect("data-available", self._on_whoami_data) |
737 | + spawner.connect("error", lambda spawner, err: self.emit("error", err)) |
738 | + spawner.needs_auth = True |
739 | + spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami") |
740 | |
741 | class UbuntuSSOAPIFake(UbuntuSSOAPI): |
742 | |
743 | @@ -243,71 +115,6 @@ |
744 | return ubuntu_sso_class |
745 | |
746 | |
747 | -class UbuntuSSOlogin(LoginBackend): |
748 | - |
749 | - NEW_ACCOUNT_URL = "https://login.launchpad.net/+standalone-login" |
750 | - FORGOT_PASSWORD_URL = "https://login.ubuntu.com/+forgot_password" |
751 | - |
752 | - SSO_AUTHENTICATE_FUNC = "authentications.authenticate" |
753 | - |
754 | - def __init__(self): |
755 | - LoginBackend.__init__(self) |
756 | - self.service = UBUNTU_SSO_SERVICE |
757 | - # we get a dict here with the following keys: |
758 | - # token |
759 | - # consumer_key (also the openid identifier) |
760 | - # consumer_secret |
761 | - # token_secret |
762 | - # name (that is just 'software-center') |
763 | - self.oauth_credentials = None |
764 | - self._oauth_credentials = None |
765 | - self._login_failure = None |
766 | - self.worker_thread = None |
767 | - |
768 | - def shutdown(self): |
769 | - self.worker_thread.shutdown() |
770 | - |
771 | - def login(self, username=None, password=None): |
772 | - if not username or not password: |
773 | - self.emit("need-username-password") |
774 | - return |
775 | - authorizer = BasicHttpAuthorizer(username, password) |
776 | - self.worker_thread = RestfulClientWorker(authorizer, self.service) |
777 | - self.worker_thread.start() |
778 | - kwargs = { "token_name" : "software-center", |
779 | - } |
780 | - self.worker_thread.queue_request(self.SSO_AUTHENTICATE_FUNC, (), kwargs, |
781 | - self._thread_authentication_done, |
782 | - self._thread_authentication_error) |
783 | - GObject.timeout_add(200, self._monitor_thread) |
784 | - |
785 | - def _monitor_thread(self): |
786 | - # glib bit of the threading, runs in the main thread |
787 | - if self._oauth_credentials: |
788 | - self.emit("login-successful", self._oauth_credentials) |
789 | - self.oauth_credentials = self._oauth_credentials |
790 | - self._oauth_credentials = None |
791 | - if self._login_failure: |
792 | - self.emit("login-failed") |
793 | - self._login_failure = None |
794 | - return True |
795 | - |
796 | - def _thread_authentication_done(self, result): |
797 | - # runs in the thread context, can not touch gui or glib |
798 | - #print "_authentication_done", result |
799 | - self._oauth_credentials = result |
800 | - |
801 | - def _thread_authentication_error(self, e): |
802 | - # runs in the thread context, can not touch gui or glib |
803 | - #print "_authentication_error", type(e) |
804 | - self._login_failure = e |
805 | - |
806 | - def __del__(self): |
807 | - #print "del" |
808 | - if self.worker_thread: |
809 | - self.worker_thread.shutdown() |
810 | - |
811 | - |
812 | # test code |
813 | def _login_success(lp, token): |
814 | print "success", lp, token |
815 | @@ -323,40 +130,30 @@ |
816 | password = sys.stdin.readline().strip() |
817 | sso.login(user, password) |
818 | |
819 | -def _error(scaagent, errormsg): |
820 | - print "_error:", errormsg |
821 | -def _whoami(sso, whoami): |
822 | - print "whoami: ", whoami |
823 | - |
824 | # interactive test code |
825 | if __name__ == "__main__": |
826 | + def _whoami(sso, result): |
827 | + print "res: ", result |
828 | + Gtk.main_quit() |
829 | + def _error(sso, result): |
830 | + print "err: ", result |
831 | + Gtk.main_quit() |
832 | + def _dbus_maybe_login_successful(ssologin, oauth_result): |
833 | + print "got token, verify it now" |
834 | + sso = UbuntuSSOAPI(oauth_result) |
835 | + sso.connect("whoami", _whoami) |
836 | + sso.connect("error", _error) |
837 | + sso.whoami() |
838 | + |
839 | + from gi.repository import Gtk |
840 | import sys |
841 | logging.basicConfig(level=logging.DEBUG) |
842 | - |
843 | - if len(sys.argv) < 2: |
844 | - print "need an argument, one of: 'sso', 'ssologin'" |
845 | - sys.exit(1) |
846 | - |
847 | - elif sys.argv[1] == "sso": |
848 | - def _dbus_maybe_login_successful(ssologin, oauth_result): |
849 | - sso = UbuntuSSOAPI(oauth_result) |
850 | - sso.connect("whoami", _whoami) |
851 | - sso.connect("error", _error) |
852 | - sso.whoami() |
853 | - from login_sso import get_sso_backend |
854 | - backend = get_sso_backend("", "appname", "help_text") |
855 | - backend.connect("login-successful", _dbus_maybe_login_successful) |
856 | - backend.login_or_register() |
857 | - |
858 | - elif sys.argv[1] == "ssologin": |
859 | - ssologin = UbuntuSSOlogin() |
860 | - ssologin.connect("login-successful", _login_success) |
861 | - ssologin.connect("login-failed", _login_failed) |
862 | - ssologin.connect("need-username-password", _login_need_user_and_password) |
863 | - ssologin.login() |
864 | - |
865 | - else: |
866 | - print "unknown option" |
867 | - sys.exit(1) |
868 | + softwarecenter.paths.datadir = "./data" |
869 | + |
870 | + from login_sso import get_sso_backend |
871 | + backend = get_sso_backend("", "appname", "help_text") |
872 | + backend.connect("login-successful", _dbus_maybe_login_successful) |
873 | + backend.login_or_register() |
874 | + Gtk.main() |
875 | |
876 | |
877 | |
878 | === modified file 'softwarecenter/enums.py' |
879 | --- softwarecenter/enums.py 2011-12-16 12:50:18 +0000 |
880 | +++ softwarecenter/enums.py 2012-01-05 15:35:32 +0000 |
881 | @@ -34,6 +34,15 @@ |
882 | BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "https://software-center.ubuntu.com" |
883 | BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "http://software-center.ubuntu.com" |
884 | |
885 | +# recommender |
886 | +RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or "https://recommender.software-center.ubuntu.com" |
887 | + |
888 | +# for the sso login |
889 | +UBUNTU_SSO_SERVICE = os.environ.get( |
890 | + "USSOC_SERVICE_URL", "https://login.ubuntu.com/") |
891 | +SSO_LOGIN_HOST = UBUNTU_SSO_SERVICE |
892 | + |
893 | + |
894 | # version of the database, every time something gets added (like |
895 | # terms for mime-type) increase this (but keep as a string!) |
896 | DB_SCHEMA_VERSION = "6" |
897 | |
898 | === modified file 'softwarecenter/paths.py' |
899 | --- softwarecenter/paths.py 2011-09-13 12:45:56 +0000 |
900 | +++ softwarecenter/paths.py 2012-01-05 15:35:32 +0000 |
901 | @@ -86,9 +86,7 @@ |
902 | # piston helpers |
903 | class PistonHelpers: |
904 | GET_REVIEWS = "piston_get_reviews_helper.py" |
905 | - GET_REVIEW_STATS = "piston_get_review_stats_helper.py" |
906 | - GET_USEFUL_VOTES = "piston_get_useful_votes_helper.py" |
907 | - SOFTWARE_CENTER_AGENT = "piston_get_scagent_available_apps.py" |
908 | + GENERIC_HELPER = "piston_generic_helper.py" |
909 | |
910 | X2GO_HELPER = "x2go_helper.py" |
911 | |
912 | |
913 | === modified file 'test/gtk3/test_views.py' |
914 | --- test/gtk3/test_views.py 2011-11-15 10:54:54 +0000 |
915 | +++ test/gtk3/test_views.py 2012-01-05 15:35:32 +0000 |
916 | @@ -13,6 +13,8 @@ |
917 | |
918 | import softwarecenter.paths |
919 | softwarecenter.paths.datadir = "../data" |
920 | +import os |
921 | +os.environ["PYTHONPATH"] = "../" |
922 | |
923 | class TestViews(unittest.TestCase): |
924 | |
925 | |
926 | === modified file 'test/test_reviews.py' |
927 | --- test/test_reviews.py 2011-11-04 14:52:52 +0000 |
928 | +++ test/test_reviews.py 2012-01-05 15:35:32 +0000 |
929 | @@ -11,6 +11,7 @@ |
930 | |
931 | import softwarecenter.paths |
932 | softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR = tempfile.mkdtemp() |
933 | +softwarecenter.paths.datadir = "../data" |
934 | |
935 | from softwarecenter.backend.reviews.rnr import ( |
936 | ReviewLoaderSpawningRNRClient as ReviewLoader) |
937 | @@ -51,6 +52,6 @@ |
938 | main_loop.iteration() |
939 | |
940 | if __name__ == "__main__": |
941 | - #import logging |
942 | - #logging.basicConfig(level=logging.DEBUG) |
943 | + import logging |
944 | + logging.basicConfig(level=logging.DEBUG) |
945 | unittest.main() |
946 | |
947 | === modified file 'test/test_ubuntu_sso_api.py' |
948 | --- test/test_ubuntu_sso_api.py 2011-06-11 07:59:59 +0000 |
949 | +++ test/test_ubuntu_sso_api.py 2012-01-05 15:35:32 +0000 |
950 | @@ -6,10 +6,10 @@ |
951 | |
952 | import os |
953 | import unittest |
954 | -from softwarecenter.backend.restfulclient import (UbuntuSSOAPIFake, |
955 | - UbuntuSSOAPI, |
956 | - get_ubuntu_sso_backend, |
957 | - ) |
958 | +from softwarecenter.backend.ubuntusso import (UbuntuSSOAPIFake, |
959 | + UbuntuSSOAPI, |
960 | + get_ubuntu_sso_backend, |
961 | + ) |
962 | |
963 | class TestSSOAPI(unittest.TestCase): |
964 | """ tests the ubuntu sso backend stuff """ |
965 | |
966 | === added file 'utils/piston-helpers/piston_generic_helper.py' |
967 | --- utils/piston-helpers/piston_generic_helper.py 1970-01-01 00:00:00 +0000 |
968 | +++ utils/piston-helpers/piston_generic_helper.py 2012-01-05 15:35:32 +0000 |
969 | @@ -0,0 +1,215 @@ |
970 | +#!/usr/bin/python |
971 | +# Copyright (C) 2011 Canonical |
972 | +# |
973 | +# Authors: |
974 | +# Michael Vogt |
975 | +# |
976 | +# This program is free software; you can redistribute it and/or modify it under |
977 | +# the terms of the GNU General Public License as published by the Free Software |
978 | +# Foundation; version 3. |
979 | +# |
980 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
981 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
982 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
983 | +# details. |
984 | +# |
985 | +# You should have received a copy of the GNU General Public License along with |
986 | +# this program; if not, write to the Free Software Foundation, Inc., |
987 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
988 | + |
989 | +import argparse |
990 | +import logging |
991 | +import os |
992 | +import json |
993 | +import pickle |
994 | +import sys |
995 | + |
996 | +from gi.repository import GObject |
997 | + |
998 | +# useful for debugging |
999 | +if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ: |
1000 | + import httplib2 |
1001 | + httplib2.debuglevel = 1 |
1002 | + |
1003 | +import piston_mini_client.auth |
1004 | + |
1005 | +try: |
1006 | + import softwarecenter |
1007 | +except ImportError: |
1008 | + if os.path.exists("../softwarecenter"): |
1009 | + sys.path.insert(0, "../") |
1010 | + else: |
1011 | + sys.path.insert(0, "/usr/share/software-center") |
1012 | + |
1013 | +import softwarecenter.paths |
1014 | +from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR |
1015 | +from softwarecenter.backend.login_sso import get_sso_backend |
1016 | + |
1017 | +from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING, |
1018 | + SOFTWARE_CENTER_SSO_DESCRIPTION, |
1019 | + ) |
1020 | + |
1021 | +# the piston import |
1022 | +from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI |
1023 | +from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI |
1024 | +from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI |
1025 | +from softwarecenter.backend.piston.sreclient_pristine import SoftwareCenterRecommenderAPI |
1026 | + |
1027 | +# patch default_service_root to the one we use |
1028 | +from softwarecenter.enums import SSO_LOGIN_HOST |
1029 | +UbuntuSsoAPI.default_service_root = SSO_LOGIN_HOST+"/api/1.0" |
1030 | + |
1031 | +from softwarecenter.enums import RECOMMENDER_HOST |
1032 | +SoftwareCenterRecommenderAPI.default_service_root = RECOMMENDER_HOST+"/api/1.0" |
1033 | + |
1034 | + |
1035 | +RatingsAndReviewsAPI # pyflakes |
1036 | +UbuntuSsoAPI # pyflakes |
1037 | +SoftwareCenterAgentAPI # pyflakes |
1038 | +SoftwareCenterRecommenderAPI |
1039 | + |
1040 | +from gettext import gettext as _ |
1041 | + |
1042 | +# helper that is only used to verify that the token is ok |
1043 | +# and trigger cleanup if not |
1044 | +class SSOLoginHelper(object): |
1045 | + |
1046 | + def __init__(self, xid=0): |
1047 | + self.oauth = None |
1048 | + self.xid = xid |
1049 | + self.loop = GObject.MainLoop(GObject.main_context_default()) |
1050 | + |
1051 | + def _login_successful(self, sso_backend, oauth_result): |
1052 | + LOG.debug("_login_successful") |
1053 | + self.oauth = oauth_result |
1054 | + # FIXME: actually verify the token against ubuntu SSO |
1055 | + self.loop.quit() |
1056 | + |
1057 | + def verify_token_sync(self, token): |
1058 | + LOG.debug("verify_token") |
1059 | + auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], |
1060 | + token["token_secret"], |
1061 | + token["consumer_key"], |
1062 | + token["consumer_secret"]) |
1063 | + api = UbuntuSsoAPI(auth=auth) |
1064 | + try: |
1065 | + res = api.whoami() |
1066 | + except: |
1067 | + LOG.exception("api.whoami failed") |
1068 | + return None |
1069 | + return res |
1070 | + |
1071 | + def clear_token(self): |
1072 | + clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING) |
1073 | + |
1074 | + def get_oauth_token_sync(self): |
1075 | + self.oauth = None |
1076 | + sso = get_sso_backend( |
1077 | + self.xid, |
1078 | + SOFTWARE_CENTER_NAME_KEYRING, |
1079 | + _(SOFTWARE_CENTER_SSO_DESCRIPTION)) |
1080 | + sso.connect("login-successful", self._login_successful) |
1081 | + sso.connect("login-failed", lambda s: self.loop.quit()) |
1082 | + sso.connect("login-canceled", lambda s: self.loop.quit()) |
1083 | + sso.login_or_register() |
1084 | + self.loop.run() |
1085 | + return self.oauth |
1086 | + |
1087 | + def get_oauth_token_and_verify_sync(self): |
1088 | + token = self.get_oauth_token_sync() |
1089 | + # check if the token is valid and reset it if it is not |
1090 | + if token and not self.verify_token_sync(token): |
1091 | + self.clear_token() |
1092 | + # re-trigger login |
1093 | + token = self.get_oauth_token_sync() |
1094 | + return token |
1095 | + |
1096 | + |
1097 | + |
1098 | +LOG = logging.getLogger(__name__) |
1099 | + |
1100 | +if __name__ == "__main__": |
1101 | + logging.basicConfig() |
1102 | + |
1103 | + # command line parser |
1104 | + parser = argparse.ArgumentParser( |
1105 | + description="Backend helper for piston-mini-client based APIs") |
1106 | + parser.add_argument("--debug", action="store_true", default=False, |
1107 | + help="enable debug output") |
1108 | + parser.add_argument("--datadir", default="/usr/share/software-center", |
1109 | + help="setup alternative datadir") |
1110 | + parser.add_argument("--ignore-cache", action="store_true", default=False, |
1111 | + help="force ignore cache") |
1112 | + parser.add_argument("--needs-auth", default=False, action="store_true", |
1113 | + help="need oauth credentials") |
1114 | + parser.add_argument("--output", default="pickle", |
1115 | + help="output result as [pickle|json|text]") |
1116 | + parser.add_argument("--parent-xid", default=0, |
1117 | + help="xid of the parent window") |
1118 | + parser.add_argument('klass', help='class to use') |
1119 | + parser.add_argument('function', help='function to call') |
1120 | + parser.add_argument('kwargs', nargs="?", |
1121 | + help='kwargs for the function call as json') |
1122 | + args = parser.parse_args() |
1123 | + |
1124 | + if args.debug: |
1125 | + logging.basicConfig(level=logging.DEBUG) |
1126 | + LOG.setLevel(logging.DEBUG) |
1127 | + |
1128 | + if args.ignore_cache: |
1129 | + cachedir = None |
1130 | + else: |
1131 | + cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "piston-helper") |
1132 | + |
1133 | + # check what we need to call |
1134 | + klass = globals()[args.klass] |
1135 | + func = args.function |
1136 | + kwargs = json.loads(args.kwargs or '{}') |
1137 | + |
1138 | + softwarecenter.paths.datadir = args.datadir |
1139 | + |
1140 | + if args.needs_auth: |
1141 | + helper = SSOLoginHelper(args.parent_xid) |
1142 | + token = helper.get_oauth_token_and_verify_sync() |
1143 | + # if we don't have a token, error here |
1144 | + if not token: |
1145 | + sys.stderr.write("ERROR: can not obtain a oauth token\n") |
1146 | + sys.exit(1) |
1147 | + |
1148 | + auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], |
1149 | + token["token_secret"], |
1150 | + token["consumer_key"], |
1151 | + token["consumer_secret"]) |
1152 | + api = klass(cachedir=cachedir, auth=auth) |
1153 | + else: |
1154 | + api = klass() |
1155 | + |
1156 | + piston_reply = None |
1157 | + # handle the args |
1158 | + f = getattr(api, func) |
1159 | + try: |
1160 | + piston_reply = f(**kwargs) |
1161 | + except: |
1162 | + LOG.exception("urclient_apps") |
1163 | + sys.exit(1) |
1164 | + |
1165 | + # print to stdout where its consumed by the parent |
1166 | + if piston_reply is None: |
1167 | + LOG.warn("no data") |
1168 | + sys.exit(0) |
1169 | + |
1170 | + # check what format to use |
1171 | + if args.output == "pickle": |
1172 | + res = pickle.dumps(piston_reply) |
1173 | + elif args.output == "json": |
1174 | + res = json.dumps(piston_reply) |
1175 | + elif args.output == "text": |
1176 | + res = piston_reply |
1177 | + |
1178 | + # and output it |
1179 | + try: |
1180 | + print res |
1181 | + except IOError: |
1182 | + # this can happen if the parent gets killed, no need to trigger |
1183 | + # apport for this |
1184 | + pass |
1185 | |
1186 | === removed file 'utils/piston-helpers/piston_get_review_stats_helper.py' |
1187 | --- utils/piston-helpers/piston_get_review_stats_helper.py 2011-07-13 14:25:55 +0000 |
1188 | +++ utils/piston-helpers/piston_get_review_stats_helper.py 1970-01-01 00:00:00 +0000 |
1189 | @@ -1,65 +0,0 @@ |
1190 | -#!/usr/bin/python |
1191 | - |
1192 | -import os |
1193 | -import pickle |
1194 | -import logging |
1195 | -import sys |
1196 | - |
1197 | -from optparse import OptionParser |
1198 | - |
1199 | -from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR |
1200 | -from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI |
1201 | - |
1202 | -LOG = logging.getLogger(__name__) |
1203 | - |
1204 | -if __name__ == "__main__": |
1205 | - logging.basicConfig() |
1206 | - |
1207 | - # common options for optparse go here |
1208 | - parser = OptionParser() |
1209 | - |
1210 | - # check options |
1211 | - parser.add_option("--origin", default="any") |
1212 | - parser.add_option("--distroseries", default="any") |
1213 | - parser.add_option("--days-delta", default=None) |
1214 | - parser.add_option("--debug", |
1215 | - action="store_true", default=False) |
1216 | - parser.add_option("--no-pickle", |
1217 | - action="store_true", default=False) |
1218 | - (options, args) = parser.parse_args() |
1219 | - |
1220 | - if options.debug: |
1221 | - LOG.setLevel(logging.DEBUG) |
1222 | - |
1223 | - cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") |
1224 | - rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) |
1225 | - |
1226 | - kwargs = {"origin": options.origin, |
1227 | - "distroseries": options.distroseries, |
1228 | - } |
1229 | - if options.days_delta: |
1230 | - kwargs["days"] = int(options.days_delta) |
1231 | - |
1232 | - # depending on the time delta, use a different call |
1233 | - piston_review_stats = [] |
1234 | - try: |
1235 | - piston_review_stats = rnrclient.review_stats(**kwargs) |
1236 | - except: |
1237 | - LOG.exception("get_review_stats") |
1238 | - sys.exit(1) |
1239 | - |
1240 | - # useful for debugging |
1241 | - if options.no_pickle: |
1242 | - print "\n".join(["pkgname=%s total=%s avg=%s" % (s.package_name, |
1243 | - s.ratings_total, |
1244 | - s.ratings_average) |
1245 | - for s in piston_review_stats]) |
1246 | - else: |
1247 | - # print to stdout where its consumed by the parent |
1248 | - try: |
1249 | - print pickle.dumps(piston_review_stats) |
1250 | - except IOError: |
1251 | - # this can happen if the parent gets killed, no need to trigger |
1252 | - # apport for this |
1253 | - sys.exit(1) |
1254 | - |
1255 | |
1256 | === renamed file 'utils/piston-helpers/piston_get_scagent_available_apps.py' => 'utils/piston-helpers/piston_get_scagent_available_apps.py.THIS' |
1257 | --- utils/piston-helpers/piston_get_scagent_available_apps.py 2012-01-05 09:53:52 +0000 |
1258 | +++ utils/piston-helpers/piston_get_scagent_available_apps.py.THIS 2012-01-05 15:35:32 +0000 |
1259 | @@ -10,64 +10,14 @@ |
1260 | |
1261 | import piston_mini_client.auth |
1262 | |
1263 | -from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING, |
1264 | - SOFTWARE_CENTER_SSO_DESCRIPTION, |
1265 | - ) |
1266 | from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR |
1267 | from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI |
1268 | -from softwarecenter.backend.login_sso import get_sso_backend |
1269 | -from softwarecenter.backend.restfulclient import UbuntuSSOAPI |
1270 | -from softwarecenter.utils import clear_token_from_ubuntu_sso |
1271 | |
1272 | from gettext import gettext as _ |
1273 | |
1274 | LOG = logging.getLogger(__name__) |
1275 | |
1276 | -class SSOLoginHelper(object): |
1277 | - def __init__(self, xid=0): |
1278 | - self.oauth = None |
1279 | - self.xid = xid |
1280 | - self.loop = GObject.MainLoop(GObject.main_context_default()) |
1281 | - |
1282 | - def _login_successful(self, sso_backend, oauth_result): |
1283 | - LOG.debug("_login_successful") |
1284 | - self.oauth = oauth_result |
1285 | - # FIXME: actually verify the token against ubuntu SSO |
1286 | - self.loop.quit() |
1287 | - |
1288 | - def verify_token(self, token): |
1289 | - LOG.debug("verify_token") |
1290 | - def _whoami_done(sso, me): |
1291 | - self._whoami = me |
1292 | - self.loop.quit() |
1293 | - self._whoami = None |
1294 | - sso = UbuntuSSOAPI(token) |
1295 | - sso.connect("whoami", _whoami_done) |
1296 | - sso.connect("error", lambda sso, err: self.loop.quit()) |
1297 | - sso.whoami() |
1298 | - self.loop.run() |
1299 | - LOG.debug("verify_token finished") |
1300 | - # check if the token is valid |
1301 | - if self._whoami is None: |
1302 | - return False |
1303 | - else: |
1304 | - return True |
1305 | - |
1306 | - def clear_token(self): |
1307 | - clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING) |
1308 | - |
1309 | - def get_oauth_token_sync(self): |
1310 | - self.oauth = None |
1311 | - sso = get_sso_backend( |
1312 | - self.xid, |
1313 | - SOFTWARE_CENTER_NAME_KEYRING, |
1314 | - _(SOFTWARE_CENTER_SSO_DESCRIPTION)) |
1315 | - sso.connect("login-successful", self._login_successful) |
1316 | - sso.connect("login-failed", lambda s: self.loop.quit()) |
1317 | - sso.connect("login-canceled", lambda s: self.loop.quit()) |
1318 | - sso.login_or_register() |
1319 | - self.loop.run() |
1320 | - return self.oauth |
1321 | +from softwarecenter.backend.piston.sso_helper import SSOLoginHelper |
1322 | |
1323 | if __name__ == "__main__": |
1324 | logging.basicConfig() |
1325 | @@ -118,12 +68,7 @@ |
1326 | # check if auth is required |
1327 | if args.command in ("available_apps_qa", "subscriptions_for_me"): |
1328 | helper = SSOLoginHelper(args.parent_xid) |
1329 | - token = helper.get_oauth_token_sync() |
1330 | - # check if the token is valid and reset it if it is not |
1331 | - if token and not helper.verify_token(token): |
1332 | - helper.clear_token() |
1333 | - # re-trigger login |
1334 | - token = helper.get_oauth_token_sync() |
1335 | + token = helper.get_oauth_token_and_verify_sync() |
1336 | # if we don't have a token, error here |
1337 | if not token: |
1338 | sys.stderr.write("ERROR: can not obtain a oauth token\n") |
1339 | |
1340 | === removed file 'utils/piston-helpers/piston_get_useful_votes_helper.py' |
1341 | --- utils/piston-helpers/piston_get_useful_votes_helper.py 2011-08-19 11:25:58 +0000 |
1342 | +++ utils/piston-helpers/piston_get_useful_votes_helper.py 1970-01-01 00:00:00 +0000 |
1343 | @@ -1,46 +0,0 @@ |
1344 | -#!/usr/bin/python |
1345 | - |
1346 | -import pickle |
1347 | -import logging |
1348 | -import sys |
1349 | - |
1350 | -from optparse import OptionParser |
1351 | -from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI |
1352 | -from piston_mini_client import APIError |
1353 | - |
1354 | -LOG = logging.getLogger(__name__) |
1355 | - |
1356 | -if __name__ == "__main__": |
1357 | - logging.basicConfig() |
1358 | - |
1359 | - # common options for optparse go here |
1360 | - parser = OptionParser() |
1361 | - |
1362 | - # check options |
1363 | - parser.add_option("--username", default=None) |
1364 | - (options, args) = parser.parse_args() |
1365 | - |
1366 | - rnrclient = RatingsAndReviewsAPI() |
1367 | - |
1368 | - useful_votes = [] |
1369 | - |
1370 | - if options.username: |
1371 | - try: |
1372 | - useful_votes = rnrclient.get_usefulness(username=options.username) |
1373 | - except ValueError as e: |
1374 | - LOG.error("failed to parse '%s'" % e.doc) |
1375 | - except APIError, e: |
1376 | - LOG.warn("_get_useful_votes_helper: no usefulness able to be retrieved for username: %s" % (options.username)) |
1377 | - LOG.debug("_get_reviews_threaded: no reviews able to be retrieved: %s" % e) |
1378 | - except: |
1379 | - LOG.exception("_get_useful_votes_helper") |
1380 | - sys.exit(1) |
1381 | - |
1382 | - # print to stdout where its consumed by the parent |
1383 | - try: |
1384 | - print pickle.dumps(useful_votes) |
1385 | - except IOError: |
1386 | - # this can happen if the parent gets killed, no need to trigger |
1387 | - # apport for this |
1388 | - pass |
1389 | - |
1390 | |
1391 | === modified file 'utils/submit_review_gtk3.py' |
1392 | --- utils/submit_review_gtk3.py 2011-10-24 07:49:37 +0000 |
1393 | +++ utils/submit_review_gtk3.py 2012-01-05 15:35:32 +0000 |
1394 | @@ -50,7 +50,7 @@ |
1395 | from gettext import gettext as _ |
1396 | from optparse import OptionParser |
1397 | |
1398 | -from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend |
1399 | +from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend |
1400 | |
1401 | import piston_mini_client |
1402 | |
1403 | @@ -490,6 +490,8 @@ |
1404 | self.ssoapi = get_ubuntu_sso_backend(self.token) |
1405 | self.ssoapi.connect("whoami", self._whoami_done) |
1406 | self.ssoapi.connect("error", self._whoami_error) |
1407 | + # this will automatically verify the token and retrigger login |
1408 | + # if its expired |
1409 | self.ssoapi.whoami() |
1410 | |
1411 | def _whoami_done(self, ssologin, result): |
1412 | @@ -500,22 +502,6 @@ |
1413 | |
1414 | def _whoami_error(self, ssologin, e): |
1415 | logging.error("whoami error '%s'" % e) |
1416 | - # HACK: clear the token from the keyring assuming that it expired |
1417 | - # or got deauthorized by the user on the website |
1418 | - # this really should be done by ubuntu-sso-client itself |
1419 | - import lazr.restfulclient.errors |
1420 | - # compat with maverick, it does not have Unauthorized yet |
1421 | - if hasattr(lazr.restfulclient.errors, "Unauthorized"): |
1422 | - errortype = lazr.restfulclient.errors.Unauthorized |
1423 | - else: |
1424 | - errortype = lazr.restfulclient.errors.HTTPError |
1425 | - if (type(e) == errortype and |
1426 | - self._whoami_token_reset_nr == 0): |
1427 | - logging.warn("authentication error, reseting token and retrying") |
1428 | - clear_token_from_ubuntu_sso(self.appname) |
1429 | - self._whoami_token_reset_nr += 1 |
1430 | - self.login(show_register=False) |
1431 | - return |
1432 | # show error |
1433 | self.status_spinner.hide() |
1434 | self.login_status_label.set_markup('<b><big>%s</big></b>' % _("Failed to log in")) |
Hey Michael! Please double check one thing, there's a funky rename in the last rev of the branch:
utils/ piston- helpers/ piston_ get_scagent_ available_ apps.py => utils/piston- helpers/ piston_ get_scagent_ available_ apps.py. THIS
There are also a few simple pyflakes fixes that we'll want to do before merging.
This is a very nice kickoff for the client-side recommendations work, thanks!!