Merge lp:~mvo/software-center/merge-prev-purchases-lp969273 into lp:software-center

Proposed by Michael Vogt
Status: Merged
Merged at revision: 3180
Proposed branch: lp:~mvo/software-center/merge-prev-purchases-lp969273
Merge into: lp:software-center
Diff against target: 1157 lines (+408/-286)
15 files modified
softwarecenter/backend/login.py (+6/-0)
softwarecenter/backend/login_sso.py (+24/-2)
softwarecenter/backend/scagent.py (+2/-1)
softwarecenter/backend/spawn_helper.py (+3/-0)
softwarecenter/backend/ubuntusso.py (+106/-9)
softwarecenter/db/database.py (+12/-0)
softwarecenter/db/update.py (+41/-50)
softwarecenter/enums.py (+0/-1)
softwarecenter/ui/gtk3/app.py (+54/-57)
tests/gtk3/test_purchase.py (+19/-16)
tests/test_database.py (+10/-2)
tests/test_reinstall_purchased.py (+112/-63)
tests/test_ubuntu_sso_api.py (+3/-3)
utils/piston-helpers/piston_generic_helper.py (+12/-82)
utils/update-software-center-agent (+4/-0)
To merge this branch: bzr merge lp:~mvo/software-center/merge-prev-purchases-lp969273
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Review via email: mp+124417@code.launchpad.net

Description of the change

This branch changes the way the reinstall previous purchases is done. It merges them on update of the
update-software-center-update tool into the per-user xapian database instead of adding them as a seperate
in-memory database. This fixes the double entries in the DB and has the nice side-effect that items already
purchased will be displayed as already purchased instead of showing up with a price.

To post a comment you must log in.
3187. By Michael Vogt

tests/gtk3/test_purchase.py: update test to latest code

3188. By Michael Vogt

trivial pep8 fixes

Revision history for this message
Michael Vogt (mvo) wrote :

Careful review appreciated, should be good, but there is quite a bit of churn.

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

I looked this over carefully and tested reinstall previous purchases under various sequences of steps. I like the new query for a purchased item, it's simpler and it's very fast. All unit test changes look good and all changed tests pass for me.

