Merge lp:~mvo/software-center/recommends into lp:software-center

Proposed by Michael Vogt
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
Reviewer Review Type Date Requested Status
software-store-developers Pending
Review via email: mp+87639@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Gary Lasker (gary-lasker) wrote :

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!!

lp:~mvo/software-center/recommends updated
2630. By Michael Vogt

add "recommend_me" call

Revision history for this message
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-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.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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"))

Subscribers

People subscribed via source and target branches