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
=== added symlink 'data/piston_generic_helper.py'
=== target is u'../utils/piston-helpers/piston_generic_helper.py'
=== added symlink 'data/piston_get_recommends.py'
=== target is u'../utils/piston-helpers/piston_get_recommends.py'
=== removed symlink 'data/piston_get_review_stats_helper.py'
=== target was u'../utils/piston-helpers/piston_get_review_stats_helper.py'
=== removed symlink 'data/piston_get_scagent_available_apps.py'
=== target was u'../utils/piston-helpers/piston_get_scagent_available_apps.py'
=== removed symlink 'data/piston_get_useful_votes_helper.py'
=== target was u'../utils/piston-helpers/piston_get_useful_votes_helper.py'
=== modified file 'debian/control'
--- debian/control 2011-12-06 14:22:20 +0000
+++ debian/control 2012-01-05 15:35:32 +0000
@@ -40,7 +40,6 @@
40 policykit-1,40 policykit-1,
41 policykit-1-gnome | policykit-1-kde,41 policykit-1-gnome | policykit-1-kde,
42 python-xdg,42 python-xdg,
43 python-lazr.restfulclient,
44 ubuntu-sso-client (>= 0.99.6),43 ubuntu-sso-client (>= 0.99.6),
45 python-piston-mini-client (>= 0.1+bzr29),44 python-piston-mini-client (>= 0.1+bzr29),
46 oneconf (>= 0.2.6),45 oneconf (>= 0.2.6),
4746
=== modified file 'softwarecenter/backend/login.py'
--- softwarecenter/backend/login.py 2011-09-15 14:24:14 +0000
+++ softwarecenter/backend/login.py 2012-01-05 15:35:32 +0000
@@ -49,10 +49,3 @@
49 raise NotImplemented49 raise NotImplemented
50 def cancel_login(self):50 def cancel_login(self):
51 self.emit("login-canceled")51 self.emit("login-canceled")
52 @property
53 def new_account_url(self):
54 return self.NEW_ACCOUNT_URL
55 @property
56 def forgoten_password_url(self):
57 return self.FORGOT_PASSWORD_URL
58
5952
=== modified file 'softwarecenter/backend/oneconfhandler/core.py'
--- softwarecenter/backend/oneconfhandler/core.py 2011-10-26 12:01:19 +0000
+++ softwarecenter/backend/oneconfhandler/core.py 2012-01-05 15:35:32 +0000
@@ -22,7 +22,7 @@
22from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY22from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY
2323
24from softwarecenter.backend.login_sso import get_sso_backend24from softwarecenter.backend.login_sso import get_sso_backend
25from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend25from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend
26from softwarecenter.utils import clear_token_from_ubuntu_sso26from softwarecenter.utils import clear_token_from_ubuntu_sso
2727
28import datetime28import datetime
@@ -176,6 +176,8 @@
176 self.ssoapi = get_ubuntu_sso_backend(token)176 self.ssoapi = get_ubuntu_sso_backend(token)
177 self.ssoapi.connect("whoami", self._whoami_done)177 self.ssoapi.connect("whoami", self._whoami_done)
178 self.ssoapi.connect("error", self._whoami_error)178 self.ssoapi.connect("error", self._whoami_error)
179 # this will automatically verify the token and retrigger login
180 # if its expired
179 self.ssoapi.whoami()181 self.ssoapi.whoami()
180182
181 def _whoami_done(self, ssologin, result):183 def _whoami_done(self, ssologin, result):
@@ -184,13 +186,5 @@
184186
185 def _whoami_error(self, ssologin, e):187 def _whoami_error(self, ssologin, e):
186 logging.error("whoami error '%s'" % e)188 logging.error("whoami error '%s'" % e)
187 # HACK: clear the token from the keyring assuming that it expired189 self._share_inventory(False)
188 # or got deauthorized by the user on the website190 return
189 # this really should be done by ubuntu-sso-client itself
190 import lazr.restfulclient.errors
191 errortype = lazr.restfulclient.errors.HTTPError
192 if (type(e) == errortype):
193 LOG.warn("authentication error, resetting token and retrying")
194 clear_token_from_ubuntu_sso(self.appname)
195 self._share_inventory(False)
196 return
197191
=== added file 'softwarecenter/backend/piston/sreclient_pristine.py'
--- softwarecenter/backend/piston/sreclient_pristine.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/piston/sreclient_pristine.py 2012-01-05 15:35:32 +0000
@@ -0,0 +1,41 @@
1from piston_mini_client import (PistonAPI, PistonResponseObject,
2 returns_list_of, returns_json)
3from piston_mini_client.validators import (validate_pattern, validate,
4 oauth_protected)
5
6# These are factored out as constants for if you need to work against a
7# server that doesn't support both schemes (like http-only dev servers)
8PUBLIC_API_SCHEME = 'http'
9AUTHENTICATED_API_SCHEME = 'https'
10
11class SoftwareCenterRecommenderAPI(PistonAPI):
12 default_service_root = 'http://localhost:8000/api/2.0'
13
14 @returns_json
15 def server_status(self):
16 return self._get('server-status', scheme=PUBLIC_API_SCHEME)
17
18 @oauth_protected
19 @returns_json
20 def profile(self):
21 return self._get('profile', scheme=PUBLIC_API_SCHEME)
22
23 @oauth_protected
24 def recommend_me(self):
25 return self._get('recommended_me', scheme=PUBLIC_API_SCHEME)
26
27 @oauth_protected
28 @validate('pkgname', str)
29 def recommend_app(self, pkgname):
30 return self._get('recommended_app/%s/' % pkgname,
31 scheme=PUBLIC_API_SCHEME)
32
33 @returns_json
34 def recommend_top(self):
35 return self._get('recommend_top', scheme=PUBLIC_API_SCHEME)
36
37 @returns_json
38 def feedback(self):
39 return self._get('feedback', scheme=PUBLIC_API_SCHEME)
40
41
042
=== added file 'softwarecenter/backend/piston/sso_helper.py'
--- softwarecenter/backend/piston/sso_helper.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/piston/sso_helper.py 2012-01-05 15:35:32 +0000
@@ -0,0 +1,85 @@
1#!/usr/bin/python
2# Copyright (C) 2012 Canonical
3#
4# Authors:
5# Michael Vogt
6#
7# This program is free software; you can redistribute it and/or modify it under
8# the terms of the GNU General Public License as published by the Free Software
9# Foundation; version 3.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20from gi.repository import GObject
21from gettext import gettext as _
22
23from softwarecenter.backend.login_sso import get_sso_backend
24from softwarecenter.backend.restfulclient import UbuntuSSOAPI
25from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
26 SOFTWARE_CENTER_SSO_DESCRIPTION,
27 )
28from softwarecenter.utils import clear_token_from_ubuntu_sso
29
30
31
32class SSOLoginHelper(object):
33 def __init__(self, xid=0):
34 self.oauth = None
35 self.xid = xid
36 self.loop = GObject.MainLoop(GObject.main_context_default())
37
38 def _login_successful(self, sso_backend, oauth_result):
39 self.oauth = oauth_result
40 # FIXME: actually verify the token against ubuntu SSO
41 self.loop.quit()
42
43 def verify_token(self, token):
44 def _whoami_done(sso, me):
45 self._whoami = me
46 self.loop.quit()
47 def _whoami_error(sso, err):
48 #print "ERRR", err
49 self.loop.quit()
50 self._whoami = None
51 sso = UbuntuSSOAPI(token)
52 sso.connect("whoami", _whoami_done)
53 sso.connect("error", _whoami_error)
54 sso.whoami()
55 self.loop.run()
56 # check if the token is valid
57 if self._whoami is None:
58 return False
59 else:
60 return True
61
62 def clear_token(self):
63 clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING)
64
65 def get_oauth_token_and_verify_sync(self):
66 token = self.get_oauth_token_sync()
67 # check if the token is valid and reset it if it is not
68 if token and not self.verify_token(token):
69 self.clear_token()
70 # re-trigger login
71 token = self.get_oauth_token_sync()
72 return token
73
74 def get_oauth_token_sync(self):
75 self.oauth = None
76 sso = get_sso_backend(
77 self.xid,
78 SOFTWARE_CENTER_NAME_KEYRING,
79 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
80 sso.connect("login-successful", self._login_successful)
81 sso.connect("login-failed", lambda s: self.loop.quit())
82 sso.connect("login-canceled", lambda s: self.loop.quit())
83 sso.login_or_register()
84 self.loop.run()
85 return self.oauth
086
=== added file 'softwarecenter/backend/piston/ubuntusso_pristine.py'
--- softwarecenter/backend/piston/ubuntusso_pristine.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/piston/ubuntusso_pristine.py 2012-01-05 15:35:32 +0000
@@ -0,0 +1,22 @@
1from piston_mini_client import PistonAPI, returns_json
2from piston_mini_client.validators import oauth_protected
3
4# These are factored out as constants for if you need to work against a
5# server that doesn't support both schemes (like http-only dev servers)
6PUBLIC_API_SCHEME = 'http'
7AUTHENTICATED_API_SCHEME = 'https'
8
9
10# this is only here because:
11# a) ubuntu-sso-client does not support verifying if the credentials
12# are still valid
13# b) the restful client interface is not really needed because we just
14# need this one single call
15class UbuntuSsoAPI(PistonAPI):
16 default_service_root = 'http://localhost:8000/api/2.0'
17
18 @oauth_protected
19 @returns_json
20 def whoami(self, id=None):
21 return self._get('accounts?ws.op=me',
22 scheme=AUTHENTICATED_API_SCHEME)
023
=== added file 'softwarecenter/backend/recommends.py'
--- softwarecenter/backend/recommends.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/recommends.py 2012-01-05 15:35:32 +0000
@@ -0,0 +1,82 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2012 Canonical
5#
6# Authors:
7# Michael Vogt
8#
9# This program is free software; you can redistribute it and/or modify it under
10# the terms of the GNU General Public License as published by the Free Software
11# Foundation; version 3.
12#
13# This program is distributed in the hope that it will be useful, but WITHOUT
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16# details.
17#
18# You should have received a copy of the GNU General Public License along with
19# this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22from gi.repository import GObject
23import logging
24import os
25
26import softwarecenter.paths
27from softwarecenter.paths import PistonHelpers
28from spawn_helper import SpawnHelper
29from softwarecenter.i18n import get_language
30from softwarecenter.distro import get_distro, get_current_arch
31
32LOG = logging.getLogger(__name__)
33
34class RecommenderAgent(GObject.GObject):
35
36 __gsignals__ = {
37 "recommend-top" : (GObject.SIGNAL_RUN_LAST,
38 GObject.TYPE_NONE,
39 (GObject.TYPE_PYOBJECT,),
40 ),
41 "error" : (GObject.SIGNAL_RUN_LAST,
42 GObject.TYPE_NONE,
43 (str,),
44 ),
45 }
46
47 def __init__(self, xid=None):
48 GObject.GObject.__init__(self)
49
50 def query_recommend_top(self):
51 # build the command
52 spawner = SpawnHelper()
53 spawner.parent_xid = self.xid
54 spawner.connect("data-available", self._on_recommend_top_data)
55 spawner.connect("error", lambda spawner, err: self.emit("error", err))
56 spawner.run_generic_piston_helper(
57 "SoftwareCenterRecommenderAPI", "recommend_top")
58
59 def _on_recommend_top_data(self, spawner, piston_top_apps):
60 self.emit("recommend-top", piston_top_apps)
61
62
63if __name__ == "__main__":
64 from gi.repository import Gtk
65
66 def _recommend_top(agent, top_apps):
67 print ("_recommend_top: %s" % top_apps)
68 def _error(agent, msg):
69 print ("got a error: %s" % msg)
70 Gtk.main_quit()
71
72 # test specific stuff
73 logging.basicConfig()
74 softwarecenter.paths.datadir = "./data"
75
76 agent = RecommenderAgent()
77 agent.connect("recommend-top", _recommend_top)
78 agent.connect("error", _error)
79 agent.query_recommend_top()
80
81
82 Gtk.main()
083
=== modified file 'softwarecenter/backend/reviews/__init__.py'
--- softwarecenter/backend/reviews/__init__.py 2011-11-07 10:04:33 +0000
+++ softwarecenter/backend/reviews/__init__.py 2012-01-05 15:35:32 +0000
@@ -19,6 +19,7 @@
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2020
21import datetime21import datetime
22import json
22import logging23import logging
23import operator24import operator
24import os25import os
@@ -107,13 +108,10 @@
107 return False108 return False
108 109
109 # run the command and add watcher110 # run the command and add watcher
110 cmd = [os.path.join(
111 softwarecenter.paths.datadir, PistonHelpers.GET_USEFUL_VOTES),
112 "--username", user,
113 ]
114 spawn_helper = SpawnHelper()111 spawn_helper = SpawnHelper()
115 spawn_helper.connect("data-available", self._on_usefulness_data)112 spawn_helper.connect("data-available", self._on_usefulness_data)
116 spawn_helper.run(cmd)113 spawn_helper.run_generic_piston_helper(
114 "RatingsAndReviewsAPI", "get_usefulness", username=user)
117115
118 def _on_usefulness_data(self, spawn_helper, results):116 def _on_usefulness_data(self, spawn_helper, results):
119 '''called if usefulness retrieved from server'''117 '''called if usefulness retrieved from server'''
120118
=== modified file 'softwarecenter/backend/reviews/rnr.py'
--- softwarecenter/backend/reviews/rnr.py 2011-11-07 08:26:13 +0000
+++ softwarecenter/backend/reviews/rnr.py 2012-01-05 15:35:32 +0000
@@ -128,22 +128,20 @@
128 except OSError:128 except OSError:
129 days_delta = 0129 days_delta = 0
130 LOG.debug("refresh with days_delta: %s" % days_delta)130 LOG.debug("refresh with days_delta: %s" % days_delta)
131 # FIXME: the server currently has bug (#757695) so we
132 # can not turn this on just yet and need to use
133 # the old "catch-all" review-stats for now
131 #origin = "any"134 #origin = "any"
132 #distroseries = self.distro.get_codename()135 #distroseries = self.distro.get_codename()
133 cmd = [os.path.join(136 spawn_helper = SpawnHelper()
134 softwarecenter.paths.datadir, PistonHelpers.GET_REVIEW_STATS),137 spawn_helper.connect("data-available", self._on_review_stats_data, callback)
135 # FIXME: the server currently has bug (#757695) so we
136 # can not turn this on just yet and need to use
137 # the old "catch-all" review-stats for now
138 #"--origin", origin,
139 #"--distroseries", distroseries,
140 ]
141 if days_delta:138 if days_delta:
142 cmd += ["--days-delta", str(days_delta)]139 spawn_helper.run_generic_piston_helper(
143 spawn_helper = SpawnHelper()140 "RatingsAndReviewsAPI", "review_stats", days=days_delta)
144 spawn_helper.connect("data-available", self._on_review_stats_data, callback)141 else:
145 spawn_helper.run(cmd)142 spawn_helper.run_generic_piston_helper(
146143 "RatingsAndReviewsAPI", "review_stats")
144
147 def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback):145 def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback):
148 """ process stdout from the helper """146 """ process stdout from the helper """
149 review_stats = self.REVIEW_STATS_CACHE147 review_stats = self.REVIEW_STATS_CACHE
150148
=== modified file 'softwarecenter/backend/scagent.py'
--- softwarecenter/backend/scagent.py 2012-01-03 15:20:56 +0000
+++ softwarecenter/backend/scagent.py 2012-01-05 15:35:32 +0000
@@ -20,6 +20,7 @@
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2121
22from gi.repository import GObject22from gi.repository import GObject
23import json
23import logging24import logging
24import os25import os
2526
@@ -56,14 +57,7 @@
56 GObject.GObject.__init__(self)57 GObject.GObject.__init__(self)
57 self.distro = get_distro()58 self.distro = get_distro()
58 self.ignore_cache = ignore_cache59 self.ignore_cache = ignore_cache
59 binary = os.path.join(60 self.xid = xid
60 softwarecenter.paths.datadir, PistonHelpers.SOFTWARE_CENTER_AGENT)
61 self.HELPER_CMD = [binary]
62 if self.ignore_cache:
63 self.HELPER_CMD.append("--ignore-cache")
64 if xid:
65 self.HELPER_CMD.append("--parent-xid")
66 self.HELPER_CMD.append(str(xid))
6761
68 def query_available(self, series_name=None, arch_tag=None):62 def query_available(self, series_name=None, arch_tag=None):
69 self._query_available(series_name, arch_tag, for_qa=False)63 self._query_available(series_name, arch_tag, for_qa=False)
@@ -78,41 +72,52 @@
78 if not arch_tag:72 if not arch_tag:
79 arch_tag = get_current_arch()73 arch_tag = get_current_arch()
80 # build the command74 # build the command
81 cmd = self.HELPER_CMD[:]75 spawner = SpawnHelper()
76 spawner.parent_xid = self.xid
77 spawner.ignore_cache = self.ignore_cache
78 spawner.connect("data-available", self._on_query_available_data)
79 spawner.connect("error", lambda spawner, err: self.emit("error", err))
82 if for_qa:80 if for_qa:
83 cmd.append("available_apps_qa")81 spawner.needs_auth = True
82 spawner.run_generic_piston_helper(
83 "SoftwareCenterAgentAPI",
84 "available_apps_qa",
85 lang=get_langugage(),
86 series=series_name,
87 arch=arch_tag)
84 else:88 else:
85 cmd.append("available_apps")89 spawner.run_generic_piston_helper(
86 cmd += [language,90 "SoftwareCenterAgentAPI",
87 series_name,91 "available_apps",
88 arch_tag,92 lang=get_language(),
89 ]93 series=series_name,
90 spawner = SpawnHelper()94 arch=arch_tag)
91 spawner.connect("data-available", self._on_query_available_data)95
92 spawner.connect("error", lambda spawner, err: self.emit("error", err))
93 spawner.run(cmd)
94 def _on_query_available_data(self, spawner, piston_available):96 def _on_query_available_data(self, spawner, piston_available):
95 self.emit("available", piston_available)97 self.emit("available", piston_available)
9698
97 def query_available_for_me(self, oauth_token, openid_identifier):99 def query_available_for_me(self, oauth_token, openid_identifier):
98 cmd = self.HELPER_CMD[:]
99 cmd.append("subscriptions_for_me")
100 spawner = SpawnHelper()100 spawner = SpawnHelper()
101 spawner.parent_xid = self.xid
102 spawner.ignore_cache = self.ignore_cache
101 spawner.connect("data-available", self._on_query_available_for_me_data)103 spawner.connect("data-available", self._on_query_available_for_me_data)
102 spawner.connect("error", lambda spawner, err: self.emit("error", err))104 spawner.connect("error", lambda spawner, err: self.emit("error", err))
103 spawner.run(cmd)105 spawner.needs_auth = True
106 spawner.run_generic_piston_helper(
107 "SoftwareCenterAgentAPI", "subscriptions_for_me")
104 def _on_query_available_for_me_data(self, spawner, piston_available_for_me):108 def _on_query_available_for_me_data(self, spawner, piston_available_for_me):
105 self.emit("available-for-me", piston_available_for_me)109 self.emit("available-for-me", piston_available_for_me)
106110
107 def query_exhibits(self):111 def query_exhibits(self):
108 cmd = self.HELPER_CMD[:]
109 cmd.append("exhibits")
110 cmd.append(get_language())
111 cmd.append(self.distro.get_codename())
112 spawner = SpawnHelper()112 spawner = SpawnHelper()
113 spawner.parent_xid = self.xid
114 spawner.ignore_cache = self.ignore_cache
113 spawner.connect("data-available", self._on_exhibits_data_available)115 spawner.connect("data-available", self._on_exhibits_data_available)
114 spawner.connect("error", lambda spawner, err: self.emit("error", err))116 spawner.connect("error", lambda spawner, err: self.emit("error", err))
115 spawner.run(cmd)117 spawner.run_generic_piston_helper(
118 "SoftwareCenterAgentAPI", "exhiits",
119 lang=get_language(), series=self.distro.get_codename())
120
116 def _on_exhibits_data_available(self, spawner, exhibits):121 def _on_exhibits_data_available(self, spawner, exhibits):
117 for exhibit in exhibits:122 for exhibit in exhibits:
118 # special case, if there is no title provided by the server123 # special case, if there is no title provided by the server
119124
=== modified file 'softwarecenter/backend/spawn_helper.py'
--- softwarecenter/backend/spawn_helper.py 2011-09-15 14:24:14 +0000
+++ softwarecenter/backend/spawn_helper.py 2012-01-05 15:35:32 +0000
@@ -30,6 +30,9 @@
30import os30import os
31import json31import json
3232
33import softwarecenter.paths
34from softwarecenter.paths import PistonHelpers
35
33from gi.repository import GObject36from gi.repository import GObject
3437
35LOG = logging.getLogger(__name__)38LOG = logging.getLogger(__name__)
@@ -59,6 +62,27 @@
59 self._io_watch = None62 self._io_watch = None
60 self._child_watch = None63 self._child_watch = None
61 self._cmd = None64 self._cmd = None
65 self.needs_auth = False
66 self.ignore_cache = False
67 self.parent_xid = None
68
69 def run_generic_piston_helper(self, klass, func, **kwargs):
70 binary = os.path.join(
71 softwarecenter.paths.datadir, PistonHelpers.GENERIC_HELPER)
72 cmd = [binary]
73 cmd += ["--datadir", softwarecenter.paths.datadir]
74 if self.needs_auth:
75 cmd.append("--needs-auth")
76 if self.ignore_cache:
77 cmd.append("--ignore-cache")
78 if self.parent_xid:
79 cmd.append("--parent-xid")
80 cmd.append(str(xid))
81 cmd += [klass, func]
82 if kwargs:
83 cmd.append(json.dumps(kwargs))
84 LOG.debug("run_generic_piston_helper()")
85 self.run(cmd)
6286
63 def run(self, cmd):87 def run(self, cmd):
64 self._cmd = cmd88 self._cmd = cmd
6589
=== renamed file 'softwarecenter/backend/restfulclient.py' => 'softwarecenter/backend/ubuntusso.py'
--- softwarecenter/backend/restfulclient.py 2012-01-05 09:53:52 +0000
+++ softwarecenter/backend/ubuntusso.py 2012-01-05 15:35:32 +0000
@@ -19,133 +19,27 @@
19# this program; if not, write to the Free Software Foundation, Inc.,19# this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2121
22import os
2322
24from gi.repository import GObject23from gi.repository import GObject
2524
26import logging25import logging
27import threading26import os
2827
29from softwarecenter.enums import BUY_SOMETHING_HOST28import softwarecenter.paths
3029from softwarecenter.paths import PistonHelpers
31# possible workaround for bug #599332 is to try to import lazr.restful30
32# import lazr.restful31import piston_mini_client.auth
33# import lazr.restfulclient
34
35from lazr.restfulclient.resource import ServiceRoot
36from lazr.restfulclient.authorize import BasicHttpAuthorizer
37from lazr.restfulclient.authorize.oauth import OAuthAuthorizer
38from oauth.oauth import OAuthToken
39
40from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
41from Queue import Queue
4232
43# mostly for testing33# mostly for testing
44from fake_review_settings import FakeReviewSettings, network_delay34from fake_review_settings import FakeReviewSettings, network_delay
4535from spawn_helper import SpawnHelper
46from login import LoginBackend36
37from softwarecenter.utils import clear_token_from_ubuntu_sso
38
39from gettext import gettext as _
4740
48LOG = logging.getLogger(__name__)41LOG = logging.getLogger(__name__)
4942
50UBUNTU_SSO_SERVICE = os.environ.get(
51 "USSOC_SERVICE_URL", "https://login.ubuntu.com/api/1.0")
52UBUNTU_SOFTWARE_CENTER_AGENT_SERVICE = BUY_SOMETHING_HOST+"/api/1.0"
53
54class AttributesObject(object):
55 """ convinient object to hold attributes """
56 MAX_REPR_STRING_SIZE = 30
57
58 def __repr__(self):
59 s = "<'%s': " % self.__class__.__name__
60 for key in vars(self):
61 value = str(getattr(self, key))
62 if len(value) > self.MAX_REPR_STRING_SIZE:
63 value = "%s..." % value[:self.MAX_REPR_STRING_SIZE]
64 s += "%s='%s';" % (key, value)
65 s += ">"
66 return s
67
68
69def restful_collection_to_real_python(restful_list):
70 """ take a restful and convert it to a python list with real python
71 objects
72 """
73 l = []
74 for entry in restful_list:
75 o = AttributesObject()
76 for attr in entry.lp_attributes:
77 setattr(o, attr, getattr(entry, attr))
78 l.append(o)
79 return l
80
81class RestfulClientWorker(threading.Thread):
82 """ a generic worker thread for a lazr.restfulclient """
83
84 def __init__(self, authorizer, service_root):
85 """ init the thread """
86 threading.Thread.__init__(self)
87 self._service_root_url = service_root
88 self._authorizer = authorizer
89 self._pending_requests = Queue()
90 self._shutdown = False
91 self.daemon = True
92 self.error = None
93 self._cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR,
94 "restfulclient")
95
96 def run(self):
97 """
98 Main thread run interface, logs into launchpad
99 """
100 LOG.debug("lp worker thread run")
101 try:
102 self.service = ServiceRoot(self._authorizer,
103 self._service_root_url,
104 self._cachedir)
105 except:
106 logging.exception("worker thread can not connect to service root")
107 self.error = "ERROR_SERVICE_ROOT"
108 self._shutdown = True
109 return
110 # loop
111 self._wait_for_commands()
112
113 def shutdown(self):
114 """Request shutdown"""
115 self._shutdown = True
116
117 def queue_request(self, func, args, kwargs, result_callback, error_callback):
118 """
119 queue a (remote) command for execution, the result_callback will
120 call with the result_list when done (that function will be
121 called async)
122 """
123 self._pending_requests.put((func, args, kwargs, result_callback, error_callback))
124
125 def _wait_for_commands(self):
126 """internal helper that waits for commands"""
127 while True:
128 while not self._pending_requests.empty():
129 LOG.debug("found pending request")
130 (func_str, args, kwargs, result_callback, error_callback) = self._pending_requests.get()
131 # run func async
132 try:
133 func = self.service
134 for part in func_str.split("."):
135 func = getattr(func, part)
136 res = func(*args, **kwargs)
137 except Exception ,e:
138 error_callback(e)
139 else:
140 result_callback(res)
141 self._pending_requests.task_done()
142 # wait a bit
143 import time
144 time.sleep(0.1)
145 if (self._shutdown and
146 self._pending_requests.empty()):
147 return
148
149class UbuntuSSOAPI(GObject.GObject):43class UbuntuSSOAPI(GObject.GObject):
15044
151 __gsignals__ = {45 __gsignals__ = {
@@ -162,46 +56,24 @@
162 56
163 def __init__(self, token):57 def __init__(self, token):
164 GObject.GObject.__init__(self)58 GObject.GObject.__init__(self)
165 self._whoami = None59 # FIXME: the token is not currently used as the helper will
166 self._error = None60 # query the keyring again
167 self.service = UBUNTU_SSO_SERVICE
168 self.token = token61 self.token = token
169 token = OAuthToken(self.token["token"], self.token["token_secret"])62
170 authorizer = OAuthAuthorizer(self.token["consumer_key"],63 def _on_whoami_data(self, spawner, piston_whoami):
171 self.token["consumer_secret"],64 self.emit("whoami", piston_whoami)
172 access_token=token)
173 # we need to init the GObject.init_threads()
174 # - if we do it globally s-c will crash on exit (LP: #907568)
175 # - if we don't do it, s-c will hang in worker_thread.start()
176 # (even though threads_init is deprecated and according to the
177 # docs is run automatically nowdays)
178 # - if we do it here some apps will still crash
179 self.worker_thread = RestfulClientWorker(authorizer, self.service)
180 self.worker_thread.start()
181 GObject.timeout_add(200, self._monitor_thread)
182
183 def _monitor_thread(self):
184 # glib bit of the threading, runs in the main thread
185 if self._whoami is not None:
186 self.emit("whoami", self._whoami)
187 self._whoami = None
188 if self._error is not None:
189 self.emit("error", self._error)
190 self._error = None
191 return True
192
193 def _thread_whoami_done(self, result):
194 self._whoami = result
195
196 def _thread_whoami_error(self, e):
197 self._error = e
19865
199 def whoami(self):66 def whoami(self):
67 """ trigger request for the getting account information, this
68 will also verify if the current token is valid and if not
69 trigger a cleanup/re-authenticate
70 """
200 LOG.debug("whoami called")71 LOG.debug("whoami called")
201 self.worker_thread.queue_request("accounts.me", (), {},72 spawner = SpawnHelper()
202 self._thread_whoami_done,73 spawner.connect("data-available", self._on_whoami_data)
203 self._thread_whoami_error)74 spawner.connect("error", lambda spawner, err: self.emit("error", err))
20475 spawner.needs_auth = True
76 spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami")
20577
206class UbuntuSSOAPIFake(UbuntuSSOAPI):78class UbuntuSSOAPIFake(UbuntuSSOAPI):
20779
@@ -243,71 +115,6 @@
243 return ubuntu_sso_class115 return ubuntu_sso_class
244116
245117
246class UbuntuSSOlogin(LoginBackend):
247
248 NEW_ACCOUNT_URL = "https://login.launchpad.net/+standalone-login"
249 FORGOT_PASSWORD_URL = "https://login.ubuntu.com/+forgot_password"
250
251 SSO_AUTHENTICATE_FUNC = "authentications.authenticate"
252
253 def __init__(self):
254 LoginBackend.__init__(self)
255 self.service = UBUNTU_SSO_SERVICE
256 # we get a dict here with the following keys:
257 # token
258 # consumer_key (also the openid identifier)
259 # consumer_secret
260 # token_secret
261 # name (that is just 'software-center')
262 self.oauth_credentials = None
263 self._oauth_credentials = None
264 self._login_failure = None
265 self.worker_thread = None
266
267 def shutdown(self):
268 self.worker_thread.shutdown()
269
270 def login(self, username=None, password=None):
271 if not username or not password:
272 self.emit("need-username-password")
273 return
274 authorizer = BasicHttpAuthorizer(username, password)
275 self.worker_thread = RestfulClientWorker(authorizer, self.service)
276 self.worker_thread.start()
277 kwargs = { "token_name" : "software-center",
278 }
279 self.worker_thread.queue_request(self.SSO_AUTHENTICATE_FUNC, (), kwargs,
280 self._thread_authentication_done,
281 self._thread_authentication_error)
282 GObject.timeout_add(200, self._monitor_thread)
283
284 def _monitor_thread(self):
285 # glib bit of the threading, runs in the main thread
286 if self._oauth_credentials:
287 self.emit("login-successful", self._oauth_credentials)
288 self.oauth_credentials = self._oauth_credentials
289 self._oauth_credentials = None
290 if self._login_failure:
291 self.emit("login-failed")
292 self._login_failure = None
293 return True
294
295 def _thread_authentication_done(self, result):
296 # runs in the thread context, can not touch gui or glib
297 #print "_authentication_done", result
298 self._oauth_credentials = result
299
300 def _thread_authentication_error(self, e):
301 # runs in the thread context, can not touch gui or glib
302 #print "_authentication_error", type(e)
303 self._login_failure = e
304
305 def __del__(self):
306 #print "del"
307 if self.worker_thread:
308 self.worker_thread.shutdown()
309
310
311# test code118# test code
312def _login_success(lp, token):119def _login_success(lp, token):
313 print "success", lp, token120 print "success", lp, token
@@ -323,40 +130,30 @@
323 password = sys.stdin.readline().strip()130 password = sys.stdin.readline().strip()
324 sso.login(user, password)131 sso.login(user, password)
325132
326def _error(scaagent, errormsg):
327 print "_error:", errormsg
328def _whoami(sso, whoami):
329 print "whoami: ", whoami
330
331# interactive test code133# interactive test code
332if __name__ == "__main__":134if __name__ == "__main__":
135 def _whoami(sso, result):
136 print "res: ", result
137 Gtk.main_quit()
138 def _error(sso, result):
139 print "err: ", result
140 Gtk.main_quit()
141 def _dbus_maybe_login_successful(ssologin, oauth_result):
142 print "got token, verify it now"
143 sso = UbuntuSSOAPI(oauth_result)
144 sso.connect("whoami", _whoami)
145 sso.connect("error", _error)
146 sso.whoami()
147
148 from gi.repository import Gtk
333 import sys149 import sys
334 logging.basicConfig(level=logging.DEBUG)150 logging.basicConfig(level=logging.DEBUG)
335151 softwarecenter.paths.datadir = "./data"
336 if len(sys.argv) < 2:152
337 print "need an argument, one of: 'sso', 'ssologin'"153 from login_sso import get_sso_backend
338 sys.exit(1)154 backend = get_sso_backend("", "appname", "help_text")
339155 backend.connect("login-successful", _dbus_maybe_login_successful)
340 elif sys.argv[1] == "sso":156 backend.login_or_register()
341 def _dbus_maybe_login_successful(ssologin, oauth_result):157 Gtk.main()
342 sso = UbuntuSSOAPI(oauth_result)
343 sso.connect("whoami", _whoami)
344 sso.connect("error", _error)
345 sso.whoami()
346 from login_sso import get_sso_backend
347 backend = get_sso_backend("", "appname", "help_text")
348 backend.connect("login-successful", _dbus_maybe_login_successful)
349 backend.login_or_register()
350
351 elif sys.argv[1] == "ssologin":
352 ssologin = UbuntuSSOlogin()
353 ssologin.connect("login-successful", _login_success)
354 ssologin.connect("login-failed", _login_failed)
355 ssologin.connect("need-username-password", _login_need_user_and_password)
356 ssologin.login()
357
358 else:
359 print "unknown option"
360 sys.exit(1)
361158
362159
363160
=== modified file 'softwarecenter/enums.py'
--- softwarecenter/enums.py 2011-12-16 12:50:18 +0000
+++ softwarecenter/enums.py 2012-01-05 15:35:32 +0000
@@ -34,6 +34,15 @@
34BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "https://software-center.ubuntu.com"34BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "https://software-center.ubuntu.com"
35BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "http://software-center.ubuntu.com"35BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "http://software-center.ubuntu.com"
3636
37# recommender
38RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or "https://recommender.software-center.ubuntu.com"
39
40# for the sso login
41UBUNTU_SSO_SERVICE = os.environ.get(
42 "USSOC_SERVICE_URL", "https://login.ubuntu.com/")
43SSO_LOGIN_HOST = UBUNTU_SSO_SERVICE
44
45
37# version of the database, every time something gets added (like 46# version of the database, every time something gets added (like
38# terms for mime-type) increase this (but keep as a string!)47# terms for mime-type) increase this (but keep as a string!)
39DB_SCHEMA_VERSION = "6"48DB_SCHEMA_VERSION = "6"
4049
=== modified file 'softwarecenter/paths.py'
--- softwarecenter/paths.py 2011-09-13 12:45:56 +0000
+++ softwarecenter/paths.py 2012-01-05 15:35:32 +0000
@@ -86,9 +86,7 @@
86# piston helpers86# piston helpers
87class PistonHelpers:87class PistonHelpers:
88 GET_REVIEWS = "piston_get_reviews_helper.py"88 GET_REVIEWS = "piston_get_reviews_helper.py"
89 GET_REVIEW_STATS = "piston_get_review_stats_helper.py"89 GENERIC_HELPER = "piston_generic_helper.py"
90 GET_USEFUL_VOTES = "piston_get_useful_votes_helper.py"
91 SOFTWARE_CENTER_AGENT = "piston_get_scagent_available_apps.py"
9290
93X2GO_HELPER = "x2go_helper.py"91X2GO_HELPER = "x2go_helper.py"
9492
9593
=== modified file 'test/gtk3/test_views.py'
--- test/gtk3/test_views.py 2011-11-15 10:54:54 +0000
+++ test/gtk3/test_views.py 2012-01-05 15:35:32 +0000
@@ -13,6 +13,8 @@
1313
14import softwarecenter.paths14import softwarecenter.paths
15softwarecenter.paths.datadir = "../data"15softwarecenter.paths.datadir = "../data"
16import os
17os.environ["PYTHONPATH"] = "../"
1618
17class TestViews(unittest.TestCase):19class TestViews(unittest.TestCase):
1820
1921
=== modified file 'test/test_reviews.py'
--- test/test_reviews.py 2011-11-04 14:52:52 +0000
+++ test/test_reviews.py 2012-01-05 15:35:32 +0000
@@ -11,6 +11,7 @@
1111
12import softwarecenter.paths12import softwarecenter.paths
13softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR = tempfile.mkdtemp()13softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR = tempfile.mkdtemp()
14softwarecenter.paths.datadir = "../data"
1415
15from softwarecenter.backend.reviews.rnr import (16from softwarecenter.backend.reviews.rnr import (
16 ReviewLoaderSpawningRNRClient as ReviewLoader)17 ReviewLoaderSpawningRNRClient as ReviewLoader)
@@ -51,6 +52,6 @@
51 main_loop.iteration()52 main_loop.iteration()
5253
53if __name__ == "__main__":54if __name__ == "__main__":
54 #import logging55 import logging
55 #logging.basicConfig(level=logging.DEBUG)56 logging.basicConfig(level=logging.DEBUG)
56 unittest.main()57 unittest.main()
5758
=== modified file 'test/test_ubuntu_sso_api.py'
--- test/test_ubuntu_sso_api.py 2011-06-11 07:59:59 +0000
+++ test/test_ubuntu_sso_api.py 2012-01-05 15:35:32 +0000
@@ -6,10 +6,10 @@
66
7import os7import os
8import unittest8import unittest
9from softwarecenter.backend.restfulclient import (UbuntuSSOAPIFake,9from softwarecenter.backend.ubuntusso import (UbuntuSSOAPIFake,
10 UbuntuSSOAPI,10 UbuntuSSOAPI,
11 get_ubuntu_sso_backend,11 get_ubuntu_sso_backend,
12 )12 )
1313
14class TestSSOAPI(unittest.TestCase):14class TestSSOAPI(unittest.TestCase):
15 """ tests the ubuntu sso backend stuff """15 """ tests the ubuntu sso backend stuff """
1616
=== added file 'utils/piston-helpers/piston_generic_helper.py'
--- utils/piston-helpers/piston_generic_helper.py 1970-01-01 00:00:00 +0000
+++ utils/piston-helpers/piston_generic_helper.py 2012-01-05 15:35:32 +0000
@@ -0,0 +1,215 @@
1#!/usr/bin/python
2# Copyright (C) 2011 Canonical
3#
4# Authors:
5# Michael Vogt
6#
7# This program is free software; you can redistribute it and/or modify it under
8# the terms of the GNU General Public License as published by the Free Software
9# Foundation; version 3.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20import argparse
21import logging
22import os
23import json
24import pickle
25import sys
26
27from gi.repository import GObject
28
29# useful for debugging
30if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ:
31 import httplib2
32 httplib2.debuglevel = 1
33
34import piston_mini_client.auth
35
36try:
37 import softwarecenter
38except ImportError:
39 if os.path.exists("../softwarecenter"):
40 sys.path.insert(0, "../")
41 else:
42 sys.path.insert(0, "/usr/share/software-center")
43
44import softwarecenter.paths
45from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
46from softwarecenter.backend.login_sso import get_sso_backend
47
48from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
49 SOFTWARE_CENTER_SSO_DESCRIPTION,
50 )
51
52# the piston import
53from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI
54from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI
55from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI
56from softwarecenter.backend.piston.sreclient_pristine import SoftwareCenterRecommenderAPI
57
58# patch default_service_root to the one we use
59from softwarecenter.enums import SSO_LOGIN_HOST
60UbuntuSsoAPI.default_service_root = SSO_LOGIN_HOST+"/api/1.0"
61
62from softwarecenter.enums import RECOMMENDER_HOST
63SoftwareCenterRecommenderAPI.default_service_root = RECOMMENDER_HOST+"/api/1.0"
64
65
66RatingsAndReviewsAPI # pyflakes
67UbuntuSsoAPI # pyflakes
68SoftwareCenterAgentAPI # pyflakes
69SoftwareCenterRecommenderAPI
70
71from gettext import gettext as _
72
73# helper that is only used to verify that the token is ok
74# and trigger cleanup if not
75class SSOLoginHelper(object):
76
77 def __init__(self, xid=0):
78 self.oauth = None
79 self.xid = xid
80 self.loop = GObject.MainLoop(GObject.main_context_default())
81
82 def _login_successful(self, sso_backend, oauth_result):
83 LOG.debug("_login_successful")
84 self.oauth = oauth_result
85 # FIXME: actually verify the token against ubuntu SSO
86 self.loop.quit()
87
88 def verify_token_sync(self, token):
89 LOG.debug("verify_token")
90 auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
91 token["token_secret"],
92 token["consumer_key"],
93 token["consumer_secret"])
94 api = UbuntuSsoAPI(auth=auth)
95 try:
96 res = api.whoami()
97 except:
98 LOG.exception("api.whoami failed")
99 return None
100 return res
101
102 def clear_token(self):
103 clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING)
104
105 def get_oauth_token_sync(self):
106 self.oauth = None
107 sso = get_sso_backend(
108 self.xid,
109 SOFTWARE_CENTER_NAME_KEYRING,
110 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
111 sso.connect("login-successful", self._login_successful)
112 sso.connect("login-failed", lambda s: self.loop.quit())
113 sso.connect("login-canceled", lambda s: self.loop.quit())
114 sso.login_or_register()
115 self.loop.run()
116 return self.oauth
117
118 def get_oauth_token_and_verify_sync(self):
119 token = self.get_oauth_token_sync()
120 # check if the token is valid and reset it if it is not
121 if token and not self.verify_token_sync(token):
122 self.clear_token()
123 # re-trigger login
124 token = self.get_oauth_token_sync()
125 return token
126
127
128
129LOG = logging.getLogger(__name__)
130
131if __name__ == "__main__":
132 logging.basicConfig()
133
134 # command line parser
135 parser = argparse.ArgumentParser(
136 description="Backend helper for piston-mini-client based APIs")
137 parser.add_argument("--debug", action="store_true", default=False,
138 help="enable debug output")
139 parser.add_argument("--datadir", default="/usr/share/software-center",
140 help="setup alternative datadir")
141 parser.add_argument("--ignore-cache", action="store_true", default=False,
142 help="force ignore cache")
143 parser.add_argument("--needs-auth", default=False, action="store_true",
144 help="need oauth credentials")
145 parser.add_argument("--output", default="pickle",
146 help="output result as [pickle|json|text]")
147 parser.add_argument("--parent-xid", default=0,
148 help="xid of the parent window")
149 parser.add_argument('klass', help='class to use')
150 parser.add_argument('function', help='function to call')
151 parser.add_argument('kwargs', nargs="?",
152 help='kwargs for the function call as json')
153 args = parser.parse_args()
154
155 if args.debug:
156 logging.basicConfig(level=logging.DEBUG)
157 LOG.setLevel(logging.DEBUG)
158
159 if args.ignore_cache:
160 cachedir = None
161 else:
162 cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "piston-helper")
163
164 # check what we need to call
165 klass = globals()[args.klass]
166 func = args.function
167 kwargs = json.loads(args.kwargs or '{}')
168
169 softwarecenter.paths.datadir = args.datadir
170
171 if args.needs_auth:
172 helper = SSOLoginHelper(args.parent_xid)
173 token = helper.get_oauth_token_and_verify_sync()
174 # if we don't have a token, error here
175 if not token:
176 sys.stderr.write("ERROR: can not obtain a oauth token\n")
177 sys.exit(1)
178
179 auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
180 token["token_secret"],
181 token["consumer_key"],
182 token["consumer_secret"])
183 api = klass(cachedir=cachedir, auth=auth)
184 else:
185 api = klass()
186
187 piston_reply = None
188 # handle the args
189 f = getattr(api, func)
190 try:
191 piston_reply = f(**kwargs)
192 except:
193 LOG.exception("urclient_apps")
194 sys.exit(1)
195
196 # print to stdout where its consumed by the parent
197 if piston_reply is None:
198 LOG.warn("no data")
199 sys.exit(0)
200
201 # check what format to use
202 if args.output == "pickle":
203 res = pickle.dumps(piston_reply)
204 elif args.output == "json":
205 res = json.dumps(piston_reply)
206 elif args.output == "text":
207 res = piston_reply
208
209 # and output it
210 try:
211 print res
212 except IOError:
213 # this can happen if the parent gets killed, no need to trigger
214 # apport for this
215 pass
0216
=== removed file 'utils/piston-helpers/piston_get_review_stats_helper.py'
--- utils/piston-helpers/piston_get_review_stats_helper.py 2011-07-13 14:25:55 +0000
+++ utils/piston-helpers/piston_get_review_stats_helper.py 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1#!/usr/bin/python
2
3import os
4import pickle
5import logging
6import sys
7
8from optparse import OptionParser
9
10from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
11from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI
12
13LOG = logging.getLogger(__name__)
14
15if __name__ == "__main__":
16 logging.basicConfig()
17
18 # common options for optparse go here
19 parser = OptionParser()
20
21 # check options
22 parser.add_option("--origin", default="any")
23 parser.add_option("--distroseries", default="any")
24 parser.add_option("--days-delta", default=None)
25 parser.add_option("--debug",
26 action="store_true", default=False)
27 parser.add_option("--no-pickle",
28 action="store_true", default=False)
29 (options, args) = parser.parse_args()
30
31 if options.debug:
32 LOG.setLevel(logging.DEBUG)
33
34 cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient")
35 rnrclient = RatingsAndReviewsAPI(cachedir=cachedir)
36
37 kwargs = {"origin": options.origin,
38 "distroseries": options.distroseries,
39 }
40 if options.days_delta:
41 kwargs["days"] = int(options.days_delta)
42
43 # depending on the time delta, use a different call
44 piston_review_stats = []
45 try:
46 piston_review_stats = rnrclient.review_stats(**kwargs)
47 except:
48 LOG.exception("get_review_stats")
49 sys.exit(1)
50
51 # useful for debugging
52 if options.no_pickle:
53 print "\n".join(["pkgname=%s total=%s avg=%s" % (s.package_name,
54 s.ratings_total,
55 s.ratings_average)
56 for s in piston_review_stats])
57 else:
58 # print to stdout where its consumed by the parent
59 try:
60 print pickle.dumps(piston_review_stats)
61 except IOError:
62 # this can happen if the parent gets killed, no need to trigger
63 # apport for this
64 sys.exit(1)
65
660
=== renamed file 'utils/piston-helpers/piston_get_scagent_available_apps.py' => 'utils/piston-helpers/piston_get_scagent_available_apps.py.THIS'
--- utils/piston-helpers/piston_get_scagent_available_apps.py 2012-01-05 09:53:52 +0000
+++ utils/piston-helpers/piston_get_scagent_available_apps.py.THIS 2012-01-05 15:35:32 +0000
@@ -10,64 +10,14 @@
1010
11import piston_mini_client.auth11import piston_mini_client.auth
1212
13from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
14 SOFTWARE_CENTER_SSO_DESCRIPTION,
15 )
16from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR13from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
17from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI14from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI
18from softwarecenter.backend.login_sso import get_sso_backend
19from softwarecenter.backend.restfulclient import UbuntuSSOAPI
20from softwarecenter.utils import clear_token_from_ubuntu_sso
2115
22from gettext import gettext as _16from gettext import gettext as _
2317
24LOG = logging.getLogger(__name__)18LOG = logging.getLogger(__name__)
2519
26class SSOLoginHelper(object):20from softwarecenter.backend.piston.sso_helper import SSOLoginHelper
27 def __init__(self, xid=0):
28 self.oauth = None
29 self.xid = xid
30 self.loop = GObject.MainLoop(GObject.main_context_default())
31
32 def _login_successful(self, sso_backend, oauth_result):
33 LOG.debug("_login_successful")
34 self.oauth = oauth_result
35 # FIXME: actually verify the token against ubuntu SSO
36 self.loop.quit()
37
38 def verify_token(self, token):
39 LOG.debug("verify_token")
40 def _whoami_done(sso, me):
41 self._whoami = me
42 self.loop.quit()
43 self._whoami = None
44 sso = UbuntuSSOAPI(token)
45 sso.connect("whoami", _whoami_done)
46 sso.connect("error", lambda sso, err: self.loop.quit())
47 sso.whoami()
48 self.loop.run()
49 LOG.debug("verify_token finished")
50 # check if the token is valid
51 if self._whoami is None:
52 return False
53 else:
54 return True
55
56 def clear_token(self):
57 clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING)
58
59 def get_oauth_token_sync(self):
60 self.oauth = None
61 sso = get_sso_backend(
62 self.xid,
63 SOFTWARE_CENTER_NAME_KEYRING,
64 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
65 sso.connect("login-successful", self._login_successful)
66 sso.connect("login-failed", lambda s: self.loop.quit())
67 sso.connect("login-canceled", lambda s: self.loop.quit())
68 sso.login_or_register()
69 self.loop.run()
70 return self.oauth
7121
72if __name__ == "__main__":22if __name__ == "__main__":
73 logging.basicConfig()23 logging.basicConfig()
@@ -118,12 +68,7 @@
118 # check if auth is required68 # check if auth is required
119 if args.command in ("available_apps_qa", "subscriptions_for_me"):69 if args.command in ("available_apps_qa", "subscriptions_for_me"):
120 helper = SSOLoginHelper(args.parent_xid)70 helper = SSOLoginHelper(args.parent_xid)
121 token = helper.get_oauth_token_sync()71 token = helper.get_oauth_token_and_verify_sync()
122 # check if the token is valid and reset it if it is not
123 if token and not helper.verify_token(token):
124 helper.clear_token()
125 # re-trigger login
126 token = helper.get_oauth_token_sync()
127 # if we don't have a token, error here72 # if we don't have a token, error here
128 if not token:73 if not token:
129 sys.stderr.write("ERROR: can not obtain a oauth token\n")74 sys.stderr.write("ERROR: can not obtain a oauth token\n")
13075
=== removed file 'utils/piston-helpers/piston_get_useful_votes_helper.py'
--- utils/piston-helpers/piston_get_useful_votes_helper.py 2011-08-19 11:25:58 +0000
+++ utils/piston-helpers/piston_get_useful_votes_helper.py 1970-01-01 00:00:00 +0000
@@ -1,46 +0,0 @@
1#!/usr/bin/python
2
3import pickle
4import logging
5import sys
6
7from optparse import OptionParser
8from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI
9from piston_mini_client import APIError
10
11LOG = logging.getLogger(__name__)
12
13if __name__ == "__main__":
14 logging.basicConfig()
15
16 # common options for optparse go here
17 parser = OptionParser()
18
19 # check options
20 parser.add_option("--username", default=None)
21 (options, args) = parser.parse_args()
22
23 rnrclient = RatingsAndReviewsAPI()
24
25 useful_votes = []
26
27 if options.username:
28 try:
29 useful_votes = rnrclient.get_usefulness(username=options.username)
30 except ValueError as e:
31 LOG.error("failed to parse '%s'" % e.doc)
32 except APIError, e:
33 LOG.warn("_get_useful_votes_helper: no usefulness able to be retrieved for username: %s" % (options.username))
34 LOG.debug("_get_reviews_threaded: no reviews able to be retrieved: %s" % e)
35 except:
36 LOG.exception("_get_useful_votes_helper")
37 sys.exit(1)
38
39 # print to stdout where its consumed by the parent
40 try:
41 print pickle.dumps(useful_votes)
42 except IOError:
43 # this can happen if the parent gets killed, no need to trigger
44 # apport for this
45 pass
46
470
=== modified file 'utils/submit_review_gtk3.py'
--- utils/submit_review_gtk3.py 2011-10-24 07:49:37 +0000
+++ utils/submit_review_gtk3.py 2012-01-05 15:35:32 +0000
@@ -50,7 +50,7 @@
50from gettext import gettext as _50from gettext import gettext as _
51from optparse import OptionParser51from optparse import OptionParser
5252
53from softwarecenter.backend.restfulclient import get_ubuntu_sso_backend53from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend
5454
55import piston_mini_client55import piston_mini_client
5656
@@ -490,6 +490,8 @@
490 self.ssoapi = get_ubuntu_sso_backend(self.token)490 self.ssoapi = get_ubuntu_sso_backend(self.token)
491 self.ssoapi.connect("whoami", self._whoami_done)491 self.ssoapi.connect("whoami", self._whoami_done)
492 self.ssoapi.connect("error", self._whoami_error)492 self.ssoapi.connect("error", self._whoami_error)
493 # this will automatically verify the token and retrigger login
494 # if its expired
493 self.ssoapi.whoami()495 self.ssoapi.whoami()
494496
495 def _whoami_done(self, ssologin, result):497 def _whoami_done(self, ssologin, result):
@@ -500,22 +502,6 @@
500502
501 def _whoami_error(self, ssologin, e):503 def _whoami_error(self, ssologin, e):
502 logging.error("whoami error '%s'" % e)504 logging.error("whoami error '%s'" % e)
503 # HACK: clear the token from the keyring assuming that it expired
504 # or got deauthorized by the user on the website
505 # this really should be done by ubuntu-sso-client itself
506 import lazr.restfulclient.errors
507 # compat with maverick, it does not have Unauthorized yet
508 if hasattr(lazr.restfulclient.errors, "Unauthorized"):
509 errortype = lazr.restfulclient.errors.Unauthorized
510 else:
511 errortype = lazr.restfulclient.errors.HTTPError
512 if (type(e) == errortype and
513 self._whoami_token_reset_nr == 0):
514 logging.warn("authentication error, reseting token and retrying")
515 clear_token_from_ubuntu_sso(self.appname)
516 self._whoami_token_reset_nr += 1
517 self.login(show_register=False)
518 return
519 # show error505 # show error
520 self.status_spinner.hide()506 self.status_spinner.hide()
521 self.login_status_label.set_markup('<b><big>%s</big></b>' % _("Failed to log in"))507 self.login_status_label.set_markup('<b><big>%s</big></b>' % _("Failed to log in"))

Subscribers

People subscribed via source and target branches