Thanks, Michael!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'softwarecenter/backend/login.py'
--- softwarecenter/backend/login.py 2012-03-19 14:20:55 +0000
+++ softwarecenter/backend/login.py 2012-09-14 13:44:26 +0000
@@ -49,5 +49,11 @@
49 def login(self, username=None, password=None):49 def login(self, username=None, password=None):
50 raise NotImplemented50 raise NotImplemented
5151
52 def login_or_register(self):
53 raise NotImplemented
54
55 def find_credentials(self):
56 raise NotImplemented
57
52 def cancel_login(self):58 def cancel_login(self):
53 self.emit("login-canceled")59 self.emit("login-canceled")
5460
=== modified file 'softwarecenter/backend/login_sso.py'
--- softwarecenter/backend/login_sso.py 2012-07-09 13:33:51 +0000
+++ softwarecenter/backend/login_sso.py 2012-09-14 13:44:26 +0000
@@ -31,6 +31,12 @@
31from softwarecenter.utils import utf831from softwarecenter.utils import utf8
32from login import LoginBackend32from login import LoginBackend
3333
34from ubuntu_sso import (
35 DBUS_BUS_NAME,
36 DBUS_CREDENTIALS_IFACE,
37 DBUS_CREDENTIALS_PATH,
38 )
39
34# mostly for testing40# mostly for testing
35from fake_review_settings import FakeReviewSettings, network_delay41from fake_review_settings import FakeReviewSettings, network_delay
3642
@@ -44,10 +50,15 @@
44 self.appname = appname50 self.appname = appname
45 self.help_text = help_text51 self.help_text = help_text
46 self.bus = dbus.SessionBus()52 self.bus = dbus.SessionBus()
47 self.proxy = self.bus.get_object(53 obj = self.bus.get_object(bus_name=DBUS_BUS_NAME,
48 'com.ubuntu.sso', '/com/ubuntu/sso/credentials')54 object_path=DBUS_CREDENTIALS_PATH,
55 follow_name_owner_changes=True)
56 self.proxy = dbus.Interface(object=obj,
57 dbus_interface=DBUS_CREDENTIALS_IFACE)
49 self.proxy.connect_to_signal("CredentialsFound",58 self.proxy.connect_to_signal("CredentialsFound",
50 self._on_credentials_found)59 self._on_credentials_found)
60 self.proxy.connect_to_signal("CredentialsNotFound",
61 self._on_credentials_not_found)
51 self.proxy.connect_to_signal("CredentialsError",62 self.proxy.connect_to_signal("CredentialsError",
52 self._on_credentials_error)63 self._on_credentials_error)
53 self.proxy.connect_to_signal("AuthorizationDenied",64 self.proxy.connect_to_signal("AuthorizationDenied",
@@ -64,6 +75,11 @@
64 p['window_id'] = self._window_id75 p['window_id'] = self._window_id
65 return p76 return p
6677
78 def find_credentials(self):
79 LOG.debug("find_crendentials()")
80 self._credentials = None
81 self.proxy.find_credentials(self.appname, self._get_params())
82
67 def login(self, username=None, password=None):83 def login(self, username=None, password=None):
68 LOG.debug("login()")84 LOG.debug("login()")
69 self._credentials = None85 self._credentials = None
@@ -74,6 +90,12 @@
74 self._credentials = None90 self._credentials = None
75 self.proxy.register(self.appname, self._get_params())91 self.proxy.register(self.appname, self._get_params())
7692
93 def _on_credentials_not_found(self, app_name):
94 LOG.debug("_on_credentials_found for '%s'" % app_name)
95 if app_name != self.appname:
96 return
97 self.emit("login-failed")
98
77 def _on_credentials_found(self, app_name, credentials):99 def _on_credentials_found(self, app_name, credentials):
78 LOG.debug("_on_credentials_found for '%s'" % app_name)100 LOG.debug("_on_credentials_found for '%s'" % app_name)
79 if app_name != self.appname:101 if app_name != self.appname:
80102
=== modified file 'softwarecenter/backend/scagent.py'
--- softwarecenter/backend/scagent.py 2012-08-29 12:07:14 +0000
+++ softwarecenter/backend/scagent.py 2012-09-14 13:44:26 +0000
@@ -93,13 +93,14 @@
93 def _on_query_available_data(self, spawner, piston_available):93 def _on_query_available_data(self, spawner, piston_available):
94 self.emit("available", piston_available)94 self.emit("available", piston_available)
9595
96 def query_available_for_me(self):96 def query_available_for_me(self, no_relogin=False):
97 spawner = SpawnHelper()97 spawner = SpawnHelper()
98 spawner.parent_xid = self.xid98 spawner.parent_xid = self.xid
99 spawner.ignore_cache = self.ignore_cache99 spawner.ignore_cache = self.ignore_cache
100 spawner.connect("data-available", self._on_query_available_for_me_data)100 spawner.connect("data-available", self._on_query_available_for_me_data)
101 spawner.connect("error", lambda spawner, err: self.emit("error", err))101 spawner.connect("error", lambda spawner, err: self.emit("error", err))
102 spawner.needs_auth = True102 spawner.needs_auth = True
103 spawner.no_relogin = no_relogin
103 spawner.run_generic_piston_helper(104 spawner.run_generic_piston_helper(
104 "SoftwareCenterAgentAPI", "subscriptions_for_me",105 "SoftwareCenterAgentAPI", "subscriptions_for_me",
105 complete_only=True)106 complete_only=True)
106107
=== modified file 'softwarecenter/backend/spawn_helper.py'
--- softwarecenter/backend/spawn_helper.py 2012-05-16 15:52:07 +0000
+++ softwarecenter/backend/spawn_helper.py 2012-09-14 13:44:26 +0000
@@ -64,6 +64,7 @@
64 self._child_watch = None64 self._child_watch = None
65 self._cmd = None65 self._cmd = None
66 self.needs_auth = False66 self.needs_auth = False
67 self.no_relogin = False
67 self.ignore_cache = False68 self.ignore_cache = False
68 self.parent_xid = None69 self.parent_xid = None
6970
@@ -76,6 +77,8 @@
76 cmd.append("--needs-auth")77 cmd.append("--needs-auth")
77 if self.ignore_cache:78 if self.ignore_cache:
78 cmd.append("--ignore-cache")79 cmd.append("--ignore-cache")
80 if self.no_relogin:
81 cmd.append("--no-relogin")
79 if self.parent_xid:82 if self.parent_xid:
80 cmd.append("--parent-xid")83 cmd.append("--parent-xid")
81 cmd.append(str(self.parent_xid))84 cmd.append(str(self.parent_xid))
8285
=== modified file 'softwarecenter/backend/ubuntusso.py'
--- softwarecenter/backend/ubuntusso.py 2012-08-30 08:57:29 +0000
+++ softwarecenter/backend/ubuntusso.py 2012-09-14 13:44:26 +0000
@@ -21,22 +21,44 @@
2121
2222
23from gi.repository import GObject23from gi.repository import GObject
24from gettext import gettext as _
2425
25import logging26import logging
26import os27import os
2728
29import piston_mini_client.auth
30import piston_mini_client.failhandlers
31
32
28import softwarecenter.paths33import softwarecenter.paths
2934
30# mostly for testing35# mostly for testing
31from fake_review_settings import FakeReviewSettings, network_delay36from fake_review_settings import FakeReviewSettings, network_delay
32from spawn_helper import SpawnHelper37from spawn_helper import SpawnHelper
33from softwarecenter.config import get_config38from softwarecenter.config import get_config
39from softwarecenter.backend.login_sso import get_sso_backend
40
41from softwarecenter.backend.piston.ubuntusso_pristine import (
42 UbuntuSsoAPI as PristineUbuntuSsoAPI,
43 )
44# patch default_service_root to the one we use
45from softwarecenter.enums import UBUNTU_SSO_SERVICE
46# *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE
47PristineUbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE
48
49from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,
50 SOFTWARE_CENTER_SSO_DESCRIPTION,
51 )
52from softwarecenter.utils import clear_token_from_ubuntu_sso_sync
3453
35LOG = logging.getLogger(__name__)54LOG = logging.getLogger(__name__)
3655
3756
38class UbuntuSSOAPI(GObject.GObject):57class UbuntuSSO(GObject.GObject):
39 """ Ubuntu SSO interface using the oauth token from the keyring """58 """ Ubuntu SSO interface using the oauth token from the keyring
59
60 The methods that work syncronous are suffixed with _sync()
61 """
4062
41 __gsignals__ = {63 __gsignals__ = {
42 "whoami": (GObject.SIGNAL_RUN_LAST,64 "whoami": (GObject.SIGNAL_RUN_LAST,
@@ -49,8 +71,11 @@
49 ),71 ),
50 }72 }
5173
52 def __init__(self):74 def __init__(self, xid=0):
53 GObject.GObject.__init__(self)75 GObject.GObject.__init__(self)
76 self.oauth = None
77 self.xid = xid
78 self.loop = GObject.MainLoop(GObject.main_context_default())
5479
55 def _on_whoami_data(self, spawner, piston_whoami):80 def _on_whoami_data(self, spawner, piston_whoami):
56 # once we have data, make sure to save it81 # once we have data, make sure to save it
@@ -71,11 +96,83 @@
71 spawner.needs_auth = True96 spawner.needs_auth = True
72 spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami")97 spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami")
7398
7499 def _login_successful(self, sso_backend, oauth_result):
75class UbuntuSSOAPIFake(UbuntuSSOAPI):100 LOG.debug("_login_successful")
101 self.oauth = oauth_result
102 self.loop.quit()
103
104 # sync calls
105 def verify_token_sync(self, token):
106 """ Verify that the token is valid
107
108 Note that this may raise httplib2 exceptions if the server
109 is not reachable
110 """
111 LOG.debug("verify_token")
112 auth = piston_mini_client.auth.OAuthAuthorizer(
113 token["token"], token["token_secret"],
114 token["consumer_key"], token["consumer_secret"])
115 api = PristineUbuntuSsoAPI(auth=auth)
116 try:
117 res = api.whoami()
118 except piston_mini_client.failhandlers.APIError as e:
119 LOG.exception("api.whoami failed with APIError: '%s'" % e)
120 return False
121 return len(res) > 0
122
123 def clear_token(self):
124 clear_token_from_ubuntu_sso_sync(SOFTWARE_CENTER_NAME_KEYRING)
125
126 def _get_sso_backend_and_connect(self):
127 sso = get_sso_backend(
128 self.xid,
129 SOFTWARE_CENTER_NAME_KEYRING,
130 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
131 sso.connect("login-successful", self._login_successful)
132 sso.connect("login-failed", lambda s: self.loop.quit())
133 sso.connect("login-canceled", lambda s: self.loop.quit())
134 return sso
135
136 def find_oauth_token_sync(self):
137 self.oauth = None
138 sso = self. _get_sso_backend_and_connect()
139 sso.find_credentials()
140 self.loop.run()
141 return self.oauth
142
143 def get_oauth_token_sync(self):
144 self.oauth = None
145 sso = self. _get_sso_backend_and_connect()
146 sso.login_or_register()
147 self.loop.run()
148 return self.oauth
149
150 def get_oauth_token_and_verify_sync(self, no_relogin=False):
151 token = self.get_oauth_token_sync()
152 # check if the token is valid and reset it if it is not
153 if token:
154 # verify token will return false if there is a API error,
155 # but there maybe httplib2 errors if there is no network,
156 # so ignore them
157 try:
158 if not self.verify_token_sync(token):
159 attempt_relogin = not no_relogin
160 if attempt_relogin:
161 self.clear_token()
162 # re-trigger login once
163 token = self.get_oauth_token_sync()
164 else:
165 return None
166 except Exception as e:
167 LOG.warn(
168 "token could not be verified (network problem?): %s" % e)
169 return token
170
171
172class UbuntuSSOAPIFake(UbuntuSSO):
76173
77 def __init__(self):174 def __init__(self):
78 UbuntuSSOAPI.__init__(self)175 UbuntuSSO.__init__(self)
79 self._fake_settings = FakeReviewSettings()176 self._fake_settings = FakeReviewSettings()
80177
81 @network_delay178 @network_delay
@@ -110,7 +207,7 @@
110 ubuntu_sso_class = UbuntuSSOAPIFake()207 ubuntu_sso_class = UbuntuSSOAPIFake()
111 LOG.warn('Using fake Ubuntu SSO API. Only meant for testing purposes')208 LOG.warn('Using fake Ubuntu SSO API. Only meant for testing purposes')
112 else:209 else:
113 ubuntu_sso_class = UbuntuSSOAPI()210 ubuntu_sso_class = UbuntuSSO()
114 return ubuntu_sso_class211 return ubuntu_sso_class
115212
116213
@@ -133,6 +230,7 @@
133 password = sys.stdin.readline().strip()230 password = sys.stdin.readline().strip()
134 sso.login(user, password)231 sso.login(user, password)
135232
233
136# interactive test code234# interactive test code
137if __name__ == "__main__":235if __name__ == "__main__":
138 def _whoami(sso, result):236 def _whoami(sso, result):
@@ -145,7 +243,7 @@
145243
146 def _dbus_maybe_login_successful(ssologin, oauth_result):244 def _dbus_maybe_login_successful(ssologin, oauth_result):
147 print "got token, verify it now"245 print "got token, verify it now"
148 sso = UbuntuSSOAPI()246 sso = UbuntuSSO()
149 sso.connect("whoami", _whoami)247 sso.connect("whoami", _whoami)
150 sso.connect("error", _error)248 sso.connect("error", _error)
151 sso.whoami()249 sso.whoami()
@@ -154,7 +252,6 @@
154 logging.basicConfig(level=logging.DEBUG)252 logging.basicConfig(level=logging.DEBUG)
155 softwarecenter.paths.datadir = "./data"253 softwarecenter.paths.datadir = "./data"
156254
157 from login_sso import get_sso_backend
158 backend = get_sso_backend("", "appname", "help_text")255 backend = get_sso_backend("", "appname", "help_text")
159 backend.connect("login-successful", _dbus_maybe_login_successful)256 backend.connect("login-successful", _dbus_maybe_login_successful)
160 backend.login_or_register()257 backend.login_or_register()
161258
=== modified file 'softwarecenter/db/database.py'
--- softwarecenter/db/database.py 2012-09-12 12:41:05 +0000
+++ softwarecenter/db/database.py 2012-09-14 13:44:26 +0000
@@ -43,6 +43,18 @@
43LOG = logging.getLogger(__name__)43LOG = logging.getLogger(__name__)
4444
4545
46def get_reinstall_previous_purchases_query():
47 """Return a query to get applications purchased
48
49 :return: a xapian query to get all the apps that are purchaed
50 """
51 # this query will give us all documents that have a purchase date != ""
52 query = xapian.Query(xapian.Query.OP_VALUE_GE,
53 XapianValues.PURCHASED_DATE,
54 "1")
55 return query
56
57
46def parse_axi_values_file(filename="/var/lib/apt-xapian-index/values"):58def parse_axi_values_file(filename="/var/lib/apt-xapian-index/values"):
47 """ parse the apt-xapian-index "values" file and provide the59 """ parse the apt-xapian-index "values" file and provide the
48 information in the self._axi_values dict60 information in the self._axi_values dict
4961
=== modified file 'softwarecenter/db/update.py'
--- softwarecenter/db/update.py 2012-09-10 09:40:48 +0000
+++ softwarecenter/db/update.py 2012-09-14 13:44:26 +0000
@@ -30,9 +30,13 @@
30from gi.repository import GObject30from gi.repository import GObject
31from piston_mini_client import PistonResponseObject31from piston_mini_client import PistonResponseObject
3232
33from softwarecenter.backend.scagent import SoftwareCenterAgent
34from softwarecenter.backend.ubuntusso import UbuntuSSO
33from softwarecenter.distro import get_distro35from softwarecenter.distro import get_distro
34from softwarecenter.utils import utf836from softwarecenter.utils import utf8
3537
38from gettext import gettext as _
39
36# py3 compat40# py3 compat
37try:41try:
38 from configparser import RawConfigParser, NoOptionError42 from configparser import RawConfigParser, NoOptionError
@@ -49,7 +53,6 @@
49 import pickle53 import pickle
5054
5155
52from gettext import gettext as _
53from glob import glob56from glob import glob
54from urlparse import urlparse57from urlparse import urlparse
5558
@@ -59,7 +62,6 @@
59 AppInfoFields,62 AppInfoFields,
60 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,63 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
61 DB_SCHEMA_VERSION,64 DB_SCHEMA_VERSION,
62 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,
63 XapianValues,65 XapianValues,
64)66)
65from softwarecenter.db.database import parse_axi_values_file67from softwarecenter.db.database import parse_axi_values_file
@@ -672,8 +674,6 @@
672 # gets confused about (appname, pkgname) duplication674 # gets confused about (appname, pkgname) duplication
673 self.sca_application.name = utf8(_("%s (already purchased)")) % utf8(675 self.sca_application.name = utf8(_("%s (already purchased)")) % utf8(
674 self.sca_application.name)676 self.sca_application.name)
675 self.sca_application.channel = (
676 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME)
677 for attr_name in ('license_key', 'license_key_path'):677 for attr_name in ('license_key', 'license_key_path'):
678 attr = getattr(self.sca_subscription, attr_name, self.NOT_DEFINED)678 attr = getattr(self.sca_subscription, attr_name, self.NOT_DEFINED)
679 if attr is self.NOT_DEFINED:679 if attr is self.NOT_DEFINED:
@@ -1007,44 +1007,6 @@
1007 return True1007 return True
10081008
10091009
1010def add_from_purchased_but_needs_reinstall_data(
1011 purchased_but_may_need_reinstall_list, db, cache):
1012 """Add application that have been purchased but may require a reinstall
1013
1014 This adds a inmemory database to the main db with the special
1015 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME channel prefix
1016
1017 :return: a xapian query to get all the apps that need reinstall
1018 """
1019 # magic
1020 db_purchased = xapian.inmemory_open()
1021 # go over the items we have
1022 for item in purchased_but_may_need_reinstall_list:
1023 # FIXME: what to do with duplicated entries? we will end
1024 # up with two xapian.Document, one for the for-pay
1025 # and one for the availalbe one from s-c-agent
1026 #try:
1027 # db.get_xapian_document(item.name,
1028 # item.package_name)
1029 #except IndexError:
1030 # # item is not in the xapian db
1031 # pass
1032 #else:
1033 # # ignore items we already have in the db, ignore
1034 # continue
1035 # index the item
1036 try:
1037 parser = SCAPurchasedApplicationParser(item)
1038 parser.index_app_info(db_purchased, cache)
1039 except:
1040 LOG.exception("error processing: %r", item)
1041 # add new in memory db to the main db
1042 db.add_database(db_purchased)
1043 # return a query
1044 query = xapian.Query("AH" + PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME)
1045 return query
1046
1047
1048def update_from_software_center_agent(db, cache, ignore_cache=False,1010def update_from_software_center_agent(db, cache, ignore_cache=False,
1049 include_sca_qa=False):1011 include_sca_qa=False):
1050 """Update the index based on the software-center-agent data."""1012 """Update the index based on the software-center-agent data."""
@@ -1056,31 +1018,60 @@
1056 sca.good_data = True1018 sca.good_data = True
1057 loop.quit()1019 loop.quit()
10581020
1021 def _available_for_me_cb(sca, available_for_me):
1022 LOG.debug("update_from_software_center_agent: available_for_me: %r",
1023 available_for_me)
1024 sca.available_for_me = available_for_me
1025 loop.quit()
1026
1059 def _error_cb(sca, error):1027 def _error_cb(sca, error):
1060 LOG.warn("update_from_software_center_agent: error: %r", error)1028 LOG.warn("update_from_software_center_agent: error: %r", error)
1061 sca.available = []
1062 sca.good_data = False1029 sca.good_data = False
1063 loop.quit()1030 loop.quit()
10641031
1065 # use the anonymous interface to s-c-agent, scales much better and is1032 context = GObject.main_context_default()
1066 # much cache friendlier1033 loop = GObject.MainLoop(context)
1067 from softwarecenter.backend.scagent import SoftwareCenterAgent1034
1068 # FIXME: honor ignore_etag here somehow with the new piston based API
1069 sca = SoftwareCenterAgent(ignore_cache)1035 sca = SoftwareCenterAgent(ignore_cache)
1070 sca.connect("available", _available_cb)1036 sca.connect("available", _available_cb)
1037 sca.connect("available-for-me", _available_for_me_cb)
1071 sca.connect("error", _error_cb)1038 sca.connect("error", _error_cb)
1072 sca.available = None1039 sca.available = []
1040 sca.available_for_me = []
1041
1042 # query what is available for me first
1043 available_for_me_pkgnames = set()
1044 # this will ensure we do not trigger a login dialog
1045 helper = UbuntuSSO()
1046 token = helper.find_oauth_token_sync()
1047 if token:
1048 sca.query_available_for_me(no_relogin=True)
1049 loop.run()
1050 for item in sca.available_for_me:
1051 try:
1052 parser = SCAPurchasedApplicationParser(item)
1053 parser.index_app_info(db, cache)
1054 available_for_me_pkgnames.add(item.application["package_name"])
1055 except:
1056 LOG.exception("error processing: %r", item)
1057
1058 # ... now query all that is available
1073 if include_sca_qa:1059 if include_sca_qa:
1074 sca.query_available_qa()1060 sca.query_available_qa()
1075 else:1061 else:
1076 sca.query_available()1062 sca.query_available()
1063
1077 # create event loop and run it until data is available1064 # create event loop and run it until data is available
1078 # (the _available_cb and _error_cb will quit it)1065 # (the _available_cb and _error_cb will quit it)
1079 context = GObject.main_context_default()
1080 loop = GObject.MainLoop(context)
1081 loop.run()1066 loop.run()
1067
1082 # process data1068 # process data
1083 for entry in sca.available:1069 for entry in sca.available:
1070
1071 # do not add stuff here thats already purchased to avoid duplication
1072 if entry.package_name in available_for_me_pkgnames:
1073 continue
1074
1084 # process events1075 # process events
1085 while context.pending():1076 while context.pending():
1086 context.iteration()1077 context.iteration()
10871078
=== modified file 'softwarecenter/enums.py'
--- softwarecenter/enums.py 2012-09-11 00:02:18 +0000
+++ softwarecenter/enums.py 2012-09-14 13:44:26 +0000
@@ -208,7 +208,6 @@
208208
209209
210# fake channels210# fake channels
211PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME = "for-pay-needs-reinstall"
212AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME = "available-for-pay"211AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME = "available-for-pay"
213212
214213
215214
=== modified file 'softwarecenter/ui/gtk3/app.py'
--- softwarecenter/ui/gtk3/app.py 2012-09-11 13:31:09 +0000
+++ softwarecenter/ui/gtk3/app.py 2012-09-14 13:44:26 +0000
@@ -47,6 +47,8 @@
47# make pyflakes shut up47# make pyflakes shut up
48softwarecenter.netstatus.NETWORK_STATE48softwarecenter.netstatus.NETWORK_STATE
4949
50from softwarecenter.backend.ubuntusso import UbuntuSSO
51
50# db imports52# db imports
51from softwarecenter.db.application import Application53from softwarecenter.db.application import Application
52from softwarecenter.db import (54from softwarecenter.db import (
@@ -84,7 +86,10 @@
84 init_sc_css_provider,86 init_sc_css_provider,
85)87)
86from softwarecenter.version import VERSION88from softwarecenter.version import VERSION
87from softwarecenter.db.database import StoreDatabase89from softwarecenter.db.database import (
90 StoreDatabase,
91 get_reinstall_previous_purchases_query,
92 )
88try:93try:
89 from aptd_gtk3 import InstallBackendUI94 from aptd_gtk3 import InstallBackendUI
90 InstallBackendUI # pyflakes95 InstallBackendUI # pyflakes
@@ -336,7 +341,7 @@
336 # for use when viewing previous purchases341 # for use when viewing previous purchases
337 self.scagent = None342 self.scagent = None
338 self.sso = None343 self.sso = None
339 self.available_for_me_query = None344 self.available_for_me_query = get_reinstall_previous_purchases_query()
340345
341 Gtk.Window.set_default_icon_name("softwarecenter")346 Gtk.Window.set_default_icon_name("softwarecenter")
342347
@@ -739,25 +744,9 @@
739 channel_manager.feed_in_private_sources_list_entries(744 channel_manager.feed_in_private_sources_list_entries(
740 private_archives)745 private_archives)
741746
742 def _on_sso_login(self, sso, oauth_result):
743 self._sso_login_successful = True
744 # appmanager needs to know about the oauth token for the reinstall
745 # previous purchases add_license_key call
746 self.app_manager.oauth_token = oauth_result
747 self.scagent.query_available_for_me()
748
749 def _on_style_updated(self, widget, init_css_callback, *args):747 def _on_style_updated(self, widget, init_css_callback, *args):
750 init_css_callback(widget, *args)748 init_css_callback(widget, *args)
751749
752 def _available_for_me_result(self, scagent, result_list):
753 #print "available_for_me_result", result_list
754 from softwarecenter.db.update import (
755 add_from_purchased_but_needs_reinstall_data)
756 available = add_from_purchased_but_needs_reinstall_data(result_list,
757 self.db, self.cache)
758 self.available_for_me_query = available
759 self.available_pane.on_previous_purchases_activated(available)
760
761 def get_icon_filename(self, iconname, iconsize):750 def get_icon_filename(self, iconname, iconsize):
762 iconinfo = self.icons.lookup_icon(iconname, iconsize, 0)751 iconinfo = self.icons.lookup_icon(iconname, iconsize, 0)
763 if not iconinfo:752 if not iconinfo:
@@ -838,30 +827,53 @@
838 d = LoginDialog(self.glaunchpad, self.datadir, parent=self.window_main)827 d = LoginDialog(self.glaunchpad, self.datadir, parent=self.window_main)
839 d.login()828 d.login()
840829
841 def _create_dbus_sso(self):830 def _on_reinstall_purchased_login(self, sso, oauth_result):
842 # see bug #773214 for the rationale, do not translate the appname831 self._sso_login_successful = True
843 #appname = _("Ubuntu Software Center")832 # appmanager needs to know about the oauth token for the reinstall
844 appname = SOFTWARE_CENTER_NAME_KEYRING833 # previous purchases add_license_key call
845 help_text = _("To reinstall previous purchases, sign in to the "834 self.app_manager.oauth_token = oauth_result
846 "Ubuntu Single Sign-On account you used to pay for them.")835
847 #window = self.window_main.get_window()836 # the software-center-agent will ensure the previous-purchases
848 #xid = self.get_window().xid837 # get merged in
849 xid = 0838 self._run_software_center_agent()
850 self.sso = get_sso_backend(xid,839
851 appname,840 # show spinner as this may take some time, the spinner will
852 help_text)841 # automatically go away when a DB refreshes
853 self.sso.connect("login-successful", self._on_sso_login)842 self.available_pane.show_appview_spinner()
854843
855 def _login_via_dbus_sso(self):844 # show previous purchased
856 self._create_dbus_sso()845 self.available_pane.on_previous_purchases_activated(
857 self.sso.login()846 self.available_for_me_query)
858847
859 def _create_scagent_if_needed(self):848 def on_menuitem_reinstall_purchases_activate(self, menuitem):
860 if not self.scagent:849 self.view_manager.set_active_view(ViewPages.AVAILABLE)
861 from softwarecenter.backend.scagent import SoftwareCenterAgent850 self.view_manager.search_entry.clear_with_no_signal()
862 self.scagent = SoftwareCenterAgent()851 # its ok to use the sync version here to get the token, this is
863 self.scagent.connect("available-for-me",852 # very quick
864 self._available_for_me_result)853 helper = UbuntuSSO()
854 token = helper.find_oauth_token_sync()
855 if token:
856 # trigger a software-center-agent run to ensure the merged in
857 # purchases are fresh
858 self._run_software_center_agent()
859 # we already have the list of available items, so just show it
860 # (no need for spinner here)
861 self.available_pane.on_previous_purchases_activated(
862 self.available_for_me_query)
863 else:
864 # see bug #773214 for the rationale, do not translate the appname
865 #appname = _("Ubuntu Software Center")
866 appname = SOFTWARE_CENTER_NAME_KEYRING
867 help_text = _(
868 "To reinstall previous purchases, sign in to the "
869 "Ubuntu Single Sign-On account you used to pay for them.")
870 #window = self.window_main.get_window()
871 #xid = self.get_window().xid
872 xid = 0
873 self.sso = get_sso_backend(xid, appname, help_text)
874 self.sso.connect(
875 "login-successful", self._on_reinstall_purchased_login)
876 self.sso.login()
865877
866 def on_menuitem_recommendations_activate(self, menu_item):878 def on_menuitem_recommendations_activate(self, menu_item):
867 rec_panel = self.available_pane.cat_view.recommended_for_you_panel879 rec_panel = self.available_pane.cat_view.recommended_for_you_panel
@@ -875,21 +887,6 @@
875 if res == Gtk.ResponseType.YES:887 if res == Gtk.ResponseType.YES:
876 rec_panel.opt_in_to_recommendations_service()888 rec_panel.opt_in_to_recommendations_service()
877889
878 def on_menuitem_reinstall_purchases_activate(self, menuitem):
879 self.view_manager.set_active_view(ViewPages.AVAILABLE)
880 self.view_manager.search_entry.clear_with_no_signal()
881 if self.available_for_me_query:
882 # we already have the list of available items, so just show it
883 # (no need for spinner here)
884 self.available_pane.on_previous_purchases_activated(
885 self.available_for_me_query)
886 else:
887 # show spinner as this may take some time
888 self.available_pane.show_appview_spinner()
889 # fetch the list of available items and show it
890 self._create_scagent_if_needed()
891 self._login_via_dbus_sso()
892
893 def on_menuitem_deauthorize_computer_activate(self, menuitem):890 def on_menuitem_deauthorize_computer_activate(self, menuitem):
894891
895 # FIXME: need Ubuntu SSO username here892 # FIXME: need Ubuntu SSO username here
896893
=== modified file 'tests/gtk3/test_purchase.py'
--- tests/gtk3/test_purchase.py 2012-08-21 08:46:26 +0000
+++ tests/gtk3/test_purchase.py 2012-09-14 13:44:26 +0000
@@ -71,7 +71,13 @@
71 do_events_with_sleep()71 do_events_with_sleep()
72 self.assertTrue(signal_mock.called)72 self.assertTrue(signal_mock.called)
7373
74 def test_reinstall_previous_purchase_display(self):74
75class PreviousPurchasesTestCase(unittest.TestCase):
76
77 @patch("softwarecenter.backend.ubuntusso.UbuntuSSO"
78 ".find_oauth_token_sync")
79 def test_reinstall_previous_purchase_display(self, mock_find_token):
80 mock_find_token.return_value = { 'not': 'important' }
75 mock_options = get_mock_options()81 mock_options = get_mock_options()
76 xapiandb = "/var/cache/software-center/"82 xapiandb = "/var/cache/software-center/"
77 app = SoftwareCenterAppGtk3(83 app = SoftwareCenterAppGtk3(
@@ -79,22 +85,19 @@
79 self.addCleanup(app.destroy)85 self.addCleanup(app.destroy)
80 # real app opens cache async86 # real app opens cache async
81 app.cache.open()87 app.cache.open()
82 # no real sso88 # .. and now pretend we clicked on the menu item
83 with patch.object(app, '_login_via_dbus_sso',89 app.window_main.show_all()
84 lambda: app._available_for_me_result(None, [])):90 app.available_pane.init_view()
85 # show it91 do_events_with_sleep()
86 app.window_main.show_all()92 app.on_menuitem_reinstall_purchases_activate(None)
87 app.available_pane.init_view()93 # it can take a bit until the sso client is ready
94 for i in range(10):
95 if (app.available_pane.get_current_page() ==
96 AvailablePane.Pages.LIST):
97 break
88 do_events_with_sleep()98 do_events_with_sleep()
89 app.on_menuitem_reinstall_purchases_activate(None)99 self.assertEqual(app.available_pane.get_current_page(),
90 # it can take a bit until the sso client is ready100 AvailablePane.Pages.LIST)
91 for i in range(100):
92 if (app.available_pane.get_current_page() ==
93 AvailablePane.Pages.LIST):
94 break
95 do_events_with_sleep()
96 self.assertEqual(app.available_pane.get_current_page(),
97 AvailablePane.Pages.LIST)
98101
99102
100if __name__ == "__main__":103if __name__ == "__main__":
101104
=== modified file 'tests/test_database.py'
--- tests/test_database.py 2012-09-14 07:31:06 +0000
+++ tests/test_database.py 2012-09-14 13:44:26 +0000
@@ -166,7 +166,11 @@
166 self.assertTrue(res)166 self.assertTrue(res)
167 self.assertEqual(db.get_doccount(), 1)167 self.assertEqual(db.get_doccount(), 1)
168168
169 def test_build_from_software_center_agent(self):169 @patch("softwarecenter.backend.ubuntusso.UbuntuSSO"
170 ".find_oauth_token_sync")
171 def test_build_from_software_center_agent(self, mock_find_oauth):
172 # pretend we have no token
173 mock_find_oauth.return_value = None
170 db = xapian.inmemory_open()174 db = xapian.inmemory_open()
171 cache = apt.Cache()175 cache = apt.Cache()
172 # monkey patch distro to ensure we get data176 # monkey patch distro to ensure we get data
@@ -365,7 +369,11 @@
365 doc.get_value(value_time) >= last_time369 doc.get_value(value_time) >= last_time
366 last_time = doc.get_value(value_time)370 last_time = doc.get_value(value_time)
367371
368 def test_for_purchase_apps_date_published(self):372 @patch("softwarecenter.backend.ubuntusso.UbuntuSSO"
373 ".find_oauth_token_sync")
374 def test_for_purchase_apps_date_published(self, mock_find_oauth):
375 # pretend we have no token
376 mock_find_oauth.return_value = None
369 #os.environ["SOFTWARE_CENTER_DEBUG_HTTP"] = "1"377 #os.environ["SOFTWARE_CENTER_DEBUG_HTTP"] = "1"
370 #os.environ["SOFTWARE_CENTER_AGENT_HOST"] = "http://sc.staging.ubuntu.com/"378 #os.environ["SOFTWARE_CENTER_AGENT_HOST"] = "http://sc.staging.ubuntu.com/"
371 # staging does not have a valid cert379 # staging does not have a valid cert
372380
=== modified file 'tests/test_reinstall_purchased.py'
--- tests/test_reinstall_purchased.py 2012-06-25 17:40:17 +0000
+++ tests/test_reinstall_purchased.py 2012-09-14 13:44:26 +0000
@@ -1,30 +1,29 @@
1import apt_pkg
2import apt
3import json1import json
4import platform2import platform
5import os
6import unittest3import unittest
7import xapian4import xapian
85
6from gi.repository import GObject
7
9from mock import patch8from mock import patch
10from piston_mini_client import PistonResponseObject9from piston_mini_client import PistonResponseObject
11from tests.utils import (10from tests.utils import (
12 DATA_DIR,11 get_test_pkg_info,
13 setup_test_env,12 setup_test_env,
13 ObjectWithSignals,
14)14)
15setup_test_env()15setup_test_env()
1616
17from softwarecenter.enums import (17from softwarecenter.enums import (
18 AppInfoFields,18 AppInfoFields,
19 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,19 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
20 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,
21 XapianValues,20 XapianValues,
22)21)
23from softwarecenter.db.database import StoreDatabase22from softwarecenter.db.database import get_reinstall_previous_purchases_query
24from softwarecenter.db.update import (23from softwarecenter.db.update import (
25 add_from_purchased_but_needs_reinstall_data,
26 SCAPurchasedApplicationParser,24 SCAPurchasedApplicationParser,
27 SCAApplicationParser,25 SCAApplicationParser,
26 update_from_software_center_agent,
28)27)
2928
30# Example taken from running:29# Example taken from running:
@@ -98,62 +97,45 @@
98 "description": "Play DVD-Videos\\r\\n\\r\\nFluendo DVD Player is a software application specially designed to\\r\\nreproduce DVD on Linux/Unix platforms, which provides end users with\\r\\nhigh quality standards.\\r\\n\\r\\nThe following features are provided:\\r\\n* Full DVD Playback\\r\\n* DVD Menu support\\r\\n* Fullscreen support\\r\\n* Dolby Digital pass-through\\r\\n* Dolby Digital 5.1 output and stereo downmixing support\\r\\n* Resume from last position support\\r\\n* Subtitle support\\r\\n* Audio selection support\\r\\n* Multiple Angles support\\r\\n* Support for encrypted discs\\r\\n* Multiregion, works in all regions\\r\\n* Multiple video deinterlacing algorithms",97 "description": "Play DVD-Videos\\r\\n\\r\\nFluendo DVD Player is a software application specially designed to\\r\\nreproduce DVD on Linux/Unix platforms, which provides end users with\\r\\nhigh quality standards.\\r\\n\\r\\nThe following features are provided:\\r\\n* Full DVD Playback\\r\\n* DVD Menu support\\r\\n* Fullscreen support\\r\\n* Dolby Digital pass-through\\r\\n* Dolby Digital 5.1 output and stereo downmixing support\\r\\n* Resume from last position support\\r\\n* Subtitle support\\r\\n* Audio selection support\\r\\n* Multiple Angles support\\r\\n* Support for encrypted discs\\r\\n* Multiregion, works in all regions\\r\\n* Multiple video deinterlacing algorithms",
99 "website": null,98 "website": null,
100 "version": "1.2.1"99 "version": "1.2.1"
101 }100 },
101 {
102 "website": "",
103 "package_name": "photobomb",
104 "video_embedded_html_urls": [ ],
105 "demo": null,
106 "keywords": "photos, pictures, editing, gwibber, twitter, facebook, drawing",
107 "video_urls": [ ],
108 "screenshot_url": "http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot-45.png",
109 "id": 83,
110 "archive_id": "commercial-ppa-uploaders/photobomb",
111 "support_url": "http://launchpad.net/photobomb",
112 "icon_url": "http://software-center.ubuntu.com/site_media/icons/2011/08/logo_64.png",
113 "binary_filesize": null,
114 "version": "",
115 "company_name": "",
116 "department": [
117 "Graphics"
118 ],
119 "tos_url": "",
120 "channel": "For Purchase",
121 "status": "Published",
122 "signing_key_id": "1024R/75254D99",
123 "description": "Easy and Social Image Editor\\nPhotobomb give you easy access to images in your social networking feeds, pictures on your computer and peripherals, and pictures on the web, and let's you draw, write, crop, combine, and generally have a blast mashing 'em all up. Then you can save off your photobomb, or tweet your creation right back to your social network.",
124 "price": "2.99",
125 "debtags": [ ],
126 "date_published": "2011-12-05 18:43:20.794802",
127 "categories": "Graphics",
128 "name": "Photobomb",
129 "license": "GNU GPL v3",
130 "screenshot_urls": [
131 "http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot-45.png"
132 ],
133 "archive_root": "https://private-ppa.launchpad.net/"
134 }
102]135]
103"""136"""
104137
105138
106class TestPurchased(unittest.TestCase):
107 """ tests the store database """
108
109 def _make_available_for_me_list(self):
110 my_subscriptions = json.loads(SUBSCRIPTIONS_FOR_ME_JSON)
111 return list(
112 PistonResponseObject.from_dict(subs) for subs in my_subscriptions)
113
114 def setUp(self):
115 # use fixture apt data
116 apt_pkg.config.set("APT::Architecture", "i386")
117 apt_pkg.config.set("Dir::State::status",
118 os.path.join(DATA_DIR, "appdetails", "var", "lib", "dpkg", "status"))
119 # create mocks
120 self.available_to_me = self._make_available_for_me_list()
121 self.cache = apt.Cache()
122
123 def test_reinstall_purchased_mock(self):
124 # test if the mocks are ok
125 self.assertEqual(len(self.available_to_me), 1)
126 self.assertEqual(
127 self.available_to_me[0].application['package_name'], "photobomb")
128
129 def test_reinstall_purchased_xapian(self):
130 db = StoreDatabase("/var/cache/software-center/xapian", self.cache)
131 db.open(use_axi=False)
132 # now create purchased debs xapian index (in memory because
133 # we store the repository passwords in here)
134 old_db_len = len(db)
135 query = add_from_purchased_but_needs_reinstall_data(
136 self.available_to_me, db, self.cache)
137 # ensure we have a new item (the available for reinstall one)
138 self.assertEqual(len(db), old_db_len+1)
139 # query
140 enquire = xapian.Enquire(db.xapiandb)
141 enquire.set_query(query)
142 matches = enquire.get_mset(0, len(db))
143 self.assertEqual(len(matches), 1)
144 distroseries = platform.dist()[2]
145 for m in matches:
146 doc = db.xapiandb.get_document(m.docid)
147 self.assertEqual(doc.get_value(XapianValues.PKGNAME), "photobomb")
148 self.assertEqual(
149 doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID),
150 "1024R/75254D99")
151 self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE),
152 "deb https://username:random3atoken@"
153 "private-ppa.launchpad.net/commercial-ppa-uploaders"
154 "/photobomb/ubuntu %s main" % distroseries)
155
156
157class SCAApplicationParserTestCase(unittest.TestCase):139class SCAApplicationParserTestCase(unittest.TestCase):
158140
159 def _make_application_parser(self, piston_application=None):141 def _make_application_parser(self, piston_application=None):
@@ -313,12 +295,11 @@
313 for key in (AppInfoFields.LICENSE_KEY, AppInfoFields.LICENSE_KEY_PATH):295 for key in (AppInfoFields.LICENSE_KEY, AppInfoFields.LICENSE_KEY_PATH):
314 self.assertIsNone(parser.get_value(key))296 self.assertIsNone(parser.get_value(key))
315297
316 def test_magic_channel(self):298 def test_purchase_date(self):
317 parser = self._make_application_parser()299 parser = self._make_application_parser()
318
319 self.assertEqual(300 self.assertEqual(
320 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,301 "2011-09-16 06:37:52",
321 parser.get_value(AppInfoFields.CHANNEL))302 parser.get_value(AppInfoFields.PURCHASED_DATE))
322303
323 def test_will_handle_supported_distros_when_available(self):304 def test_will_handle_supported_distros_when_available(self):
324 # When the fix for bug 917109 reaches production, we will be305 # When the fix for bug 917109 reaches production, we will be
@@ -367,5 +348,73 @@
367 SCAPurchasedApplicationParser.update_debline(orig_debline))348 SCAPurchasedApplicationParser.update_debline(orig_debline))
368349
369350
351class TestAvailableForMeMerging(unittest.TestCase):
352
353 def setUp(self):
354 self.available_for_me = self._make_available_for_me_list()
355 self.available = self._make_available_list()
356
357 def _make_available_for_me_list(self):
358 my_subscriptions = json.loads(SUBSCRIPTIONS_FOR_ME_JSON)
359 return list(
360 PistonResponseObject.from_dict(subs) for subs in my_subscriptions)
361
362 def _make_available_list(self):
363 available_apps = json.loads(AVAILABLE_APPS_JSON)
364 return list(
365 PistonResponseObject.from_dict(subs) for subs in available_apps)
366
367 def _make_fake_scagent(self, available_data, available_for_me_data):
368 sca = ObjectWithSignals()
369 sca.query_available = lambda **kwargs: GObject.timeout_add(
370 100, lambda: sca.emit('available', sca, available_data))
371 sca.query_available_for_me = lambda **kwargs: GObject.timeout_add(
372 100, lambda: sca.emit('available-for-me',
373 sca, available_for_me_data))
374 return sca
375
376 def test_reinstall_purchased_mock(self):
377 # test if the mocks are ok
378 self.assertEqual(len(self.available_for_me), 1)
379 self.assertEqual(
380 self.available_for_me[0].application['package_name'], "photobomb")
381
382 @patch("softwarecenter.db.update.SoftwareCenterAgent")
383 @patch("softwarecenter.db.update.UbuntuSSO")
384 def test_reinstall_purchased_xapian(self, mock_helper, mock_agent):
385 small_available = [ self.available[0] ]
386 mock_agent.return_value = self._make_fake_scagent(
387 small_available, self.available_for_me)
388
389 db = xapian.inmemory_open()
390 cache = get_test_pkg_info()
391
392 # now create purchased debs xapian index (in memory because
393 # we store the repository passwords in here)
394 old_db_len = db.get_doccount()
395 update_from_software_center_agent(db, cache)
396 # ensure we have the new item
397 self.assertEqual(db.get_doccount(), old_db_len+2)
398 # query
399 query = get_reinstall_previous_purchases_query()
400 enquire = xapian.Enquire(db)
401 enquire.set_query(query)
402 matches = enquire.get_mset(0, db.get_doccount())
403 self.assertEqual(len(matches), 1)
404 distroseries = platform.dist()[2]
405 for m in matches:
406 doc = db.get_document(m.docid)
407 self.assertEqual(doc.get_value(XapianValues.PKGNAME), "photobomb")
408 self.assertEqual(
409 doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID),
410 "1024R/75254D99")
411 self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE),
412 "deb https://username:random3atoken@"
413 "private-ppa.launchpad.net/commercial-ppa-uploaders"
414 "/photobomb/ubuntu %s main" % distroseries)
415
416
370if __name__ == "__main__":417if __name__ == "__main__":
418 import logging
419 logging.basicConfig(level=logging.DEBUG)
371 unittest.main()420 unittest.main()
372421
=== modified file 'tests/test_ubuntu_sso_api.py'
--- tests/test_ubuntu_sso_api.py 2012-05-30 18:39:55 +0000
+++ tests/test_ubuntu_sso_api.py 2012-09-14 13:44:26 +0000
@@ -6,7 +6,7 @@
6)6)
7setup_test_env()7setup_test_env()
8from softwarecenter.backend.ubuntusso import (UbuntuSSOAPIFake,8from softwarecenter.backend.ubuntusso import (UbuntuSSOAPIFake,
9 UbuntuSSOAPI,9 UbuntuSSO,
10 get_ubuntu_sso_backend,10 get_ubuntu_sso_backend,
11 )11 )
1212
@@ -15,7 +15,7 @@
1515
16 def test_fake_and_real_provide_similar_methods(self):16 def test_fake_and_real_provide_similar_methods(self):
17 """ test if the real and fake sso provide the same functions """17 """ test if the real and fake sso provide the same functions """
18 sso_real = UbuntuSSOAPI18 sso_real = UbuntuSSO
19 sso_fake = UbuntuSSOAPIFake19 sso_fake = UbuntuSSOAPIFake
20 # ensure that both fake and real implement the same methods20 # ensure that both fake and real implement the same methods
21 self.assertEqual(21 self.assertEqual(
@@ -25,7 +25,7 @@
25 def test_get_ubuntu_backend(self):25 def test_get_ubuntu_backend(self):
26 # test that we get the real one26 # test that we get the real one
27 self.assertEqual(type(get_ubuntu_sso_backend()),27 self.assertEqual(type(get_ubuntu_sso_backend()),
28 UbuntuSSOAPI)28 UbuntuSSO)
29 # test that we get the fake one29 # test that we get the fake one
30 os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1"30 os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1"
31 self.assertEqual(type(get_ubuntu_sso_backend()),31 self.assertEqual(type(get_ubuntu_sso_backend()),
3232
=== modified file 'utils/piston-helpers/piston_generic_helper.py'
--- utils/piston-helpers/piston_generic_helper.py 2012-08-15 08:36:50 +0000
+++ utils/piston-helpers/piston_generic_helper.py 2012-09-14 13:44:26 +0000
@@ -25,7 +25,6 @@
25import pickle25import pickle
26import sys26import sys
2727
28from gi.repository import GObject
2928
30# useful for debugging29# useful for debugging
31if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ:30if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ:
@@ -45,13 +44,8 @@
4544
46import softwarecenter.paths45import softwarecenter.paths
47from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR46from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
48from softwarecenter.backend.login_sso import get_sso_backend
4947
50from softwarecenter.enums import (SOFTWARE_CENTER_NAME_KEYRING,48from softwarecenter.backend.ubuntusso import UbuntuSSO
51 SOFTWARE_CENTER_SSO_DESCRIPTION,
52 )
53
54from softwarecenter.utils import clear_token_from_ubuntu_sso_sync
5549
56# the piston import50# the piston import
57from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI51from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI
@@ -60,90 +54,23 @@
60from softwarecenter.backend.piston.sreclient_pristine import (54from softwarecenter.backend.piston.sreclient_pristine import (
61 SoftwareCenterRecommenderAPI)55 SoftwareCenterRecommenderAPI)
6256
57
58from softwarecenter.enums import RECOMMENDER_HOST
59SoftwareCenterRecommenderAPI.default_service_root = \
60 RECOMMENDER_HOST + "/api/1.0"
61
62
63# patch default_service_root to the one we use63# patch default_service_root to the one we use
64from softwarecenter.enums import UBUNTU_SSO_SERVICE64from softwarecenter.enums import UBUNTU_SSO_SERVICE
65# *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE65# *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE
66UbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE66UbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE
6767
68from softwarecenter.enums import RECOMMENDER_HOST
69SoftwareCenterRecommenderAPI.default_service_root = \
70 RECOMMENDER_HOST + "/api/1.0"
71
7268
73RatingsAndReviewsAPI # pyflakes69RatingsAndReviewsAPI # pyflakes
74UbuntuSsoAPI # pyflakes70UbuntuSsoAPI # pyflakes
75SoftwareCenterAgentAPI # pyflakes71SoftwareCenterAgentAPI # pyflakes
76SoftwareCenterRecommenderAPI # pyflakes72SoftwareCenterRecommenderAPI # pyflakes
7773
78from gettext import gettext as _
79
80
81# helper that is only used to verify that the token is ok
82# and trigger cleanup if not
83class SSOLoginHelper(object):
84
85 def __init__(self, xid=0):
86 self.oauth = None
87 self.xid = xid
88 self.loop = GObject.MainLoop(GObject.main_context_default())
89
90 def _login_successful(self, sso_backend, oauth_result):
91 LOG.debug("_login_successful")
92 self.oauth = oauth_result
93 # FIXME: actually verify the token against ubuntu SSO
94 self.loop.quit()
95
96 def verify_token_sync(self, token):
97 """ Verify that the token is valid
98
99 Note that this may raise httplib2 exceptions if the server
100 is not reachable
101 """
102 LOG.debug("verify_token")
103 auth = piston_mini_client.auth.OAuthAuthorizer(
104 token["token"], token["token_secret"],
105 token["consumer_key"], token["consumer_secret"])
106 api = UbuntuSsoAPI(auth=auth)
107 try:
108 res = api.whoami()
109 except piston_mini_client.failhandlers.APIError as e:
110 LOG.exception("api.whoami failed with APIError: '%s'" % e)
111 return False
112 return len(res) > 0
113
114 def clear_token(self):
115 clear_token_from_ubuntu_sso_sync(SOFTWARE_CENTER_NAME_KEYRING)
116
117 def get_oauth_token_sync(self):
118 self.oauth = None
119 sso = get_sso_backend(
120 self.xid,
121 SOFTWARE_CENTER_NAME_KEYRING,
122 _(SOFTWARE_CENTER_SSO_DESCRIPTION))
123 sso.connect("login-successful", self._login_successful)
124 sso.connect("login-failed", lambda s: self.loop.quit())
125 sso.connect("login-canceled", lambda s: self.loop.quit())
126 sso.login_or_register()
127 self.loop.run()
128 return self.oauth
129
130 def get_oauth_token_and_verify_sync(self):
131 token = self.get_oauth_token_sync()
132 # check if the token is valid and reset it if it is not
133 if token:
134 # verify token will return false if there is a API error,
135 # but there maybe httplib2 errors if there is no network,
136 # so ignore them
137 try:
138 if not self.verify_token_sync(token):
139 self.clear_token()
140 # re-trigger login once
141 token = self.get_oauth_token_sync()
142 except Exception as e:
143 LOG.warn(
144 "token could not be verified (network problem?): %s" % e)
145 return token
146
14774
148LOG = logging.getLogger(__name__)75LOG = logging.getLogger(__name__)
14976
@@ -164,6 +91,8 @@
164 help="force disable offline mode")91 help="force disable offline mode")
165 parser.add_argument("--needs-auth", default=False, action="store_true",92 parser.add_argument("--needs-auth", default=False, action="store_true",
166 help="need oauth credentials")93 help="need oauth credentials")
94 parser.add_argument("--no-relogin", default=False, action="store_true",
95 help="do not attempt relogin if token is invalid")
167 parser.add_argument("--output", default="pickle",96 parser.add_argument("--output", default="pickle",
168 help="output result as [pickle|json|text]")97 help="output result as [pickle|json|text]")
169 parser.add_argument("--parent-xid", default=0,98 parser.add_argument("--parent-xid", default=0,
@@ -191,8 +120,9 @@
191 softwarecenter.paths.datadir = args.datadir120 softwarecenter.paths.datadir = args.datadir
192121
193 if args.needs_auth:122 if args.needs_auth:
194 helper = SSOLoginHelper(args.parent_xid)123 helper = UbuntuSSO(args.parent_xid)
195 token = helper.get_oauth_token_and_verify_sync()124 token = helper.get_oauth_token_and_verify_sync(
125 no_relogin=args.no_relogin)
196 # if we don't have a token, error here126 # if we don't have a token, error here
197 if not token:127 if not token:
198 # it may happen that the parent is closed already so the pipe128 # it may happen that the parent is closed already so the pipe
199129
=== modified file 'utils/update-software-center-agent'
--- utils/update-software-center-agent 2012-03-16 11:09:25 +0000
+++ utils/update-software-center-agent 2012-09-14 13:44:26 +0000
@@ -96,6 +96,10 @@
96 "a write lock on %s" % pathname)96 "a write lock on %s" % pathname)
97 sys.exit(1)97 sys.exit(1)
9898
99 # ensure permissions are 0700 as this may contain repo passwords from
100 # the reinstall-previous-purchase repos
101 os.chmod(pathname, 0o700)
102
99 # the following requires a http connection, so we do it in a 103 # the following requires a http connection, so we do it in a
100 # seperate database104 # seperate database
101 include_sca_qa = "SOFTWARE_CENTER_AGENT_INCLUDE_QA" in os.environ105 include_sca_qa = "SOFTWARE_CENTER_AGENT_INCLUDE_QA" in os.environ

Subscribers

People subscribed via source and target branches