Merge lp:~mvo/software-center/replace-restfulclient-with-piston into lp:software-center

Proposed by Michael Vogt
Status: Merged
Merged at revision: 2637
Proposed branch: lp:~mvo/software-center/replace-restfulclient-with-piston
Merge into: lp:software-center
Diff against target: 1365 lines (+377/-662)
19 files modified
debian/control (+0/-1)
softwarecenter/backend/login.py (+0/-7)
softwarecenter/backend/oneconfhandler/core.py (+6/-13)
softwarecenter/backend/piston/ubuntusso_pristine.py (+22/-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 (+50/-255)
softwarecenter/enums.py (+5/-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 (+6/-11)
utils/piston-helpers/piston_generic_helper.py (+209/-0)
utils/piston-helpers/piston_get_review_stats_helper.py (+0/-65)
utils/piston-helpers/piston_get_scagent_available_apps.py (+0/-197)
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/replace-restfulclient-with-piston
Reviewer Review Type Date Requested Status
software-store-developers Pending
Review via email: mp+87636@code.launchpad.net

Description of the change

This branch removes the need for lazr.restfulclient by replacing it with a piston-mini-client based helper.

It also cleansup the use of the piston-mini-client helpers and unifies most of them into the piston_generic_helper.

To post a comment you must log in.
Revision history for this message
Kiwinote (kiwinote) wrote :

in line 118 of backend/scagent.py we want 'exhibits' rather than 'exhiits'

Revision history for this message
Gary Lasker (gary-lasker) wrote :

Hi mvo! So I made the fix that kiwinote mentioned above, plus some small fixes that I *think* are needed to make Ubuntu SSO work, and finally some pyflakes cleanup. I pushed this to:

  lp:~gary-lasker/software-center/replace-restfulclient-with-piston-tweaks

Please take a look at the SSO changes in particular as I want to be sure these are correct.

I think that if these small changes look rigfht to you, we are good to go for this branch. Let's talk tomorrow if you like, to make sure everything's just right.

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'
=== 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:32:23 +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:32:23 +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:32:23 +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
@@ -172,10 +172,11 @@
172 def _maybe_login_successful(self, sso, oauth_result):172 def _maybe_login_successful(self, sso, oauth_result):
173 """ called after we have the token, then we go and figure out our name """173 """ called after we have the token, then we go and figure out our name """
174 logging.debug("_maybe_login_successful")174 logging.debug("_maybe_login_successful")
175 token = oauth_result175 self.ssoapi = get_ubuntu_sso_backend()
176 self.ssoapi = get_ubuntu_sso_backend(token)
177 self.ssoapi.connect("whoami", self._whoami_done)176 self.ssoapi.connect("whoami", self._whoami_done)
178 self.ssoapi.connect("error", self._whoami_error)177 self.ssoapi.connect("error", self._whoami_error)
178 # this will automatically verify the keyring token and retrigger
179 # login (once) if its expired
179 self.ssoapi.whoami()180 self.ssoapi.whoami()
180181
181 def _whoami_done(self, ssologin, result):182 def _whoami_done(self, ssologin, result):
@@ -184,13 +185,5 @@
184185
185 def _whoami_error(self, ssologin, e):186 def _whoami_error(self, ssologin, e):
186 logging.error("whoami error '%s'" % e)187 logging.error("whoami error '%s'" % e)
187 # HACK: clear the token from the keyring assuming that it expired188 self._share_inventory(False)
188 # or got deauthorized by the user on the website189 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
197190
=== 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:32:23 +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
=== 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:32:23 +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:32:23 +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:32:23 +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:32:23 +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:32:23 +0000
@@ -19,134 +19,29 @@
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):
44 """ Ubuntu SSO interface using the oauth token from the keyring """
15045
151 __gsignals__ = {46 __gsignals__ = {
152 "whoami" : (GObject.SIGNAL_RUN_LAST,47 "whoami" : (GObject.SIGNAL_RUN_LAST,
@@ -160,52 +55,27 @@
16055
161 }56 }
162 57
163 def __init__(self, token):58 def __init__(self):
164 GObject.GObject.__init__(self)59 GObject.GObject.__init__(self)
165 self._whoami = None60
166 self._error = None61 def _on_whoami_data(self, spawner, piston_whoami):
167 self.service = UBUNTU_SSO_SERVICE62 self.emit("whoami", piston_whoami)
168 self.token = token
169 token = OAuthToken(self.token["token"], self.token["token_secret"])
170 authorizer = OAuthAuthorizer(self.token["consumer_key"],
171 self.token["consumer_secret"],
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
19863
199 def whoami(self):64 def whoami(self):
65 """ trigger request for the getting account information, this
66 will also verify if the current token is valid and if not
67 trigger a cleanup/re-authenticate
68 """
200 LOG.debug("whoami called")69 LOG.debug("whoami called")
201 self.worker_thread.queue_request("accounts.me", (), {},70 spawner = SpawnHelper()
202 self._thread_whoami_done,71 spawner.connect("data-available", self._on_whoami_data)
203 self._thread_whoami_error)72 spawner.connect("error", lambda spawner, err: self.emit("error", err))
20473 spawner.needs_auth = True
74 spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami")
20575
206class UbuntuSSOAPIFake(UbuntuSSOAPI):76class UbuntuSSOAPIFake(UbuntuSSOAPI):
20777
208 def __init__(self, token):78 def __init__(self):
209 GObject.GObject.__init__(self)79 GObject.GObject.__init__(self)
210 self._fake_settings = FakeReviewSettings()80 self._fake_settings = FakeReviewSettings()
21181
@@ -231,83 +101,18 @@
231 def _make_error():101 def _make_error():
232 return 'HTTP Error 401: Unauthorized'102 return 'HTTP Error 401: Unauthorized'
233103
234def get_ubuntu_sso_backend(token):104def get_ubuntu_sso_backend():
235 """ 105 """
236 factory that returns an ubuntu sso loader singelton106 factory that returns an ubuntu sso loader singelton
237 """107 """
238 if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ:108 if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ:
239 ubuntu_sso_class = UbuntuSSOAPIFake(token)109 ubuntu_sso_class = UbuntuSSOAPIFake()
240 LOG.warn('Using fake Ubuntu SSO API. Only meant for testing purposes')110 LOG.warn('Using fake Ubuntu SSO API. Only meant for testing purposes')
241 else:111 else:
242 ubuntu_sso_class = UbuntuSSOAPI(token)112 ubuntu_sso_class = UbuntuSSOAPI()
243 return ubuntu_sso_class113 return ubuntu_sso_class
244114
245115
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 code116# test code
312def _login_success(lp, token):117def _login_success(lp, token):
313 print "success", lp, token118 print "success", lp, token
@@ -323,40 +128,30 @@
323 password = sys.stdin.readline().strip()128 password = sys.stdin.readline().strip()
324 sso.login(user, password)129 sso.login(user, password)
325130
326def _error(scaagent, errormsg):
327 print "_error:", errormsg
328def _whoami(sso, whoami):
329 print "whoami: ", whoami
330
331# interactive test code131# interactive test code
332if __name__ == "__main__":132if __name__ == "__main__":
133 def _whoami(sso, result):
134 print "res: ", result
135 Gtk.main_quit()
136 def _error(sso, result):
137 print "err: ", result
138 Gtk.main_quit()
139 def _dbus_maybe_login_successful(ssologin, oauth_result):
140 print "got token, verify it now"
141 sso = UbuntuSSOAPI()
142 sso.connect("whoami", _whoami)
143 sso.connect("error", _error)
144 sso.whoami()
145
146 from gi.repository import Gtk
333 import sys147 import sys
334 logging.basicConfig(level=logging.DEBUG)148 logging.basicConfig(level=logging.DEBUG)
335149 softwarecenter.paths.datadir = "./data"
336 if len(sys.argv) < 2:150
337 print "need an argument, one of: 'sso', 'ssologin'"151 from login_sso import get_sso_backend
338 sys.exit(1)152 backend = get_sso_backend("", "appname", "help_text")
339153 backend.connect("login-successful", _dbus_maybe_login_successful)
340 elif sys.argv[1] == "sso":154 backend.login_or_register()
341 def _dbus_maybe_login_successful(ssologin, oauth_result):155 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)
361156
362157
363158
=== modified file 'softwarecenter/enums.py'
--- softwarecenter/enums.py 2011-12-16 12:50:18 +0000
+++ softwarecenter/enums.py 2012-01-05 15:32:23 +0000
@@ -34,6 +34,11 @@
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# for the sso login
38UBUNTU_SSO_SERVICE = os.environ.get(
39 "USSOC_SERVICE_URL", "https://login.ubuntu.com/")
40SSO_LOGIN_HOST = UBUNTU_SSO_SERVICE
41
37# version of the database, every time something gets added (like 42# version of the database, every time something gets added (like
38# terms for mime-type) increase this (but keep as a string!)43# terms for mime-type) increase this (but keep as a string!)
39DB_SCHEMA_VERSION = "6"44DB_SCHEMA_VERSION = "6"
4045
=== modified file 'softwarecenter/paths.py'
--- softwarecenter/paths.py 2011-09-13 12:45:56 +0000
+++ softwarecenter/paths.py 2012-01-05 15:32:23 +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:32:23 +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:32:23 +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:32:23 +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 """
@@ -24,17 +24,12 @@
24 set([x for x in dir(sso_fake) if not x.startswith("_")]))24 set([x for x in dir(sso_fake) if not x.startswith("_")]))
2525
26 def test_get_ubuntu_backend(self):26 def test_get_ubuntu_backend(self):
27 token = { 'token' : 'tokenvalue',
28 'token_secret' : 'tokensecretvalue',
29 'consumer_key' : 'consumerkeyvalue',
30 'consumer_secret' : 'consumersecretvalue',
31 }
32 # test that we get the real one27 # test that we get the real one
33 self.assertEqual(type(get_ubuntu_sso_backend(token)),28 self.assertEqual(type(get_ubuntu_sso_backend()),
34 UbuntuSSOAPI)29 UbuntuSSOAPI)
35 # test that we get the fake one30 # test that we get the fake one
36 os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1"31 os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1"
37 self.assertEqual(type(get_ubuntu_sso_backend(token)),32 self.assertEqual(type(get_ubuntu_sso_backend()),
38 UbuntuSSOAPIFake)33 UbuntuSSOAPIFake)
39 # clean the environment34 # clean the environment
40 del os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"]35 del os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"]
4136
=== 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:32:23 +0000
@@ -0,0 +1,209 @@
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
56
57RatingsAndReviewsAPI # pyflakes
58UbuntuSsoAPI # pyflakes
59SoftwareCenterAgentAPI # pyflakes
60
61# patch default_service_root to the one we use
62from softwarecenter.enums import SSO_LOGIN_HOST
63UbuntuSsoAPI.default_service_root = SSO_LOGIN_HOST+"/api/1.0"
64
65from gettext import gettext as _
66
67# helper that is only used to verify that the token is ok
68# and trigger cleanup if not
69class SSOLoginHelper(object):
70
71 def __init__(self, xid=0):
72 self.oauth = None
73 self.xid = xid
74 self.loop = GObject.MainLoop(GObject.main_context_default())
75
76 def _login_successful(self, sso_backend, oauth_result):
77 LOG.debug("_login_successful")
78 self.oauth = oauth_result
79 # FIXME: actually verify the token against ubuntu SSO
80 self.loop.quit()
81
82 def verify_token_sync(self, token):
83 LOG.debug("verify_token")
84 auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
85 token["token_secret"],
86 token["consumer_key"],
87 token["consumer_secret"])
88 api = UbuntuSsoAPI(auth=auth)
89 try:
90 res = api.whoami()
91 except:
92 LOG.exception("api.whoami failed")
93 return None
94 return res
95
96 def clear_token(self):
97 clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING)
98
99 def get_oauth_token_sync(self):
100 self.oauth = None
101 sso = get_sso_backend(
102 self.xid,
103 SOFTWARE_CENTER_NAME_KEYRING,
104 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
105 sso.connect("login-successful", self._login_successful)
106 sso.connect("login-failed", lambda s: self.loop.quit())
107 sso.connect("login-canceled", lambda s: self.loop.quit())
108 sso.login_or_register()
109 self.loop.run()
110 return self.oauth
111
112 def get_oauth_token_and_verify_sync(self):
113 token = self.get_oauth_token_sync()
114 # check if the token is valid and reset it if it is not
115 if token and not self.verify_token_sync(token):
116 self.clear_token()
117 # re-trigger login
118 token = self.get_oauth_token_sync()
119 return token
120
121
122
123LOG = logging.getLogger(__name__)
124
125if __name__ == "__main__":
126 logging.basicConfig()
127
128 # command line parser
129 parser = argparse.ArgumentParser(
130 description="Backend helper for piston-mini-client based APIs")
131 parser.add_argument("--debug", action="store_true", default=False,
132 help="enable debug output")
133 parser.add_argument("--datadir", default="/usr/share/software-center",
134 help="setup alternative datadir")
135 parser.add_argument("--ignore-cache", action="store_true", default=False,
136 help="force ignore cache")
137 parser.add_argument("--needs-auth", default=False, action="store_true",
138 help="need oauth credentials")
139 parser.add_argument("--output", default="pickle",
140 help="output result as [pickle|json|text]")
141 parser.add_argument("--parent-xid", default=0,
142 help="xid of the parent window")
143 parser.add_argument('klass', help='class to use')
144 parser.add_argument('function', help='function to call')
145 parser.add_argument('kwargs', nargs="?",
146 help='kwargs for the function call as json')
147 args = parser.parse_args()
148
149 if args.debug:
150 logging.basicConfig(level=logging.DEBUG)
151 LOG.setLevel(logging.DEBUG)
152
153 if args.ignore_cache:
154 cachedir = None
155 else:
156 cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "piston-helper")
157
158 # check what we need to call
159 klass = globals()[args.klass]
160 func = args.function
161 kwargs = json.loads(args.kwargs or '{}')
162
163 softwarecenter.paths.datadir = args.datadir
164
165 if args.needs_auth:
166 helper = SSOLoginHelper(args.parent_xid)
167 token = helper.get_oauth_token_and_verify_sync()
168 # if we don't have a token, error here
169 if not token:
170 sys.stderr.write("ERROR: can not obtain a oauth token\n")
171 sys.exit(1)
172
173 auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
174 token["token_secret"],
175 token["consumer_key"],
176 token["consumer_secret"])
177 api = klass(cachedir=cachedir, auth=auth)
178 else:
179 api = klass()
180
181 piston_reply = None
182 # handle the args
183 f = getattr(api, func)
184 try:
185 piston_reply = f(**kwargs)
186 except:
187 LOG.exception("urclient_apps")
188 sys.exit(1)
189
190 # print to stdout where its consumed by the parent
191 if piston_reply is None:
192 LOG.warn("no data")
193 sys.exit(0)
194
195 # check what format to use
196 if args.output == "pickle":
197 res = pickle.dumps(piston_reply)
198 elif args.output == "json":
199 res = json.dumps(piston_reply)
200 elif args.output == "text":
201 res = piston_reply
202
203 # and output it
204 try:
205 print res
206 except IOError:
207 # this can happen if the parent gets killed, no need to trigger
208 # apport for this
209 pass
0210
=== 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
=== removed file 'utils/piston-helpers/piston_get_scagent_available_apps.py'
--- 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 1970-01-01 00:00:00 +0000
@@ -1,197 +0,0 @@
1#!/usr/bin/python
2
3from gi.repository import GObject
4
5import argparse
6import logging
7import os
8import pickle
9import sys
10
11import piston_mini_client.auth
12
13from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
14 SOFTWARE_CENTER_SSO_DESCRIPTION,
15 )
16from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
17from 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
21
22from gettext import gettext as _
23
24LOG = logging.getLogger(__name__)
25
26class SSOLoginHelper(object):
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
71
72if __name__ == "__main__":
73 logging.basicConfig()
74
75 # command line parser
76 parser = argparse.ArgumentParser(description="Helper for software-center-agent")
77 parser.add_argument("--debug", action="store_true", default=False,
78 help="enable debug output")
79 parser.add_argument("--ignore-cache", action="store_true", default=False,
80 help="force ignore cache")
81 parser.add_argument("--parent-xid", default=0,
82 help="xid of the parent window")
83
84 subparser = parser.add_subparsers(title="Commands")
85 # available_apps
86 command = subparser.add_parser("available_apps")
87 command.add_argument("lang")
88 command.add_argument("series")
89 command.add_argument("arch")
90 command.set_defaults(command="available_apps")
91
92 # available_apps_qa
93 command = subparser.add_parser("available_apps_qa")
94 command.add_argument("lang")
95 command.add_argument("series")
96 command.add_argument("arch")
97 command.set_defaults(command="available_apps_qa")
98 # subscriptions
99 command = subparser.add_parser("subscriptions_for_me")
100 command.set_defaults(command="subscriptions_for_me")
101 # exhibits
102 command = subparser.add_parser("exhibits")
103 command.add_argument("lang")
104 command.add_argument("series")
105 command.set_defaults(command="exhibits")
106
107 args = parser.parse_args()
108
109 if args.debug:
110 LOG.setLevel(logging.DEBUG)
111
112 if args.ignore_cache:
113 cachedir = None
114 else:
115 cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "scaclient")
116
117
118 # check if auth is required
119 if args.command in ("available_apps_qa", "subscriptions_for_me"):
120 helper = SSOLoginHelper(args.parent_xid)
121 token = helper.get_oauth_token_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 here
128 if not token:
129 sys.stderr.write("ERROR: can not obtain a oauth token\n")
130 sys.exit(1)
131
132 auth = piston_mini_client.auth.OAuthAuthorizer(token["token"],
133 token["token_secret"],
134 token["consumer_key"],
135 token["consumer_secret"])
136 scaclient = SoftwareCenterAgentAPI(cachedir=cachedir, auth=auth)
137 else:
138 scaclient = SoftwareCenterAgentAPI(cachedir=cachedir)
139
140 piston_reply = None
141
142 # common kwargs
143 if args.command in ("available_apps", "available_apps_qa"):
144 kwargs = {"lang": args.lang,
145 "series": args.series,
146 "arch": args.arch
147 }
148
149 # handle the args
150 if args.command == "available_apps":
151 try:
152 piston_reply = scaclient.available_apps(**kwargs)
153 except:
154 LOG.exception("available_apps")
155 sys.exit(1)
156
157 elif args.command == "available_apps_qa":
158 try:
159 piston_reply = scaclient.available_apps_qa(**kwargs)
160 except:
161 LOG.exception("available_apps_qa")
162 sys.exit(1)
163 elif args.command == "subscriptions_for_me":
164 try:
165 piston_reply = scaclient.subscriptions_for_me(complete_only=True)
166 # the new piston API send the data in a nasty format, most
167 # interessting stuff is in the "application" dict, move it
168 # back int othe main object here so that the parser understands it
169 for item in piston_reply:
170 for k, v in item.application.iteritems():
171 setattr(item, k, v)
172 except:
173 LOG.exception("subscriptions_for_me")
174 sys.exit(1)
175 if args.command == "exhibits":
176 try:
177 piston_reply = scaclient.exhibits(lang=args.lang, series=args.series)
178 except:
179 LOG.exception("exhibits")
180 sys.exit(1)
181
182 if args.debug:
183 LOG.debug("reply: %s" % piston_reply)
184 for item in piston_reply:
185 for var in vars(item):
186 print "%s: %s" % (var, getattr(item, var))
187 print "\n\n"
188
189
190 # print to stdout where its consumed by the parent
191 if piston_reply is not None:
192 try:
193 print pickle.dumps(piston_reply)
194 except IOError:
195 # this can happen if the parent gets killed, no need to trigger
196 # apport for this
197 pass
1980
=== 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:32:23 +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