diff -Nru software-center-5.1.12/data/delete_review_gtk3.py software-center-5.1.13/data/delete_review_gtk3.py --- software-center-5.1.12/data/delete_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/delete_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/emblems/sc-emblem-favorite-not.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/emblems/sc-emblem-favorite-not.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/emblems/sc-emblem-favorite.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/emblems/sc-emblem-favorite.png differ diff -Nru software-center-5.1.12/data/expunge-cache.py software-center-5.1.13/data/expunge-cache.py --- software-center-5.1.12/data/expunge-cache.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/expunge-cache.py 2012-03-16 08:30:18.000000000 +0000 @@ -10,11 +10,13 @@ import time import sys + class ExpungeCache(object): + def __init__(self, dirs, args): self.dirs = dirs # days to keep data in the cache (0 == disabled) - self.keep_time = 60*60*24* args.by_days + self.keep_time = 60 * 60 * 24 * args.by_days self.keep_only_http200 = args.by_unsuccessful_http_states self.dry_run = args.dry_run Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons/24x24/status/softwarecenter-progress.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons/24x24/status/softwarecenter-progress.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons/32x32/animations/softwarecenter-loading.gif and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons/32x32/animations/softwarecenter-loading.gif differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons/32x32/animations/softwarecenter-loading-installed.gif and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons/32x32/animations/softwarecenter-loading-installed.gif differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/128x128/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/128x128/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/16x16/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/16x16/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/22x22/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/22x22/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/24x24/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/24x24/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/32x32/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/32x32/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/48x48/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/48x48/apps/softwarecenter.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/icons_unbranded/64x64/apps/softwarecenter.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/icons_unbranded/64x64/apps/softwarecenter.png differ diff -Nru software-center-5.1.12/data/icons_unbranded/scalable/apps/category-show-all.svg software-center-5.1.13/data/icons_unbranded/scalable/apps/category-show-all.svg --- software-center-5.1.12/data/icons_unbranded/scalable/apps/category-show-all.svg 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/icons_unbranded/scalable/apps/category-show-all.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,1074 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru software-center-5.1.12/data/icons_unbranded/scalable/apps/softwarecenter.svg software-center-5.1.13/data/icons_unbranded/scalable/apps/softwarecenter.svg --- software-center-5.1.12/data/icons_unbranded/scalable/apps/softwarecenter.svg 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/icons_unbranded/scalable/apps/softwarecenter.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,1539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/images/arrows.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/images/arrows.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/images/clouds.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/images/clouds.png differ diff -Nru software-center-5.1.12/data/modify_review_gtk3.py software-center-5.1.13/data/modify_review_gtk3.py --- software-center-5.1.12/data/modify_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/modify_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/data/piston_generic_helper.py software-center-5.1.13/data/piston_generic_helper.py --- software-center-5.1.12/data/piston_generic_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/piston_generic_helper.py 2012-03-16 08:30:18.000000000 +0000 @@ -56,14 +56,17 @@ from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI -from softwarecenter.backend.piston.sreclient_pristine import SoftwareCenterRecommenderAPI +from softwarecenter.backend.piston.sreclient_pristine import ( + SoftwareCenterRecommenderAPI) # patch default_service_root to the one we use -from softwarecenter.enums import SSO_LOGIN_HOST -UbuntuSsoAPI.default_service_root = SSO_LOGIN_HOST+"/api/1.0" +from softwarecenter.enums import UBUNTU_SSO_SERVICE +# *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE +UbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE from softwarecenter.enums import RECOMMENDER_HOST -SoftwareCenterRecommenderAPI.default_service_root = RECOMMENDER_HOST+"/api/1.0" +SoftwareCenterRecommenderAPI.default_service_root = \ + RECOMMENDER_HOST + "/api/1.0" RatingsAndReviewsAPI # pyflakes @@ -73,6 +76,7 @@ from gettext import gettext as _ + # helper that is only used to verify that the token is ok # and trigger cleanup if not class SSOLoginHelper(object): @@ -90,10 +94,9 @@ def verify_token_sync(self, token): LOG.debug("verify_token") - auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], - token["token_secret"], - token["consumer_key"], - token["consumer_secret"]) + auth = piston_mini_client.auth.OAuthAuthorizer( + token["token"], token["token_secret"], + token["consumer_key"], token["consumer_secret"]) api = UbuntuSsoAPI(auth=auth) try: res = api.whoami() @@ -128,7 +131,6 @@ return token - LOG = logging.getLogger(__name__) if __name__ == "__main__": @@ -179,10 +181,9 @@ sys.stderr.write("ERROR: can not obtain a oauth token\n") sys.exit(1) - auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], - token["token_secret"], - token["consumer_key"], - token["consumer_secret"]) + auth = piston_mini_client.auth.OAuthAuthorizer( + token["token"], token["token_secret"], + token["consumer_key"], token["consumer_secret"]) api = klass(cachedir=cachedir, auth=auth) else: api = klass(cachedir=cachedir) diff -Nru software-center-5.1.12/data/report_review_gtk3.py software-center-5.1.13/data/report_review_gtk3.py --- software-center-5.1.12/data/report_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/report_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/data/submit_review_gtk3.py software-center-5.1.13/data/submit_review_gtk3.py --- software-center-5.1.12/data/submit_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/submit_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/data/submit_usefulness_gtk3.py software-center-5.1.13/data/submit_usefulness_gtk3.py --- software-center-5.1.12/data/submit_usefulness_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/submit_usefulness_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/data/templates/WKTestWidget.html software-center-5.1.13/data/templates/WKTestWidget.html --- software-center-5.1.12/data/templates/WKTestWidget.html 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/templates/WKTestWidget.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ - - - - - - - - -

Test widget

- -

- Some text with a substitute value: '${key}'. -

- - - - - - - - - Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/ui/gtk3/art/corner-label.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/ui/gtk3/art/corner-label.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/ui/gtk3/art/globalpane-bg.png and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/ui/gtk3/art/globalpane-bg.png differ Binary files /tmp/pdaBoZfIGj/software-center-5.1.12/data/ui/gtk3/art/globalpane-bg.xcf and /tmp/Ix9HaF1nrz/software-center-5.1.13/data/ui/gtk3/art/globalpane-bg.xcf differ diff -Nru software-center-5.1.12/data/ui/gtk3/dialogs.ui software-center-5.1.13/data/ui/gtk3/dialogs.ui --- software-center-5.1.12/data/ui/gtk3/dialogs.ui 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/ui/gtk3/dialogs.ui 2012-03-20 08:00:09.000000000 +0000 @@ -1,6 +1,6 @@ - + False 5 @@ -23,6 +23,7 @@ Cancel + False True True False @@ -37,6 +38,7 @@ Repair + False True True True @@ -64,8 +66,6 @@ False 24 12 - 2 - 2 True @@ -162,6 +162,7 @@ Cancel + False True True False @@ -176,6 +177,7 @@ Remove + False True True True @@ -212,6 +214,9 @@ 2 2 + + + True False @@ -268,9 +273,6 @@ 6 - - - @@ -311,6 +313,7 @@ Cancel + False True True False @@ -325,6 +328,7 @@ Remove + False True True True @@ -352,8 +356,6 @@ False 6 6 - 2 - 2 True @@ -446,6 +448,7 @@ gtk-cancel + False True True True @@ -461,6 +464,7 @@ Retry + False True True True @@ -505,6 +509,7 @@ OK + False True True True @@ -541,6 +546,9 @@ 2 2 + + + True False @@ -604,9 +612,6 @@ 2 - - - diff -Nru software-center-5.1.12/data/ui/gtk3/GBTestWidget.ui software-center-5.1.13/data/ui/gtk3/GBTestWidget.ui --- software-center-5.1.12/data/ui/gtk3/GBTestWidget.ui 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/ui/gtk3/GBTestWidget.ui 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ - - - - - - - - True - vertical - - - - - - True - True - - - True - vertical - - - True - Click me - - - 0 - - - - - button - True - True - True - - - - 1 - - - - - - - - - - True - page 1 - - - False - - - - - True - vertical - - - - - - True - label - - - 1 - - - - - - - - 1 - - - - - True - page 2 - - - 1 - False - - - - - True - - - - - - True - - - 1 - - - - - - - - 2 - - - - - True - page 3 - - - 2 - False - - - - - 1 - - - - - - - - - diff -Nru software-center-5.1.12/data/ui/gtk3/login.ui software-center-5.1.13/data/ui/gtk3/login.ui --- software-center-5.1.12/data/ui/gtk3/login.ui 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/ui/gtk3/login.ui 1970-01-01 00:00:00.000000000 +0000 @@ -1,228 +0,0 @@ - - - - - - 5 - normal - False - - - True - - - True - - - True - 0 - - - False - False - 0 - - - - - True - 6 - - - True - 0 - 1 - Login - - - - - - - False - False - 0 - - - - - True - 12 - - - True - E-mail - - - False - False - 0 - - - - - True - True - - - - 1 - - - - - 1 - - - - - I have an Ubuntu Account - True - True - False - none - True - True - - - 2 - - - - - True - 2 - 2 - - - True - 0 - Password - - - - - True - True - False - - - - 1 - 2 - - - - - Remember password - True - False - True - - - 1 - 2 - 1 - 2 - - - - - - - - 3 - - - - - True - 12 - - - I want to register now - True - True - False - True - radiobutton_review_login_have_account - - - - - 4 - - - - - I forgot my password - True - True - False - True - radiobutton_review_login_have_account - - - 5 - - - - - 18 - 1 - - - - - 18 - 1 - - - - - True - end - - - gtk-cancel - True - True - True - True - 0.47999998927116394 - - - False - False - 0 - - - - - Continue - True - True - True - 0.43000000715255737 - - - False - False - 1 - - - - - False - end - 0 - - - - - - button_login_cancel - button_login_continue - - - diff -Nru software-center-5.1.12/data/ui/gtk3/SoftwareCenter.ui software-center-5.1.13/data/ui/gtk3/SoftwareCenter.ui --- software-center-5.1.12/data/ui/gtk3/SoftwareCenter.ui 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/ui/gtk3/SoftwareCenter.ui 2012-03-20 10:12:24.000000000 +0000 @@ -158,12 +158,6 @@ - - True - False - - - True False @@ -454,6 +448,22 @@ + + + True + False + + + + + True + False + False + Turn On Recommendations… + True + + + @@ -503,6 +513,16 @@ + + Terms of Service + True + False + False + False + + + + gtk-about True diff -Nru software-center-5.1.12/data/update-software-center-agent software-center-5.1.13/data/update-software-center-agent --- software-center-5.1.12/data/update-software-center-agent 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/update-software-center-agent 2012-03-19 08:35:49.000000000 +0000 @@ -75,7 +75,11 @@ # setup path pathname = XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT+".tmp" if not os.path.exists(pathname): - os.makedirs(pathname) + try: + os.makedirs(pathname) + except OSError as e: + logging.warn("Could not create agent dir '%s' (%s)'" % ( + pathname, e)) # check that we can write if not os.access(pathname, os.W_OK): diff -Nru software-center-5.1.12/data/x2go_helper.py software-center-5.1.13/data/x2go_helper.py --- software-center-5.1.12/data/x2go_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/data/x2go_helper.py 2012-03-16 08:30:18.000000000 +0000 @@ -1,5 +1,11 @@ #!/usr/bin/python -u -import x2go, gevent, sys, fcntl, os, shlex +import fcntl +import gevent +import os +import shlex +import sys +import x2go + def connect(server, port, login, password, session): print "PROGRESS: creating" diff -Nru software-center-5.1.12/debian/changelog software-center-5.1.13/debian/changelog --- software-center-5.1.12/debian/changelog 2012-03-09 07:55:51.000000000 +0000 +++ software-center-5.1.13/debian/changelog 2012-03-20 10:53:06.000000000 +0000 @@ -1,3 +1,104 @@ +software-center (5.1.13) precise; urgency=low + + [ Anthony Lenton ] + * lp:~elachuni/software-center/pep8-test-part5, + lp:~elachuni/software-center/pep8-test-part6, + lp:~elachuni/software-center/pep8-test-part7, + lp:~elachuni/software-center/pep8-test-part8, + lp:~elachuni/software-center/pep8-test-part9, + lp:~elachuni/software-center/pep8-test-part10, + lp:~elachuni/software-center/pep8-test-part11, + lp:~elachuni/software-center/pep8-test-part12, + lp:~elachuni/software-center/pep8-test-part13, + lp:~elachuni/software-center/pep8-test-part14, + lp:~elachuni/software-center/pep8-test-part15, + lp:~elachuni/software-center/pep8-test-part16, + lp:~elachuni/software-center/pep8-test-part17, + lp:~elachuni/software-center/pep8-test-part18, + lp:~elachuni/software-center/pep8-test-part19, + lp:~elachuni/software-center/pep8-test-part20, + lp:~elachuni/software-center/pep8-test-part21, + lp:~elachuni/software-center/pep8-test-part22: + - many, many, many pep8 fixes + * lp:~elachuni/software-center/unify-ussoc-envvar: + - fix USSOC_SERVICE_URL env variable + + [ Gabor Kelemen ] + * lp:~kelemeng/software-center/bug953812: (LP: #953812) + - Translate review sorting criteria + - Fix the Recommendation spinner text translations + + [ Michael Vogt ] + * lp:~mvo/software-center/small-sso-fixes: + - fix the clear_credentials() call if a token is no + longer valid + * lp:~mvo/software-center/lp955005: + - don't display ratings for the case where an app + cannot be found (LP: #955005) + * lp:~mvo/software-center/opengl-driver-blacklist: + - add support for video driver blacklisting + * lp:~mvo/software-center/lp856559: + - fix case where the search term is not always + cleared in the search entry (LP: #856559) + * lp:~mvo/software-center/misc-fixes: + - when a package can not be authenticated, do not fail + (hard) but instead offer to "repair" the situation using + a "reload" and try the install again (LP: #876278) + - do not crash in if os.makedirs has a race condition + (LP: #956680) + - fix the proxy handling with 12.04 as the "enabled" property + is deprecated now. it will also not mess with the proxy + environment on non-gnome systems (LP: #742564) + * lp:~mvo/software-center/lp780812: + - ensure that the distroseries is updated in the sources + deb line when updating a purchased item (LP: #780812) + * lp:~mvo/software-center/recommendations-opt-out: + - fix double-results when opting-in and back out of of + the recommendations service repeatedly + * lp:~mvo/software-center/tos-dialog: + - display a terms-of-service dialog before the first purchase + * lp:~mvo/software-center/lp957599: + - add a new unit test test_spawn_helper.py + * lp:~mvo/software-center/track-db-open: + - improve db logging + + [ Gary Lasker ] + * lp:~gary-lasker/software-center/fix-lp955048: + - String fix for the "Recommended For You" panel. LP: #955048 + * lp:~gary-lasker/software-center/fix-lp856559: + - Clears the search entry field before doing a reinstall + previous purchases (LP: #856559) + * lp:~gary-lasker/software-center/recommendations-categories-view: + - display recommendations in the category views as well + * lp:~gary-lasker/software-center/recommendations-opt-out: + - implements the recommendations opt-out feature as specified + in the SoftwareCenter spec + * lp:~gary-lasker/software-center/number-recommended-items-per-spec: + - simple branch that updates the number of recommended items displayed + in the panels per spec + * lp:~gary-lasker/software-center/fix-crash-lp951238: + - fix for a crash that can occur if we get a refresh_apps + before the app_view pane is ready LP: #951238 + * lp:~gary-lasker/software-center/fix-crash-lp943605: + - Don't crash if a valid value for the version is not returned + by the call to _get_version_for_archive_suite (LP: #943605) + * lp:~gary-lasker/software-center/recommends-panel-visual-tweaks-lp942109: + - addresses the remaining visual tweaks for the recommendations + opt-in panel + + [ Kiwinote ] + * lp:~kiwinote/software-center/less-is-more, + lp:~kiwinote/software-center/less-is-more2: + - clean out a bunch of unused code / files + * software-center/ui/gtk3/widgets/apptreeview.py: + - play catchup with Gtk overrides (LP: #959530) + * lp:~kiwinote/software-center/ellipses: + - fix use real ellipses instead of "..." in the new string + * lp:~kiwinote/software-center/markupescape-and-utf8-fixes: + - fix utf8 issues and markup escaping + + -- Michael Vogt Tue, 20 Mar 2012 11:53:06 +0100 + software-center (5.1.12) precise; urgency=low [ Michael Vogt ] diff -Nru software-center-5.1.12/debian/control software-center-5.1.13/debian/control --- software-center-5.1.12/debian/control 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/debian/control 2012-03-20 08:40:44.000000000 +0000 @@ -52,7 +52,6 @@ update-notifier, software-properties-gtk, sessioninstaller, - zeitgeist-core, lzma Conflicts: gnome-app-install (<< 1), oneconf (<< 0.2.6.1) Replaces: gnome-app-install diff -Nru software-center-5.1.12/po/POTFILES.in software-center-5.1.13/po/POTFILES.in --- software-center-5.1.12/po/POTFILES.in 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/po/POTFILES.in 2012-03-20 08:40:44.000000000 +0000 @@ -30,7 +30,6 @@ softwarecenter/utils.py [type: gettext/glade]data/ui/gtk3/SoftwareCenter.ui [type: gettext/glade]data/ui/gtk3/dialogs.ui -[type: gettext/glade]data/ui/gtk3/login.ui [type: gettext/glade]data/ui/gtk3/report_abuse.ui [type: gettext/glade]data/ui/gtk3/submit_review.ui [type: gettext/glade]data/ui/gtk3/submit_usefulness.ui @@ -39,22 +38,22 @@ softwarecenter/ui/gtk3/app.py softwarecenter/ui/gtk3/review_gui_helper.py softwarecenter/ui/gtk3/dialogs/__init__.py +softwarecenter/ui/gtk3/dialogs/dialog_tos.py softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py softwarecenter/ui/gtk3/models/appstore2.py softwarecenter/ui/gtk3/models/pendingstore.py -softwarecenter/ui/gtk3/models/viewstore.py softwarecenter/ui/gtk3/panes/availablepane.py softwarecenter/ui/gtk3/panes/historypane.py softwarecenter/ui/gtk3/panes/installedpane.py softwarecenter/ui/gtk3/panes/pendingpane.py softwarecenter/ui/gtk3/panes/softwarepane.py -softwarecenter/ui/gtk3/panes/unused__channelpane.py softwarecenter/ui/gtk3/panes/viewswitcher.py softwarecenter/ui/gtk3/views/appdetailsview.py softwarecenter/ui/gtk3/views/appview.py softwarecenter/ui/gtk3/views/catview_gtk.py softwarecenter/ui/gtk3/views/purchaseview.py +softwarecenter/ui/gtk3/views/webkit.py softwarecenter/ui/gtk3/widgets/apptreeview.py softwarecenter/ui/gtk3/widgets/backforward.py softwarecenter/ui/gtk3/widgets/buttons.py @@ -65,7 +64,6 @@ softwarecenter/ui/gtk3/widgets/searchentry.py softwarecenter/ui/gtk3/widgets/stars.py softwarecenter/ui/gtk3/widgets/thumbnail.py -softwarecenter/ui/gtk3/widgets/unused__pathbar.py softwarecenter/ui/gtk3/widgets/videoplayer.py softwarecenter/ui/gtk3/widgets/weblivedialog.py softwarecenter/ui/gtk3/widgets/recommendations.py diff -Nru software-center-5.1.12/po/POTFILES.skip software-center-5.1.13/po/POTFILES.skip --- software-center-5.1.12/po/POTFILES.skip 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/po/POTFILES.skip 2012-03-19 19:43:39.000000000 +0000 @@ -1,15 +1,8 @@ -data/delete_review.py data/delete_review_gtk3.py -data/modify_review.py data/modify_review_gtk3.py -data/report_review.py data/report_review_gtk3.py -data/submit_review.py data/submit_review_gtk3.py -data/submit_usefulness.py data/submit_usefulness_gtk3.py -data/ui/gtk/GBTestWidget.ui -data/ui/gtk3/GBTestWidget.ui doc/example_plugin.py test/gtk3/test_appdetailsview.py test/gtk3/test_reviews.py diff -Nru software-center-5.1.12/run_against_rnr_testing_env.sh software-center-5.1.13/run_against_rnr_testing_env.sh --- software-center-5.1.12/run_against_rnr_testing_env.sh 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/run_against_rnr_testing_env.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -#!/bin/sh - -export SOFTWARE_CENTER_REVIEWS_HOST="http://184.82.116.62/reviews/api/1.0" - - -# sso -export USSOC_SERVICE_URL="https://login.staging.ubuntu.com/api/1.0" -pkill -f ubuntu-sso-login -python /usr/lib/ubuntu-sso-client/ubuntu-sso-login & - -# s-c -export PYTHONPATH=$(pwd) -./software-center $@ diff -Nru software-center-5.1.12/run_against_testing_env.sh software-center-5.1.13/run_against_testing_env.sh --- software-center-5.1.12/run_against_testing_env.sh 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.1.13/run_against_testing_env.sh 2012-03-15 09:05:38.000000000 +0000 @@ -0,0 +1,13 @@ +#!/bin/sh + +export SOFTWARE_CENTER_REVIEWS_HOST="http://184.82.116.62/reviews/api/1.0" +export SOFTWARE_CENTER_RECOMMENDER_HOST="http://rec.staging.ubuntu.com" + +# sso +export USSOC_SERVICE_URL="https://login.staging.ubuntu.com/api/1.0/" +pkill -f ubuntu-sso-login +python /usr/lib/ubuntu-sso-client/ubuntu-sso-login & + +# s-c +export PYTHONPATH=$(pwd) +./software-center $@ diff -Nru software-center-5.1.12/setup.py software-center-5.1.13/setup.py --- software-center-5.1.12/setup.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/setup.py 2012-03-19 19:43:39.000000000 +0000 @@ -51,14 +51,15 @@ gtkbuilder, flags=re.DOTALL) open(fname, "w").write(gtkbuilder) + def merge_extras_ubuntu_com_channel_file(): # update ubuntu-extras.list.in (this will not be part of debian as # its killed of in debian/rules on a non-ubuntu build) DISTROSERIES = platform.dist()[2] channelfile = "data/channels/Ubuntu/ubuntu-extras.list" - s=open(channelfile+".in").read() + s = open(channelfile + ".in").read() open(channelfile, "w").write(s.replace("#DISTROSERIES#", DISTROSERIES)) - + # update version.py line = open("debian/changelog").readline() @@ -130,9 +131,6 @@ glob.glob("data/ui/gtk3/art/icons/*.png")), ('share/software-center/default_banner', glob.glob("data/default_banner/*")), - # html - ('share/software-center/templates/', - glob.glob("data/templates/*.html")), # dbus ('../etc/dbus-1/system.d/', ["data/com.ubuntu.SoftwareCenter.conf"]), @@ -150,7 +148,7 @@ ['debian/source_software-center.py']), # extra software channels (can be distro specific) ('/usr/share/app-install/channels/', - glob.glob("data/channels/%s/*" % DISTRO) ), + glob.glob("data/channels/%s/*" % DISTRO)), ], cmdclass={"build": build_extra.build_extra, "build_i18n": build_i18n.build_i18n, diff -Nru software-center-5.1.12/softwarecenter/backend/channel_impl/aptchannels.py software-center-5.1.13/softwarecenter/backend/channel_impl/aptchannels.py --- software-center-5.1.12/softwarecenter/backend/channel_impl/aptchannels.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/channel_impl/aptchannels.py 2012-03-19 15:02:54.000000000 +0000 @@ -39,13 +39,14 @@ LOG = logging.getLogger(__name__) + class AptChannelsManager(ChannelsManager): def __init__(self, db): self.db = db self.distro = get_distro() self.backend = get_install_backend() - self.backend.connect("channels-changed", + self.backend.connect("channels-changed", self._remove_no_longer_needed_extra_channels) # kick off a background check for changes that may have been made # in the channels list @@ -60,7 +61,7 @@ """ return a list of SoftwareChannel objects in display order according to: - Distribution, Partners, PPAs alphabetically, + Distribution, Partners, PPAs alphabetically, Other channels alphabetically, Unknown channel last """ return self._get_channels() @@ -70,7 +71,7 @@ """ return a list of SoftwareChannel objects displaying installed packages only in display order according to: - Distribution, Partners, PPAs alphabetically, + Distribution, Partners, PPAs alphabetically, Other channels alphabetically, Unknown channel last """ return self._get_channels(installed_only=True) @@ -89,7 +90,7 @@ return the new channel object """ # print name, icon, query - channel = SoftwareChannel(name, None, None, + channel = SoftwareChannel(name, None, None, channel_icon=icon, channel_query=query) self.extra_channels.append(channel) @@ -113,7 +114,7 @@ """ this feeds in a private sources.list entry that is available to the user (like a private PPA) that may or - may not be active + may not be active """ # FIXME: strip out password and use apt/auth.conf potential_new_entry = SourceEntry(source_entry) @@ -150,14 +151,14 @@ def _check_for_channel_updates_timer(self): """ - run a background timer to see if the a-x-i data we have is + run a background timer to see if the a-x-i data we have is still fresh or if the cache has changed since """ # this is expensive and does not need UI to we shove it out channel_update = os.path.join( softwarecenter.paths.datadir, "update-software-center-channels") (pid, stdin, stdout, stderr) = GObject.spawn_async( - [channel_update], + [channel_update], flags=GObject.SPAWN_DO_NOT_REAP_CHILD) GObject.child_watch_add( pid, self._on_check_for_channel_updates_finished) @@ -166,13 +167,14 @@ # exit status of 1 means stuff changed if os.WEXITSTATUS(condition) == 1: self.db.reopen() - + def _get_channels(self, installed_only=False): """ - (internal) implements 'channels()' and 'channels_installed_only()' properties + (internal) implements 'channels()' and 'channels_installed_only()' + properties """ distro_channel_name = self.distro.get_distro_channel_name() - + # gather the set of software channels and order them other_channel_list = [] cached_origins = [] @@ -181,12 +183,13 @@ continue channel_name = channel_iter.term[3:] channel_origin = "" - + # get origin information for this channel m = self.db.xapiandb.postlist_begin(channel_iter.term) doc = self.db.xapiandb.get_document(m.get_docid()) for term_iter in doc.termlist(): - if term_iter.term.startswith("XOO") and len(term_iter.term) > 3: + if (term_iter.term.startswith("XOO") and + len(term_iter.term) > 3): channel_origin = term_iter.term[3:] break self._logger.debug("channel_name: %s" % channel_name) @@ -194,7 +197,7 @@ if channel_origin not in cached_origins: other_channel_list.append((channel_name, channel_origin)) cached_origins.append(channel_origin) - + dist_channel = None partner_channel = None for_purchase_channel = None @@ -207,9 +210,9 @@ for (channel_name, channel_origin) in other_channel_list: if not channel_name: unknown_channel.append(SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + channel_origin, + None, + installed_only=installed_only)) elif channel_name == distro_channel_name: dist_channel = (SoftwareChannel(distro_channel_name, channel_origin, @@ -217,52 +220,54 @@ installed_only=installed_only)) elif channel_name == "Partner archive": partner_channel = SoftwareChannel(channel_name, - channel_origin, - "partner", - installed_only=installed_only) + channel_origin, + "partner", + installed_only=installed_only) elif channel_name == "notdownloadable": if installed_only: local_channel = SoftwareChannel(channel_name, - None, - None, - installed_only=installed_only) + None, + None, + installed_only=installed_only) elif (channel_origin and - channel_origin.startswith("LP-PPA-commercial-ppa-uploaders")): + channel_origin.startswith("LP-PPA-commercial-ppa-uploaders")): # do not display commercial private PPAs, they will all be # displayed in the "for-purchase" node anyway pass elif channel_origin and channel_origin.startswith("LP-PPA"): if channel_origin == "LP-PPA-app-review-board": new_apps_channel = SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only) + channel_origin, + None, + installed_only=installed_only) else: ppa_channels.append(SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + channel_origin, + None, + installed_only=installed_only)) # TODO: detect generic repository source (e.g., Google, Inc.) else: other_channels.append(SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + channel_origin, + None, + installed_only=installed_only)) - # always display the partner channel, even if its source is not enabled + # always display the partner channel, even if its source is not enabled if not partner_channel and distro_channel_name == "Ubuntu": partner_channel = SoftwareChannel("Partner archive", "Canonical", - "partner", + "partner", installed_only=installed_only) - - # create a "magic" channel to display items available for purchase - for_purchase_query = xapian.Query("AH" + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME) - for_purchase_channel = SoftwareChannel("For Purchase", "software-center-agent", None, - channel_icon=None, # FIXME: need an icon - channel_query=for_purchase_query, - installed_only=installed_only) - + + # create a "magic" channel to display items available for purchase + for_purchase_query = xapian.Query("AH" + + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME) + for_purchase_channel = SoftwareChannel("For Purchase", + "software-center-agent", None, + channel_icon=None, # FIXME: need an icon + channel_query=for_purchase_query, + installed_only=installed_only) + # set them in order channels = [] if dist_channel is not None: @@ -286,4 +291,3 @@ else: channel._channel_view_id = ViewPages.AVAILABLE return channels - diff -Nru software-center-5.1.12/softwarecenter/backend/channel.py software-center-5.1.13/softwarecenter/backend/channel.py --- software-center-5.1.12/softwarecenter/backend/channel.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/channel.py 2012-03-19 16:41:16.000000000 +0000 @@ -24,13 +24,14 @@ from softwarecenter.distro import get_distro -from softwarecenter.enums import (SortMethods, +from softwarecenter.enums import (SortMethods, Icons, ViewPages, ) LOG = logging.getLogger(__name__) + class ChannelsManager(object): def __init__(self, db, **kwargs): self.distro = get_distro() @@ -52,10 +53,11 @@ # private def _get_channels_from_db(self, installed_only=False): """ - (internal) implements 'channels()' and 'channels_installed_only()' properties + (internal) implements 'channels()' and 'channels_installed_only()' + properties """ distro_channel_origin = self.distro.get_distro_channel_name() - + # gather the set of software channels and order them other_channel_list = [] cached_origins = [] @@ -64,12 +66,13 @@ continue channel_name = channel_iter.term[3:] channel_origin = "" - + # get origin information for this channel m = self.db.xapiandb.postlist_begin(channel_iter.term) doc = self.db.xapiandb.get_document(m.get_docid()) for term_iter in doc.termlist(): - if term_iter.term.startswith("XOO") and len(term_iter.term) > 3: + if (term_iter.term.startswith("XOO") and + len(term_iter.term) > 3): channel_origin = term_iter.term[3:] break LOG.debug("channel_name: %s" % channel_name) @@ -77,7 +80,7 @@ if channel_origin not in cached_origins: other_channel_list.append((channel_name, channel_origin)) cached_origins.append(channel_origin) - + dist_channel = None other_channels = [] unknown_channel = [] @@ -85,26 +88,30 @@ for (channel_name, channel_origin) in other_channel_list: if not channel_name: - unknown_channel.append(SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + unknown_channel.append(SoftwareChannel( + channel_name, + channel_origin, + None, + installed_only=installed_only)) elif channel_origin == distro_channel_origin: - dist_channel = (SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + dist_channel = (SoftwareChannel( + channel_name, + channel_origin, + None, + installed_only=installed_only)) elif channel_name == "notdownloadable": if installed_only: - local_channel = SoftwareChannel(channel_name, - None, - None, - installed_only=installed_only) + local_channel = SoftwareChannel( + channel_name, + None, + None, + installed_only=installed_only) else: - other_channels.append(SoftwareChannel(channel_name, - channel_origin, - None, - installed_only=installed_only)) + other_channels.append(SoftwareChannel( + channel_name, + channel_origin, + None, + installed_only=installed_only)) # set them in order channels = [] @@ -122,13 +129,14 @@ channel._channel_view_id = ViewPages.AVAILABLE return channels + class SoftwareChannel(object): """ class to represent a software channel """ - + ICON_SIZE = 24 - + def __init__(self, channel_name, channel_origin, channel_component, source_entry=None, installed_only=False, channel_icon=None, channel_query=None, @@ -148,13 +156,16 @@ # distro specific stuff self.distro = get_distro() # configure the channel - self._channel_display_name = self._get_display_name_for_channel(channel_name, channel_origin, channel_component) + self._channel_display_name = self._get_display_name_for_channel( + channel_name, channel_origin, channel_component) if channel_icon is None: - self._channel_icon = self._get_icon_for_channel(channel_name, channel_origin, channel_component) + self._channel_icon = self._get_icon_for_channel( + channel_name, channel_origin, channel_component) else: self._channel_icon = channel_icon if channel_query is None: - self._channel_query = self._get_channel_query_for_channel(channel_name, channel_origin, channel_component) + self._channel_query = self._get_channel_query_for_channel( + channel_name, channel_origin, channel_component) else: self._channel_query = channel_query # a sources.list entry attached to the channel (this is currently @@ -162,36 +173,36 @@ self._source_entry = source_entry # when the channel needs to be added to the systems sources.list self.needs_adding = False - + @property def name(self): """ return the channel name as represented in the xapian database """ return self._channel_name - - @property + + @property def origin(self): """ return the channel origin as represented in the xapian database """ return self._channel_origin - - @property + + @property def component(self): """ return the channel component as represented in the xapian database """ return self._channel_component - - @property + + @property def display_name(self): """ return the display name for the corresponding channel for use in the UI """ return self._channel_display_name - - @property + + @property def icon(self): """ return the icon that corresponds to each channel based @@ -205,18 +216,19 @@ return the xapian query to be used with this software channel """ return self._channel_query - - @property + + @property def sort_mode(self): """ return the sort mode for this software channel """ return self._channel_sort_mode - + # TODO: implement __cmp__ so that sort for channels is encapsulated # here as well - def _get_display_name_for_channel(self, channel_name, channel_origin, channel_component): + def _get_display_name_for_channel(self, channel_name, channel_origin, + channel_component): if channel_component == "partner": channel_display_name = _("Canonical Partners") elif not channel_origin: @@ -232,8 +244,9 @@ else: return channel_name return channel_display_name - - def _get_icon_for_channel(self, channel_name, channel_origin, channel_component): + + def _get_icon_for_channel(self, channel_name, channel_origin, + channel_component): if channel_component == "partner": channel_icon = "partner" elif not channel_name: @@ -253,16 +266,17 @@ else: channel_icon = "unknown-channel" return channel_icon - - def _get_channel_query_for_channel(self, channel_name, channel_origin, channel_component): - + + def _get_channel_query_for_channel(self, channel_name, channel_origin, + channel_component): + if channel_component == "partner": q1 = xapian.Query("XOCpartner") q2 = xapian.Query("AH%s-partner" % self.distro.get_codename()) channel_query = xapian.Query(xapian.Query.OP_OR, q1, q2) # show only apps when displaying the new apps archive elif channel_name == "Application Review Board PPA": - channel_query = xapian.Query(xapian.Query.OP_AND, + channel_query = xapian.Query(xapian.Query.OP_AND, xapian.Query("XOL" + channel_name), xapian.Query("ATapplication")) elif channel_origin: @@ -292,14 +306,14 @@ self, channel_name, "all", None, installed_only=installed_only, channel_icon=Icons.FALLBACK) - return # overrides - def _get_display_name_for_channel(self, channel_name, channel_origin, channel_component): + def _get_display_name_for_channel(self, channel_name, channel_origin, + channel_component): return channel_name def _get_channel_query_for_channel(self, *args): - return None + pass class AllAvailableChannel(AllChannel): @@ -313,28 +327,34 @@ def __init__(self): AllChannel.__init__(self, _("All Installed"), True) + # singleton channels_manager = None + + def get_channels_manager(db): global channels_manager if channels_manager is None: from softwarecenter.enums import USE_PACKAGEKIT_BACKEND if not USE_PACKAGEKIT_BACKEND: - from softwarecenter.backend.channel_impl.aptchannels import AptChannelsManager + from softwarecenter.backend.channel_impl.aptchannels import ( + AptChannelsManager) channels_manager = AptChannelsManager(db) else: channels_manager = ChannelsManager(db) return channels_manager + def is_channel_available(channelname): - from softwarecenter.backend.channel_impl.aptchannels import AptChannelsManager + from softwarecenter.backend.channel_impl.aptchannels import ( + AptChannelsManager) return AptChannelsManager.channel_available(channelname) if __name__ == "__main__": distro = get_distro() - channel = SoftwareChannel(distro.get_distro_channel_name(), + channel = SoftwareChannel(distro.get_distro_channel_name(), None, None) print(channel) - channel = SoftwareChannel(distro.get_distro_channel_name(), None, "partner") + channel = SoftwareChannel(distro.get_distro_channel_name(), None, + "partner") print(channel) - diff -Nru software-center-5.1.12/softwarecenter/backend/fake_review_settings.py software-center-5.1.13/softwarecenter/backend/fake_review_settings.py --- software-center-5.1.12/softwarecenter/backend/fake_review_settings.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/fake_review_settings.py 2012-03-19 16:41:16.000000000 +0000 @@ -4,7 +4,8 @@ import pickle from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR -# decorator to add a fake network delay if set + +# decorator to add a fake network delay if set # in FakeReviewSettings.fake_network_delay def network_delay(fn): def slp(self, *args, **kwargs): @@ -14,23 +15,25 @@ time.sleep(delay) return fn(self, *args, **kwargs) return slp - + + class FakeReviewSettings(object): - '''An object that simply holds settings which are used by RatingsAndReviewsAPI - in the rnrclient_fake module. Using this module allows a developer to test - the reviews functionality without any interaction with a reviews server. - Each setting here provides complete control over how the 'server' will - respond. Changes to these settings should be made to the class attributes - directly without creating an instance of this class. - The intended usage is for unit tests where a predictable response is - required and where the application should THINK it has spoken to a - server. - The unit test would make changes to settings in this class before + '''An object that simply holds settings which are used by + RatingsAndReviewsAPI in the rnrclient_fake module. Using this module + allows a developer to test the reviews functionality without any + interaction with a reviews server. Each setting here provides complete + control over how the 'server' will respond. Changes to these settings + should be made to the class attributes directly without creating an + instance of this class. + The intended usage is for unit tests where a predictable response is + required and where the application should THINK it has spoken to a + server. + The unit test would make changes to settings in this class before running the unit test. ''' - + _FAKE_SETTINGS = {} - + #general settings #***************************** #delay (in seconds) before returning from any of the fake rnr methods @@ -41,45 +44,46 @@ #***************************** #raises APIError if True _FAKE_SETTINGS['server_response_error'] = False - + #review stats #***************************** #raises APIError if True - _FAKE_SETTINGS['review_stats_error'] = False - + _FAKE_SETTINGS['review_stats_error'] = False + #the following has no effect if review_stats_error = True - #determines the number of package stats (i.e. ReviewStats list size) to return - #max 15 packages (any number higher than 15 will still return 15) + #determines the number of package stats (i.e. ReviewStats list size) to + #return max 15 packages (any number higher than 15 will still return 15) _FAKE_SETTINGS['packages_returned'] = 10 - + #get reviews #***************************** #raises APIError if True _FAKE_SETTINGS['get_reviews_error'] = False - #number of pages of 10 reviews to return before returning the number specified - #in the reviews_returned value below + #number of pages of 10 reviews to return before returning the number + # specified in the reviews_returned value below _FAKE_SETTINGS['review_pages'] = 1 #the following has no effect if get_reviews_error = True - #determines number of reviews to return + #determines number of reviews to return # (Accepts 0 to n but should really be between 1 and 10) _FAKE_SETTINGS['reviews_returned'] = 3 - + #get review #***************************** #raises APIError if True _FAKE_SETTINGS['get_review_error'] = False - + #submit review #***************************** #raises APIError if True _FAKE_SETTINGS['submit_review_error'] = False - #fake username(str) and review_id(int) to give back with a successful review + #fake username(str) and review_id(int) to give back with a successful + # review #leave as None to generate a random username and review_id _FAKE_SETTINGS['reviewer_username'] = None _FAKE_SETTINGS['submit_review_id'] = None - + #flag review #***************************** #raises APIError if True @@ -88,43 +92,43 @@ _FAKE_SETTINGS['flagger_username'] = None #fake package name (str) to give back as flagged app _FAKE_SETTINGS['flag_package_name'] = None - + #submit usefulness #***************************** #raises APIError if True _FAKE_SETTINGS['submit_usefulness_error'] = False - + #the following has no effect if submit_usefulness_error = True #which string to pretend the server returned #choices are "Created", "Updated", "Not modified" _FAKE_SETTINGS['usefulness_response_string'] = "Created" - + #get usefulness #***************************** #raises APIError if True _FAKE_SETTINGS['get_usefulness_error'] = False - + #the following has no effect if get_usefulness_error = True #how many usefulness votes to return _FAKE_SETTINGS['votes_returned'] = 5 - - #pre-configured review ids to return in the result + + #pre-configured review ids to return in the result #if you don't complete this or enter less review ids than votes_returned #above, it will be random - _FAKE_SETTINGS['required_review_ids'] = [3,6,15] - + _FAKE_SETTINGS['required_review_ids'] = [3, 6, 15] + #THE FOLLOWING SETTINGS RELATE TO LOGIN SSO FUNCTIONALITY # LoginBackendDbusSSO # login() #*********************** - # what to fake the login response as + # what to fake the login response as # choices (strings): "successful", "failed", "denied" _FAKE_SETTINGS['login_response'] = "successful" - + # UbuntuSSOAPI # whoami() #*********************** - # what to fake whoami response as + # what to fake whoami response as # choices (strings): "whoami", "error" _FAKE_SETTINGS['whoami_response'] = "whoami" #this only has effect if whoami_response = 'whoami' @@ -132,65 +136,65 @@ #expects a string or None (for a random username) _FAKE_SETTINGS['whoami_username'] = None - def __init__(self, defaults=False): - '''Initialises the object and loads the settings into the _FAKE_SETTINGS - dict.. If defaults is passed as True any existing settings in the cache - file are ignored and the cache file is overwritten with the defaults - set in the class. This is useful if you don't want previously used - settings from the cache file being used again''' + '''Initialises the object and loads the settings into the + _FAKE_SETTINGS dict.. If defaults is passed as True any existing + settings in the cache file are ignored and the cache file is + overwritten with the defaults set in the class. This is useful if + you don't want previously used settings from the cache file being + used again''' fname = 'fake_review_settings.p' self.LOCATION = os.path.join(SOFTWARE_CENTER_CACHE_DIR, fname) if defaults: self._save_settings() else: self._update_from_file() - return - + def update_setting(self, key_name, new_value): - '''Takes a string (key_name) which corresponds to a setting in this object - and updates it with the value passed in (new_value). + '''Takes a string (key_name) which corresponds to a setting in this + object and updates it with the value passed in (new_value). Raises a NameError if the setting name doesn't exist''' - + if not key_name in self._FAKE_SETTINGS: - raise NameError ('Setting key name %s does not exist' % key_name) + raise NameError('Setting key name %s does not exist' % key_name) else: self._FAKE_SETTINGS[key_name] = new_value self._save_settings() return - + def update_multiple(self, settings): - '''Takes a dict (settings) of key,value pairs to perform multiple updates - in one action, then saves. Dict being passed should contain only keys that - match settings in this object or a NameError will be raised''' + '''Takes a dict (settings) of key,value pairs to perform multiple + updates in one action, then saves. Dict being passed should contain + only keys that match settings in this object or a NameError will be + raised''' for key, value in settings.items(): if not key in self._FAKE_SETTINGS: - raise NameError ('Setting key name %s does not exist' % key) - + raise NameError('Setting key name %s does not exist' % key) + for key, value in settings.items(): self._FAKE_SETTINGS[key] = value self._save_settings() return - + def get_setting(self, key_name): - '''Takes a string (key_name) which corresponds to a setting in this object, - gets the latest copy of it from the file and returns the setting. - Raises a NameError if the setting name doesn't exist''' + '''Takes a string (key_name) which corresponds to a setting in this + object, gets the latest copy of it from the file and returns the + setting. Raises a NameError if the setting name doesn't exist''' if not key_name in self._FAKE_SETTINGS: - raise NameError ('Setting %s does not exist' % key_name) + raise NameError('Setting %s does not exist' % key_name) else: self._update_from_file() return self._FAKE_SETTINGS[key_name] - + def _update_from_file(self): '''Loads existing settings from cache file into _FAKE_SETTINGS dict''' if os.path.exists(self.LOCATION): try: self._FAKE_SETTINGS = pickle.load(open(self.LOCATION)) except: - os.rename(self.LOCATION, self.LOCATION+".fail") + os.rename(self.LOCATION, self.LOCATION + ".fail") return - + def _save_settings(self): """write the dict out to cache file""" try: diff -Nru software-center-5.1.12/softwarecenter/backend/__init__.py software-center-5.1.13/softwarecenter/backend/__init__.py --- software-center-5.1.12/softwarecenter/backend/__init__.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/__init__.py 2012-03-19 15:02:54.000000000 +0000 @@ -21,5 +21,3 @@ # mvo: this is only there to make pyflakes silent (otherwise it thinks # its a unused import) get_install_backend - - diff -Nru software-center-5.1.12/softwarecenter/backend/installbackend_impl/aptd.py software-center-5.1.13/softwarecenter/backend/installbackend_impl/aptd.py --- software-center-5.1.12/softwarecenter/backend/installbackend_impl/aptd.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/installbackend_impl/aptd.py 2012-03-19 08:35:49.000000000 +0000 @@ -41,7 +41,7 @@ from softwarecenter.db.application import Application from softwarecenter.backend.transactionswatcher import ( - BaseTransactionsWatcher, + BaseTransactionsWatcher, BaseTransaction, TransactionFinishedResult, TransactionProgress) @@ -52,6 +52,8 @@ # its important that we only have a single dbus BusConnection # per address when using the fake dbus aptd buses = {} + + def get_dbus_bus(): if "SOFTWARE_CENTER_APTD_FAKE" in os.environ: global buses @@ -63,6 +65,7 @@ bus = dbus.SystemBus() return bus + class FakePurchaseTransaction(object): def __init__(self, app, iconname): self.pkgname = app.pkgname @@ -70,6 +73,7 @@ self.iconname = iconname self.progress = 0 + class AptdaemonTransaction(BaseTransaction): def __init__(self, trans): self._trans = trans @@ -126,9 +130,10 @@ trans = AptdaemonTransaction(trans) return real_handler(trans, *args) + class AptdaemonTransactionsWatcher(BaseTransactionsWatcher): - """ - base class for objects that need to watch the aptdaemon + """ + base class for objects that need to watch the aptdaemon for transaction changes. it registers a handler for the daemon going away and reconnects when it appears again """ @@ -144,7 +149,7 @@ #print "_register_active_transactions_watch", connection bus = get_dbus_bus() apt_daemon = client.get_aptdaemon(bus=bus) - apt_daemon.connect_to_signal("ActiveTransactionsChanged", + apt_daemon.connect_to_signal("ActiveTransactionsChanged", self._on_transactions_changed) current, queued = apt_daemon.GetActiveTransactions() self._on_transactions_changed(current, queued) @@ -158,54 +163,54 @@ trans = client.get_transaction(tid) return AptdaemonTransaction(trans) except dbus.DBusException: - return None + pass class AptdaemonBackend(GObject.GObject, InstallBackend): """ software center specific code that interacts with aptdaemon """ - __gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST, + __gsignals__ = {'transaction-started': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, - (str,str,str,str)), + (str, str, str, str)), # emits a TransactionFinished object - 'transaction-finished':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'transaction-stopped':(GObject.SIGNAL_RUN_FIRST, + 'transaction-finished': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'transaction-stopped': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,)), - 'transactions-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (str,int,)), + 'transactions-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'transaction-progress-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (str, int,)), # the number/names of the available channels changed - 'channels-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (bool,)), + 'channels-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (bool,)), # cache reload emits this specific signal as well - 'reload-finished':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, bool,)), + 'reload-finished': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, bool,)), } def __init__(self): GObject.GObject.__init__(self) - + bus = get_dbus_bus() self.aptd_client = client.AptClient(bus=bus) self.pending_transactions = {} self._transactions_watcher = AptdaemonTransactionsWatcher() self._transactions_watcher.connect("lowlevel-transactions-changed", - self._on_lowlevel_transactions_changed) + self._on_lowlevel_transactions_changed) # dict of pkgname -> FakePurchaseTransaction self.pending_purchases = {} self._progress_signal = None self._logger = logging.getLogger("softwarecenter.backend") # the AptdaemonBackendUI code self.ui = None - + def _axi_finished(self, res): self.emit("channels-changed", res) @@ -216,7 +221,7 @@ # axi is optional, so just do nothing if its not installed try: axi = dbus.Interface( - system_bus.get_object("org.debian.AptXapianIndex","/"), + system_bus.get_object("org.debian.AptXapianIndex", "/"), "org.debian.AptXapianIndex") except dbus.DBusException as e: self._logger.warning("axi can not be updated '%s'" % e) @@ -234,22 +239,26 @@ def fix_broken_depends(self): try: trans = yield self.aptd_client.fix_broken_depends(defer=True) - self.emit("transaction-started", "", "", trans.tid, TransactionTypes.REPAIR) + self.emit("transaction-started", "", "", trans.tid, + TransactionTypes.REPAIR) yield self._run_transaction(trans, None, None, None) except Exception as error: self._on_trans_error(error) # FIXME: upgrade add-ons here @inline_callbacks - def upgrade(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + def upgrade(self, app, iconname, addons_install=[], addons_remove=[], + metadata=None): """ upgrade a single package """ pkgname = app.pkgname appname = app.appname try: trans = yield self.aptd_client.upgrade_packages([pkgname], defer=True) - self.emit("transaction-started", pkgname, appname, trans.tid, TransactionTypes.UPGRADE) - yield self._run_transaction(trans, pkgname, appname, iconname, metadata) + self.emit("transaction-started", pkgname, appname, trans.tid, + TransactionTypes.UPGRADE) + yield self._run_transaction(trans, pkgname, appname, iconname, + metadata) except Exception as error: self._on_trans_error(error, pkgname) @@ -257,9 +266,10 @@ # @inline_callbacks # def _simulate_remove_multiple(self, pkgnames): # try: -# trans = yield self.aptd_client.remove_packages(pkgnames, +# trans = yield self.aptd_client.remove_packages(pkgnames, # defer=True) -# trans.connect("dependencies-changed", self._on_dependencies_changed) +# trans.connect("dependencies-changed", +# self._on_dependencies_changed) # except Exception: # logging.exception("simulate_remove") # return_value(trans) @@ -278,22 +288,25 @@ # gtk.main_iteration() # time.sleep(0.01) - @inline_callbacks - def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + def remove(self, app, iconname, addons_install=[], addons_remove=[], + metadata=None): """ remove a single package """ pkgname = app.pkgname appname = app.appname try: trans = yield self.aptd_client.remove_packages([pkgname], defer=True) - self.emit("transaction-started", pkgname, appname, trans.tid, TransactionTypes.REMOVE) - yield self._run_transaction(trans, pkgname, appname, iconname, metadata) + self.emit("transaction-started", pkgname, appname, trans.tid, + TransactionTypes.REMOVE) + yield self._run_transaction(trans, pkgname, appname, iconname, + metadata) except Exception as error: self._on_trans_error(error, pkgname) @inline_callbacks - def remove_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): + def remove_multiple(self, apps, iconnames, addons_install=[], + addons_remove=[], metadatas=None): """ queue a list of packages for removal """ if metadatas == None: metadatas = [] @@ -303,7 +316,8 @@ yield self.remove(app, iconname, metadata) @inline_callbacks - def install(self, app, iconname, filename=None, addons_install=[], addons_remove=[], metadata=None, force=False): + def install(self, app, iconname, filename=None, addons_install=[], + addons_remove=[], metadata=None, force=False): """Install a single package from the archive If filename is given a local deb package is installed instead. """ @@ -317,23 +331,26 @@ # force means on lintian failure trans = yield self.aptd_client.install_file( filename, force=force, defer=True) - self.emit("transaction-started", pkgname, appname, trans.tid, TransactionTypes.INSTALL) + self.emit("transaction-started", pkgname, appname, trans.tid, + TransactionTypes.INSTALL) yield trans.set_meta_data(sc_filename=filename, defer=True) else: install = [pkgname] + addons_install remove = addons_remove - reinstall = remove = purge = upgrade =downgrade = [] + reinstall = remove = purge = upgrade = downgrade = [] trans = yield self.aptd_client.commit_packages( - install, reinstall, remove, purge, upgrade, downgrade, + install, reinstall, remove, purge, upgrade, downgrade, defer=True) - self.emit("transaction-started", pkgname, appname, trans.tid, TransactionTypes.INSTALL) + self.emit("transaction-started", pkgname, appname, trans.tid, + TransactionTypes.INSTALL) yield self._run_transaction( trans, pkgname, appname, iconname, metadata) except Exception as error: self._on_trans_error(error, pkgname) @inline_callbacks - def install_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): + def install_multiple(self, apps, iconnames, addons_install=[], + addons_remove=[], metadatas=None): """ queue a list of packages for install """ if metadatas == None: metadatas = [] @@ -341,20 +358,22 @@ metadatas.append(None) for app, iconname, metadata in zip(apps, iconnames, metadatas): yield self.install(app, iconname, metadata=metadata) - + @inline_callbacks - def apply_changes(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + def apply_changes(self, app, iconname, addons_install=[], + addons_remove=[], metadata=None): """ install and remove add-ons """ pkgname = app.pkgname appname = app.appname try: install = addons_install remove = addons_remove - reinstall = remove = purge = upgrade =downgrade = [] + reinstall = remove = purge = upgrade = downgrade = [] trans = yield self.aptd_client.commit_packages( - install, reinstall, remove, purge, upgrade, downgrade, + install, reinstall, remove, purge, upgrade, downgrade, defer=True) - self.emit("transaction-started", pkgname, appname, trans.tid, TransactionTypes.APPLY) + self.emit("transaction-started", pkgname, appname, trans.tid, + TransactionTypes.APPLY) yield self._run_transaction(trans, pkgname, appname, iconname) except Exception as error: self._on_trans_error(error) @@ -365,12 +384,12 @@ # check if the sourcespart is there, if not, do a full reload # this can happen when the "partner" repository is added, it # will be in the main sources.list already and this means that - # aptsources will just enable it instead of adding a extra + # aptsources will just enable it instead of adding a extra # sources.list.d file (LP: #666956) d = apt_pkg.config.find_dir("Dir::Etc::sourceparts") if (not sources_list or not os.path.exists(os.path.join(d, sources_list))): - sources_list="" + sources_list = "" try: trans = yield self.aptd_client.update_cache( sources_list=sources_list, defer=True) @@ -404,18 +423,18 @@ continue sourcepart = os.path.basename(channelfile) yield self.add_sources_list_entry(entry, sourcepart) - keyfile = channelfile.replace(".list",".key") + keyfile = channelfile.replace(".list", ".key") if os.path.exists(keyfile): - trans = yield self.aptd_client.add_vendor_key_from_file(keyfile, wait=True) + trans = yield self.aptd_client.add_vendor_key_from_file( + keyfile, wait=True) # don't use self._run_transaction() here, to avoid sending # uneeded signals yield trans.run(defer=True) yield self.reload(sourcepart) @inline_callbacks - def add_vendor_key_from_keyserver(self, keyid, - keyserver="hkp://keyserver.ubuntu.com:80/", - metadata=None): + def add_vendor_key_from_keyserver(self, keyid, + keyserver="hkp://keyserver.ubuntu.com:80/", metadata=None): # strip the keysize if "/" in keyid: keyid = keyid.split("/")[1] @@ -452,8 +471,8 @@ @inline_callbacks def authenticate_for_purchase(self): - """ - helper that authenticates with aptdaemon for a purchase operation + """ + helper that authenticates with aptdaemon for a purchase operation """ bus = get_dbus_bus() name = bus.get_unique_name() @@ -462,12 +481,13 @@ yield policykit1.check_authorization_by_name(name, action, flags=flags) @inline_callbacks - def add_license_key(self, license_key, license_key_path, license_key_oauth, pkgname): + def add_license_key(self, license_key, license_key_path, + license_key_oauth, pkgname): """ add a license key for a purchase. """ self._logger.debug( "adding license_key for pkg '%s' of len: %i" % ( pkgname, len(license_key))) - + # HOME based license keys if license_key_path and license_key_path.startswith("~"): # check if its inside HOME and if so, just create it @@ -503,19 +523,19 @@ license_key_path, json_oauth_token=None, purchase=True): - """ + """ a convenience method that combines all of the steps needed to install a for-pay application, including adding the source entry and the vendor key, reloading the package list, and finally installing the specified application once the package list reload has completed. """ - self.emit("transaction-started", app.pkgname, app.appname, "FIXME-NEED-ID-HERE", TransactionTypes.INSTALL) - self._logger.info("add_repo_add_key_and_install_app() '%s' '%s' '%s'"% ( - # re.sub() out the password from the log - re.sub("deb https://.*@", "", deb_line), - signing_key_id, - app)) + self.emit("transaction-started", app.pkgname, app.appname, + "FIXME-NEED-ID-HERE", TransactionTypes.INSTALL) + self._logger.info("add_repo_add_key_and_install_app() '%s' '%s' '%s'" % + (re.sub("deb https://.*@", "", deb_line), # strip out password + signing_key_id, + app)) if purchase: # pre-authenticate @@ -537,15 +557,17 @@ # add the metadata early, add_sources_list_entry is a transaction # too - trans_metadata = {'sc_add_repo_and_install_appname' : app.appname, - 'sc_add_repo_and_install_pkgname' : app.pkgname, - 'sc_add_repo_and_install_deb_line' : deb_line, - 'sc_iconname' : iconname, - 'sc_add_repo_and_install_try' : "1", - 'sc_add_repo_and_install_license_key' : license_key or "", - 'sc_add_repo_and_install_license_key_path' : license_key_path or "", - 'sc_add_repo_and_install_license_key_token' : json_oauth_token or "", - } + trans_metadata = { + 'sc_add_repo_and_install_appname': app.appname, + 'sc_add_repo_and_install_pkgname': app.pkgname, + 'sc_add_repo_and_install_deb_line': deb_line, + 'sc_iconname': iconname, + 'sc_add_repo_and_install_try': "1", + 'sc_add_repo_and_install_license_key': license_key or "", + 'sc_add_repo_and_install_license_key_path': license_key_path or "", + 'sc_add_repo_and_install_license_key_token': \ + json_oauth_token or "", + } self._logger.info("add_sources_list_entry()") sourcepart = yield self.add_sources_list_entry(deb_line) @@ -560,8 +582,9 @@ yield self._reload_for_commercial_repo(app, trans_metadata, sourcepart) @inline_callbacks - def _reload_for_commercial_repo_defer(self, app, trans_metadata, sources_list): - """ + def _reload_for_commercial_repo_defer(self, app, trans_metadata, + sources_list): + """ helper that reloads and registers a callback for when the reload is finished """ @@ -570,7 +593,7 @@ # otherwise the daemon will fail because he does not know # the new package name yet self.connect("reload-finished", - self._on_reload_for_add_repo_and_install_app_finished, + self._on_reload_for_add_repo_and_install_app_finished, trans_metadata, app) # reload to ensure we have the new package data yield self.reload(sources_list=sources_list, metadata=trans_metadata) @@ -588,29 +611,34 @@ return False @inline_callbacks - def _on_reload_for_add_repo_and_install_app_finished(self, backend, trans, - result, metadata, app): - """ + def _on_reload_for_add_repo_and_install_app_finished(self, backend, trans, + result, metadata, app): + """ callback that is called once after reload was queued and will trigger the install of the for-pay package itself (after that it will automatically de-register) """ - #print "_on_reload_for_add_repo_and_install_app_finished", trans, result, backend, self._reload_signal_id - self._logger.info("_on_reload_for_add_repo_and_install_app_finished() %s %s %s" % (trans, result, app)) + #print "_on_reload_for_add_repo_and_install_app_finished", trans, \ + # result, backend, self._reload_signal_id + self._logger.info("_on_reload_for_add_repo_and_install_app_finished() " + "%s %s %s" % (trans, result, app)) # check if this is the transaction we waiting for key = "sc_add_repo_and_install_pkgname" - if not (key in trans.meta_data and trans.meta_data[key] == app.pkgname): + if not (key in trans.meta_data and + trans.meta_data[key] == app.pkgname): return_value(None) # get the debline and check if we have a release.gpg file deb_line = trans.meta_data["sc_add_repo_and_install_deb_line"] license_key = trans.meta_data["sc_add_repo_and_install_license_key"] - license_key_path = trans.meta_data["sc_add_repo_and_install_license_key_path"] - license_key_oauth = trans.meta_data["sc_add_repo_and_install_license_key_token"] + license_key_path = trans.meta_data[ + "sc_add_repo_and_install_license_key_path"] + license_key_oauth = trans.meta_data[ + "sc_add_repo_and_install_license_key_token"] release_filename = release_filename_in_lists_from_deb_line(deb_line) lists_dir = apt_pkg.config.find_dir("Dir::State::lists") - release_signature = os.path.join(lists_dir, release_filename)+".gpg" + release_signature = os.path.join(lists_dir, release_filename) + ".gpg" self._logger.info("looking for '%s'" % release_signature) # no Release.gpg in the newly added repository, try again, # this can happen e.g. on odd network proxies @@ -625,15 +653,15 @@ # FIXME: this logic will *fail* if the sources.list of the user # was broken before - # run install action if the repo was added successfully + # run install action if the repo was added successfully if result: self.emit("channels-changed", True) # we use aptd_client.install_packages() here instead - # of just + # of just # self.install(app, "", metadata=metadata) - # go get less authentication prompts (because of the 03_auth_me_less - # patch in aptdaemon) + # go get less authentication prompts (because of the + # 03_auth_me_less patch in aptdaemon) try: self._logger.info("install_package()") trans = yield self.aptd_client.install_packages( @@ -648,7 +676,7 @@ # but I wonder if we should ease that restriction if license_key and not os.path.exists(license_key_path): yield self.add_license_key( - license_key, license_key_path, license_key_oauth, + license_key, license_key_path, license_key_oauth, app.pkgname) else: @@ -668,14 +696,16 @@ # whole re-try machinery will not survive anyway if the local # s-c instance is closed self._logger.info("queuing reload in 30s") - trans.meta_data["sc_add_repo_and_install_try"]= str(retry+1) - sourcepart = trans.meta_data["sc_add_repo_and_install_sources_list"] + trans.meta_data["sc_add_repo_and_install_try"] = str(retry + 1) + sourcepart = trans.meta_data[ + "sc_add_repo_and_install_sources_list"] GObject.timeout_add_seconds(30, self._reload_for_commercial_repo, app, trans.meta_data, sourcepart) # internal helpers def _on_lowlevel_transactions_changed(self, watcher, current, pending): - # cleanup progress signal (to be sure to not leave dbus matchers around) + # cleanup progress signal (to be sure to not leave dbus + # matchers around) if self._progress_signal: GObject.source_remove(self._progress_signal) self._progress_signal = None @@ -683,7 +713,8 @@ if current: try: trans = client.get_transaction(current) - self._progress_signal = trans.connect("progress-changed", self._on_progress_changed) + self._progress_signal = trans.connect("progress-changed", + self._on_progress_changed) except dbus.DBusException: pass @@ -693,12 +724,14 @@ if not tid: continue try: - trans = client.get_transaction(tid, error_handler=lambda x: True) + trans = client.get_transaction(tid, + error_handler=lambda x: True) except dbus.DBusException: continue trans_progress = TransactionProgress(trans) try: - self.pending_transactions[trans_progress.pkgname] = trans_progress + self.pending_transactions[trans_progress.pkgname] = \ + trans_progress except KeyError: # if its not a transaction from us (sc_pkgname) still # add it with the tid as key to get accurate results @@ -709,19 +742,20 @@ self.inject_fake_transactions_and_emit_changed_signal() def inject_fake_transactions_and_emit_changed_signal(self): - """ + """ ensures that the fake transactions are considered and emits transactions-changed signal with the right pending transactions """ # inject a bunch FakePurchaseTransaction into the transations dict for pkgname in self.pending_purchases: - self.pending_transactions[pkgname] = self.pending_purchases[pkgname] + self.pending_transactions[pkgname] = \ + self.pending_purchases[pkgname] # and emit the signal self.emit("transactions-changed", self.pending_transactions) def _on_progress_changed(self, trans, progress): - """ - internal helper that gets called on our package transaction progress + """ + internal helper that gets called on our package transaction progress (only showing pkg progress currently) """ try: @@ -741,7 +775,8 @@ return # hide any private ppa details in the error message since it may # appear in the logs for LP bugs and potentially in screenshots as well - cleaned_error_details = obfuscate_private_ppa_details(trans.error_details) + cleaned_error_details = obfuscate_private_ppa_details( + trans.error_details) msg = utf8("%s: %s\n%s\n\n%s") % ( utf8(_("Error")), utf8(enums.get_error_string_from_enum(trans.error_code)), @@ -750,22 +785,33 @@ self._logger.error("error in _on_trans_finished '%s'" % msg) # show dialog to the user and exit (no need to reopen the cache) if not trans.error_code: - # sometimes aptdaemon doesn't return a value for error_code when the network - # connection has become unavailable; in that case, we will assume it's a - # failure during a package download because that is the only case where we - # see this happening - this avoids display of an empty error dialog and - # correctly prompts the user to check their network connection (see LP: #747172) - # FIXME: fix aptdaemon to return a valid error_code under all conditions + # sometimes aptdaemon doesn't return a value for error_code + # when the network connection has become unavailable; in + # that case, we will assume it's a failure during a package + # download because that is the only case where we see this + # happening - this avoids display of an empty error dialog + # and correctly prompts the user to check their network + # connection (see LP: #747172) + # FIXME: fix aptdaemon to return a valid error_code under + # all conditions trans.error_code = enums.ERROR_PACKAGE_DOWNLOAD_FAILED # show dialog to the user and exit (no need to reopen # the cache) res = self.ui.error(None, - utf8(enums.get_error_string_from_enum(trans.error_code)), - utf8(enums.get_error_description_from_enum(trans.error_code)), - utf8(cleaned_error_details), - utf8(alternative_action)) + utf8(enums.get_error_string_from_enum(trans.error_code)), + utf8(enums.get_error_description_from_enum(trans.error_code)), + utf8(cleaned_error_details), + utf8(alternative_action)) return res + def _get_app_and_icon_and_deb_from_trans(self, trans): + meta_copy = trans.meta_data.copy() + app = Application(meta_copy.pop("sc_appname", None), + meta_copy.pop("sc_pkgname")) + iconname = meta_copy.pop("sc_iconname", None) + filename = meta_copy.pop("sc_filename", "") + return app, iconname, filename, meta_copy + def _on_trans_finished(self, trans, enum): """callback when a aptdaemon transaction finished""" self._logger.debug("_on_transaction_finished: %s %s %s" % ( @@ -773,22 +819,35 @@ # show error if enum == enums.EXIT_FAILED: - # Handle invalid packages separately - if (trans.error and - trans.error.code == enums.ERROR_INVALID_PACKAGE_FILE): - action = _("_Ignore and install") - res = self._show_transaction_failed_dialog(trans, enum, action) - if res == "yes": - # Reinject the transaction - meta_copy = trans.meta_data.copy() - app = Application(meta_copy.pop("sc_appname", None), - meta_copy.pop("sc_pkgname")) - iconname = meta_copy.pop("sc_iconname", None) - filename = meta_copy.pop("sc_filename") - self.install(app, iconname, filename, [], [], - metadata=meta_copy, force=True) - return - elif not "sc_add_repo_and_install_ignore_errors" in trans.meta_data: + if trans.error: + # Handle invalid packages separately + if trans.error.code == enums.ERROR_INVALID_PACKAGE_FILE: + action = _("_Ignore and install") + res = self._show_transaction_failed_dialog( + trans, enum, action) + if res == "yes": + # Reinject the transaction + app, iconname, filename, meta_copy = \ + self._get_app_and_icon_and_deb_from_trans(trans) + self.install(app, iconname, filename, [], [], + metadata=meta_copy, force=True) + return + # on unauthenticated errors, try a "repair" using the + # reload functionatlity + elif trans.error.code == enums.ERROR_PACKAGE_UNAUTHENTICATED: + action = _("Repair") + res = self._show_transaction_failed_dialog( + trans, enum, action) + if res == "yes": + app, iconname, filename, meta_copy = \ + self._get_app_and_icon_and_deb_from_trans(trans) + self.reload() + self.install(app, iconname, filename, [], [], + metadata=meta_copy) + return + + elif (not "sc_add_repo_and_install_ignore_errors" in + trans.meta_data): self._show_transaction_failed_dialog(trans, enum) # send finished signal, use "" here instead of None, because @@ -807,7 +866,8 @@ self.emit("reload-finished", trans, enum != enums.EXIT_FAILED) # send appropriate signals self.inject_fake_transactions_and_emit_changed_signal() - self.emit("transaction-finished", TransactionFinishedResult(trans, enum != enums.EXIT_FAILED)) + self.emit("transaction-finished", TransactionFinishedResult(trans, + enum != enums.EXIT_FAILED)) @inline_callbacks def _config_file_conflict(self, transaction, old, new): @@ -851,7 +911,8 @@ # setup debconf only if we have a pkg yield trans.set_debconf_frontend("gnome", defer=True) trans.set_remove_obsoleted_depends(True, defer=True) - self._progress_signal = trans.connect("progress-changed", self._on_progress_changed) + self._progress_signal = trans.connect("progress-changed", + self._on_progress_changed) self.pending_transactions[pkgname] = TransactionProgress(trans) # generic metadata if metadata: @@ -899,4 +960,3 @@ backend.enable_component("multiverse") from gi.repository import Gtk Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/backend/installbackend_impl/packagekitd.py software-center-5.1.13/softwarecenter/backend/installbackend_impl/packagekitd.py --- software-center-5.1.12/softwarecenter/backend/installbackend_impl/packagekitd.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/installbackend_impl/packagekitd.py 2012-03-20 08:40:44.000000000 +0000 @@ -24,21 +24,23 @@ from gi.repository import PackageKitGlib as packagekit from softwarecenter.enums import TransactionTypes -from softwarecenter.backend.transactionswatcher import (BaseTransactionsWatcher, - BaseTransaction, - TransactionFinishedResult, - TransactionProgress) +from softwarecenter.backend.transactionswatcher import ( + BaseTransactionsWatcher, + BaseTransaction, + TransactionFinishedResult, + TransactionProgress +) from softwarecenter.backend.installbackend import InstallBackend -from softwarecenter.backend.installbackend_impl import packagekit_enums # temporary, must think of better solution from softwarecenter.db.pkginfo import get_pkg_info LOG = logging.getLogger("softwarecenter.backend.packagekit") + class PackagekitTransaction(BaseTransaction): _meta_data = {} - + def __init__(self, trans): """ trans -- a PkProgress object """ GObject.GObject.__init__(self) @@ -50,18 +52,26 @@ because PK DBus exposes only a generic Changed, without specifying the property changed """ - self._trans.connect('notify::role', self._emit, 'role-changed', 'role') - self._trans.connect('notify::status', self._emit, 'status-changed', 'status') - self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage') - #self._trans.connect('notify::subpercentage', self._emit, 'progress-changed', 'subpercentage') # SC UI does not support subprogress - self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage') - self._trans.connect('notify::allow-cancel', self._emit, 'cancellable-changed', 'allow-cancel') + self._trans.connect('notify::role', self._emit, + 'role-changed', 'role') + self._trans.connect('notify::status', self._emit, + 'status-changed', 'status') + self._trans.connect('notify::percentage', self._emit, + 'progress-changed', 'percentage') + # SC UI does not support subprogress: + #self._trans.connect('notify::subpercentage', self._emit, + # 'progress-changed', 'subpercentage') + self._trans.connect('notify::percentage', self._emit, + 'progress-changed', 'percentage') + self._trans.connect('notify::allow-cancel', self._emit, + 'cancellable-changed', 'allow-cancel') # connect the delete: - proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid) + proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', + self.tid) trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction') trans.connect_to_signal("Destroy", self._remove) - + def _emit(self, *args): prop, what = args[-1], args[-2] self.emit(what, self._trans.get_property(prop)) @@ -69,27 +79,33 @@ @property def tid(self): return self._trans.get_property('transaction-id') + @property def status_details(self): - return self.get_status_description() # FIXME + return self.get_status_description() # FIXME + @property def meta_data(self): return self._meta_data + @property def cancellable(self): return self._trans.get_property('allow-cancel') + @property def progress(self): return self._trans.get_property('percentage') def get_role_description(self, role=None): role = role if role is not None else self._trans.get_property('role') - return self.meta_data.get('sc_appname', packagekit_enums.role_enum_to_localised_present(role)) + return self.meta_data.get('sc_appname', + packagekit.role_enum_to_localised_present(role)) def get_status_description(self, status=None): - status = status if status is not None else self._trans.get_property('status') + if status is None: + status = self._trans.get_property('status') - return packagekit_enums.status_enum_to_localised_text(status) + return packagekit.info_enum_to_localised_present(status) def is_waiting(self): """ return true if a time consuming task is taking place """ @@ -107,7 +123,8 @@ status <= packagekit.StatusEnum.DOWNLOAD_UPDATEINFO) def cancel(self): - proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid) + proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', + self.tid) trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction') trans.Cancel() @@ -120,6 +137,7 @@ del PackagekitTransactionsWatcher._tlist[self.tid] LOG.debug("Delete transaction %s" % self.tid) + class PackagekitTransactionsWatcher(BaseTransactionsWatcher): _tlist = {} @@ -128,9 +146,10 @@ self.client = packagekit.Client() bus = dbus.SystemBus() - proxy = bus.get_object('org.freedesktop.PackageKit', '/org/freedesktop/PackageKit') + proxy = bus.get_object('org.freedesktop.PackageKit', + '/org/freedesktop/PackageKit') daemon = dbus.Interface(proxy, 'org.freedesktop.PackageKit') - daemon.connect_to_signal("TransactionListChanged", + daemon.connect_to_signal("TransactionListChanged", self._on_transactions_changed) queued = daemon.GetTransactionList() self._on_transactions_changed(queued) @@ -161,29 +180,30 @@ return trans return PackagekitTransactionsWatcher._tlist[tid] + class PackagekitBackend(GObject.GObject, InstallBackend): - - __gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST, + + __gsignals__ = {'transaction-started': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, - (str,str,str,str)), + (str, str, str, str)), # emits a TransactionFinished object - 'transaction-finished':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'transaction-stopped':(GObject.SIGNAL_RUN_FIRST, + 'transaction-finished': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'transaction-stopped': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,)), - 'transactions-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (str,int,)), + 'transactions-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'transaction-progress-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (str, int,)), # the number/names of the available channels changed # FIXME: not emitted. - 'channels-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (bool,)), + 'channels-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (bool,)), } def __init__(self): @@ -192,7 +212,7 @@ # transaction details for setting as meta self.new_pkgname, self.new_appname, self.new_iconname = '', '', '' - + # this is public exposed self.pending_transactions = {} @@ -202,11 +222,12 @@ self._transactions_watcher = PackagekitTransactionsWatcher() self._transactions_watcher.connect('lowlevel-transactions-changed', - self._on_lowlevel_transactions_changed) + self._on_lowlevel_transactions_changed) def upgrade(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None): - pass # FIXME implement it + pass # FIXME implement it + def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): self.remove_multiple((app,), (iconname,), @@ -220,26 +241,29 @@ appnames = [app.appname for app in apps] # keep track of pkg, app and icon for setting them as meta - self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0] + self.new_pkgname = pkgnames[0] + self.new_appname = appnames[0] + self.new_iconname = iconnames[0] # temporary hack pkgnames = self._fix_pkgnames(pkgnames) self.client.remove_packages_async(pkgnames, - False, # allow deps - False, # autoremove - None, # cancellable + False, # allow deps + False, # autoremove + None, # cancellable self._on_progress_changed, - None, # progress data - self._on_remove_ready, # callback ready - None # callback data + None, # progress data + self._on_remove_ready, # callback ready + None # callback data ) - self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.REMOVE) + self.emit("transaction-started", pkgnames[0], appnames[0], 0, + TransactionTypes.REMOVE) def install(self, app, iconname, filename=None, addons_install=[], addons_remove=[], metadata=None): if filename is not None: - LOG.error("Filename not implemented") # FIXME + LOG.error("Filename not implemented") # FIXME else: self.install_multiple((app,), (iconname,), addons_install, addons_remove, metadata @@ -252,7 +276,9 @@ appnames = [app.appname for app in apps] # keep track of pkg, app and icon for setting them as meta - self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0] + self.new_pkgname = pkgnames[0] + self.new_appname = appnames[0] + self.new_iconname = iconnames[0] # temporary hack pkgnames = self._fix_pkgnames(pkgnames) @@ -263,19 +289,21 @@ # PackageKit from installing untrusted packages # (in general, all enabled repos should have GPG signatures, # which is enough for being marked "trusted", but still) - self.client.install_packages_async(True, # only trusted + self.client.install_packages_async(True, # only trusted pkgnames, - None, # cancellable + None, # cancellable self._on_progress_changed, - None, # progress data - self._on_install_ready, # GAsyncReadyCallback + None, # progress data + self._on_install_ready, # GAsyncReadyCallback None # ready data ) - self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.INSTALL) + self.emit("transaction-started", pkgnames[0], appnames[0], 0, + TransactionTypes.INSTALL) def apply_changes(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None): pass + def reload(self, sources_list=None, metadata=None): """ reload package list """ pass @@ -313,15 +341,19 @@ if self.new_pkgname not in self.pending_transactions: self.pending_transactions[self.new_pkgname] = trans - #LOG.debug("Progress update %s %s %s %s" % (status, ptype, progress.get_property('transaction-id'),progress.get_property('status'))) + # LOG.debug("Progress update %s %s %s %s" % + # (status, ptype, progress.get_property('transaction-id'), + # progress.get_property('status'))) if status == packagekit.StatusEnum.FINISHED: LOG.debug("Transaction finished %s" % tid) - self.emit("transaction-finished", TransactionFinishedResult(trans, True)) + self.emit("transaction-finished", + TransactionFinishedResult(trans, True)) if status == packagekit.StatusEnum.CANCEL: LOG.debug("Transaction canceled %s" % tid) - self.emit("transaction-stopped", TransactionFinishedResult(trans, True)) + self.emit("transaction-stopped", + TransactionFinishedResult(trans, True)) if ptype == packagekit.ProgressType.PACKAGE: # this should be done better @@ -348,7 +380,8 @@ trans = self._transactions_watcher.get_transaction(tid) trans_progress = TransactionProgress(trans) try: - self.pending_transactions[trans_progress.pkgname] = trans_progress + self.pending_transactions[ + trans_progress.pkgname] = trans_progress except: self.pending_transactions[trans.tid] = trans_progress @@ -375,7 +408,7 @@ loop = dbus.mainloop.glib.DBusGMainLoop() dbus.set_default_main_loop(loop) - + backend = PackagekitBackend() pkginfo = get_pkg_info() if pkginfo[package].is_installed: @@ -387,4 +420,3 @@ from gi.repository import Gtk Gtk.main() #print backend._fix_pkgnames(('cheese',)) - diff -Nru software-center-5.1.12/softwarecenter/backend/installbackend_impl/packagekit_enums.py software-center-5.1.13/softwarecenter/backend/installbackend_impl/packagekit_enums.py --- software-center-5.1.12/softwarecenter/backend/installbackend_impl/packagekit_enums.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/installbackend_impl/packagekit_enums.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# Copyright (C) 2007-2008 Richard Hughes -# 2011 Giovanni Campagna -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -# stolen from gnome-packagekit, which is GPL2+ - -from gi.repository import PackageKitGlib as packagekit - -# this requires packagekit 0.7.2 or better -def status_enum_to_localised_text (status): - return packagekit.info_enum_to_localised_present(status) - -def role_enum_to_localised_present (role): - return packagekit.role_enum_to_localised_present(role) diff -Nru software-center-5.1.12/softwarecenter/backend/installbackend.py software-center-5.1.13/softwarecenter/backend/installbackend.py --- software-center-5.1.12/softwarecenter/backend/installbackend.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/installbackend.py 2012-03-19 16:41:16.000000000 +0000 @@ -18,32 +18,46 @@ from softwarecenter.utils import UnimplementedError + class InstallBackend(object): def __init__(self): self.pending_transactions = {} self.pending_purchases = [] - def upgrade(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + def upgrade(self, app, iconname, addons_install=[], addons_remove=[], + metadata=None): pass - def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + + def remove(self, app, iconname, addons_install=[], addons_remove=[], + metadata=None): pass - def remove_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): + + def remove_multiple(self, apps, iconnames, addons_install=[], + addons_remove=[], metadatas=None): pass - def install(self, app, iconname, filename=None, addons_install=[], addons_remove=[], metadata=None): + + def install(self, app, iconname, filename=None, addons_install=[], + addons_remove=[], metadata=None): pass - def install_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): + + def install_multiple(self, apps, iconnames, addons_install=[], + addons_remove=[], metadatas=None): pass - def apply_changes(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): + + def apply_changes(self, app, iconname, addons_install=[], + addons_remove=[], metadata=None): pass + def reload(self, sources_list=None, metadata=None): """ reload package list """ pass + class InstallBackendUI(object): def ask_config_file_conflict(self, old, new): """ show a conffile conflict and ask what to do - Return "keep" to keep the old one + Return "keep" to keep the old one "replace" to replace the old with the new one """ raise UnimplementedError("need custom ask_config_file_conflict method") @@ -53,24 +67,27 @@ return True if medium is provided, False to cancel """ raise UnimplementedError("need custom ask_medium_required method") - - def error(self, parent, primary, secondary, details=None, alternative_action=None): + + def error(self, parent, primary, secondary, details=None, + alternative_action=None): """ show an error dialog """ raise UnimplementedError("need custom error method") # singleton install_backend = None + + def get_install_backend(): global install_backend if install_backend is None: from softwarecenter.enums import USE_PACKAGEKIT_BACKEND if not USE_PACKAGEKIT_BACKEND: - from softwarecenter.backend.installbackend_impl.aptd import AptdaemonBackend + from softwarecenter.backend.installbackend_impl.aptd import ( + AptdaemonBackend) install_backend = AptdaemonBackend() else: - from softwarecenter.backend.installbackend_impl.packagekitd import PackagekitBackend + from softwarecenter.backend.installbackend_impl.packagekitd \ + import PackagekitBackend install_backend = PackagekitBackend() return install_backend - - diff -Nru software-center-5.1.12/softwarecenter/backend/launchpad.py software-center-5.1.13/softwarecenter/backend/launchpad.py --- software-center-5.1.12/softwarecenter/backend/launchpad.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/launchpad.py 2012-03-19 16:41:16.000000000 +0000 @@ -35,7 +35,7 @@ # py3 compat try: from queue import Queue - Queue # pyflakes + Queue # pyflakes except ImportError: from Queue import Queue @@ -55,16 +55,18 @@ LOGIN_STATE_AUTH_FAILURE = "auth-fail" LOGIN_STATE_USER_CANCEL = "user-cancel" + class UserCancelException(Exception): """ user pressed cancel """ pass + class LaunchpadlibWorker(threading.Thread): """The launchpadlib worker thread - it does not touch the UI and only communicates via the following: "login_state" - the current LOGIN_STATE_* value - + To input reviews call "queue_review()" When no longer needed, call "shutdown()" """ @@ -128,7 +130,7 @@ try: self._launchpad = Launchpad.login_with( 'software-center', SERVICE_ROOT, cachedir, - allow_access_levels = access_level, + allow_access_levels=access_level, authorizer_class=AuthorizeRequestTokenFromThread) self.display_name = self._launchpad.me.display_name except Exception as e: @@ -141,7 +143,8 @@ (service_root, launchpadlib_dir, cache_path, service_root_dir) = Launchpad._get_paths(SERVICE_ROOT, cachedir) credentials_path = os.path.join(service_root_dir, 'credentials') - consumer_credentials_path = os.path.join(credentials_path, 'software-center') + consumer_credentials_path = os.path.join(credentials_path, + 'software-center') # --- if os.path.exists(consumer_credentials_path): os.remove(consumer_credentials_path) @@ -150,11 +153,12 @@ self.login_state = LOGIN_STATE_SUCCESS self._logger.debug("/done %s" % self._launchpad) + class AuthorizeRequestTokenFromThread(RequestTokenAuthorizationEngine): """ Internal helper that updates the login_state of the modul global lp_worker_thread object """ - def __init__ (self, *args, **kwargs): + def __init__(self, *args, **kwargs): super(AuthorizeRequestTokenFromThread, self).__init__(*args, **kwargs) self._logger = logging.getLogger("softwarecenter.backend") @@ -167,7 +171,7 @@ return o def input_username(self, cached_username, suggested_message): - self._logger.debug( "input_username: %s" %self.lp_worker.login_state) + self._logger.debug("input_username: %s" % self.lp_worker.login_state) # otherwise go into ASK state if not self.lp_worker.login_state in (LOGIN_STATE_ASK_USER_AND_PASS, LOGIN_STATE_AUTH_FAILURE, @@ -185,7 +189,8 @@ return self.lp_worker.login_username def input_password(self, suggested_message): - self._logger.debug( "Input password size %s" % len(self.lp_worker.login_password)) + self._logger.debug("Input password size %s" % + len(self.lp_worker.login_password)) return self.lp_worker.login_password def input_access_level(self, available_levels, suggested_message, @@ -217,7 +222,7 @@ """ NEW_ACCOUNT_URL = "https://login.launchpad.net/+standalone-login" - FORGOT_PASSWORD_URL = "https://login.launchpad.net/+standalone-login" + FORGOT_PASSWORD_URL = "https://login.launchpad.net/+standalone-login" def __init__(self): LoginBackend.__init__(self) @@ -238,13 +243,13 @@ lp_worker_thread.shutdown() def enter_username_password(self, user, password): - """ + """ provider username and password, ususally used when the need-username-password signal was send """ lp_worker_thread.login_username = user lp_worker_thread.login_password = password - lp_worker_thread.login_state = LOGIN_STATE_HAS_USER_AND_PASS + lp_worker_thread.login_state = LOGIN_STATE_HAS_USER_AND_PASS def login(self, username=None, password=None): if username and password: @@ -254,17 +259,17 @@ def cancel_login(self): lp_worker_thread.login_state = LOGIN_STATE_USER_CANCEL - + def get_subscribed_archives(self): """ return list of sources.list entries """ urls = lp_worker_thread._launchpad.me.getArchiveSubscriptionURLs() return self._format_archive_subscription_urls_as_deb_lines(urls) - + def _format_archive_subscription_urls_as_deb_lines(self, urls): deb_lines = ["deb %s %s main" % (url, self.distro.get_codename()) \ for url in urls] return deb_lines - + def get_subscribed_archives_async(self, callback): """ get the available subscribed archives and run 'callback' when they become availalbe @@ -300,10 +305,16 @@ print ("success %s" % lp) print(lp.get_subscribed_archives()) print(lp.get_subscribed_archives_async(_result_callback)) + + def _login_failed(lp): print ("fail %s" % lp) + + def _result_callback(result_list): print("_result_callback %s" % result_list) + + def _login_need_user_and_password(lp): import sys sys.stdout.write("user: ") diff -Nru software-center-5.1.12/softwarecenter/backend/login.py software-center-5.1.13/softwarecenter/backend/login.py --- software-center-5.1.12/softwarecenter/backend/login.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/login.py 2012-03-19 16:41:16.000000000 +0000 @@ -21,31 +21,33 @@ from gi.repository import GObject + class LoginBackend(GObject.GObject): NEW_ACCOUNT_URL = None FORGOT_PASSWORD_URL = None __gsignals__ = { - "login-successful" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, + "login-successful": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,), ), - "login-failed" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), - "login-canceled" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), - "need-username-password" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), + "login-failed": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), + "login-canceled": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), + "need-username-password": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), } def login(self, username=None, password=None): raise NotImplemented + def cancel_login(self): self.emit("login-canceled") diff -Nru software-center-5.1.12/softwarecenter/backend/login_sso.py software-center-5.1.13/softwarecenter/backend/login_sso.py --- software-center-5.1.12/softwarecenter/backend/login_sso.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/login_sso.py 2012-03-19 16:41:16.000000000 +0000 @@ -36,6 +36,7 @@ LOG = logging.getLogger(__name__) + class LoginBackendDbusSSO(LoginBackend): def __init__(self, window_id, appname, help_text): @@ -45,11 +46,11 @@ self.bus = dbus.SessionBus() self.proxy = self.bus.get_object( 'com.ubuntu.sso', '/com/ubuntu/sso/credentials') - self.proxy.connect_to_signal("CredentialsFound", + self.proxy.connect_to_signal("CredentialsFound", self._on_credentials_found) - self.proxy.connect_to_signal("CredentialsError", + self.proxy.connect_to_signal("CredentialsError", self._on_credentials_error) - self.proxy.connect_to_signal("AuthorizationDenied", + self.proxy.connect_to_signal("AuthorizationDenied", self._on_authorization_denied) self._window_id = window_id self._credentials = None @@ -66,7 +67,7 @@ LOG.debug("login()") self._credentials = None self.proxy.login(self.appname, self._get_params()) - + def login_or_register(self): LOG.debug("login_or_register()") self._credentials = None @@ -82,9 +83,8 @@ if self._credentials != credentials: self.emit("login-successful", credentials) self._credentials = credentials - - def _on_credentials_error(self, app_name, error, detailed_error): + def _on_credentials_error(self, app_name, error, detailed_error=""): LOG.error("_on_credentails_error for %s: %s (%s)" % ( app_name, error, detailed_error)) if app_name != self.appname: @@ -107,55 +107,62 @@ self.help_text = help_text self._window_id = window_id self._fake_settings = FakeReviewSettings() - + @network_delay def login(self, username=None, password=None): response = self._fake_settings.get_setting('login_response') - + if response == "successful": self.emit("login-successful", self._return_credentials()) elif response == "failed": self.emit("login-failed") elif response == "denied": self.cancel_login() - + return - + def login_or_register(self): #fake functionality for this is no different to fake login() self.login() return - + def _random_unicode_string(self, length): retval = '' - for i in range(0,length): + for i in range(0, length): retval = retval + random.choice(string.letters + string.digits) return retval.decode('utf-8') - + def _return_credentials(self): - c = dbus.Dictionary( + c = dbus.Dictionary( { - dbus.String(u'consumer_secret'): dbus.String(self._random_unicode_string(30)), - dbus.String(u'token') : dbus.String(self._random_unicode_string(50)), - dbus.String(u'consumer_key') : dbus.String(self._random_unicode_string(7)), - dbus.String(u'name') : dbus.String('Ubuntu Software Center @ ' + self._random_unicode_string(6)), - dbus.String(u'token_secret') : dbus.String(self._random_unicode_string(50)) - }, + dbus.String(u'consumer_secret'): dbus.String( + self._random_unicode_string(30)), + dbus.String(u'token'): dbus.String( + self._random_unicode_string(50)), + dbus.String(u'consumer_key'): dbus.String( + self._random_unicode_string(7)), + dbus.String(u'name'): dbus.String('Ubuntu Software Center @ ' + + self._random_unicode_string(6)), + dbus.String(u'token_secret'): dbus.String( + self._random_unicode_string(50)) + }, signature=dbus.Signature('ss') ) return c + def get_sso_backend(window_id, appname, help_text): - """ + """ factory that returns an sso loader singelton """ if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: sso_class = LoginBackendDbusSSOFake(window_id, appname, help_text) - LOG.warn('Using fake login SSO functionality. Only meant for testing purposes') + LOG.warn('Using fake login SSO functionality. Only meant for ' + 'testing purposes') else: sso_class = LoginBackendDbusSSO(window_id, appname, help_text) return sso_class - + if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/softwarecenter/backend/oneconfhandler/core.py software-center-5.1.13/softwarecenter/backend/oneconfhandler/core.py --- software-center-5.1.12/softwarecenter/backend/oneconfhandler/core.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/oneconfhandler/core.py 2012-03-20 08:00:09.000000000 +0000 @@ -17,12 +17,12 @@ # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - from oneconf.dbusconnect import DbusConnect from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY from softwarecenter.backend.login_sso import get_sso_backend from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend +from softwarecenter.enums import SOFTWARE_CENTER_NAME_KEYRING import datetime from gi.repository import GObject @@ -32,45 +32,43 @@ LOG = logging.getLogger(__name__) + class OneConfHandler(GObject.GObject): __gsignals__ = { - "show-oneconf-changed" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "last-time-sync-changed" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), + "show-oneconf-changed": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "last-time-sync-changed": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), } - - + def __init__(self, oneconfviewpickler): '''Controller of the installed pane''' - + LOG.debug("OneConf Handler init") super(OneConfHandler, self).__init__() - - # FIXME: should be an enum common to OneConf and here - self.appname = "Ubuntu Software Center" - + # OneConf stuff self.oneconf = DbusConnect() self.oneconf.hosts_dbus_object.connect_to_signal('hostlist_changed', - self.refresh_hosts) + self.refresh_hosts) self.oneconf.hosts_dbus_object.connect_to_signal('packagelist_changed', - self._on_store_packagelist_changed) + self._on_store_packagelist_changed) self.oneconf.hosts_dbus_object.connect_to_signal('latestsync_changed', - self.on_new_latest_oneconf_sync_timestamp) + self.on_new_latest_oneconf_sync_timestamp) self.already_registered_hostids = [] self.is_current_registered = False - + self.oneconfviewpickler = oneconfviewpickler - + # refresh host list self._refreshing_hosts = False - GObject.timeout_add_seconds(MIN_TIME_WITHOUT_ACTIVITY, self.get_latest_oneconf_sync) + GObject.timeout_add_seconds(MIN_TIME_WITHOUT_ACTIVITY, + self.get_latest_oneconf_sync) GObject.idle_add(self.refresh_hosts) def refresh_hosts(self): @@ -94,27 +92,28 @@ self.already_registered_hostids.append(hostid) if current: is_current_registered = share_inventory - + # ensure we are logged to ubuntu sso to activate the view if self.is_current_registered != is_current_registered: self.sync_between_computers(is_current_registered) - + self._refreshing_hosts = False def get_latest_oneconf_sync(self): '''Get latest sync state in OneConf. - + This function is also the "ping" letting OneConf service alive''' LOG.debug("get latest sync state") timestamp = self.oneconf.get_last_sync_date() self.on_new_latest_oneconf_sync_timestamp(timestamp) return True - + def on_new_latest_oneconf_sync_timestamp(self, timestamp): '''Callback computing the right message for latest sync time''' try: last_sync = datetime.datetime.fromtimestamp(float(timestamp)) - today = datetime.datetime.strptime(str(datetime.date.today()), '%Y-%m-%d') + today = datetime.datetime.strptime(str(datetime.date.today()), + '%Y-%m-%d') the_daybefore = today - datetime.timedelta(days=1) if last_sync > today: @@ -122,65 +121,70 @@ elif last_sync < today and last_sync > the_daybefore: msg = _("Last sync yesterday %s") % last_sync.strftime('%H:%M') else: - msg = _("Last sync %s") % last_sync.strftime('%Y-%m-%d %H:%M') + msg = _("Last sync %s") % last_sync.strftime('%Y-%m-%d %H:%M') except (TypeError, ValueError): - msg = _("To sync with another computer, choose “Sync Between Computers” from that computer.") + msg = _("To sync with another computer, choose “Sync Between " + "Computers” from that computer.") self.emit("last-time-sync-changed", msg) def _share_inventory(self, share_inventory): - '''set oneconf state and emit signal for installed view to show or not oneconf''' + '''set oneconf state and emit signal for installed view to show or + not oneconf + ''' if share_inventory == self.is_current_registered: return self.is_current_registered = share_inventory - LOG.debug("change share inventory state to %s", share_inventory) + LOG.debug("change share inventory state to %s", share_inventory) self.oneconf.set_share_inventory(share_inventory) self.get_latest_oneconf_sync() self.emit("show-oneconf-changed", share_inventory) def sync_between_computers(self, sync_on, hostid=None): '''toggle the sync on and off if needed between computers. - + If hostid is not None, sync_between_computer can be used to stop sharing for other computers''' LOG.debug("Toggle sync between computers: %s", sync_on) - + if sync_on: self._try_login() else: if hostid: self.oneconf.set_share_inventory(False, hostid=hostid) - else: # localhost + else: # localhost self._share_inventory(False) - + def _on_store_packagelist_changed(self, hostid): '''pass the message to the view controller''' self.oneconfviewpickler.store_packagelist_changed(hostid) - - # SSO login part - + # SSO login part def _try_login(self): '''Try to get the credential or login on ubuntu sso''' logging.debug("OneConf login()") - help_text = _("With multiple Ubuntu computers, you can publish their inventories online to compare the software installed on each\n" - "No-one else will be able to see what you have installed.") - self.sso = get_sso_backend(0, - self.appname, help_text) + help_text = _("With multiple Ubuntu computers, you can publish " + "their inventories online to compare the software " + "installed on each\nNo-one else will be able to see " + "what you have installed.") + self.sso = get_sso_backend( + 0, SOFTWARE_CENTER_NAME_KEYRING, help_text) self.sso.connect("login-successful", self._maybe_login_successful) self.sso.connect("login-canceled", self._login_canceled) self.sso.login_or_register() - + def _login_canceled(self, sso): self._share_inventory(False) - + def _maybe_login_successful(self, sso, oauth_result): - """ called after we have the token, then we go and figure out our name """ + """called after we have the token, then we go and figure out our + name + """ logging.debug("_maybe_login_successful") self.ssoapi = get_ubuntu_sso_backend() self.ssoapi.connect("whoami", self._whoami_done) self.ssoapi.connect("error", self._whoami_error) - # this will automatically verify the keyring token and retrigger + # this will automatically verify the keyring token and retrigger # login (once) if its expired self.ssoapi.whoami() @@ -191,4 +195,3 @@ def _whoami_error(self, ssologin, e): logging.error("whoami error '%s'" % e) self._share_inventory(False) - return diff -Nru software-center-5.1.12/softwarecenter/backend/oneconfhandler/__init__.py software-center-5.1.13/softwarecenter/backend/oneconfhandler/__init__.py --- software-center-5.1.12/softwarecenter/backend/oneconfhandler/__init__.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/oneconfhandler/__init__.py 2012-03-19 08:35:49.000000000 +0000 @@ -20,7 +20,9 @@ # singleton oneconf_handler = None -def get_oneconf_handler(oneconfviewpickler = None): + + +def get_oneconf_handler(oneconfviewpickler=None): global oneconf_handler try: from softwarecenter.backend.oneconfhandler.core import OneConfHandler @@ -30,10 +32,11 @@ oneconf_handler = OneConfHandler(oneconfviewpickler) return oneconf_handler + def is_oneconf_available(): try: from softwarecenter.backend.oneconfhandler.core import OneConfHandler - OneConfHandler # pyflakes + OneConfHandler # pyflakes return True except ImportError: pass diff -Nru software-center-5.1.12/softwarecenter/backend/piston/rnrclient_fake.py software-center-5.1.13/softwarecenter/backend/piston/rnrclient_fake.py --- software-center-5.1.12/softwarecenter/backend/piston/rnrclient_fake.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/rnrclient_fake.py 2012-03-19 08:35:49.000000000 +0000 @@ -17,36 +17,40 @@ AUTHENTICATED_API_SCHEME = 'https' from rnrclient_pristine import ReviewRequest, ReviewsStats, ReviewDetails -from softwarecenter.backend.fake_review_settings import FakeReviewSettings, network_delay +from softwarecenter.backend.fake_review_settings import ( + FakeReviewSettings, + network_delay, +) import json import random import time class RatingsAndReviewsAPI(PistonAPI): - """A fake client pretending to be RAtingsAndReviewsAPI from rnrclient_pristine. - Uses settings from test.fake_review_settings.FakeReviewSettings - to provide predictable responses to methods that try to use the - RatingsAndReviewsAPI for testing purposes (i.e. without network activity). - To use this, instead of importing from rnrclient_pristine, you can import - from rnrclient_fake instead. + """A fake client pretending to be RAtingsAndReviewsAPI from + rnrclient_pristine. Uses settings from + test.fake_review_settings.FakeReviewSettings + to provide predictable responses to methods that try to use the + RatingsAndReviewsAPI for testing purposes (i.e. without network + activity). + To use this, instead of importing from rnrclient_pristine, you can + import from rnrclient_fake instead. """ - + default_service_root = 'http://localhost:8000/reviews/api/1.0' default_content_type = 'application/x-www-form-urlencoded' _exception_msg = 'Fake RatingsAndReviewsAPI raising fake exception' - _PACKAGE_NAMES = ['armagetronad', 'compizconfig-settings-manager', 'file-roller', - 'aisleriot', 'p7zip-full', 'compiz-core', 'banshee', - 'gconf-editor', 'nanny', '3depict', 'apturl', 'jockey-gtk', - 'alex4', 'bzr-explorer', 'aqualung'] - _USERS = ["Joe Doll", "John Foo", "Cat Lala", "Foo Grumpf", + _PACKAGE_NAMES = ['armagetronad', 'compizconfig-settings-manager', + 'file-roller', 'aisleriot', 'p7zip-full', 'compiz-core', + 'banshee', 'gconf-editor', 'nanny', '3depict', 'apturl', + 'jockey-gtk', 'alex4', 'bzr-explorer', 'aqualung'] + _USERS = ["Joe Doll", "John Foo", "Cat Lala", "Foo Grumpf", "Bar Tender", "Baz Lightyear"] _SUMMARIES = ["Cool", "Medium", "Bad", "Too difficult"] - _TEXT = ["Review text number 1", "Review text number 2", + _TEXT = ["Review text number 1", "Review text number 2", "Review text number 3", "Review text number 4"] _fake_settings = FakeReviewSettings() - @returns_json @network_delay def server_status(self): @@ -54,32 +58,31 @@ raise APIError(self._exception_msg) return json.dumps('ok') - @validate_pattern('origin', r'[0-9a-z+-.:/]+', required=False) @validate_pattern('distroseries', r'\w+', required=False) @validate('days', int, required=False) @returns_list_of(ReviewsStats) @network_delay def review_stats(self, origin='any', distroseries='any', days=None, - valid_days=(1,3,7)): + valid_days=(1, 3, 7)): if self._fake_settings.get_setting('review_stats_error'): raise APIError(self._exception_msg) - + if self._fake_settings.get_setting('packages_returned') > 15: quantity = 15 else: quantity = self._fake_settings.get_setting('packages_returned') - + stats = [] - - for i in range (0, quantity): - s = {'app_name':'', - 'package_name':self._PACKAGE_NAMES[i], - 'ratings_total': str(random.randrange(1,200)), - 'ratings_average': str(random.randrange(0,5)) + + for i in range(0, quantity): + s = {'app_name': '', + 'package_name': self._PACKAGE_NAMES[i], + 'ratings_total': str(random.randrange(1, 200)), + 'ratings_average': str(random.randrange(0, 5)) } stats.append(s) - + return json.dumps(stats) @validate_pattern('language', r'\w+', required=False) @@ -94,7 +97,7 @@ @network_delay def get_reviews(self, packagename, language='any', origin='any', distroseries='any', version='any', appname='', page=1, sort='helpful'): - + # work out how many reviews to return for pagination if page <= self._fake_settings.get_setting('review_pages'): num_reviews = 10 @@ -102,10 +105,10 @@ num_reviews = self._fake_settings.get_setting('reviews_returned') else: num_reviews = 0 - + if self._fake_settings.get_setting('get_reviews_error'): raise APIError(self._exception_msg) - + reviews = self._make_fake_reviews(packagename, num_reviews) return json.dumps(reviews) @@ -124,9 +127,11 @@ def submit_review(self, review): if self._fake_settings.get_setting('submit_review_error'): raise APIError(self._exception_msg) - - user = self._fake_settings.get_setting('reviewer_username') or random.choice(self._USERS) - review_id = self._fake_settings.get_setting('submit_review_id') or random.randint(1,10000) + + user = self._fake_settings.get_setting( + 'reviewer_username') or random.choice(self._USERS) + review_id = self._fake_settings.get_setting( + 'submit_review_id') or random.randint(1, 10000) r = { "origin": review.origin, "rating": review.rating, @@ -156,13 +161,15 @@ def flag_review(self, review_id, reason, text): if self._fake_settings.get_setting('flag_review_error'): raise APIError(self._exception_msg) - - mod_id = random.randint(1,500) - pkg = self._fake_settings.get_setting('flag_package_name') or random.choice(self._PACKAGE_NAMES) - username = self._fake_settings.get_setting('flagger_username') or random.choice(self._USERS) + + mod_id = random.randint(1, 500) + pkg = self._fake_settings.get_setting( + 'flag_package_name') or random.choice(self._PACKAGE_NAMES) + username = self._fake_settings.get_setting( + 'flagger_username') or random.choice(self._USERS) f = { - "user_id": random.randint(1,500), + "user_id": random.randint(1, 500), "description": text, "review_moderation_id": mod_id, "_user_cache": self._make_user_cache(username), @@ -170,7 +177,7 @@ "_review_moderation_cache": { "status": 0, "review_id": review_id, - "_review_cache": self._make_fake_reviews(packagename=pkg, + "_review_cache": self._make_fake_reviews(packagename=pkg, single_id=review_id), "moderation_text": text, "date_moderated": None, @@ -191,7 +198,8 @@ def submit_usefulness(self, review_id, useful): if self._fake_settings.get_setting('submit_usefulness_error'): raise APIError(self._exception_msg) - return json.dumps(self._fake_settings.get_setting('usefulness_response_string')) + return json.dumps(self._fake_settings.get_setting( + 'usefulness_response_string')) @validate('review_id', int, required=False) @validate_pattern('username', r'[^\n]+', required=False) @@ -200,24 +208,24 @@ def get_usefulness(self, review_id=None, username=None): if not username and not review_id: return None - + if self._fake_settings.get_setting('get_usefulness_error'): raise APIError(self._exception_msg) - + #just return a single fake item if the review_id was supplied if review_id: if username: response_user = username else: response_user = random.choice(self._USERS) - + response = { - 'username':response_user, - 'useful':random.choice(['True','False']), - 'review_id':review_id + 'username': response_user, + 'useful': random.choice(['True', 'False']), + 'review_id': review_id } return json.dumps([response]) - + #set up review ids to honour requested and also add randoms quantity = self._fake_settings.get_setting('votes_returned') id_list = self._fake_settings.get_setting('required_review_ids') @@ -228,23 +236,23 @@ rand_id_start = 0 else: rand_id_start = max(id_list) - + votes = [] - + for i in range(0, quantity): #assign review ids requested if any still exist try: id = id_list[i] except IndexError: - id = random.randint(rand_id_start,10000) - + id = random.randint(rand_id_start, 10000) + u = { - 'username': username, - 'useful': random.choice(['True','False']), - 'review_id' : id + 'username': username, + 'useful': random.choice(['True', 'False']), + 'review_id': id } votes.append(u) - + return json.dumps(votes) @validate('review_id', int) @@ -261,29 +269,28 @@ def modify_review(self, review_id, rating, summary, review_text): """Modify an existing review""" return json.dumps(self._make_fake_reviews()[0]) - - - def _make_fake_reviews(self, packagename='compiz-core', + + def _make_fake_reviews(self, packagename='compiz-core', quantity=1, single_id=None): """Make and return a requested quantity of fake reviews""" - + reviews = [] - + for i in range(0, quantity): if quantity == 1 and single_id: id = single_id else: - id = i*3 - + id = i * 3 + r = { "origin": "ubuntu", - "rating": random.randint(1,5), + "rating": random.randint(1, 5), "hide": False, "app_name": "", "language": "en", "reviewer_username": random.choice(self._USERS), - "usefulness_total": random.randint(3,6), - "usefulness_favorable": random.randint(1,3), + "usefulness_total": random.randint(3, 6), + "usefulness_favorable": random.randint(1, 3), "review_text": random.choice(self._TEXT), "date_deleted": None, "summary": random.choice(self._SUMMARIES), @@ -295,13 +302,13 @@ "distroseries": "natty" } reviews.append(r) - + #get_review wants a dict but get_reviews wants a list of dicts if single_id: return r else: return reviews - + def _make_user_cache(self, username): return { "username": username, @@ -313,7 +320,6 @@ "is_staff": False, "last_login": time.strftime("%Y-%m-%d %H:%M:%S"), "password": "!", - "id": random.randint(1,500), + "id": random.randint(1, 500), "date_joined": time.strftime("%Y-%m-%d %H:%M:%S") } - diff -Nru software-center-5.1.12/softwarecenter/backend/piston/rnrclient_pristine.py software-center-5.1.13/softwarecenter/backend/piston/rnrclient_pristine.py --- software-center-5.1.12/softwarecenter/backend/piston/rnrclient_pristine.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/rnrclient_pristine.py 2012-03-19 08:35:49.000000000 +0000 @@ -29,6 +29,7 @@ 'rating', 'language', 'origin', 'distroseries', 'arch_tag') app_name = '' + class ReviewsStats(PistonResponseObject): """A ratings summary for a package/app. @@ -82,7 +83,7 @@ @validate('days', int, required=False) @returns_list_of(ReviewsStats) def review_stats(self, origin='any', distroseries='any', days=None, - valid_days=(1,3,7)): + valid_days=(1, 3, 7)): """Fetch ratings for a particular distroseries""" url = 'review-stats/{0}/{1}/'.format(origin, distroseries) if days is not None: @@ -115,7 +116,7 @@ appname = quote_plus(';' + appname) return self._get('reviews/filter/%s/%s/%s/%s/%s%s/page/%s/%s/' % ( language, origin, distroseries, version, packagename, - appname, page, sort), + appname, page, sort), scheme=PUBLIC_API_SCHEME) @validate('review_id', int) @@ -183,6 +184,10 @@ @returns(ReviewDetails) def modify_review(self, review_id, rating, summary, review_text): """Modify an existing review""" - data = {'rating':rating, 'summary':summary, 'review_text':review_text} + data = { + 'rating': rating, + 'summary': summary, + 'review_text': review_text + } return self._put('/reviews/modify/%s/' % review_id, data=data, scheme=AUTHENTICATED_API_SCHEME) diff -Nru software-center-5.1.12/softwarecenter/backend/piston/rnrclient.py software-center-5.1.13/softwarecenter/backend/piston/rnrclient.py --- software-center-5.1.12/softwarecenter/backend/piston/rnrclient.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/rnrclient.py 2012-03-19 08:35:49.000000000 +0000 @@ -31,15 +31,18 @@ # get the server to use from softwarecenter.distro import get_distro distro = get_distro() -SERVER_ROOT=distro.REVIEWS_SERVER +SERVER_ROOT = distro.REVIEWS_SERVER try: if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: - from softwarecenter.backend.piston.rnrclient_fake import RatingsAndReviewsAPI + from softwarecenter.backend.piston.rnrclient_fake import ( + RatingsAndReviewsAPI + ) RatingsAndReviewsAPI.default_service_root = SERVER_ROOT import rnrclient_fake rnrclient_fake - LOG.warn("using FAKE review api, data returned will be dummy data only") + LOG.warn("using FAKE review api, data returned will be dummy " + "data only") else: # patch default_service_root from rnrclient_pristine import RatingsAndReviewsAPI @@ -68,22 +71,24 @@ # dump all reviews for stat in rnr.review_stats(): print("stats for (pkg='%s', app: '%s'): avg=%s total=%s" % ( - stat.package_name, stat.app_name, stat.ratings_average, stat.ratings_total)) + stat.package_name, stat.app_name, stat.ratings_average, + stat.ratings_total)) reviews = rnr.get_reviews( language="any", origin="ubuntu", distroseries="natty", packagename=stat.package_name, appname=urllib.quote_plus(stat.app_name.encode("utf-8"))) for review in reviews: - print("rating: %s user=%s" % (review.rating, review.reviewer_username)) + print("rating: %s user=%s" % (review.rating, + review.reviewer_username)) print(review.summary) print(review.review_text) print("\n") - + # get individual ones - reviews = rnr.get_reviews(language="en",origin="ubuntu",distroseries="maverick", - packagename="unace", appname="ACE") + reviews = rnr.get_reviews(language="en", origin="ubuntu", + distroseries="maverick", packagename="unace", appname="ACE") print(reviews) - print(rnr.get_reviews(language="en",origin="ubuntu",distroseries="natty", + print(rnr.get_reviews(language="en", origin="ubuntu", distroseries="natty", packagename="aclock.app")) print(rnr.get_reviews(language="en", origin="ubuntu", distroseries="natty", packagename="unace", appname="ACE")) diff -Nru software-center-5.1.12/softwarecenter/backend/piston/scaclient_pristine.py software-center-5.1.13/softwarecenter/backend/piston/scaclient_pristine.py --- software-center-5.1.12/softwarecenter/backend/piston/scaclient_pristine.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/scaclient_pristine.py 2012-03-19 08:35:49.000000000 +0000 @@ -8,6 +8,7 @@ PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' + class SoftwareCenterAgentAPI(PistonAPI): default_service_root = 'http://localhost:8000/api/2.0' diff -Nru software-center-5.1.12/softwarecenter/backend/piston/scaclient.py software-center-5.1.13/softwarecenter/backend/piston/scaclient.py --- software-center-5.1.12/softwarecenter/backend/piston/scaclient.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/scaclient.py 2012-03-19 08:35:49.000000000 +0000 @@ -14,7 +14,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # -# taken from lp:~canonical-ca-hackers/software-center/scaclient +# taken from lp:~canonical-ca-hackers/software-center/scaclient # and put into scaclient_pristine.py import logging @@ -31,26 +31,27 @@ from softwarecenter.enums import BUY_SOMETHING_HOST try: from scaclient_pristine import SoftwareCenterAgentAPI - SoftwareCenterAgentAPI.default_service_root = BUY_SOMETHING_HOST+"/api/2.0" + SoftwareCenterAgentAPI.default_service_root = \ + BUY_SOMETHING_HOST + "/api/2.0" except: logging.exception("need python-piston-mini client") sys.exit(1) - if __name__ == "__main__": sca = SoftwareCenterAgentAPI() - + lang = "en" series = "natty" arch = "i386" available = sca.available_apps(lang=lang, series=series, arch=arch) print(available) - - available_for_qa = sca.available_apps_qa(lang=lang, series=series, arch=arch) + + available_for_qa = sca.available_apps_qa(lang=lang, series=series, + arch=arch) print(available_for_qa) - + for_me = sca.subscriptions_for_me() print(for_me) diff -Nru software-center-5.1.12/softwarecenter/backend/piston/sreclient_pristine.py software-center-5.1.13/softwarecenter/backend/piston/sreclient_pristine.py --- software-center-5.1.12/softwarecenter/backend/piston/sreclient_pristine.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/sreclient_pristine.py 2012-03-19 08:35:49.000000000 +0000 @@ -11,6 +11,7 @@ PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' + class SoftwareCenterRecommenderAPI(PistonAPI): default_service_root = 'http://localhost:8000/api/1.0' diff -Nru software-center-5.1.12/softwarecenter/backend/piston/sso_helper.py software-center-5.1.13/softwarecenter/backend/piston/sso_helper.py --- software-center-5.1.12/softwarecenter/backend/piston/sso_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/piston/sso_helper.py 2012-03-19 08:35:49.000000000 +0000 @@ -28,13 +28,12 @@ from softwarecenter.utils import clear_token_from_ubuntu_sso - class SSOLoginHelper(object): def __init__(self, xid=0): self.oauth = None self.xid = xid self.loop = GObject.MainLoop(GObject.main_context_default()) - + def _login_successful(self, sso_backend, oauth_result): self.oauth = oauth_result # FIXME: actually verify the token against ubuntu SSO @@ -44,6 +43,7 @@ def _whoami_done(sso, me): self._whoami = me self.loop.quit() + def _whoami_error(sso, err): #print "ERRR", err self.loop.quit() @@ -74,7 +74,7 @@ def get_oauth_token_sync(self): self.oauth = None sso = get_sso_backend( - self.xid, + self.xid, SOFTWARE_CENTER_NAME_KEYRING, _(SOFTWARE_CENTER_SSO_DESCRIPTION)) sso.connect("login-successful", self._login_successful) diff -Nru software-center-5.1.12/softwarecenter/backend/recagent.py software-center-5.1.13/softwarecenter/backend/recagent.py --- software-center-5.1.12/softwarecenter/backend/recagent.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/recagent.py 2012-03-19 17:06:07.000000000 +0000 @@ -31,47 +31,48 @@ LOG = logging.getLogger(__name__) + class RecommenderAgent(GObject.GObject): __gsignals__ = { - "server-status" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "profile" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "submit-profile-finished" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, str), - ), - "submit-anon-profile-finished" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "recommend-me" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "recommend-app" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "recommend-all-apps" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "recommend-top" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (str,), - ), + "server-status": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "profile": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "submit-profile-finished": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, str), + ), + "submit-anon-profile-finished": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "recommend-me": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "recommend-app": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "recommend-all-apps": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "recommend-top": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (str,), + ), } - + def __init__(self, xid=None): GObject.GObject.__init__(self) self.xid = xid @@ -86,17 +87,17 @@ spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "server_status") - + def post_submit_profile(self, db): """ This will post the users profile to the recommender server - and also generate the UUID for the user if that is not + and also generate the UUID for the user if that is not there yet """ # if we have not already set a recommender UUID, now is the time # to do it if not self.recommender_uuid: self.recommender_uuid = get_uuid() - installed_pkglist = [app.pkgname + installed_pkglist = [app.pkgname for app in get_installed_apps_list(db)] data = self._generate_submit_profile_data(self.recommender_uuid, installed_pkglist) @@ -110,7 +111,7 @@ "SoftwareCenterRecommenderAPI", "submit_profile", data=data) - + def post_submit_anon_profile(self, uuid, installed_packages, extra): # build the command spawner = SpawnHelper() @@ -124,7 +125,7 @@ uuid=uuid, installed_packages=installed_packages, extra=extra) - + def query_profile(self, pkgnames): # build the command spawner = SpawnHelper() @@ -146,7 +147,7 @@ spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_me") - + def query_recommend_app(self, pkgname): # build the command spawner = SpawnHelper() @@ -157,7 +158,7 @@ "SoftwareCenterRecommenderAPI", "recommend_app", pkgname=pkgname) - + def query_recommend_all_apps(self): # build the command spawner = SpawnHelper() @@ -166,7 +167,7 @@ spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_all_apps") - + def query_recommend_top(self): # build the command spawner = SpawnHelper() @@ -175,33 +176,47 @@ spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_top") - + + def is_opted_in(self): + """ + Return True is the user is currently opted-in to the recommender + service + """ + if self.recommender_uuid: + return True + else: + return False + + def opt_out(self): + self.recommender_uuid = "" + def _on_server_status_data(self, spawner, piston_server_status): self.emit("server-status", piston_server_status) - + def _on_profile_data(self, spawner, piston_profile): self.emit("profile", piston_profile) - + def _on_submit_profile_data(self, spawner, piston_submit_profile): - self.emit("submit-profile-finished", - piston_submit_profile, + self.emit("submit-profile-finished", + piston_submit_profile, self.recommender_uuid) - - def _on_submit_anon_profile_data(self, spawner, piston_submit_anon_profile): + + def _on_submit_anon_profile_data(self, spawner, + piston_submit_anon_profile): self.emit("submit-anon_profile", piston_submit_anon_profile) def _on_recommend_me_data(self, spawner, piston_me_apps): self.emit("recommend-me", piston_me_apps) - + def _on_recommend_app_data(self, spawner, piston_app): self.emit("recommend-app", piston_app) - + def _on_recommend_all_apps_data(self, spawner, piston_all_apps): self.emit("recommend-all-apps", piston_all_apps) - + def _on_recommend_top_data(self, spawner, piston_top_apps): self.emit("recommend-top", piston_top_apps) - + def _get_recommender_uuid(self): """ returns the recommender UUID value, which can be empty if it has not yet been set (indicating that the user has not yet @@ -213,24 +228,24 @@ if recommender_uuid: return recommender_uuid return "" - + def _generate_submit_profile_data(self, recommender_uuid, package_list): - submit_profile_data = [ - { - 'uuid': recommender_uuid, - 'package_list': package_list - } - ] + submit_profile_data = [{ + 'uuid': recommender_uuid, + 'package_list': package_list + }] return submit_profile_data - + if __name__ == "__main__": from gi.repository import Gtk def _recommend_top(agent, top_apps): print ("_recommend_top: %s" % top_apps) + def _recommend_me(agent, top_apps): print ("_recommend_me: %s" % top_apps) + def _error(agent, msg): print ("got a error: %s" % msg) Gtk.main_quit() @@ -246,5 +261,4 @@ agent.query_recommend_top() agent.query_recommend_me() - Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/backend/reviews/__init__.py software-center-5.1.13/softwarecenter/backend/reviews/__init__.py --- software-center-5.1.12/softwarecenter/backend/reviews/__init__.py 2012-03-08 19:59:06.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/reviews/__init__.py 2012-03-19 08:35:49.000000000 +0000 @@ -36,7 +36,7 @@ # py3 compat try: import cPickle as pickle - pickle # pyflakes + pickle # pyflakes except ImportError: import pickle @@ -58,51 +58,56 @@ LOG = logging.getLogger(__name__) + class ReviewStats(object): def __init__(self, app): self.app = app self.ratings_average = None self.ratings_total = 0 - self.rating_spread = [0,0,0,0,0] + self.rating_spread = [0, 0, 0, 0, 0] self.dampened_rating = 3.00 + def __repr__(self): - return ("" % - (self.app, self.ratings_average, self.ratings_total, + return ("" % + (self.app, self.ratings_average, self.ratings_total, self.rating_spread, self.dampened_rating)) - + class UsefulnessCache(object): USEFULNESS_CACHE = {} - + def __init__(self, try_server=False): fname = "usefulness.p" self.USEFULNESS_CACHE_FILE = os.path.join(SOFTWARE_CENTER_CACHE_DIR, - fname) - + fname) + self._retrieve_votes_from_cache() - #Only try to get votes from the server if required, otherwise just use cache + # Only try to get votes from the server if required, otherwise + # just use cache if try_server: self._retrieve_votes_from_server() - + def _retrieve_votes_from_cache(self): if os.path.exists(self.USEFULNESS_CACHE_FILE): try: - self.USEFULNESS_CACHE = pickle.load(open(self.USEFULNESS_CACHE_FILE)) + self.USEFULNESS_CACHE = pickle.load( + open(self.USEFULNESS_CACHE_FILE)) except: LOG.exception("usefulness cache load fallback failure") - os.rename(self.USEFULNESS_CACHE_FILE, self.USEFULNESS_CACHE_FILE+".fail") - return - + os.rename(self.USEFULNESS_CACHE_FILE, + self.USEFULNESS_CACHE_FILE + ".fail") + def _retrieve_votes_from_server(self): LOG.debug("_retrieve_votes_from_server started") user = get_person_from_config() - + if not user: - LOG.warn("Could not get usefulness from server, no username in config file") + LOG.warn("Could not get usefulness from server, no username " + "in config file") return False - + # run the command and add watcher spawn_helper = SpawnHelper() spawn_helper.connect("data-available", self._on_usefulness_data) @@ -116,8 +121,9 @@ for result in results: self.USEFULNESS_CACHE[str(result['review_id'])] = result['useful'] if not self.save_usefulness_cache_file(): - LOG.warn("Read usefulness results from server but failed to write to cache") - + LOG.warn("Read usefulness results from server but failed to " + "write to cache") + def save_usefulness_cache_file(self): """write the dict out to cache file""" cachedir = SOFTWARE_CENTER_CACHE_DIR @@ -129,19 +135,22 @@ return True except: return False - + def add_usefulness_vote(self, review_id, useful): - """pass a review id and useful boolean vote and save it into the dict, then try to save to cache file""" + """pass a review id and useful boolean vote and save it into the + dict, then try to save to cache file + """ self.USEFULNESS_CACHE[str(review_id)] = useful if self.save_usefulness_cache_file(): return True return False - + def check_for_usefulness(self, review_id): - """pass a review id and get a True/False useful back or None if the review_id is not in the dict""" + """pass a review id and get a True/False useful back or None if the + review_id is not in the dict + """ return self.USEFULNESS_CACHE.get(str(review_id)) - - + class Review(object): """A individual review object """ @@ -163,20 +172,23 @@ self.version = "" self.usefulness_total = 0 self.usefulness_favorable = 0 - # this will be set if tryint to submit usefulness for this review failed + # this will be set if tryint to submit usefulness for this review + # failed self.usefulness_submit_error = False self.delete_error = False self.modify_error = False + def __repr__(self): return "[Review id=%s review_text='%s' reviewer_username='%s']" % ( self.id, self.review_text, self.reviewer_username) + def __cmp__(self, other): # first compare version, high version number first vc = upstream_version_compare(self.version, other.version) if vc != 0: return vc # then wilson score - uc = cmp(wilson_score(self.usefulness_favorable, + uc = cmp(wilson_score(self.usefulness_favorable, self.usefulness_total), wilson_score(other.usefulness_favorable, other.usefulness_total)) @@ -184,9 +196,10 @@ return uc # last is date t1 = datetime.datetime.strptime(self.date_created, '%Y-%m-%d %H:%M:%S') - t2 = datetime.datetime.strptime(other.date_created, '%Y-%m-%d %H:%M:%S') + t2 = datetime.datetime.strptime(other.date_created, + '%Y-%m-%d %H:%M:%S') return cmp(t1, t2) - + @classmethod def from_piston_mini_client(cls, other): """ converts the rnrclieent reviews we get into @@ -209,14 +222,15 @@ setattr(review, k, v) return review + class ReviewLoader(GObject.GObject): """A loader that returns a review object list""" __gsignals__ = { - "refresh-review-stats-finished" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), + "refresh-review-stats-finished": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), } # cache the ReviewStats @@ -236,19 +250,21 @@ self.REVIEW_STATS_CACHE_FILE = os.path.join(SOFTWARE_CENTER_CACHE_DIR, fname) self.REVIEW_STATS_BSDDB_FILE = "%s__%s.%s.db" % ( - self.REVIEW_STATS_CACHE_FILE, - bdb.DB_VERSION_MAJOR, + self.REVIEW_STATS_CACHE_FILE, + bdb.DB_VERSION_MAJOR, bdb.DB_VERSION_MINOR) self.language = get_language() if os.path.exists(self.REVIEW_STATS_CACHE_FILE): try: - self.REVIEW_STATS_CACHE = pickle.load(open(self.REVIEW_STATS_CACHE_FILE)) + self.REVIEW_STATS_CACHE = pickle.load( + open(self.REVIEW_STATS_CACHE_FILE)) self._cache_version_old = self._missing_histogram_in_cache() except: LOG.exception("review stats cache load failure") - os.rename(self.REVIEW_STATS_CACHE_FILE, self.REVIEW_STATS_CACHE_FILE+".fail") - + os.rename(self.REVIEW_STATS_CACHE_FILE, + self.REVIEW_STATS_CACHE_FILE + ".fail") + def _missing_histogram_in_cache(self): '''iterate through review stats to see if it has been fully reloaded with new histogram data from server update''' @@ -260,7 +276,7 @@ def get_reviews(self, application, callback, page=1, language=None, sort=0, relaxed=False): - """run callback f(app, review_list) + """run callback f(app, review_list) with list of review objects for the given db.database.Application object """ @@ -282,7 +298,6 @@ return self.REVIEW_STATS_CACHE[application] except ValueError: pass - return None def refresh_review_stats(self, callback): """ get the review statists and call callback when its there """ @@ -322,7 +337,7 @@ """ write out the full REVIEWS_STATS_CACHE as a pickle """ pickle.dump(self.REVIEW_STATS_CACHE, open(self.REVIEW_STATS_CACHE_FILE, "w")) - + def _dump_bsddbm_for_unity(self, outfile, outdir): """ write out the subset that unity needs of the REVIEW_STATS_CACHE as a C friendly (using struct) bsddb @@ -330,59 +345,59 @@ env = bdb.DBEnv() if not os.path.exists(outdir): os.makedirs(outdir) - env.open (outdir, - bdb.DB_CREATE | bdb.DB_INIT_CDB | bdb.DB_INIT_MPOOL | - bdb.DB_NOMMAP, # be gentle on e.g. nfs mounts - 0600) - db = bdb.DB (env) - db.open (outfile, - dbtype=bdb.DB_HASH, - mode=0600, - flags=bdb.DB_CREATE) + env.open(outdir, + bdb.DB_CREATE | bdb.DB_INIT_CDB | bdb.DB_INIT_MPOOL | + bdb.DB_NOMMAP, # be gentle on e.g. nfs mounts + 0600) + db = bdb.DB(env) + db.open(outfile, + dbtype=bdb.DB_HASH, + mode=0600, + flags=bdb.DB_CREATE) for (app, stats) in self.REVIEW_STATS_CACHE.iteritems(): # pkgname is ascii by policy, so its fine to use str() here - db[str(app.pkgname)] = struct.pack('iii', + db[str(app.pkgname)] = struct.pack('iii', stats.ratings_average or 0, stats.ratings_total, stats.dampened_rating) - db.close () - env.close () - + db.close() + env.close() + def get_top_rated_apps(self, quantity=12, category=None): """Returns a list of the packages with the highest 'rating' based on the dampened rating calculated from the ReviewStats rating spread. Also optionally takes a category (string) to filter by""" cache = self.REVIEW_STATS_CACHE - + if category: applist = self._get_apps_for_category(category) cache = self._filter_cache_with_applist(cache, applist) - + #create a list of tuples with (Application,dampened_rating) dr_list = [] for item in cache.items(): - if hasattr(item[1],'dampened_rating'): + if hasattr(item[1], 'dampened_rating'): dr_list.append((item[0], item[1].dampened_rating)) else: dr_list.append((item[0], 3.00)) - + #sorted the list descending by dampened rating sorted_dr_list = sorted(dr_list, key=operator.itemgetter(1), reverse=True) - + #return the quantity requested or as much as we can if quantity < len(sorted_dr_list): return_qty = quantity else: return_qty = len(sorted_dr_list) - + top_rated = [] - for i in range (0,return_qty): + for i in range(0, return_qty): top_rated.append(sorted_dr_list[i][0]) - + return top_rated - + def _filter_cache_with_applist(self, cache, applist): """Take the review cache and filter it to only include the apps that also appear in the applist passed in""" @@ -391,25 +406,25 @@ if key.pkgname in applist: filtered_cache[key] = cache[key] return filtered_cache - + def _get_apps_for_category(self, category): query = get_query_for_category(self.db, category) if not query: LOG.warn("_get_apps_for_category: received invalid category") return [] - + pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = StoreDatabase(pathname, self.cache) db.open() docs = db.get_docs_from_query(query) - + #from the db docs, return a list of pkgnames applist = [] for doc in docs: applist.append(db.get_pkgname(doc)) return applist - def spawn_write_new_review_ui(self, translated_app, version, iconname, + def spawn_write_new_review_ui(self, translated_app, version, iconname, origin, parent_xid, datadir, callback): """Spawn the UI for writing a new review and adds it automatically to the reviews DB. @@ -424,7 +439,8 @@ """ pass - def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir, callback): + def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, + datadir, callback): """Spawn a helper to submit a usefulness vote.""" pass @@ -432,14 +448,16 @@ """Spawn a helper to delete a review.""" pass - def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, callback): + def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, + callback): """Spawn a helper to modify a review.""" pass class ReviewLoaderFake(ReviewLoader): - USERS = ["Joe Doll", "John Foo", "Cat Lala", "Foo Grumpf", "Bar Tender", "Baz Lightyear"] + USERS = ["Joe Doll", "John Foo", "Cat Lala", "Foo Grumpf", "Bar Tender", + "Baz Lightyear"] SUMMARIES = ["Cool", "Medium", "Bad", "Too difficult"] IPSUM = "no ipsum\n\nstill no ipsum" @@ -447,12 +465,16 @@ ReviewLoader.__init__(self, cache, db) self._review_stats_cache = {} self._reviews_cache = {} + def _random_person(self): return random.choice(self.USERS) + def _random_text(self): return random.choice(self.LOREM.split("\n\n")) + def _random_summary(self): return random.choice(self.SUMMARIES) + def get_reviews(self, application, callback, page=1, language=None, sort=0, relaxed=False): if not application in self._review_stats_cache: @@ -462,43 +484,48 @@ reviews = [] for i in range(0, stats.ratings_total): review = Review(application) - review.id = random.randint(1,50000) + review.id = random.randint(1, 50000) # FIXME: instead of random, try to match the avg_rating - review.rating = random.randint(1,5) + review.rating = random.randint(1, 5) review.summary = self._random_summary() review.date_created = time.strftime("%Y-%m-%d %H:%M:%S") review.reviewer_username = self._random_person() - review.review_text = self._random_text().replace("\n","") + review.review_text = self._random_text().replace("\n", "") review.usefulness_total = random.randint(1, 20) review.usefulness_favorable = random.randint(1, 20) reviews.append(review) self._reviews_cache[application] = reviews reviews = self._reviews_cache[application] callback(application, reviews) + def get_review_stats(self, application): if not application in self._review_stats_cache: stat = ReviewStats(application) - stat.ratings_average = random.randint(1,5) - stat.ratings_total = random.randint(1,20) + stat.ratings_average = random.randint(1, 5) + stat.ratings_total = random.randint(1, 20) self._review_stats_cache[application] = stat return self._review_stats_cache[application] + def refresh_review_stats(self, callback): review_stats = [] callback(review_stats) + class ReviewLoaderFortune(ReviewLoaderFake): def __init__(self, cache, db): ReviewLoaderFake.__init__(self, cache, db) self.LOREM = "" for i in range(10): - out = subprocess.Popen(["fortune"], stdout=subprocess.PIPE).communicate()[0] + out = subprocess.Popen(["fortune"], + stdout=subprocess.PIPE).communicate()[0] self.LOREM += "\n\n%s" % out + class ReviewLoaderTechspeak(ReviewLoaderFake): """ a test review loader that does not do any network io and returns random review texts """ - LOREM=u"""This package is using cloud based technology that will + LOREM = u"""This package is using cloud based technology that will make it suitable in a distributed environment where soup and xml-rpc are used. The backend is written in C++ but the frontend code will utilize dynamic languages lika LUA to provide a execution environment @@ -547,6 +574,7 @@ your finger tips. This has limitless possibilities and will permeate every facet of your life. Believe the hype.""" + class ReviewLoaderIpsum(ReviewLoaderFake): """ a test review loader that does not do any network io and returns random lorem ipsum review texts @@ -554,7 +582,8 @@ #This text is under public domain #Lorem ipsum #Cicero - LOREM=u"""lorem ipsum "dolor" äöü sit amet consetetur sadipscing elitr sed diam nonumy + LOREM = u"""lorem ipsum "dolor" äöü sit amet consetetur sadipscing elitr +sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat sed diam voluptua at vero eos et accusam et justo duo dolores et ea rebum stet clita kasd gubergren no sea takimata sanctus est lorem ipsum dolor sit amet lorem @@ -632,7 +661,7 @@ callback(application, []) def get_review_stats(self, application): - return None + pass def refresh_review_stats(self, callback): review_stats = [] @@ -640,8 +669,10 @@ review_loader = None + + def get_review_loader(cache, db=None): - """ + """ factory that returns a reviews loader singelton """ global review_loader @@ -666,6 +697,7 @@ def callback(app, reviews): print "app callback:" print app, reviews + def stats_callback(stats): print "stats callback:" print stats @@ -675,14 +707,16 @@ cache = get_pkg_info() cache.open() - db = StoreDatabase(XAPIAN_BASE_PATH+"/xapian", cache) + db = StoreDatabase(XAPIAN_BASE_PATH + "/xapian", cache) db.open() # rnrclient loader app = Application("ACE", "unace") #app = Application("", "2vcard") - from softwarecenter.backend.reviews.rnr import ReviewLoaderSpawningRNRClient + from softwarecenter.backend.reviews.rnr import ( + ReviewLoaderSpawningRNRClient + ) loader = ReviewLoaderSpawningRNRClient(cache, db) print loader.refresh_review_stats(stats_callback) print loader.get_reviews(app, callback) @@ -694,7 +728,7 @@ main.run() # default loader - app = Application("","2vcard") + app = Application("", "2vcard") loader = get_review_loader(cache, db) loader.refresh_review_stats(stats_callback) loader.get_reviews(app, callback) diff -Nru software-center-5.1.12/softwarecenter/backend/reviews/rnr.py software-center-5.1.13/softwarecenter/backend/reviews/rnr.py --- software-center-5.1.12/softwarecenter/backend/reviews/rnr.py 2012-03-08 19:37:37.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/reviews/rnr.py 2012-03-19 08:35:49.000000000 +0000 @@ -24,7 +24,12 @@ import time from softwarecenter.backend.spawn_helper import SpawnHelper -from softwarecenter.backend.reviews import ReviewLoader, Review, ReviewStats, UsefulnessCache +from softwarecenter.backend.reviews import ( + ReviewLoader, + Review, + ReviewStats, + UsefulnessCache, + ) from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails from softwarecenter.db.database import Application @@ -40,15 +45,15 @@ LOG = logging.getLogger(__name__) -# this code had several incernations: +# this code had several incernations: # - python threads, slow and full of latency (GIL) -# - python multiprocesing, crashed when accessibility was turned on, +# - python multiprocesing, crashed when accessibility was turned on, # does not work in the quest session (#743020) # - GObject.spawn_async() looks good so far (using the SpawnHelper code) class ReviewLoaderSpawningRNRClient(ReviewLoader): """ loader that uses multiprocessing to call rnrclient and a glib timeout watcher that polls periodically for the - data + data """ def __init__(self, cache, db, distro=None): @@ -64,7 +69,7 @@ self.rnrclient._offline_mode = not network_state_is_connected() # reviews - def get_reviews(self, translated_app, callback, page=1, + def get_reviews(self, translated_app, callback, page=1, language=None, sort=0, relaxed=False): """ public api, triggers fetching a review and calls callback when its ready @@ -99,11 +104,12 @@ return distroseries = self.distro.get_codename() # run the command and add watcher - cmd = [os.path.join(softwarecenter.paths.datadir, PistonHelpers.GET_REVIEWS), - "--language", language, - "--origin", origin, - "--distroseries", distroseries, - "--pkgname", str(app.pkgname), # ensure its str, not unicode + cmd = [os.path.join(softwarecenter.paths.datadir, + PistonHelpers.GET_REVIEWS), + "--language", language, + "--origin", origin, + "--distroseries", distroseries, + "--pkgname", str(app.pkgname), # ensure its str, not unicode "--page", str(page), "--sort", sort_method, ] @@ -112,7 +118,8 @@ "data-available", self._on_reviews_helper_data, app, callback) spawn_helper.run(cmd) - def _on_reviews_helper_data(self, spawn_helper, piston_reviews, app, callback): + def _on_reviews_helper_data(self, spawn_helper, piston_reviews, app, + callback): # convert into our review objects reviews = [] for r in piston_reviews: @@ -127,7 +134,7 @@ """ public api, refresh the available statistics """ try: mtime = os.path.getmtime(self.REVIEW_STATS_CACHE_FILE) - days_delta = int((time.time() - mtime) // (24*60*60)) + days_delta = int((time.time() - mtime) // (24 * 60 * 60)) days_delta += 1 except OSError: days_delta = 0 @@ -138,19 +145,22 @@ #origin = "any" #distroseries = self.distro.get_codename() spawn_helper = SpawnHelper() - spawn_helper.connect("data-available", self._on_review_stats_data, callback) + spawn_helper.connect("data-available", self._on_review_stats_data, + callback) if days_delta: spawn_helper.run_generic_piston_helper( "RatingsAndReviewsAPI", "review_stats", days=days_delta) else: spawn_helper.run_generic_piston_helper( "RatingsAndReviewsAPI", "review_stats") - - def _on_review_stats_data(self, spawn_helper, piston_review_stats, callback): + + def _on_review_stats_data(self, spawn_helper, piston_review_stats, + callback): """ process stdout from the helper """ review_stats = self.REVIEW_STATS_CACHE - if self._cache_version_old and self._server_has_histogram(piston_review_stats): + if self._cache_version_old and self._server_has_histogram( + piston_review_stats): self.REVIEW_STATS_CACHE = {} self.save_review_stats_cache_file() self.refresh_review_stats(callback) @@ -164,7 +174,7 @@ if r.histogram: s.rating_spread = json.loads(r.histogram) else: - s.rating_spread = [0,0,0,0,0] + s.rating_spread = [0, 0, 0, 0, 0] s.dampened_rating = calc_dr(s.rating_spread) review_stats[s.app] = s self.REVIEW_STATS_CACHE = review_stats @@ -181,13 +191,13 @@ # writing new reviews spawns external helper # FIXME: instead of the callback we should add proper gobject signals - def spawn_write_new_review_ui(self, translated_app, version, iconname, + def spawn_write_new_review_ui(self, translated_app, version, iconname, origin, parent_xid, datadir, callback, done_callback=None): """ this spawns the UI for writing a new review and adds it automatically to the reviews DB """ app = translated_app.get_untranslated_app(self.db) - cmd = [os.path.join(datadir, RNRApps.SUBMIT_REVIEW), + cmd = [os.path.join(datadir, RNRApps.SUBMIT_REVIEW), "--pkgname", app.pkgname, "--iconname", iconname, "--parent-xid", "%s" % parent_xid, @@ -216,7 +226,7 @@ # FIXME: ideally this would be stored in ubuntu-sso-client # but it dosn't so we store it here save_person_to_config(review.reviewer_username) - if not app in self._reviews: + if not app in self._reviews: self._reviews[app] = [] self._reviews[app].insert(0, Review.from_piston_mini_client(review)) callback(app, self._reviews[app]) @@ -227,18 +237,19 @@ operation is complete it will call callback with the updated review list """ - cmd = [os.path.join(datadir, RNRApps.REPORT_REVIEW), + cmd = [os.path.join(datadir, RNRApps.REPORT_REVIEW), "--review-id", review_id, "--parent-xid", "%s" % parent_xid, "--datadir", datadir, ] spawn_helper = SpawnHelper("json") - spawn_helper.connect("exited", - self._on_report_abuse_finished, + spawn_helper.connect("exited", + self._on_report_abuse_finished, review_id, callback) spawn_helper.run(cmd) - def _on_report_abuse_finished(self, spawn_helper, exitcode, review_id, callback): + def _on_report_abuse_finished(self, spawn_helper, exitcode, review_id, + callback): """ called when report_absuse finished """ LOG.debug("hide id %s " % review_id) if exitcode == 0: @@ -247,33 +258,36 @@ if str(review.id) == str(review_id): # remove the one we don't want to see anymore self._reviews[app].remove(review) - callback(app, self._reviews[app], None, 'remove', review) + callback(app, self._reviews[app], None, 'remove', + review) break - - def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir, callback): - cmd = [os.path.join(datadir, RNRApps.SUBMIT_USEFULNESS), + def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, + datadir, callback): + cmd = [os.path.join(datadir, RNRApps.SUBMIT_USEFULNESS), "--review-id", "%s" % review_id, "--is-useful", "%s" % int(is_useful), "--parent-xid", "%s" % parent_xid, "--datadir", datadir, ] spawn_helper = SpawnHelper(format="none") - spawn_helper.connect("exited", - self._on_submit_usefulness_finished, + spawn_helper.connect("exited", + self._on_submit_usefulness_finished, review_id, is_useful, callback) spawn_helper.connect("error", self._on_submit_usefulness_error, review_id, callback) spawn_helper.run(cmd) - def _on_submit_usefulness_finished(self, spawn_helper, res, review_id, is_useful, callback): + def _on_submit_usefulness_finished(self, spawn_helper, res, review_id, + is_useful, callback): """ called when report_usefulness finished """ - # "Created", "Updated", "Not modified" - + # "Created", "Updated", "Not modified" - # once lp:~mvo/rnr-server/submit-usefulness-result-strings makes it response = spawn_helper._stdout if response == '"Not modified"': - self._on_submit_usefulness_error(spawn_helper, response, review_id, callback) + self._on_submit_usefulness_error(spawn_helper, response, review_id, + callback) return LOG.debug("usefulness id %s " % review_id) @@ -284,37 +298,42 @@ if str(review.id) == str(review_id): # update usefulness, older servers do not send # usefulness_{total,favorable} so we use getattr - review.usefulness_total = getattr(review, "usefulness_total", 0) + 1 + review.usefulness_total = getattr(review, + "usefulness_total", 0) + 1 if is_useful: - review.usefulness_favorable = getattr(review, "usefulness_favorable", 0) + 1 - callback(app, self._reviews[app], useful_votes, 'replace', review) + review.usefulness_favorable = getattr(review, + "usefulness_favorable", 0) + 1 + callback(app, self._reviews[app], useful_votes, + 'replace', review) break - def _on_submit_usefulness_error(self, spawn_helper, error_str, review_id, callback): - LOG.warn("submit usefulness id=%s failed with error: %s" % - (review_id, error_str)) - for (app, reviews) in self._reviews.items(): - for review in reviews: - if str(review.id) == str(review_id): - review.usefulness_submit_error = True - callback(app, self._reviews[app], None, 'replace', review) - break + def _on_submit_usefulness_error(self, spawn_helper, error_str, review_id, + callback): + LOG.warn("submit usefulness id=%s failed with error: %s" % + (review_id, error_str)) + for (app, reviews) in self._reviews.items(): + for review in reviews: + if str(review.id) == str(review_id): + review.usefulness_submit_error = True + callback(app, self._reviews[app], None, 'replace', review) + break def spawn_delete_review_ui(self, review_id, parent_xid, datadir, callback): - cmd = [os.path.join(datadir, RNRApps.DELETE_REVIEW), + cmd = [os.path.join(datadir, RNRApps.DELETE_REVIEW), "--review-id", "%s" % review_id, "--parent-xid", "%s" % parent_xid, "--datadir", datadir, ] spawn_helper = SpawnHelper(format="none") - spawn_helper.connect("exited", - self._on_delete_review_finished, + spawn_helper.connect("exited", + self._on_delete_review_finished, review_id, callback) spawn_helper.connect("error", self._on_delete_review_error, review_id, callback) spawn_helper.run(cmd) - def _on_delete_review_finished(self, spawn_helper, res, review_id, callback): + def _on_delete_review_finished(self, spawn_helper, res, review_id, + callback): """ called when delete_review finished""" LOG.debug("delete id %s " % review_id) for (app, reviews) in self._reviews.items(): @@ -323,37 +342,41 @@ # remove the one we don't want to see anymore self._reviews[app].remove(review) callback(app, self._reviews[app], None, 'remove', review) - break + break - def _on_delete_review_error(self, spawn_helper, error_str, review_id, callback): + def _on_delete_review_error(self, spawn_helper, error_str, review_id, + callback): """called if delete review errors""" - LOG.warn("delete review id=%s failed with error: %s" % (review_id, error_str)) + LOG.warn("delete review id=%s failed with error: %s" % (review_id, + error_str)) for (app, reviews) in self._reviews.items(): for review in reviews: if str(review.id) == str(review_id): review.delete_error = True - callback(app, self._reviews[app], action='replace', + callback(app, self._reviews[app], action='replace', single_review=review) break - def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, callback): + def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id, + callback): """ this spawns the UI for writing a new review and adds it automatically to the reviews DB """ - cmd = [os.path.join(datadir, RNRApps.MODIFY_REVIEW), + cmd = [os.path.join(datadir, RNRApps.MODIFY_REVIEW), "--parent-xid", "%s" % parent_xid, "--iconname", iconname, "--datadir", "%s" % datadir, "--review-id", "%s" % review_id, ] spawn_helper = SpawnHelper(format="json") - spawn_helper.connect("data-available", - self._on_modify_review_finished, + spawn_helper.connect("data-available", + self._on_modify_review_finished, review_id, callback) spawn_helper.connect("error", self._on_modify_review_error, review_id, callback) spawn_helper.run(cmd) - def _on_modify_review_finished(self, spawn_helper, review_json, review_id, callback): + def _on_modify_review_finished(self, spawn_helper, review_json, review_id, + callback): """called when modify_review finished""" LOG.debug("_on_modify_review_finished") #review_json = spawn_helper._stdout @@ -365,17 +388,19 @@ self._reviews[app].remove(review) new_review = Review.from_piston_mini_client(mod_review) self._reviews[app].insert(0, new_review) - callback(app, self._reviews[app], action='replace', + callback(app, self._reviews[app], action='replace', single_review=new_review) break - def _on_modify_review_error(self, spawn_helper, error_str, review_id, callback): + def _on_modify_review_error(self, spawn_helper, error_str, review_id, + callback): """called if modify review errors""" - LOG.debug("modify review id=%s failed with error: %s" % (review_id, error_str)) + LOG.debug("modify review id=%s failed with error: %s" % + (review_id, error_str)) for (app, reviews) in self._reviews.items(): for review in reviews: if str(review.id) == str(review_id): review.modify_error = True - callback(app, self._reviews[app], action='replace', + callback(app, self._reviews[app], action='replace', single_review=review) break diff -Nru software-center-5.1.12/softwarecenter/backend/scagent.py software-center-5.1.13/softwarecenter/backend/scagent.py --- software-center-5.1.12/softwarecenter/backend/scagent.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/scagent.py 2012-03-19 16:41:16.000000000 +0000 @@ -29,27 +29,28 @@ LOG = logging.getLogger(__name__) + class SoftwareCenterAgent(GObject.GObject): __gsignals__ = { - "available-for-me" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "available" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "exhibits" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (str,), - ), + "available-for-me": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "available": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "exhibits": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (str,), + ), } - + def __init__(self, ignore_cache=False, xid=None): GObject.GObject.__init__(self) self.distro = get_distro() @@ -103,7 +104,8 @@ "SoftwareCenterAgentAPI", "subscriptions_for_me", complete_only=True) - def _on_query_available_for_me_data(self, spawner, piston_available_for_me): + def _on_query_available_for_me_data(self, spawner, + piston_available_for_me): self.emit("available-for-me", piston_available_for_me) def query_exhibits(self): @@ -113,7 +115,7 @@ spawner.connect("data-available", self._on_exhibits_data_available) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( - "SoftwareCenterAgentAPI", "exhibits", + "SoftwareCenterAgentAPI", "exhibits", lang=get_language(), series=self.distro.get_codename()) def _on_exhibits_data_available(self, spawner, exhibits): @@ -123,18 +125,22 @@ if not hasattr(exhibit, "title_translated"): if exhibit.html: from softwarecenter.utils import get_title_from_html - exhibit.title_translated = get_title_from_html(exhibit.html) + exhibit.title_translated = get_title_from_html( + exhibit.html) else: exhibit.title_translated = "" self.emit("exhibits", exhibits) - + if __name__ == "__main__": def _available(agent, available): print ("_available: %s" % available) + def _available_for_me(agent, available_for_me): print ("_availalbe_for_me: %s" % available_for_me) + def _exhibits(agent, exhibits): print ("exhibits: " % exhibits) + def _error(agent, msg): print ("got a error" % msg) #gtk.main_quit() diff -Nru software-center-5.1.12/softwarecenter/backend/spawn_helper.py software-center-5.1.13/softwarecenter/backend/spawn_helper.py --- software-center-5.1.12/softwarecenter/backend/spawn_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/spawn_helper.py 2012-03-19 15:02:54.000000000 +0000 @@ -22,7 +22,7 @@ # py3 compat try: import cPickle as pickle - pickle # pyflakes + pickle # pyflakes except ImportError: import pickle @@ -37,21 +37,22 @@ LOG = logging.getLogger(__name__) + class SpawnHelper(GObject.GObject): - + __gsignals__ = { - "data-available" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "exited" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (int,), - ), - "error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (str,), - ), + "data-available": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "exited": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (int,), + ), + "error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (str,), + ), } def __init__(self, format="pickle"): @@ -87,7 +88,7 @@ def run(self, cmd): self._cmd = cmd (pid, stdin, stdout, stderr) = GObject.spawn_async( - cmd, flags = GObject.SPAWN_DO_NOT_REAP_CHILD, + cmd, flags=GObject.SPAWN_DO_NOT_REAP_CHILD, standard_output=True, standard_error=True) LOG.debug("running: '%s' as pid: '%s'" % (cmd, pid)) self._child_watch = GObject.child_watch_add( @@ -104,7 +105,7 @@ else: LOG.warn("exit code %s from helper for '%s'" % (res, self._cmd)) # check stderr - err = os.read(stderr, 4*1024) + err = os.read(stderr, 4 * 1024) self._stderr = err if err: LOG.warn("got error from helper: '%s'" % err) @@ -122,7 +123,8 @@ data = "" while True: s = os.read(stdout, 1024) - if not s: break + if not s: + break data += s os.close(stdout) self._stdout = data diff -Nru software-center-5.1.12/softwarecenter/backend/transactionswatcher.py software-center-5.1.13/softwarecenter/backend/transactionswatcher.py --- software-center-5.1.12/softwarecenter/backend/transactionswatcher.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/transactionswatcher.py 2012-03-19 15:02:54.000000000 +0000 @@ -18,42 +18,47 @@ from gi.repository import GObject + class BaseTransaction(GObject.GObject): """ wrapper class for install backend dbus Transaction objects """ - __gsignals__ = {'progress-details-changed':(GObject.SIGNAL_RUN_FIRST, + __gsignals__ = {'progress-details-changed': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (int, int, int, int, int, int)), - 'progress-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'status-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'cancellable-changed':(GObject.SIGNAL_RUN_FIRST, + 'progress-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'status-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'cancellable-changed': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT, )), - 'role-changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, )), - 'deleted':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - []), + 'role-changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, )), + 'deleted': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + []), } @property def tid(self): pass + @property def status_details(self): pass + @property def meta_data(self): return {} + @property def cancellable(self): return False + @property def progress(self): return False @@ -73,22 +78,25 @@ def cancel(self): pass + class BaseTransactionsWatcher(GObject.GObject): - """ - base class for objects that need to watch the install backend + """ + base class for objects that need to watch the install backend for transaction changes. provides a "lowlevel-transactions-changed" signal """ - __gsignals__ = {'lowlevel-transactions-changed': (GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (str,GObject.TYPE_PYOBJECT)), + __gsignals__ = {'lowlevel-transactions-changed': ( + GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (str, GObject.TYPE_PYOBJECT)), } def get_transaction(self, tid): """ should return a _Transaction object """ - return None + pass + class TransactionFinishedResult(object): """ represents the result of a transaction """ @@ -101,6 +109,7 @@ self.pkgname = None self.meta_data = None + class TransactionProgress(object): """ represents the progress of the transaction """ def __init__(self, trans): @@ -110,14 +119,18 @@ # singleton _tw = None + + def get_transactions_watcher(): global _tw if _tw is None: from softwarecenter.enums import USE_PACKAGEKIT_BACKEND if not USE_PACKAGEKIT_BACKEND: - from softwarecenter.backend.installbackend_impl.aptd import AptdaemonTransactionsWatcher + from softwarecenter.backend.installbackend_impl.aptd import ( + AptdaemonTransactionsWatcher) _tw = AptdaemonTransactionsWatcher() else: - from softwarecenter.backend.installbackend_impl.packagekitd import PackagekitTransactionsWatcher - _tw = PackagekitTransactionsWatcher() + from softwarecenter.backend.installbackend_impl.packagekitd \ + import PackagekitTransactionsWatcher + _tw = PackagekitTransactionsWatcher() return _tw diff -Nru software-center-5.1.12/softwarecenter/backend/ubuntusso.py software-center-5.1.13/softwarecenter/backend/ubuntusso.py --- software-center-5.1.12/softwarecenter/backend/ubuntusso.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/ubuntusso.py 2012-03-19 15:02:54.000000000 +0000 @@ -33,24 +33,24 @@ LOG = logging.getLogger(__name__) + class UbuntuSSOAPI(GObject.GObject): """ Ubuntu SSO interface using the oauth token from the keyring """ __gsignals__ = { - "whoami" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - "error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), - + "whoami": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), + "error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), } - + def __init__(self): GObject.GObject.__init__(self) - + def _on_whoami_data(self, spawner, piston_whoami): self.emit("whoami", piston_whoami) @@ -66,6 +66,7 @@ spawner.needs_auth = True spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami") + class UbuntuSSOAPIFake(UbuntuSSOAPI): def __init__(self): @@ -76,26 +77,28 @@ def whoami(self): if self._fake_settings.get_setting('whoami_response') == "whoami": self.emit("whoami", self._create_whoami_response()) - elif self._fake_settings.get_setting('whoami_response') == "error": + elif self._fake_settings.get_setting('whoami_response') == "error": self.emit("error", self._make_error()) - + def _create_whoami_response(self): - username = self._fake_settings.get_setting('whoami_username') or "anyuser" + username = (self._fake_settings.get_setting('whoami_username') or + "anyuser") response = { - u'username': username.decode('utf-8'), - u'preferred_email': u'user@email.com', - u'displayname': u'Fake User', - u'unverified_emails': [], - u'verified_emails': [], + u'username': username.decode('utf-8'), + u'preferred_email': u'user@email.com', + u'displayname': u'Fake User', + u'unverified_emails': [], + u'verified_emails': [], u'openid_identifier': u'fnerkWt' } return response - + def _make_error(): return 'HTTP Error 401: Unauthorized' + def get_ubuntu_sso_backend(): - """ + """ factory that returns an ubuntu sso loader singelton """ if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: @@ -109,8 +112,12 @@ # test code def _login_success(lp, token): print "success", lp, token + + def _login_failed(lp): print "fail", lp + + def _login_need_user_and_password(sso): import sys sys.stdout.write("user: ") @@ -126,9 +133,11 @@ def _whoami(sso, result): print "res: ", result Gtk.main_quit() + def _error(sso, result): print "err: ", result Gtk.main_quit() + def _dbus_maybe_login_successful(ssologin, oauth_result): print "got token, verify it now" sso = UbuntuSSOAPI() @@ -145,5 +154,3 @@ backend.connect("login-successful", _dbus_maybe_login_successful) backend.login_or_register() Gtk.main() - - diff -Nru software-center-5.1.12/softwarecenter/backend/unitylauncher.py software-center-5.1.13/softwarecenter/backend/unitylauncher.py --- software-center-5.1.12/softwarecenter/backend/unitylauncher.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/unitylauncher.py 2012-03-19 15:02:54.000000000 +0000 @@ -21,6 +21,7 @@ LOG = logging.getLogger(__name__) + class UnityLauncherInfo(object): """ Simple class to keep track of application details needed for Unity launcher integration @@ -42,19 +43,20 @@ self.icon_size = icon_size self.app_install_desktop_file_path = app_install_desktop_file_path self.trans_id = trans_id - + + class UnityLauncher(object): """ Implements the integration between Software Center and the Unity launcher """ - + def send_application_to_launcher(self, pkgname, launcher_info): """ send a dbus message to the Unity launcher service to initiate the add to launcher functionality for the specified application - """ - LOG.debug("sending dbus signal to Unity launcher for application: ", + """ + LOG.debug("sending dbus signal to Unity launcher for application: ", launcher_info.name) - LOG.debug(" launcher_info.icon_file_path: ", + LOG.debug(" launcher_info.icon_file_path: ", launcher_info.icon_file_path) LOG.debug(" launcher_info.app_install_desktop_file_path: ", launcher_info.app_install_desktop_file_path) diff -Nru software-center-5.1.12/softwarecenter/backend/weblive_pristine.py software-center-5.1.13/softwarecenter/backend/weblive_pristine.py --- software-center-5.1.12/softwarecenter/backend/weblive_pristine.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/weblive_pristine.py 2012-03-19 15:02:54.000000000 +0000 @@ -11,14 +11,18 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -# taken from lp:~weblive-dev/weblive/ltsp-cluster-agent-weblive/client/weblive.py +# taken from +# lp:~weblive-dev/weblive/trunk/client/weblive.py # and put into weblive_pristine.py until a ubuntu package is in main -import urllib, urllib2, json +import json +import urllib +import urllib2 + class WebLiveJsonError(Exception): def __init__(self, value): @@ -27,6 +31,7 @@ def __str__(self): return repr(self.value) + class WebLiveError(Exception): def __init__(self, value): self.value = value @@ -34,19 +39,23 @@ def __str__(self): return repr(self.value) + class WebLiveLocale(object): def __init__(self, locale, description): self.locale = locale self.description = description + class WebLivePackage(object): def __init__(self, pkgname, version, autoinstall): self.pkgname = pkgname self.version = version self.autoinstall = autoinstall + class WebLiveServer(object): - def __init__(self, name, title, description, timelimit, userlimit, users, autoinstall): + def __init__(self, name, title, description, timelimit, userlimit, + users, autoinstall): self.name = name self.title = title self.description = description @@ -56,37 +65,47 @@ self.autoinstall = autoinstall def __repr__(self): - return "[WebLiveServer: %s (%s - %s), timelimit=%s, userlimit=%s, current_users=%s, autoinstall=%s" % ( - self.name, self.title, self.description, self.timelimit, self.userlimit, self.current_users, self.autoinstall) + return ("[WebLiveServer: %s (%s - %s), timelimit=%s, userlimit=%s, " + "current_users=%s, autoinstall=%s") % ( + self.name, self.title, self.description, self.timelimit, + self.userlimit, self.current_users, self.autoinstall) + class WebLiveEverythingServer(WebLiveServer): - def __init__(self, name, title, description, timelimit, userlimit, users, autoinstall, locales, packages): + def __init__(self, name, title, description, timelimit, userlimit, + users, autoinstall, locales, packages): self.locales = [WebLiveLocale(x[0], x[1]) for x in locales] self.packages = [WebLivePackage(x[0], x[1], x[2]) for x in packages] - WebLiveServer.__init__(self, name, title, description, timelimit, userlimit, users, autoinstall) + WebLiveServer.__init__(self, name, title, description, timelimit, + userlimit, users, autoinstall) def __repr__(self): - return "[WebLiveServer: %s (%s - %s), timelimit=%s, userlimit=%s, current_users=%s, autoinstall=%s, nr_locales=%s, nr_pkgs=%s" % ( - self.name, self.title, self.description, self.timelimit, self.userlimit, self.current_users, self.autoinstall, len(self.locales), len(self.packages)) + return ("[WebLiveServer: %s (%s - %s), timelimit=%s, userlimit=%s, " + "current_users=%s, autoinstall=%s, nr_locales=%s, nr_pkgs=%s") % ( + self.name, self.title, self.description, self.timelimit, + self.userlimit, self.current_users, self.autoinstall, + len(self.locales), len(self.packages)) -class WebLive: - def __init__(self,url,as_object=False): - self.url=url - self.as_object=as_object - def do_query(self,query): - page=urllib2.Request(self.url,urllib.urlencode({'query':json.dumps(query)})) +class WebLive: + def __init__(self, url, as_object=False): + self.url = url + self.as_object = as_object + + def do_query(self, query): + page = urllib2.Request(self.url, urllib.urlencode( + {'query': json.dumps(query)})) try: - response=urllib2.urlopen(page) + response = urllib2.urlopen(page) except urllib2.HTTPError, e: raise WebLiveJsonError("HTTP return code: %s" % e.code) except urllib2.URLError, e: raise WebLiveJsonError("Failed to reach server: %s" % e.reason) try: - reply=json.loads(response.read()) + reply = json.loads(response.read()) except ValueError: raise WebLiveJsonError("Returned json object is invalid.") @@ -96,37 +115,44 @@ elif reply['message'] == -2: raise WebLiveJsonError("Missing parameter") elif reply['message'] == -3: - raise WebLiveJsonError("Function '%s' isn't exported over JSON." % query['action']) + raise WebLiveJsonError("Function '%s' isn't exported " + "over JSON." % query['action']) else: - raise WebLiveJsonError("Unknown error code: %s" % reply['message']) + raise WebLiveJsonError("Unknown error code: %s" % + reply['message']) if 'message' not in reply: raise WebLiveJsonError("Invalid json reply") return reply - def create_user(self,serverid,username,fullname,password,session,locale): - query={} - query['action']='create_user' - query['serverid']=serverid - query['username']=username - query['fullname']=fullname - query['password']=password - query['session']=session - query['locale']=locale - reply=self.do_query(query) + def create_user(self, serverid, username, fullname, password, + session, locale): + query = {} + query['action'] = 'create_user' + query['serverid'] = serverid + query['username'] = username + query['fullname'] = fullname + query['password'] = password + query['session'] = session + query['locale'] = locale + reply = self.do_query(query) if type(reply['message']) != type([]): if reply['message'] == 1: raise WebLiveError("Reached user limit, return false.") elif reply['message'] == 2: - raise WebLiveError("Different user with same username already exists.") + raise WebLiveError("Different user with same username " + "already exists.") elif reply['message'] == 3: - raise WebLiveError("Invalid fullname, must only contain alphanumeric characters and spaces.") + raise WebLiveError("Invalid fullname, must only contain " + "alphanumeric characters and spaces.") elif reply['message'] == 4: - raise WebLiveError("Invalid login, must only contain lowercase letters.") + raise WebLiveError("Invalid login, must only contain " + "lowercase letters.") elif reply['message'] == 5: - raise WebLiveError("Invalid password, must contain only alphanumeric characters.") + raise WebLiveError("Invalid password, must contain only " + "alphanumeric characters.") elif reply['message'] == 7: raise WebLiveError("Invalid server: %s" % serverid) else: @@ -135,20 +161,20 @@ return reply['message'] def list_everything(self): - query={} - query['action']='list_everything' - reply=self.do_query(query) + query = {} + query['action'] = 'list_everything' + reply = self.do_query(query) if type(reply['message']) != type({}): raise WebLiveError("Invalid value, expected '%s' and got '%s'." - % (type({}),type(reply['message']))) + % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: - servers=[] + servers = [] for server in reply['message']: - attr=reply['message'][server] + attr = reply['message'][server] servers.append(WebLiveEverythingServer( server, attr['title'], @@ -161,15 +187,15 @@ attr['packages'])) return servers - def list_locales(self,serverid): - query={} - query['action']='list_locales' - query['serverid']=serverid - reply=self.do_query(query) + def list_locales(self, serverid): + query = {} + query['action'] = 'list_locales' + query['serverid'] = serverid + reply = self.do_query(query) if type(reply['message']) != type([]): raise WebLiveError("Invalid value, expected '%s' and got '%s'." - % (type({}),type(reply['message']))) + % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] @@ -177,28 +203,28 @@ return [WebLiveLocale(x[0], x[1]) for x in reply['message']] def list_package_blacklist(self): - query={} - query['action']='list_package_blacklist' - reply=self.do_query(query) + query = {} + query['action'] = 'list_package_blacklist' + reply = self.do_query(query) if type(reply['message']) != type([]): raise WebLiveError("Invalid value, expected '%s' and got '%s'." - % (type({}),type(reply['message']))) + % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: return [WebLivePackage(x, None, None) for x in reply['message']] - def list_packages(self,serverid): - query={} - query['action']='list_packages' - query['serverid']=serverid - reply=self.do_query(query) + def list_packages(self, serverid): + query = {} + query['action'] = 'list_packages' + query['serverid'] = serverid + reply = self.do_query(query) if type(reply['message']) != type([]): raise WebLiveError("Invalid value, expected '%s' and got '%s'." - % (type({}),type(reply['message']))) + % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] @@ -206,20 +232,20 @@ return [WebLivePackage(x[0], x[1], x[2]) for x in reply['message']] def list_servers(self): - query={} - query['action']='list_servers' - reply=self.do_query(query) + query = {} + query['action'] = 'list_servers' + reply = self.do_query(query) if type(reply['message']) != type({}): raise WebLiveError("Invalid value, expected '%s' and got '%s'." - % (type({}),type(reply['message']))) + % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: - servers=[] + servers = [] for server in reply['message']: - attr=reply['message'][server] + attr = reply['message'][server] servers.append(WebLiveServer( server, attr['title'], diff -Nru software-center-5.1.12/softwarecenter/backend/weblive.py software-center-5.1.13/softwarecenter/backend/weblive.py --- software-center-5.1.12/softwarecenter/backend/weblive.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/weblive.py 2012-03-19 15:02:54.000000000 +0000 @@ -14,11 +14,11 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -# taken from lp:~weblive-dev/weblive/ltsp-cluster-agent-weblive/client/weblive.py +# taken from lp:~weblive-dev/weblive/trunk/client/weblive.py # and put into weblive_pristine.py import re @@ -34,6 +34,7 @@ from weblive_pristine import WebLive import softwarecenter.paths + class WebLiveBackend(object): """ Backend for interacting with the WebLive service """ @@ -42,12 +43,12 @@ 'https://weblive.stgraber.org/weblive/json') def __init__(self): - self.weblive = WebLive(self.URL,True) + self.weblive = WebLive(self.URL, True) self.available_servers = [] - for client in (WebLiveClientX2GO,WebLiveClientQTNX): + for client in (WebLiveClientX2GO, WebLiveClientQTNX): if client.is_supported(): - self.client=client() + self.client = client() break self._ready = Event() @@ -63,7 +64,7 @@ """ Get all the available data from WebLive """ self._ready.clear() - servers=self.weblive.list_everything() + servers = self.weblive.list_everything() self._ready.set() return servers @@ -77,7 +78,9 @@ p.start() def is_pkgname_available_on_server(self, pkgname, serverid=None): - """ Check if the package is available (on all servers or on 'serverid') """ + """Check if the package is available (on all servers or + on 'serverid') + """ for server in self.available_servers: if not serverid or server.name == serverid: @@ -89,7 +92,7 @@ def get_servers_for_pkgname(self, pkgname): """ Return a list of servers having a given package """ - servers=[] + servers = [] for server in self.available_servers: # No point in returning a server that's full if server.current_users >= server.userlimit: @@ -104,32 +107,38 @@ session="desktop", wait=False): """ Create a user on 'serverid' and start the session """ - # Use the boot_id to get a temporary unique identifier (till next reboot) + # Use the boot_id to get a temporary unique identifier + # (till next reboot) if os.path.exists('/proc/sys/kernel/random/boot_id'): - uuid=open('/proc/sys/kernel/random/boot_id','r').read().strip().replace('-','') + uuid = open('/proc/sys/kernel/random/boot_id', + 'r').read().strip().replace('-', '') random.seed(uuid) # Generate a 20 characters string based on the boot_id - identifier=''.join(random.choice(string.ascii_lowercase) for x in range (20)) + identifier = ''.join(random.choice(string.ascii_lowercase) + for x in range(20)) # Use the current username as the GECOS on the server # if it's invalid (by weblive's standard), use "WebLive user" instead - fullname=str(os.environ.get('USER','WebLive user')) - if not re.match("^[A-Za-z0-9 ]*$",fullname) or len(fullname) == 0: - fullname='WebLive user' + fullname = str(os.environ.get('USER', 'WebLive user')) + if not re.match("^[A-Za-z0-9 ]*$", fullname) or len(fullname) == 0: + fullname = 'WebLive user' # Send the user's locale so it's automatically selected when connecting - locale=os.environ.get("LANG","None").replace("UTF-8","utf8") + locale = os.environ.get("LANG", "None").replace("UTF-8", "utf8") # Create the user and retrieve host and port of the target server - connection=self.weblive.create_user(serverid, identifier, fullname, identifier, session, locale) + connection = self.weblive.create_user(serverid, identifier, fullname, + identifier, session, locale) # Connect using x2go or fallback to qtnx if not available if (self.client): - self.client.start_session(connection[0], connection[1], session, identifier, identifier, wait) + self.client.start_session(connection[0], connection[1], session, + identifier, identifier, wait) else: raise IOError("No remote desktop client available.") + class WebLiveClient(GObject.GObject): """ Generic WebLive client """ @@ -215,20 +224,21 @@ os.mkdir(os.path.expanduser('~/.qtnx')) # Generate qtnx's configuration file - filename=os.path.expanduser('~/.qtnx/%s-%s-%s.nxml') % ( - host, port, session.replace("/","_")) - nxml=open(filename,"w+") - config=self.NXML_TEMPLATE - config=config.replace("WL_NAME","%s-%s-%s" % (host, port, session.replace("/","_"))) - config=config.replace("WL_SERVER", host) - config=config.replace("WL_PORT",str(port)) - config=config.replace("WL_COMMAND","weblive-session %s" % session) + filename = os.path.expanduser('~/.qtnx/%s-%s-%s.nxml') % ( + host, port, session.replace("/", "_")) + nxml = open(filename, "w+") + config = self.NXML_TEMPLATE + config = config.replace("WL_NAME", "%s-%s-%s" % (host, port, + session.replace("/", "_"))) + config = config.replace("WL_SERVER", host) + config = config.replace("WL_PORT", str(port)) + config = config.replace("WL_COMMAND", "weblive-session %s" % session) nxml.write(config) nxml.close() # Prepare qtnx call cmd = [self.BINARY_PATH, - '%s-%s-%s' % (str(host), str(port), session.replace("/","_")), + '%s-%s-%s' % (str(host), str(port), session.replace("/", "_")), username, password] @@ -237,11 +247,11 @@ if self.helper_progress == 10: self.state = "connected" - self.emit("connected",False) + self.emit("connected", False) return False else: - self.emit("progress",self.helper_progress * 10) - self.helper_progress+=1 + self.emit("progress", self.helper_progress * 10) + self.helper_progress += 1 return True def qtnx_start_timer(): @@ -249,22 +259,22 @@ status, we countdown from 20s """ - self.helper_progress=0 + self.helper_progress = 0 qtnx_countdown() - GObject.timeout_add_seconds( - 2, qtnx_countdown) + GObject.timeout_add_seconds(2, qtnx_countdown) qtnx_start_timer() if wait == False: # Start in the background and attach a watch for when it exits (self.helper_pid, stdin, stdout, stderr) = GObject.spawn_async( - cmd, standard_input=True, standard_output=True, standard_error=True, - flags=GObject.SPAWN_DO_NOT_REAP_CHILD) - GObject.child_watch_add(self.helper_pid, self._on_qtnx_exit,filename) + cmd, standard_input=True, standard_output=True, + standard_error=True, flags=GObject.SPAWN_DO_NOT_REAP_CHILD) + GObject.child_watch_add(self.helper_pid, self._on_qtnx_exit, + filename) else: # Start it and wait till it finishes - p=subprocess.Popen(cmd) + p = subprocess.Popen(cmd) p.wait() def _on_qtnx_exit(self, pid, status, filename): @@ -276,6 +286,7 @@ if os.path.exists(filename): os.remove(filename) + class WebLiveClientX2GO(WebLiveClient): """ x2go client """ @@ -295,23 +306,27 @@ """ Start a session using x2go """ # Start in the background and attach a watch for when it exits - cmd = [os.path.join(softwarecenter.paths.datadir, softwarecenter.paths.X2GO_HELPER)] + cmd = [os.path.join(softwarecenter.paths.datadir, + softwarecenter.paths.X2GO_HELPER)] (self.helper_pid, stdin, stdout, stderr) = GObject.spawn_async( - cmd, standard_input=True, standard_output=True, standard_error=True, - flags=GObject.SPAWN_DO_NOT_REAP_CHILD) - self.helper_stdin=os.fdopen(stdin,"w") - self.helper_stdout=os.fdopen(stdout) - self.helper_stderr=os.fdopen(stderr) + cmd, standard_input=True, standard_output=True, + standard_error=True, flags=GObject.SPAWN_DO_NOT_REAP_CHILD) + self.helper_stdin = os.fdopen(stdin, "w") + self.helper_stdout = os.fdopen(stdout) + self.helper_stderr = os.fdopen(stderr) # Add a watch for when the process exits GObject.child_watch_add(self.helper_pid, self._on_x2go_exit) # Add a watch on stdout - GObject.io_add_watch(self.helper_stdout, GObject.IO_IN, self._on_x2go_activity) + GObject.io_add_watch(self.helper_stdout, GObject.IO_IN, + self._on_x2go_activity) # Start the connection self.state = "connecting" - self.helper_stdin.write("CONNECT: \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n" % (host, port, username, password, session)) + self.helper_stdin.write( + "CONNECT: \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n" % + (host, port, username, password, session)) self.helper_stdin.flush() def disconnect_session(self): @@ -329,17 +344,17 @@ def _on_x2go_activity(self, stdout, condition): """ Called when something appears on stdout """ - line=stdout.readline().strip() + line = stdout.readline().strip() if line.startswith("PROGRESS: "): if line.endswith("creating"): - self.emit("progress",10) + self.emit("progress", 10) elif line.endswith("connecting"): - self.emit("progress",30) + self.emit("progress", 30) elif line.endswith("starting"): - self.emit("progress",60) + self.emit("progress", 60) elif line == "CONNECTED": - self.emit("connected",True) + self.emit("connected", True) self.state = "connected" elif line == "DISCONNECTED": self.emit("disconnected") @@ -355,6 +370,8 @@ # singleton _weblive_backend = None + + def get_weblive_backend(): global _weblive_backend if _weblive_backend is None: @@ -374,4 +391,6 @@ print(weblive.available_servers) # Start firefox on the first available server and wait for it to finish - weblive.create_automatic_user_and_run_session(serverid=weblive.available_servers[0].name,session="firefox",wait=True) + weblive.create_automatic_user_and_run_session( + serverid=weblive.available_servers[0].name, session="firefox", + wait=True) diff -Nru software-center-5.1.12/softwarecenter/backend/zeitgeist_simple.py software-center-5.1.13/softwarecenter/backend/zeitgeist_simple.py --- software-center-5.1.12/softwarecenter/backend/zeitgeist_simple.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/backend/zeitgeist_simple.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,136 +0,0 @@ -# Copyright (C) 2009-2010 Canonical -# -# Authors: -# Seif Lotfy -# Michael Vogt -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import logging -import time -LOG = logging.getLogger("sofwarecenter.zeitgeist") - -try: - from zeitgeist.client import ZeitgeistClient - from zeitgeist.datamodel import Event, Interpretation, ResultType -except ImportError: - LOG.exception("zeitgeist import failed") - ZEITGEIST_AVAILABLE = False -else: - ZEITGEIST_AVAILABLE = True - -class SoftwareCenterZeitgeist(): - """ simple wrapper around zeitgeist """ - - def __init__(self): - try: - self.zg_client = ZeitgeistClient() - except Exception as e: - logging.warn("can not get zeitgeist client: '%s'" % e) - self.zg_client = None - - def get_usage_counter(self, application, callback, timerange=None): - """Request the usage count as integer for the given application. - When the request is there, "callback" is called. A optional - timerange like [time.time(), time.time() - 30*24*60*60] can - also be specified - """ - # helper - def _callback(event_ids): - callback(len(event_ids)) - # no client or empty query -> empty result - if not self.zg_client or not application: - callback(0) - return - # the app we are looking for - application = "application://"+application.split("/")[-1] - # the event_templates - e1 = Event.new_for_values( - actor=application, interpretation=Interpretation.MODIFY_EVENT.uri) - e2 = Event.new_for_values( - actor=application, interpretation=Interpretation.CREATE_EVENT.uri) - # run it - self.zg_client.find_event_ids_for_templates( - [e1, e2], _callback, timerange=timerange, num_events=0) - - def get_popular_mimetypes(self, callback, num=3): - """ get the "num" (default to 3) most popular mimetypes based - on the last 1000 events that zeitgeist recorded and - call "callback" with [(count1, "mime1"), (count2, "mime2"), ...] - as arguement - """ - def _callback(events): - # gather - mimetypes = {} - for event in events: - if event.subjects is None: - continue - mimetype = event.subjects[0].mimetype - if not mimetype in mimetypes: - mimetypes[mimetype] = 0 - mimetypes[mimetype] += 1 - # return early if empty - results = [] - if not mimetypes: - callback([]) - # convert to result and sort - for k, v in mimetypes.items(): - results.append([v, k]) - results.sort(reverse = True) - # tell the client about it - callback(results[:num]) - # no zeitgeist - if not self.zg_client: - return - # trigger event (actual processing is done in _callback) - # FIXME: investigate how result_type MostRecentEvents or - # MostRecentSubjects would affect the results - self.zg_client.find_events_for_templates( - [], _callback, num_events=1000, - result_type=ResultType.MostRecentEvents) - -class SoftwareCenterZeitgeistDummy(): - def get_usage_counter(self, application, callback, timerange=None): - callback(0) - def get_popular_mimetypes(self, callback): - callback([]) - -# singleton -if ZEITGEIST_AVAILABLE: - zeitgeist_singleton = SoftwareCenterZeitgeist() -else: - zeitgeist_singleton = SoftwareCenterZeitgeistDummy() - -if __name__ == "__main__": - - def _callback_counter(events): - print("test _callback: %s" % events) - # all time gedit - zeitgeist_singleton.get_usage_counter("gedit.desktop", _callback_counter) - - # yesterday gedit - end = time.time() - start = end - 24*60*60 - zeitgeist_singleton.get_usage_counter("gedit.desktop", _callback_counter, - timerange=[start, end]) - - # most popular - def _callback_popular(mimetypes): - print("test _callback: ") - for tuple in mimetypes: - print(tuple) - zeitgeist_singleton.get_popular_mimetypes(_callback_popular) - - from gi.repository import Gtk - Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/cmdfinder.py software-center-5.1.13/softwarecenter/cmdfinder.py --- software-center-5.1.12/softwarecenter/cmdfinder.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/cmdfinder.py 2012-03-16 08:30:18.000000000 +0000 @@ -22,19 +22,20 @@ import os import logging -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + class CmdFinder(object): """ helper class that can find binaries in packages """ # standard ubuntu PATH - PATH = [ "/usr/local/sbin", - "/usr/local/bin", - "/usr/sbin", - "/usr/bin", - "/sbin", - "/bin", - "/usr/games" + PATH = ["/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", + "/sbin", + "/bin", + "/usr/games", ] def __init__(self, cache): @@ -44,7 +45,7 @@ def _is_exec(self, f): return (os.path.dirname(f) in self.PATH and os.path.exists(f) and - not os.path.isdir(f) and + not os.path.isdir(f) and os.access(f, os.X_OK)) def _get_exec_candidates(self, pkg): @@ -70,15 +71,3 @@ cmds = self._get_exec_candidates(pkg) cmds += self._find_alternatives_for_cmds(cmds) return sorted([os.path.basename(p) for p in cmds]) - -#~ -#~ class CmdFinderWidget(gtk.VBox, CmdFinder): -#~ - #~ def __init__(self, cache): - #~ CmdFinder.__init__(self, cache) - #~ return -#~ - #~ def cmds_from_pkgname(self, pkgname): - #~ cmds = CmdFinder.cmds_from_pkgname(self, pkgname) - - diff -Nru software-center-5.1.12/softwarecenter/config.py software-center-5.1.13/softwarecenter/config.py --- software-center-5.1.12/softwarecenter/config.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/config.py 2012-03-16 08:30:18.000000000 +0000 @@ -19,14 +19,16 @@ # py3 compat try: from configparser import SafeConfigParser - SafeConfigParser # pyflakes + SafeConfigParser # pyflakes except ImportError: from ConfigParser import SafeConfigParser import os from paths import SOFTWARE_CENTER_CONFIG_FILE + class SoftwareCenterConfig(SafeConfigParser): + def __init__(self, config): SafeConfigParser.__init__(self) if not os.path.exists(os.path.dirname(config)): @@ -37,18 +39,21 @@ except: # don't crash on a corrupted config file pass + def write(self): - tmpname = self.configfile+".new" - f=open(tmpname, "w") + tmpname = self.configfile + ".new" + f = open(tmpname, "w") SafeConfigParser.write(self, f) f.close() os.rename(tmpname, self.configfile) - -_software_center_config = None + + +_software_center_config = None + + def get_config(filename=SOFTWARE_CENTER_CONFIG_FILE): """ get the global config class """ global _software_center_config if not _software_center_config: _software_center_config = SoftwareCenterConfig(filename) return _software_center_config - diff -Nru software-center-5.1.12/softwarecenter/db/appfilter.py software-center-5.1.13/softwarecenter/db/appfilter.py --- software-center-5.1.12/softwarecenter/db/appfilter.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/appfilter.py 2012-03-19 08:35:49.000000000 +0000 @@ -5,15 +5,18 @@ AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, ) + class GlobalFilter(object): def __init__(self): self.supported_only = False global_filter = GlobalFilter() + def get_global_filter(): return global_filter + class AppFilter(xapian.MatchDecider): """ Filter that can be hooked into xapian get_mset to filter for criteria that @@ -30,49 +33,62 @@ self.installed_only = False self.not_installed_only = False self.restricted_list = False + @property def required(self): - """ True if the filter is in a state that it should be part of a query """ + """True if the filter is in a state that it should be part of a + query + """ return (self.available_only or global_filter.supported_only or - self.installed_only or + self.installed_only or self.not_installed_only or self.restricted_list) + def set_available_only(self, v): self.available_only = v + def set_supported_only(self, v): global_filter.supported_only = v + def set_installed_only(self, v): self.installed_only = v + def set_not_installed_only(self, v): self.not_installed_only = v + def set_restricted_list(self, v): self.restricted_list = v + def get_supported_only(self): return global_filter.supported_only + def __eq__(self, other): - if self is None and other is not None: + if self is None and other is not None: return True - if self is None or other is None: + if self is None or other is None: return False return (self.installed_only == other.installed_only and self.not_installed_only == other.not_installed_only and global_filter.supported_only == other.supported_only and self.restricted_list == other.restricted_list) + def __ne__(self, other): return not self.__eq__(other) + def __call__(self, doc): """return True if the package should be displayed""" # get pkgname from document - pkgname = self.db.get_pkgname(doc) + pkgname = self.db.get_pkgname(doc) #logging.debug( # "filter: supported_only: %s installed_only: %s '%s'" % ( # self.supported_only, self.installed_only, pkgname)) if self.available_only: # an item is considered available if it is either found # in the cache or is available for purchase - if (not pkgname in self.cache and - not doc.get_value(XapianValues.ARCHIVE_CHANNEL) == AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME): + if (not pkgname in self.cache and + not doc.get_value(XapianValues.ARCHIVE_CHANNEL) == + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME): return False if self.installed_only: if (not pkgname in self.cache or @@ -85,16 +101,17 @@ if global_filter.supported_only: if not self.distro.is_supported(self.cache, doc, pkgname): return False - if self.restricted_list != False: # keep != False as the set can be empty + if self.restricted_list != False: # keep != False as the set can be + # empty if not pkgname in self.restricted_list: return False return True + def copy(self): """ create a new copy of the given filter """ - new_filter= AppFilter(self.db, self.cache) + new_filter = AppFilter(self.db, self.cache) new_filter.available_only = self.available_only new_filter.installed_only = self.installed_only new_filter.not_installed_only = self.not_installed_only new_filter.restricted_list = self.restricted_list return new_filter - diff -Nru software-center-5.1.12/softwarecenter/db/application.py software-center-5.1.13/softwarecenter/db/application.py --- software-center-5.1.12/softwarecenter/db/application.py 2012-03-08 15:15:30.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/application.py 2012-03-20 08:39:44.000000000 +0000 @@ -37,6 +37,7 @@ LOG = logging.getLogger(__name__) + # this is a very lean class as its used in the main listview # and there are a lot of application objects in memory class Application(object): @@ -74,9 +75,11 @@ if self.appname: return self.appname return self.pkgname + @property def popcon(self): return self._popcon + # get a AppDetails object for this Applications def get_details(self, db): """ return a new AppDetails object for this application """ @@ -90,7 +93,8 @@ doc = db.get_xapian_document(self.appname, self.pkgname) except IndexError: return self - untranslated_application = doc.get_value(XapianValues.APPNAME_UNTRANSLATED) + untranslated_application = doc.get_value( + XapianValues.APPNAME_UNTRANSLATED) uapp = Application(untranslated_application, self.pkgname) return uapp @@ -108,11 +112,12 @@ return appname else: return db.get_summary(doc) + @staticmethod def get_display_summary(db, doc): """ Return the application summary as it should be displayed in the UI - If the appname is defined, return the application summary, else return - the application's pkgname (per the spec) + If the appname is defined, return the application summary, else + return the application's pkgname (per the spec) """ if doc: if db.get_appname(doc): @@ -123,12 +128,17 @@ # special methods def __hash__(self): return ("%s:%s" % (self.appname, self.pkgname)).__hash__() + def __cmp__(self, other): return self.apps_cmp(self, other) + def __str__(self): return utf8("%s,%s") % (utf8(self.appname), utf8(self.pkgname)) + def __repr__(self): - return "[Application: appname=%s pkgname=%s]" % (self.appname, self.pkgname) + return "[Application: appname=%s pkgname=%s]" % (self.appname, + self.pkgname) + @staticmethod def apps_cmp(x, y): """ sort method for the applications """ @@ -143,16 +153,17 @@ else: return cmp(x.pkgname, y.pkgname) + # the details class AppDetails(GObject.GObject): """ The details for a Application. This contains all the information we have available like website etc """ - __gsignals__ = {"screenshots-available" : (GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), + __gsignals__ = {"screenshots-available": (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), } def __init__(self, db, doc=None, application=None): @@ -214,7 +225,10 @@ not channel_matches and not section_matches): self._error = _("Not found") - self._error_not_found = utf8(_(u"There isn\u2019t a software package called \u201c%s\u201D in your current software sources.")) % utf8(self.pkgname) + self._error_not_found = utf8( + _(u"There isn\u2019t a " + u"software package called \u201c%s\u201D in your " + u"current software sources.")) % utf8(self.pkgname) def same_app(self, other): return self.pkgname == other.pkgname @@ -241,7 +255,6 @@ raise ValueError("pkg '%s' has not archive_suite '%s'" % ( pkg, archive_suite)) - @property def channelname(self): if self._doc: @@ -251,7 +264,8 @@ return channel else: # check if we have an apturl request to enable a channel - channel_matches = re.findall(r'channel=([0-9a-z,-]*)', self._app.request) + channel_matches = re.findall(r'channel=([0-9a-z,-]*)', + self._app.request) if channel_matches: channel = channel_matches[0] channelfile = APP_INSTALL_CHANNELS_PATH + channel + ".list" @@ -268,7 +282,7 @@ def eulafile(self): channel = self.channelname if channel: - eulafile = APP_INSTALL_CHANNELS_PATH + channel + ".eula" + eulafile = APP_INSTALL_CHANNELS_PATH + channel + ".eula" if os.path.exists(eulafile): return eulafile @@ -292,11 +306,14 @@ return comp # then apturl requests else: - section_matches = re.findall(r'section=([a-z]+)', self._app.request) + section_matches = re.findall(r'section=([a-z]+)', + self._app.request) if section_matches: valid_section_matches = [] for section_match in section_matches: - if self._unavailable_component(component_to_check=section_match) and valid_section_matches.count(section_match) == 0: + if (self._unavailable_component( + component_to_check=section_match) and + valid_section_matches.count(section_match) == 0): valid_section_matches.append(section_match) if valid_section_matches: return ('&').join(valid_section_matches) @@ -305,6 +322,7 @@ def desktop_file(self): if self._doc: return self._doc.get_value(XapianValues.DESKTOP_FILE) + @property def description(self): if self._pkg: @@ -328,8 +346,11 @@ return self._error # this may have changed since we inited the appdetails elif self.pkg_state == PkgStates.NOT_FOUND: - self._error = _("Not found") - self._error_not_found = utf8(_(u"There isn\u2019t a software package called \u201c%s\u201D in your current software sources.")) % utf8(self.pkgname) + self._error = _("Not found") + self._error_not_found = utf8( + _(u"There isn\u2019t a software " + u"package called \u201c%s\u201D in your current software " + u"sources.")) % utf8(self.pkgname) return self._error_not_found @property @@ -354,7 +375,8 @@ @property def cached_icon_file_path(self): if self._doc: - return os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, self._db.get_iconname(self._doc)) + return os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, + self._db.get_iconname(self._doc)) @property def installation_date(self): @@ -416,8 +438,8 @@ @property def display_summary(self): """ Return the application summary as it should be displayed in the UI - If the appname is defined, return the application summary, else return - the application's pkgname (per the spec) + If the appname is defined, return the application summary, else + return the application's pkgname (per the spec) """ if self._doc: return Application.get_display_summary(self._db, self._doc) @@ -456,7 +478,7 @@ # not-automatic channels (like experimental/backports) if self._pkg: if self._pkg.installed and self._app.archive_suite: - archive_suites = [origin.archive + archive_suites = [origin.archive for origin in self._pkg.installed.origins] if not self._app.archive_suite in archive_suites: return PkgStates.FORCE_VERSION @@ -477,8 +499,11 @@ if self._unavailable_channel(): return PkgStates.NEEDS_SOURCE else: - self._error = _("Not found") - self._error_not_found = utf8(_(u"There isn\u2019t a software package called \u201c%s\u201D in your current software sources.")) % utf8(self.pkgname) + self._error = _("Not found") + self._error_not_found = utf8( + _(u"There isn\u2019t a " + u"software package called \u201c%s\u201D in your " + u"current software sources.")) % utf8(self.pkgname) return PkgStates.NOT_FOUND else: if self.price: @@ -505,10 +530,14 @@ if self.component: components = self.component.split('&') for component in components: - if component and self._unavailable_component(component_to_check=component): + if component and self._unavailable_component( + component_to_check=component): return PkgStates.NEEDS_SOURCE - self._error = _("Not found") - self._error_not_found = utf8(_(u"There isn\u2019t a software package called \u201c%s\u201D in your current software sources.")) % utf8(self.pkgname) + self._error = _("Not found") + self._error_not_found = utf8( + _(u"There isn\u2019t a software " + u"package called \u201c%s\u201D in your current " + u"software sources.")) % utf8(self.pkgname) return PkgStates.NOT_FOUND return PkgStates.UNKNOWN @@ -520,7 +549,8 @@ @property def supported_distros(self): if self._doc: - supported_series = self._doc.get_value(XapianValues.SC_SUPPORTED_DISTROS) + supported_series = self._doc.get_value( + XapianValues.SC_SUPPORTED_DISTROS) if not supported_series: return {} @@ -552,8 +582,10 @@ if screenshot_url: return screenshot_url.split(",")[0] # else use the default - return self._distro.SCREENSHOT_LARGE_URL % { 'pkgname' : self.pkgname, - 'version' : self.version or 0 } + return self._distro.SCREENSHOT_LARGE_URL % { + 'pkgname': self.pkgname, + 'version': self.version or 0, + } @property def screenshots(self): @@ -561,10 +593,11 @@ "query_multiple_screenshos" was run before and emited the signal """ if not self._screenshot_list: - return [ {'small_image_url': self.thumbnail, - 'large_image_url': self.screenshot, - 'version': self.version}, - ] + return [{ + 'small_image_url': self.thumbnail, + 'large_image_url': self.screenshot, + 'version': self.version, + }] return self._screenshot_list @property @@ -583,17 +616,18 @@ screenshot_url = self._doc.get_value(XapianValues.SCREENSHOT_URLS) if screenshot_url and len(screenshot_url.split(",")) > 1: for screenshot in screenshot_url.split(","): - screenshot_list.append({'small_image_url' : screenshot, - 'large_image_url' : screenshot, - 'version' : self.version, - }) + screenshot_list.append({ + 'small_image_url': screenshot, + 'large_image_url': screenshot, + 'version': self.version, + }) return screenshot_list def query_multiple_screenshots(self): """ query if multiple screenshots for the given app are available and if so, emit "screenshots-available" signal """ - # get screenshot list from the db, if that is empty thats fine, + # get screenshot list from the db, if that is empty thats fine, # and we will query the screenshot server if not self._screenshot_list: self._screenshot_list = self._get_multiple_screenshots_from_db() @@ -612,7 +646,7 @@ LOG.exception("failed to load content") def _sort_screenshots_by_best_version(self, screenshot_list): - """ take a screenshot result dict from screenshots.debian.org + """ take a screenshot result dict from screenshots.debian.org and sort it """ from softwarecenter.utils import version_compare @@ -624,9 +658,9 @@ screenshot_list.remove(item) # now sort from high to low return sorted( - screenshot_list, - cmp=lambda a,b: version_compare(a["version"] or '', - b["version"] or ''), + screenshot_list, + cmp=lambda a, b: version_compare(a["version"] or '', + b["version"] or ''), reverse=True) def _gio_screenshots_json_download_complete_cb(self, source, result, path): @@ -649,7 +683,6 @@ self._screenshot_list = self._sort_screenshots_by_best_version( screenshot_list) self.emit("screenshots-available", self._screenshot_list) - return @property def summary(self): @@ -671,8 +704,10 @@ if self._doc.get_value(XapianValues.THUMBNAIL_URL): return self._doc.get_value(XapianValues.THUMBNAIL_URL) # else use the default - return self._distro.SCREENSHOT_THUMB_URL % { 'pkgname' : self.pkgname, - 'version' : self.version or 0} + return self._distro.SCREENSHOT_THUMB_URL % { + 'pkgname': self.pkgname, + 'version': self.version or 0, + } @property def video_url(self): @@ -681,9 +716,10 @@ if self._doc.get_value(XapianValues.VIDEO_URL): return self._doc.get_value(XapianValues.VIDEO_URL) # else use the video server - #return self._distro.VIDEO_URL % { 'pkgname' : self.pkgname, - # 'version' : self.version or 0} - return None + #return self._distro.VIDEO_URL % { + # 'pkgname' : self.pkgname, + # 'version' : self.version or 0, + #} @property def version(self): @@ -693,9 +729,12 @@ else: ver = self._get_version_for_archive_suite( self._pkg, self._app.archive_suite) - return ver.version - elif self._doc: + if ver: + return ver.version + if self._doc: return self._doc.get_value(XapianValues.VERSION_INFO) + LOG.warn("no version information found for '%s'" % self.pkgname) + return "" def get_not_automatic_archive_versions(self): """ this will return list of tuples (version, archive_suites) @@ -745,7 +784,8 @@ # apturl minver matches if not self.pkg_state == PkgStates.INSTALLED: if self._app.request: - minver_matches = re.findall(r'minver=[a-z,0-9,-,+,.,~]*', self._app.request) + minver_matches = re.findall(r'minver=[a-z,0-9,-,+,.,~]*', + self._app.request) if minver_matches and self.version: minver = minver_matches[0][7:] from softwarecenter.utils import version_compare @@ -763,10 +803,11 @@ sources = source_to_enable.split('&') sources_length = len(sources) if sources_length == 1: - warning = _("Available from the \"%s\" source.") % sources[0] + warning = (_("Available from the \"%s\" source.") % + sources[0]) elif sources_length > 1: - # Translators: the visible string is constructed concatenating - # the following 3 strings like this: + # Translators: the visible string is constructed + # concatenating the following 3 strings like this: # Available from the following sources: %s, ... %s, %s. warning = _("Available from the following sources: ") # Cycle through all, but the last @@ -805,7 +846,7 @@ if tag.startswith(REGIONTAG): # we found a region tag, now the region must match res = False - if tag == REGIONTAG+my_region: + if tag == REGIONTAG + my_region: # we have the right region return True return res @@ -821,27 +862,29 @@ def hardware_requirements(self): result = {} try: - from debtagshw.debtagshw import DebtagsAvailableHW - hw = DebtagsAvailableHW() - result = hw.get_hardware_support_for_tags( - self.tags) + from softwarecenter.hw import get_hardware_support_for_tags + result = get_hardware_support_for_tags(self.tags) except ImportError: LOG.warn("failed to import debtagshw") return result return result def _unavailable_channel(self): - """ Check if the given doc refers to a channel that is currently not enabled """ + """ Check if the given doc refers to a channel that is currently + not enabled + """ return not is_channel_available(self.channelname) def _unavailable_component(self, component_to_check=None): - """ Check if the given doc refers to a component that is currently not enabled """ + """ Check if the given doc refers to a component that is currently + not enabled + """ if component_to_check: component = component_to_check elif self.component: component = self.component else: - component = self._doc.get_value(XapianValues.ARCHIVE_SECTION) + component = self._doc.get_value(XapianValues.ARCHIVE_SECTION) if not component: return False distro_codename = self._distro.get_codename() @@ -868,7 +911,8 @@ details.append(" installation_date: %s" % self.installation_date) details.append(" purchase_date: %s" % self.purchase_date) details.append(" license: %s" % self.license) - details.append(" license_key: %s" % self.license_key[0:3] + len(self.license_key)*"*") + details.append(" license_key: %s" % self.license_key[0:3] + + len(self.license_key) * "*") details.append(" license_key_path: %s" % self.license_key_path) details.append(" date_published: %s" % self.date_published) details.append(" maintenance_status: %s" % self.maintenance_status) diff -Nru software-center-5.1.12/softwarecenter/db/categories.py software-center-5.1.13/softwarecenter/db/categories.py --- software-center-5.1.12/softwarecenter/db/categories.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/categories.py 2012-03-20 10:52:42.000000000 +0000 @@ -37,19 +37,22 @@ from softwarecenter.db.utils import get_query_for_pkgnames from softwarecenter.paths import APP_INSTALL_PATH from softwarecenter.region import get_region_cached +from softwarecenter.utils import utf8 from gettext import gettext as _ # not possible not use local logger LOG = logging.getLogger(__name__) + def get_category_by_name(categories, untrans_name): # find a specific category cat = [cat for cat in categories if cat.untranslated_name == untrans_name] - if cat: + if cat: return cat[0] return None + def categories_sorted_by_name(categories): # sort categories by name sorted_catnames = [] @@ -66,7 +69,8 @@ sorted_cats.append(cat) break return sorted_cats - + + def get_query_for_category(db, untranslated_category_name): cat_parser = CategoriesParser(db) categories = cat_parser.parse_applications_menu(APP_INSTALL_PATH) @@ -80,7 +84,7 @@ class Category(GObject.GObject): """represents a menu category""" def __init__(self, untranslated_name, name, iconname, query, - only_unallocated=True, dont_display=False, flags=[], + only_unallocated=True, dont_display=False, flags=[], subcategories=[], sortmode=SortMethods.BY_ALPHABET, item_limit=0): GObject.GObject.__init__(self) @@ -129,21 +133,35 @@ class RecommendedForYouCategory(Category): __gsignals__ = { - "needs-refresh" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), - "recommender-agent-error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_STRING,), - ), + "needs-refresh": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), + "recommender-agent-error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_STRING,), + ), } - def __init__(self): + def __init__(self, subcategory=None): + self.subcategory = subcategory + if subcategory: + # this is the set of recommendations for a given subcategory + cat_title = u"Recommended For You in %s" % ( + subcategory.untranslated_name) + tr_title = utf8(_("Recommended For You in %s")) % utf8( + subcategory.name) + else: + # this is the full set of recommendations for e.g. the lobby view + cat_title = u"Recommended For You" + tr_title = _("Recommended For You") super(RecommendedForYouCategory, self).__init__( - u"Recommended for You", _("Recommended for You"), None, - xapian.Query(),flags=['available-only', 'not-installed-only'], - item_limit=60) + cat_title, + tr_title, + None, + xapian.Query(), + flags=['available-only', 'not-installed-only'], + item_limit=60) self.recommender_agent = RecommenderAgent() self.recommender_agent.connect( "recommend-me", self._recommend_me_result) @@ -155,32 +173,41 @@ pkgs = [] for item in result_list['data']: pkgs.append(item['package_name']) - self.query = get_query_for_pkgnames(pkgs) + if self.subcategory: + self.query = xapian.Query(xapian.Query.OP_AND, + get_query_for_pkgnames(pkgs), + self.subcategory.query) + else: + self.query = get_query_for_pkgnames(pkgs) self.emit("needs-refresh") def _recommender_agent_error(self, recommender_agent, msg): - LOG.warn("Error while accessing the recommender service: %s" + LOG.warn("Error while accessing the recommender service: %s" % msg) self.emit("recommender-agent-error", msg) - + + class AppRecommendationsCategory(Category): __gsignals__ = { - "needs-refresh" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), - "recommender-agent-error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_STRING,), - ), + "needs-refresh": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), + "recommender-agent-error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_STRING,), + ), } def __init__(self, pkgname): super(AppRecommendationsCategory, self).__init__( - u"People Also Installed", _("People Also Installed"), None, - xapian.Query(),flags=['available-only', 'not-installed-only'], - item_limit=4) + u"People Also Installed", + _(u"People Also Installed"), + None, + xapian.Query(), + flags=['available-only', 'not-installed-only'], + item_limit=4) self.recommender_agent = RecommenderAgent() self.recommender_agent.connect( "recommend-app", self._recommend_app_result) @@ -196,12 +223,13 @@ self.emit("needs-refresh") def _recommender_agent_error(self, recommender_agent, msg): - LOG.warn("Error while accessing the recommender service: %s" + LOG.warn("Error while accessing the recommender service: %s" % msg) self.emit("recommender-agent-error", msg) + class CategoriesParser(object): - """ + """ Parser that is able to read the categories from a menu file """ @@ -214,8 +242,8 @@ """ parse a application menu and return a list of Category objects """ categories = [] # we support multiple menu files and menu drop ins - menu_files = [ datadir+"/desktop/software-center.menu" ] - menu_files += glob.glob(datadir+"/menu.d/*.menu") + menu_files = [datadir + "/desktop/software-center.menu"] + menu_files += glob.glob(datadir + "/menu.d/*.menu") for f in menu_files: if not os.path.exists(f): continue @@ -230,7 +258,7 @@ # post processing for # now build the unallocated queries, once for top-level, # and for the subcategories. this means that subcategories - # can have a "OnlyUnallocated/" that applies only to + # can have a "OnlyUnallocated/" that applies only to # unallocated entries in their sublevel for cat in categories: self._build_unallocated_queries(cat.subcategories) @@ -246,15 +274,14 @@ currently used for the CURRENT_REGION """ region = "%s" % get_region_cached()["countrycode"] - self._template_dict = { 'CURRENT_REGION' : region, - } + self._template_dict = {'CURRENT_REGION': region} def _substitute_string_if_needed(self, t): """ substitute the given string with the current supported dynamic menu keys """ return string.Template(t).substitute(self._template_dict) - + def _cat_sort_cmp(self, a, b): """sort helper for the categories sorting""" #print "cmp: ", a.name, b.name @@ -277,7 +304,7 @@ LOG.debug("reading '%s'" % fname) cp.read(fname) try: - untranslated_name = name = cp.get("Desktop Entry","Name") + untranslated_name = name = cp.get("Desktop Entry", "Name") except Exception: LOG.warn("'%s' has no name" % fname) return None @@ -286,7 +313,7 @@ except: gettext_domain = None try: - icon = cp.get("Desktop Entry","Icon") + icon = cp.get("Desktop Entry", "Icon") except Exception: icon = "applications-other" name = cp.get_desktop("Name", translated=True) @@ -303,7 +330,8 @@ for operator_elem in element.getchildren(): # get the query-text if operator_elem.text: - qtext = self._substitute_string_if_needed(operator_elem.text).lower() + qtext = self._substitute_string_if_needed( + operator_elem.text).lower() # parse the indivdual element if operator_elem.tag == "Not": query = self._parse_and_or_not_tag( @@ -314,7 +342,7 @@ query = xapian.Query(xapian.Query.OP_AND, or_elem, query) elif operator_elem.tag == "Category": LOG.debug("adding: %s" % operator_elem.text) - q = xapian.Query("AC"+qtext) + q = xapian.Query("AC" + qtext) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCSection": LOG.debug("adding section: %s" % operator_elem.text) @@ -323,54 +351,58 @@ # FIXME: ponder if it makes sense to simply write # out XS in update-software-center instead of AE? q = xapian.Query(xapian.Query.OP_OR, - xapian.Query("XS"+qtext), - xapian.Query("AE"+qtext)) + xapian.Query("XS" + qtext), + xapian.Query("AE" + qtext)) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCType": LOG.debug("adding type: %s" % operator_elem.text) - q = xapian.Query("AT"+qtext) + q = xapian.Query("AT" + qtext) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCDebtag": LOG.debug("adding debtag: %s" % operator_elem.text) - q = xapian.Query("XT"+qtext) + q = xapian.Query("XT" + qtext) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCChannel": LOG.debug("adding channel: %s" % operator_elem.text) - q = xapian.Query("AH"+qtext) + q = xapian.Query("AH" + qtext) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCOrigin": LOG.debug("adding origin: %s" % operator_elem.text) # FIXME: origin is currently case-sensitive?!? - q = xapian.Query("XOO"+operator_elem.text) + q = xapian.Query("XOO" + operator_elem.text) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCPkgname": LOG.debug("adding tag: %s" % operator_elem.text) # query both axi and s-c - q1 = xapian.Query("AP"+qtext) + q1 = xapian.Query("AP" + qtext) q = xapian.Query(xapian.Query.OP_OR, q1, - xapian.Query("XP"+qtext)) + xapian.Query("XP" + qtext)) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCPkgnameWildcard": LOG.debug("adding tag: %s" % operator_elem.text) # query both axi and s-c s = "pkg_wildcard:%s" % qtext - q = self.db.xapian_parser.parse_query(s, xapian.QueryParser.FLAG_WILDCARD) + q = self.db.xapian_parser.parse_query(s, + xapian.QueryParser.FLAG_WILDCARD) query = xapian.Query(xapian_op, query, q) - else: - LOG.warn("UNHANDLED: %s %s" % (operator_elem.tag, operator_elem.text)) + else: + LOG.warn("UNHANDLED: %s %s" % (operator_elem.tag, + operator_elem.text)) return query def _parse_include_tag(self, element): for include in element.getchildren(): if include.tag == "Or": query = xapian.Query() - return self._parse_and_or_not_tag(include, query, xapian.Query.OP_OR) + return self._parse_and_or_not_tag(include, query, + xapian.Query.OP_OR) if include.tag == "And": query = xapian.Query("") - return self._parse_and_or_not_tag(include, query, xapian.Query.OP_AND) + return self._parse_and_or_not_tag(include, query, + xapian.Query.OP_AND) # without "and" tag we take the first entry elif include.tag == "Category": - return xapian.Query("AC"+include.text.lower()) + return xapian.Query("AC" + include.text.lower()) else: LOG.warn("UNHANDLED: _parse_include_tag: %s" % include.tag) # empty query matches all @@ -389,8 +421,9 @@ item_limit = 0 for element in item.getchildren(): # ignore inline translations, we use gettext for this - if (element.tag == "Name" and - '{http://www.w3.org/XML/1998/namespace}lang' in element.attrib): + if (element.tag == "Name" and + '{http://www.w3.org/XML/1998/namespace}lang' in + element.attrib): continue if element.tag == "Name": untranslated_name = element.text @@ -425,22 +458,24 @@ subcategories.append(subcat) else: LOG.warn("UNHANDLED tag in _parse_menu_tag: %s" % element.tag) - + if untranslated_name and query: - return Category(untranslated_name, name, icon, query, only_unallocated, dont_display, flags, subcategories, sortmode, item_limit) + return Category(untranslated_name, name, icon, query, + only_unallocated, dont_display, flags, subcategories, + sortmode, item_limit) else: - LOG.warn("UNHANDLED entry: %s %s %s %s" % (name, - untranslated_name, - icon, + LOG.warn("UNHANDLED entry: %s %s %s %s" % (name, + untranslated_name, + icon, query)) return None def _verify_supported_sort_mode(self, sortmode): """ verify that we use a sortmode that we know and can handle """ # always supported - if sortmode in (SortMethods.UNSORTED, - SortMethods.BY_ALPHABET, - SortMethods.BY_TOP_RATED, + if sortmode in (SortMethods.UNSORTED, + SortMethods.BY_ALPHABET, + SortMethods.BY_TOP_RATED, SortMethods.BY_SEARCH_RANKING): return True # only supported with a apt-xapian-index version that has the @@ -462,7 +497,8 @@ continue for cat in categories: if cat.name != cat_unalloc.name: - cat_unalloc.query = xapian.Query(xapian.Query.OP_AND_NOT, cat_unalloc.query, cat.query) + cat_unalloc.query = xapian.Query(xapian.Query.OP_AND_NOT, + cat_unalloc.query, cat.query) #print cat_unalloc.name, cat_unalloc.query return @@ -530,4 +566,5 @@ 'Translation': 'Developer Tools;Localization', 'Profiling': 'Developer Tools;Profiling', 'RevisionControl': 'Developer Tools;Version Control', -'WebDevelopment': 'Developer Tools;Web Development',} +'WebDevelopment': 'Developer Tools;Web Development', +} diff -Nru software-center-5.1.12/softwarecenter/db/database.py software-center-5.1.13/softwarecenter/db/database.py --- software-center-5.1.12/softwarecenter/db/database.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/database.py 2012-03-20 11:12:30.000000000 +0000 @@ -40,8 +40,11 @@ from softwarecenter.paths import XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT from gettext import gettext as _ +LOG = logging.getLogger(__name__) + + def parse_axi_values_file(filename="/var/lib/apt-xapian-index/values"): - """ parse the apt-xapian-index "values" file and provide the + """ parse the apt-xapian-index "values" file and provide the information in the self._axi_values dict """ axi_values = {} @@ -55,6 +58,7 @@ axi_values[key] = int(value) return axi_values + class SearchQuery(list): """ a list wrapper for a search query. it can take a search string or a list of search strings @@ -69,6 +73,7 @@ self.append(query_string_or_list) else: self.extend(query_string_or_list) + def __eq__(self, other): # turn single querries into a single item list if isinstance(other, xapian.Query): @@ -76,26 +81,32 @@ q1 = [str(q) for q in self] q2 = [str(q) for q in other] return q1 == q2 + def __ne__(self, other): return not self.__eq__(other) + def __repr__(self): return "[%s]" % ",".join([str(q) for q in self]) + class LocaleSorter(xapian.KeyMaker): """ Sort in a locale friendly way by using locale.xtrxfrm """ def __init__(self, db): super(LocaleSorter, self).__init__() self.db = db + def __call__(self, doc): return locale.strxfrm( doc.get_value(self.db._axi_values["display_name"])) + class TopRatedSorter(xapian.KeyMaker): """ Sort using the top rated data """ def __init__(self, db, review_loader): super(TopRatedSorter, self).__init__() self.db = db self.review_loader = review_loader + def __call__(self, doc): app = Application(self.db.get_appname(doc), self.db.get_pkgname(doc)) @@ -105,6 +116,7 @@ return xapian.sortable_serialise(stats.dampened_rating) return xapian.sortable_serialise(0) + class StoreDatabase(GObject.GObject): """thin abstraction for the xapian database with convenient functions""" @@ -117,13 +129,14 @@ "suite;tool") # signal emited - __gsignals__ = {"reopen" : (GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - ()), - "open" : (GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (GObject.TYPE_STRING,)), + __gsignals__ = {"reopen": (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + ()), + "open": (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (GObject.TYPE_STRING,)), } + def __init__(self, pathname=None, cache=None): GObject.GObject.__init__(self) if pathname is None: @@ -135,11 +148,10 @@ self._additional_databases = [] # the xapian values as read from /var/lib/apt-xapian-index/values self._axi_values = {} - self._logger = logging.getLogger("softwarecenter.db") # we open one db per thread, thread names are reused eventually # so no memory leak - self._db_per_thread = {} - self._parser_per_thread = {} + self._db_per_thread = {} + self._parser_per_thread = {} @property def xapiandb(self): @@ -154,7 +166,8 @@ """ returns a per thread query parser """ thread_name = threading.current_thread().name if not thread_name in self._parser_per_thread: - self._parser_per_thread[thread_name] = self._get_new_xapian_parser() + xapian_parser = self._get_new_xapian_parser() + self._parser_per_thread[thread_name] = xapian_parser return self._parser_per_thread[thread_name] def _get_new_xapiandb(self): @@ -164,8 +177,8 @@ axi = xapian.Database("/var/lib/apt-xapian-index/index") xapiandb.add_database(axi) except: - self._logger.exception("failed to add apt-xapian-index") - if (self._use_agent and + LOG.exception("failed to add apt-xapian-index") + if (self._use_agent and os.path.exists(XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT)): try: sca = xapian.Database(XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT) @@ -188,14 +201,16 @@ xapian_parser.add_prefix("pkg_wildcard", "AP") xapian_parser.set_default_op(xapian.Query.OP_AND) return xapian_parser - + def open(self, pathname=None, use_axi=True, use_agent=True): """ open the database """ + LOG.info("open() database: path=%s use_axi=%s " + "use_agent=%s" % (pathname, use_axi, use_agent)) if pathname: self._db_pathname = pathname # clean existing DBs on open - self._db_per_thread = {} - self._parser_per_thread = {} + self._db_per_thread = {} + self._parser_per_thread = {} # add the apt-xapian-database for here (we don't do this # for now as we do not have a good way to integrate non-apps # with the UI) @@ -223,24 +238,27 @@ def schema_version(self): """Return the version of the database layout - + This is useful to ensure we force a rebuild if its older than what we expect """ return self.xapiandb.get_metadata("db-schema-version") def reopen(self): - " reopen the database " + """ reopen the database """ + LOG.info("reopen() database") self.open(use_axi=self._use_axi, use_agent=self._use_agent) self.emit("reopen") @property def popcon_max(self): - popcon_max = xapian.sortable_unserialise(self.xapiandb.get_metadata("popcon_max_desktop")) + popcon_max = xapian.sortable_unserialise(self.xapiandb.get_metadata( + "popcon_max_desktop")) assert popcon_max > 0 return popcon_max - def get_query_list_from_search_entry(self, search_term, category_query=None): + def get_query_list_from_search_entry(self, search_term, + category_query=None): """ get xapian.Query from a search term string and a limit the search to the given category """ @@ -248,7 +266,7 @@ """ helper that adds the current category to the query""" if not category_query: return query - return xapian.Query(xapian.Query.OP_AND, + return xapian.Query(xapian.Query.OP_AND, category_query, query) # empty query returns a query that matches nothing (for performance @@ -268,11 +286,12 @@ orig_search_term = search_term for item in self.SEARCH_GREYLIST_STR.split(";"): (search_term, n) = re.subn('\\b%s\\b' % item, '', search_term) - if n: - self._logger.debug("greylist changed search term: '%s'" % search_term) + if n: + LOG.debug("greylist changed search term: '%s'" % + search_term) # restore query if it was just greylist words if search_term == '': - self._logger.debug("grey-list replaced all terms, restoring") + LOG.debug("grey-list replaced all terms, restoring") search_term = orig_search_term # we have to strip the leading and trailing whitespaces to avoid having # different results for e.g. 'font ' and 'font' (LP: #506419) @@ -284,25 +303,25 @@ pkg_query = xapian.Query() for term in search_term.split(): pkg_query = xapian.Query(xapian.Query.OP_OR, - xapian.Query("XP"+term), + xapian.Query("XP" + term), pkg_query) pkg_query = _add_category_to_query(pkg_query) # get a search query - if not ':' in search_term: # ie, not a mimetype query + if not ':' in search_term: # ie, not a mimetype query # we need this to work around xapian oddness - search_term = search_term.replace('-','_') - fuzzy_query = self.xapian_parser.parse_query(search_term, - xapian.QueryParser.FLAG_PARTIAL| - xapian.QueryParser.FLAG_BOOLEAN) + search_term = search_term.replace('-', '_') + fuzzy_query = self.xapian_parser.parse_query(search_term, + xapian.QueryParser.FLAG_PARTIAL | + xapian.QueryParser.FLAG_BOOLEAN) # if the query size goes out of hand, omit the FLAG_PARTIAL # (LP: #634449) if fuzzy_query.get_length() > 1000: - fuzzy_query = self.xapian_parser.parse_query(search_term, + fuzzy_query = self.xapian_parser.parse_query(search_term, xapian.QueryParser.FLAG_BOOLEAN) # now add categories fuzzy_query = _add_category_to_query(fuzzy_query) - return SearchQuery([pkg_query,fuzzy_query]) + return SearchQuery([pkg_query, fuzzy_query]) def get_matches_from_query(self, query, start=0, end=-1, category=None): enquire = xapian.Enquire(self.xapiandb) @@ -314,7 +333,7 @@ if category: query = xapian.Query(xapian.Query.OP_AND, category.query, query) enquire.set_query(query) - if end == -1: + if end == -1: end = len(self) return enquire.get_mset(start, end) @@ -324,30 +343,30 @@ def get_spelling_correction(self, search_term): # get a search query - if not ':' in search_term: # ie, not a mimetype query + if not ':' in search_term: # ie, not a mimetype query # we need this to work around xapian oddness - search_term = search_term.replace('-','_') + search_term = search_term.replace('-', '_') self.xapian_parser.parse_query( search_term, xapian.QueryParser.FLAG_SPELLING_CORRECTION) return self.xapian_parser.get_corrected_query_string() - def get_most_popular_applications_for_mimetype(self, mimetype, - only_uninstalled=True, num=3): + def get_most_popular_applications_for_mimetype(self, mimetype, + only_uninstalled=True, num=3): """ return a list of the most popular applications for the given - mimetype + mimetype """ # sort by popularity by default enquire = xapian.Enquire(self.xapiandb) enquire.set_sort_by_value_then_relevance(XapianValues.POPCON) # query mimetype - query = xapian.Query("AM%s"%mimetype) + query = xapian.Query("AM%s" % mimetype) enquire.set_query(query) # mset just needs to be "big enough"" matches = enquire.get_mset(0, 100) apps = [] for match in matches: doc = match.document - app = Application(self.get_appname(doc),self.get_pkgname(doc), + app = Application(self.get_appname(doc), self.get_pkgname(doc), popcon=self.get_popcon(doc)) if only_uninstalled: if app.get_details(self).pkg_state == PkgStates.UNINSTALLED: @@ -364,9 +383,9 @@ channel = doc.get_value(XapianValues.ARCHIVE_CHANNEL) # if we do not have the summary in the xapian db, get it # from the apt cache - if not summary and self._aptcache.ready: + if not summary and self._aptcache.ready: pkgname = self.get_pkgname(doc) - if (pkgname in self._aptcache and + if (pkgname in self._aptcache and self._aptcache[pkgname].candidate): return self._aptcache[pkgname].candidate.summary elif channel: @@ -384,7 +403,7 @@ def get_pkgname(self, doc): """ Return a packagename from a xapian document """ pkgname = doc.get_value(XapianValues.PKGNAME) - # if there is no value it means we use the apt-xapian-index + # if there is no value it means we use the apt-xapian-index # that stores the pkgname in the data field or as a value if not pkgname: # the doc says that get_value() is quicker than get_data() @@ -409,10 +428,11 @@ def pkg_in_category(self, pkgname, cat_query): """ Return True if the given pkg is in the given category """ - pkg_query1 = xapian.Query("AP"+pkgname) - pkg_query2 = xapian.Query("XP"+pkgname) + pkg_query1 = xapian.Query("AP" + pkgname) + pkg_query2 = xapian.Query("XP" + pkgname) pkg_query = xapian.Query(xapian.Query.OP_OR, pkg_query1, pkg_query2) - pkg_and_cat_query = xapian.Query(xapian.Query.OP_AND, pkg_query, cat_query) + pkg_and_cat_query = xapian.Query(xapian.Query.OP_AND, pkg_query, + cat_query) enquire = xapian.Enquire(self.xapiandb) enquire.set_query(pkg_and_cat_query) matches = enquire.get_mset(0, len(self)) @@ -424,10 +444,10 @@ """ Return set of docids with the matching applications for the given pkgname """ result = set() - for m in self.xapiandb.postlist("AP"+pkgname): + for m in self.xapiandb.postlist("AP" + pkgname): result.add(m.docid) return result - + def get_icon_download_url(self, doc): """ Return the url of the icon or None """ url = doc.get_value(XapianValues.ICON_URL) @@ -444,39 +464,41 @@ def get_xapian_document(self, appname, pkgname): """ Get the machting xapian document for appname, pkgname - + If no document is found, raise a IndexError """ - #self._logger.debug("get_xapian_document app='%s' pkg='%s'" % (appname,pkgname)) + #LOG.debug("get_xapian_document app='%s' pkg='%s'" % (appname, + # pkgname)) # first search for appname in the app-install-data namespace - for m in self.xapiandb.postlist("AA"+appname): + for m in self.xapiandb.postlist("AA" + appname): doc = self.xapiandb.get_document(m.docid) if doc.get_value(XapianValues.PKGNAME) == pkgname: return doc # then search for pkgname in the app-install-data namespace - for m in self.xapiandb.postlist("AP"+pkgname): + for m in self.xapiandb.postlist("AP" + pkgname): doc = self.xapiandb.get_document(m.docid) if doc.get_value(XapianValues.PKGNAME) == pkgname: return doc # then look for matching packages from a-x-i - for m in self.xapiandb.postlist("XP"+pkgname): + for m in self.xapiandb.postlist("XP" + pkgname): doc = self.xapiandb.get_document(m.docid) return doc # no matching document found - raise IndexError("No app '%s' for '%s' in database" % (appname,pkgname)) + raise IndexError("No app '%s' for '%s' in database" % (appname, + pkgname)) def is_appname_duplicated(self, appname): """Check if the given appname is stored multiple times in the db This can happen for generic names like "Terminal" """ - for (i, m) in enumerate(self.xapiandb.postlist("AA"+appname)): + for (i, m) in enumerate(self.xapiandb.postlist("AA" + appname)): if i > 0: return True return False def get_installed_purchased_packages(self): """ return a set() of packagenames of purchased apps that are - currently installed + currently installed """ for_purchase_query = xapian.Query( "AH" + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME) @@ -500,21 +522,22 @@ return list(origins) def get_exact_matches(self, pkgnames=[]): - """ Returns a list of fake MSetItems. If the pkgname is available, then - MSetItem.document is pkgnames proper xapian document. If the pkgname - is not available, then MSetItem is actually an Application. """ + """Returns a list of fake MSetItems. If the pkgname is available, then + MSetItem.document is pkgnames proper xapian document. If the pkgname + is not available, then MSetItem is actually an Application. + """ matches = [] for pkgname in pkgnames: app = Application('', pkgname.split('?')[0]) if '?' in pkgname: app.request = pkgname.split('?')[1] match = app - for m in self.xapiandb.postlist("XP"+app.pkgname): + for m in self.xapiandb.postlist("XP" + app.pkgname): match = self.xapiandb.get_document(m.docid) - for m in self.xapiandb.postlist("AP"+app.pkgname): + for m in self.xapiandb.postlist("AP" + app.pkgname): match = self.xapiandb.get_document(m.docid) matches.append(FakeMSetItem(match)) - return matches + return matches def __len__(self): """return the doc count of the database""" @@ -526,6 +549,7 @@ doc = self.xapiandb.get_document(it.docid) yield doc + class FakeMSetItem(): def __init__(self, doc): self.document = doc @@ -551,9 +575,8 @@ print(doc.get_data()) # test origin - query = xapian.Query("XOL"+"Ubuntu") + query = xapian.Query("XOL" + "Ubuntu") enquire = xapian.Enquire(db.xapiandb) enquire.set_query(query) matches = enquire.get_mset(0, len(db)) print("Ubuntu origin: %s" % len(matches)) - diff -Nru software-center-5.1.12/softwarecenter/db/debfile.py software-center-5.1.13/softwarecenter/db/debfile.py --- software-center-5.1.12/softwarecenter/db/debfile.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/debfile.py 2012-03-15 09:34:35.000000000 +0000 @@ -26,6 +26,7 @@ from softwarecenter.enums import PkgStates from softwarecenter.utils import ExecutionTime, utf8 + class DebFileApplication(Application): def __init__(self, debfile): @@ -37,13 +38,15 @@ pkgname = debname.split('_')[0].lower() # call the constructor Application.__init__(self, pkgname=pkgname, request=debfile) + def get_details(self, db): with ExecutionTime("get_details for DebFileApplication"): details = AppDetailsDebFile(db, application=self) return details + class AppDetailsDebFile(AppDetails): - + def __init__(self, db, doc=None, application=None): super(AppDetailsDebFile, self).__init__(db, doc, application) if doc: @@ -59,16 +62,20 @@ self._pkg = None if not os.path.exists(self._app.request): self._error = _("Not found") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d does not exist.")) % utf8(self._app.request) + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "does not exist.")) % utf8(self._app.request) else: mimetype = guess_type(self._app.request) if mimetype[0] != "application/x-debian-package": - self._error = _("Not found") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d is not a software package.")) % utf8(self._app.request) + self._error = _("Not found") + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "is not a software package.")) % utf8( + self._app.request) else: # deb files which are corrupt - self._error = _("Internal Error") - self._error_not_found = utf8(_(u"The file \u201c%s\u201d could not be opened.")) % utf8(self._app.request) + self._error = _("Internal Error") + self._error_not_found = utf8(_(u"The file \u201c%s\u201d " + "could not be opened.")) % utf8(self._app.request) return if self.pkgname and self.pkgname != self._app.pkgname: @@ -77,7 +84,7 @@ # load pkg cache self._pkg = None - if (self._app.pkgname in self._cache and + if (self._app.pkgname in self._cache and self._cache[self._app.pkgname].candidate): self._pkg = self._cache[self._app.pkgname] # load xapian document @@ -102,7 +109,7 @@ @property def maintenance_status(self): - return None + pass @property def pkgname(self): @@ -132,7 +139,7 @@ return PkgStates.UPGRADABLE else: return PkgStates.UNINSTALLED - + @property def summary(self): if self._deb: @@ -155,7 +162,7 @@ def version(self): if self._deb: return self._deb._sections["Version"] - + @property def installed_size(self): installed_size = 0 @@ -170,18 +177,26 @@ def warning(self): # FIXME: use more concise warnings if self._deb: - deb_state = self._deb.compare_to_version_in_cache(use_installed=False) + deb_state = self._deb.compare_to_version_in_cache( + use_installed=False) if deb_state == DebPackage.VERSION_NONE: - return utf8(_("Only install this file if you trust the origin.")) + return utf8( + _("Only install this file if you trust the origin.")) elif (not self._cache[self.pkgname].installed and self._cache[self.pkgname].candidate and - self._cache[self.pkgname].candidate.downloadable): + self._cache[self.pkgname].candidate.downloadable): if deb_state == DebPackage.VERSION_OUTDATED: - return utf8(_("Please install \"%s\" via your normal software channels. Only install this file if you trust the origin.")) % utf8(self.name) + return utf8(_("Please install \"%s\" via your normal " + "software channels. Only install this file if you " + "trust the origin.")) % utf8(self.name) elif deb_state == DebPackage.VERSION_SAME: - return utf8(_("Please install \"%s\" via your normal software channels. Only install this file if you trust the origin.")) % utf8(self.name) + return utf8(_("Please install \"%s\" via your normal " + "software channels. Only install this file if you " + "trust the origin.")) % utf8(self.name) elif deb_state == DebPackage.VERSION_NEWER: - return utf8(_("An older version of \"%s\" is available in your normal software channels. Only install this file if you trust the origin.")) % utf8(self.name) + return utf8(_("An older version of \"%s\" is available in " + "your normal software channels. Only install this " + "file if you trust the origin.")) % utf8(self.name) @property def website(self): diff -Nru software-center-5.1.12/softwarecenter/db/enquire.py software-center-5.1.13/softwarecenter/db/enquire.py --- software-center-5.1.12/softwarecenter/db/enquire.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/enquire.py 2012-03-15 09:34:35.000000000 +0000 @@ -25,7 +25,7 @@ from gi.repository import GObject from softwarecenter.enums import (SortMethods, - XapianValues, + XapianValues, NonAppVisibility, DEFAULT_SEARCH_LIMIT) from softwarecenter.db.database import ( @@ -33,21 +33,21 @@ from softwarecenter.distro import get_distro from softwarecenter.utils import ExecutionTime -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) class AppEnquire(GObject.GObject): """ - A interface to enquire data from a xapian database. + A interface to enquire data from a xapian database. It can combined with any xapian querry and with a generic filter function (that can filter on data not available in xapian) """ # signal emited - __gsignals__ = {"query-complete" : (GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - ()), + __gsignals__ = {"query-complete": (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + ()), } def __init__(self, cache, db): @@ -87,7 +87,7 @@ # generate a name and ensure we never have two threads # with the same name names = [thread.name for thread in threading.enumerate()] - for i in range(threading.active_count()+1, 0, -1): + for i in range(threading.active_count() + 1, 0, -1): thread_name = 'ThreadedQuery-%s' % i if not thread_name in names: break @@ -98,7 +98,7 @@ # don't block the UI while the thread is running context = GObject.main_context_default() while not self._perform_search_complete: - time.sleep(0.02) # 50 fps + time.sleep(0.02) # 50 fps while context.pending(): context.iteration() t.join() @@ -108,7 +108,7 @@ def _get_estimate_nr_apps_and_nr_pkgs(self, enquire, q, xfilter): # filter out docs of pkgs of which there exists a doc of the app - enquire.set_query(xapian.Query(xapian.Query.OP_AND, + enquire.set_query(xapian.Query(xapian.Query.OP_AND, q, xapian.Query("ATapplication"))) try: @@ -118,17 +118,17 @@ return (0, 0) nr_apps = tmp_matches.get_matches_estimated() - enquire.set_query(xapian.Query(xapian.Query.OP_AND_NOT, + enquire.set_query(xapian.Query(xapian.Query.OP_AND_NOT, q, xapian.Query("XD"))) tmp_matches = enquire.get_mset(0, len(self.db), None, xfilter) nr_pkgs = tmp_matches.get_matches_estimated() - nr_apps return (nr_apps, nr_pkgs) def _blocking_perform_search(self): - # WARNING this call may run in a thread, so its *not* + # WARNING this call may run in a thread, so its *not* # allowed to touch gtk, otherwise hell breaks loose - # performance only: this is only needed to avoid the + # performance only: this is only needed to avoid the # python __call__ overhead for each item if we can avoid it # use a unique instance of both enquire and xapian database @@ -152,18 +152,19 @@ # for searches we may want to disable show/hide terms = [term for term in q] - exact_pkgname_query = (len(terms) == 1 and + exact_pkgname_query = (len(terms) == 1 and terms[0].startswith("XP")) with ExecutionTime("calculate nr_apps and nr_pkgs: "): - nr_apps, nr_pkgs = self._get_estimate_nr_apps_and_nr_pkgs(enquire, q, xfilter) + nr_apps, nr_pkgs = self._get_estimate_nr_apps_and_nr_pkgs( + enquire, q, xfilter) self.nr_apps += nr_apps self.nr_pkgs += nr_pkgs # only show apps by default (unless in always visible mode) if self.nonapps_visible != NonAppVisibility.ALWAYS_VISIBLE: if not exact_pkgname_query: - q = xapian.Query(xapian.Query.OP_AND, + q = xapian.Query(xapian.Query.OP_AND, xapian.Query("ATapplication"), q) @@ -171,14 +172,14 @@ # filter out docs of pkgs of which there exists a doc of the app # FIXME: make this configurable again? - enquire.set_query(xapian.Query(xapian.Query.OP_AND_NOT, + enquire.set_query(xapian.Query(xapian.Query.OP_AND_NOT, q, xapian.Query("XD"))) # sort results # cataloged time - what's new category if self.sortmode == SortMethods.BY_CATALOGED_TIME: - if (self.db._axi_values and + if (self.db._axi_values and "catalogedtime" in self.db._axi_values): enquire.set_sort_by_value( self.db._axi_values["catalogedtime"], reverse=True) @@ -195,7 +196,7 @@ # use the default enquire.set_sort_by_relevance() pass # display name - all categories / channels - elif (self.db._axi_values and + elif (self.db._axi_values and "display_name" in self.db._axi_values): enquire.set_sort_by_key(LocaleSorter(self.db), reverse=False) # fallback to pkgname - if needed? @@ -203,7 +204,7 @@ else: enquire.set_sort_by_value_then_relevance( XapianValues.PKGNAME, False) - + #~ try: if self.limit == 0: matches = enquire.get_mset(0, len(self.db), None, xfilter) @@ -213,8 +214,8 @@ #~ except: #~ logging.exception("get_mset") #~ matches = [] - - # promote exact matches to a "app", this will make the + + # promote exact matches to a "app", this will make the # show/hide technical items work correctly if exact_pkgname_query and len(matches) == 1: self.nr_apps += 1 @@ -237,7 +238,6 @@ # wake up the UI if run in a search thread self._perform_search_complete = True - return def get_estimated_matches_count(self, query): with ExecutionTime("estimate item count for query: '%s'" % query): @@ -250,9 +250,9 @@ nr_pkgs = len(tmp_matches) return nr_pkgs - def set_query(self, search_query, + def set_query(self, search_query, limit=DEFAULT_SEARCH_LIMIT, - sortmode=SortMethods.UNSORTED, + sortmode=SortMethods.UNSORTED, filter=None, exact=False, nonapps_visible=NonAppVisibility.MAYBE_VISIBLE, @@ -270,14 +270,15 @@ - `exact`: If true, indexes of queries without matches will be maintained in the store (useful to show e.g. a row with "??? not found") - - `nonapps_visible`: decide whether adding non apps in the model or not. - Can be NonAppVisibility.ALWAYS_VISIBLE/NonAppVisibility.MAYBE_VISIBLE + - `nonapps_visible`: decide whether adding non apps in the model or + not. Can be NonAppVisibility.ALWAYS_VISIBLE + /NonAppVisibility.MAYBE_VISIBLE /NonAppVisibility.NEVER_VISIBLE - (NonAppVisibility.MAYBE_VISIBLE will return non apps result - if no matching apps is found) - - `nonblocking_load`: set to False to execute the query inside the current - thread. Defaults to True to allow the search to be - performed without blocking the UI. + (NonAppVisibility.MAYBE_VISIBLE will return non + apps result if no matching apps is found) + - `nonblocking_load`: set to False to execute the query inside the + current thread. Defaults to True to allow the + search to be performed without blocking the UI. - 'persistent_duplicate_filter': if True allows filtering of duplicate matches across multiple queries """ @@ -321,7 +322,8 @@ # pkgnames = [] # for m in self.matches: # doc = xdb.get_document(m.docid) -# pkgnames.append(doc.get_value(XapianValues.PKGNAME) or doc.get_data()) +# pkgnames.append(doc.get_value(XapianValues.PKGNAME) or +# doc.get_data()) # return pkgnames # def get_applications(self): @@ -339,5 +341,3 @@ """ get the xapian.Document objects of the current matches """ xdb = self.db.xapiandb return [xdb.get_document(m.docid) for m in self._matches] - - diff -Nru software-center-5.1.12/softwarecenter/db/history_impl/apthistory.py software-center-5.1.13/softwarecenter/db/history_impl/apthistory.py --- software-center-5.1.12/softwarecenter/db/history_impl/apthistory.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/history_impl/apthistory.py 2012-03-15 09:33:51.000000000 +0000 @@ -33,7 +33,7 @@ try: import cPickle as pickle - pickle # pyflakes + pickle # pyflakes except ImportError: import pickle @@ -43,20 +43,22 @@ from softwarecenter.utils import ExecutionTime from softwarecenter.db.history import Transaction, PackageHistory + def ascii_lower(key): ascii_trans_table = string.maketrans(string.ascii_uppercase, string.ascii_lowercase) return key.translate(ascii_trans_table) + class AptTransaction(Transaction): - PKGACTIONS=["Install", "Upgrade", "Downgrade", "Remove", "Purge"] + PKGACTIONS = ["Install", "Upgrade", "Downgrade", "Remove", "Purge"] def __init__(self, sec): self.start_date = datetime.strptime(sec["Start-Date"], "%Y-%m-%d %H:%M:%S") # set the object attributes "install", "upgrade", "downgrade", # "remove", "purge", error - for k in self.PKGACTIONS+["Error"]: + for k in self.PKGACTIONS + ["Error"]: # we use ascii_lower for issues described in LP: #581207 attr = ascii_lower(k) if k in sec: @@ -68,13 +70,14 @@ @staticmethod def _fixup_history_item(s): """ strip history item string and add missing ")" if needed """ - s=s.strip() + s = s.strip() # remove the infomation about the architecture s = re.sub(":\w+", "", s) if "(" in s and not s.endswith(")"): - s+=")" + s += ")" return s + class AptHistory(PackageHistory): def __init__(self, use_cache=True): @@ -95,6 +98,7 @@ @property def transactions(self): return self._transactions + @property def history_ready(self): return self._history_ready @@ -114,7 +118,7 @@ cachetime = os.path.getmtime(p) except: LOG.exception("failed to load cache") - for history_gz_file in sorted(glob.glob(self.history_file+".*.gz"), + for history_gz_file in sorted(glob.glob(self.history_file + ".*.gz"), cmp=self._mtime_cmp): if os.path.getmtime(history_gz_file) < cachetime: LOG.debug("skipping already cached '%s'" % history_gz_file) @@ -124,8 +128,8 @@ if use_cache: pickle.dump(self._transactions, open(p, "w")) self._history_ready = True - - def _scan(self, history_file, rescan = False): + + def _scan(self, history_file, rescan=False): LOG.debug("_scan: '%s' (%s)" % (history_file, rescan)) try: tagfile = apt_pkg.TagFile(open(history_file)) @@ -136,7 +140,7 @@ # keep the UI alive while self.main_context.pending(): self.main_context.iteration() - # ignore records with + # ignore records with try: trans = AptTransaction(stanza) except (KeyError, ValueError): @@ -151,16 +155,16 @@ # so we could (and should) do a binary search if not trans in self._transactions: self._transactions.insert(0, trans) - + def _on_apt_history_changed(self, monitor, afile, other_file, event): - if event == Gio.FileMonitorEvent.CHANGES_DONE_HINT: - self._scan(self.history_file, rescan = True) + if event == Gio.FileMonitorEvent.CHANGES_DONE_HINT: + self._scan(self.history_file, rescan=True) if self.update_callback: self.update_callback() - - def set_on_update(self,update_callback): - self.update_callback=update_callback - + + def set_on_update(self, update_callback): + self.update_callback = update_callback + def get_installed_date(self, pkg_name): installed_date = None for trans in self._transactions: @@ -169,7 +173,7 @@ installed_date = trans.start_date return installed_date return installed_date - + def _find_in_terminal_log(self, date, term_file): found = False term_lines = [] @@ -191,9 +195,8 @@ term_lines = self._find_in_terminal_log(date, open(term)) # now search the older history if not term_lines: - for f in glob.glob(term+".*.gz"): + for f in glob.glob(term + ".*.gz"): term_lines = self._find_in_terminal_log(date, gzip.open(f)) if term_lines: return term_lines return term_lines - diff -Nru software-center-5.1.12/softwarecenter/db/history_impl/packagekit.py software-center-5.1.13/softwarecenter/db/history_impl/packagekit.py --- software-center-5.1.12/softwarecenter/db/history_impl/packagekit.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/history_impl/packagekit.py 2012-03-15 09:33:51.000000000 +0000 @@ -22,6 +22,7 @@ LOG = logging.getLogger(__name__) + class PackagekitTransaction(Transaction): def __init__(self, pktrans): self.start_date = datetime.strptime(pktrans.props.timespec, @@ -31,7 +32,8 @@ self.upgrade = [] self.downgrade = [] self.remove = [] - self.purge = [] # can't happen with a Packagekit backend (is mapped to remove) + self.purge = [] # can't happen with a Packagekit backend (is mapped + # to remove) # parse transaction data lines = pktrans.props.data.split('\n') @@ -51,13 +53,15 @@ elif action == 'removing': self.remove.append(package_name) else: - # ignore other actions (include cleanup, downloading and untrusted) + # ignore other actions (include cleanup, downloading and + # untrusted) continue except: LOG.warn("malformed line emitted by PackageKit, was %s" % line) + class PackagekitHistory(PackageHistory): - """ Represents the history of the transactions """ + """ Represents the history of the transactions """ def __init__(self, use_cache=True): self._use_cache = use_cache @@ -66,9 +70,9 @@ self._client = PackageKitGlib.Client() self._client.get_old_transactions_async(0, - None, # cancellable - lambda *args, **kwargs: None, None, # progress callback - self._transactions_received, None) + None, # cancellable + lambda *args, **kwargs: None, None, # progress callback + self._transactions_received, None) @property def history_ready(self): @@ -86,7 +90,7 @@ def get_installed_date(self, pkg_name): """Return the date that the given package name got instaled """ - return None + pass def _transactions_received(self, client, async_result, user_data): try: diff -Nru software-center-5.1.12/softwarecenter/db/history.py software-center-5.1.13/softwarecenter/db/history.py --- software-center-5.1.12/softwarecenter/db/history.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/db/history.py 2012-03-15 09:34:35.000000000 +0000 @@ -20,8 +20,9 @@ LOG = logging.getLogger(__name__) + class Transaction(object): - """ Represents an pkg transaction + """ Represents an pkg transaction o Attributes: - 'start_date': the start date/time of the transaction as datetime @@ -29,23 +30,29 @@ contain the list of packagenames affected by this action """ - PKGACTIONS=["Install", "Upgrade", "Downgrade", "Remove", "Purge"] + PKGACTIONS = ["Install", "Upgrade", "Downgrade", "Remove", "Purge"] def __init__(self): pass def __len__(self): - count=0 + count = 0 for k in self.PKGACTIONS: count += len(getattr(self, k.lower())) return count + def __repr__(self): - return ('%s set. " - "Are you sure you want to continue?") % (appname, cache[m].installed.summary) + "include new items in %s set. " + "Are you sure you want to continue?") % (appname, + cache[m].installed.summary) button_text = _("Remove Anyway") depends = [] break @@ -71,7 +75,7 @@ depends = None break return (primary, button_text) - + def get_license_text(self, component): if component in ("main",): return _("Meets the Debian Free Software Guidelines") @@ -106,7 +110,8 @@ return True return False - def get_maintenance_status(self, cache, appname, pkgname, component, channelname): + def get_maintenance_status(self, cache, appname, pkgname, component, + channelname): """Return the maintenance status of a package.""" if not hasattr(cache, '_cache') or not pkgname: return @@ -143,16 +148,17 @@ elif pkg_archive == "unstable": return _("Debian does not provide critical updates " "for %s") % appname - return def get_supported_query(self): import xapian - query1 = xapian.Query("XOL"+"Debian") - query2 = xapian.Query("XOC"+"main") + query1 = xapian.Query("XOL" + "Debian") + query2 = xapian.Query("XOC" + "main") return xapian.Query(xapian.Query.OP_AND, query1, query2) if __name__ == "__main__": cache = apt.Cache() - print(cache.get_maintenance_status(cache, "synaptic app", "synaptic", "main", None)) - print(cache.get_maintenance_status(cache, "3dchess app", "3dchess", "universe", None)) + print(cache.get_maintenance_status(cache, "synaptic app", "synaptic", + "main", None)) + print(cache.get_maintenance_status(cache, "3dchess app", "3dchess", + "universe", None)) diff -Nru software-center-5.1.12/softwarecenter/distro/Fedora.py software-center-5.1.13/softwarecenter/distro/Fedora.py --- software-center-5.1.12/softwarecenter/distro/Fedora.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/distro/Fedora.py 2012-03-19 08:35:49.000000000 +0000 @@ -22,13 +22,14 @@ from softwarecenter.distro import Distro from gettext import gettext as _ + class Fedora(Distro): DISTROSERIES = [ - 'Beefy Miracle', - 'Verne', - 'Lovelock', - 'Laughlin', - 'Leonidas', + 'Beefy Miracle', + 'Verne', + 'Lovelock', + 'Laughlin', + 'Leonidas', 'Constantine', ] @@ -37,25 +38,29 @@ # screenshot handling # FIXME - fedora should get its own proxy eventually - SCREENSHOT_THUMB_URL = "http://screenshots.ubuntu.com/thumbnail-with-version/%(pkgname)s/%(version)s" - SCREENSHOT_LARGE_URL = "http://screenshots.ubuntu.com/screenshot-with-version/%(pkgname)s/%(version)s" + SCREENSHOT_THUMB_URL = ("http://screenshots.ubuntu.com/" + "thumbnail-with-version/%(pkgname)s/%(version)s") + SCREENSHOT_LARGE_URL = ("http://screenshots.ubuntu.com/" + "screenshot-with-version/%(pkgname)s/%(version)s") SCREENSHOT_JSON_URL = "http://screenshots.ubuntu.com/json/package/%s" - + # reviews # FIXME: fedora will want to get their own review server instance at # some point I imagine :) (or a alternative backend) # - REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://reviews.ubuntu.com/reviews/api/1.0" - REVIEWS_URL = REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/" + REVIEWS_SERVER = (os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or + "http://reviews.ubuntu.com/reviews/api/1.0") + REVIEWS_URL = (REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/" + "%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/") - REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats" + REVIEW_STATS_URL = REVIEWS_SERVER + "/review-stats" def get_distro_channel_name(self): """ The name of the primary repository """ return "fedora" def get_distro_channel_description(self): - """ The description of the main distro channel + """ The description of the main distro channel Overrides what's present in yum.conf for fedora, updates, updates-testing, their respective -source and -debuginfo """ @@ -70,7 +75,7 @@ button_text = _("Remove All") return (primary, button_text) - + def get_license_text(self, component): # with a PackageKit backend, component is always 'main' # (but we have license in the individual packages) @@ -86,13 +91,13 @@ origin = cache.get_origin(pkgname) return origin == 'fedora' or origin == 'updates' - def get_maintenance_status(self, cache, appname, pkgname, component, channelname): + def get_maintenance_status(self, cache, appname, pkgname, component, + channelname): # FIXME - return + pass def get_supported_query(self): import xapian - query1 = xapian.Query("XOO"+"fedora") - query2 = xapian.Query("XOO"+"updates") + query1 = xapian.Query("XOO" + "fedora") + query2 = xapian.Query("XOO" + "updates") return xapian.Query(xapian.Query.OP_OR, query1, query2) - diff -Nru software-center-5.1.12/softwarecenter/distro/__init__.py software-center-5.1.13/softwarecenter/distro/__init__.py --- software-center-5.1.12/softwarecenter/distro/__init__.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/distro/__init__.py 2012-03-19 08:35:49.000000000 +0000 @@ -36,9 +36,12 @@ # are found DISTROSERIES = [] - # base path for the review summary, the JS will append %i.png (with i={1,5}) - REVIEW_SUMMARY_STARS_BASE_PATH = "/usr/share/software-center/images/review-summary" - REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://localhost:8000" + # base path for the review summary, the JS will append %i.png + # (with i={1,5}) + REVIEW_SUMMARY_STARS_BASE_PATH = ("/usr/share/software-center/images/" + "review-summary") + REVIEWS_SERVER = (os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or + "http://localhost:8000") # You need to set this var to enable purchases PURCHASE_APP_URL = "" @@ -50,23 +53,23 @@ """Return a new generic Distro instance.""" def get_app_name(self): - """ - The name of the application (as displayed in the main window and + """ + The name of the application (as displayed in the main window and the about window) """ return _("Software Center") def get_app_description(self): - """ + """ The description of the application displayed in the about dialog """ - return _("Lets you choose from thousands of applications available for your system.") - + return _("Lets you choose from thousands of applications available " + "for your system.") def get_distro_channel_name(self): """ The name of the main channel in the Release file (e.g. Ubuntu)""" return "none" - + def get_distro_channel_description(self): """ The description for the main distro channel """ return "none" @@ -83,15 +86,16 @@ self._distro_code_name = platform.dist()[2] return self._distro_code_name - def get_maintenance_status(self, cache, appname, pkgname, component, channelname): + def get_maintenance_status(self, cache, appname, pkgname, component, + channelname): raise UnimplementedError def get_license_text(self, component): raise UnimplementedError def is_supported(self, cache, doc, pkgname): - """ - return True if the given document and pkgname is supported by + """ + return True if the given document and pkgname is supported by the distribution """ raise UnimplementedError @@ -105,7 +109,8 @@ return _("Supported Software") def get_install_warning_text(self, cache, pkg, appname, depends): - primary = utf8(_("To install %s, these items must be removed:")) % utf8(appname) + primary = (utf8(_("To install %s, these items must be removed:")) % + utf8(appname)) button_text = _("Install Anyway") # alter it if a meta-package is affected @@ -113,7 +118,8 @@ if cache[m].section == "metapackages": primary = utf8(_("If you install %s, future updates will not " "include new items in %s set. " - "Are you sure you want to continue?")) % (utf8(appname), cache[m].installed.summary) + "Are you sure you want to continue?")) % ( + utf8(appname), cache[m].installed.summary) button_text = _("Install Anyway") depends = [] break @@ -121,9 +127,9 @@ # alter it if an important meta-package is affected for m in self.IMPORTANT_METAPACKAGES: if m in depends: - primary = utf8(_("Installing %s may cause core applications to " - "be removed. " - "Are you sure you want to continue?")) % utf8(appname) + primary = utf8(_("Installing %s may cause core applications " + "to be removed. Are you sure you want to " + "continue?")) % utf8(appname) button_text = _("Install Anyway") depends = None break @@ -133,16 +139,17 @@ def get_deauthorize_text(self, account_name, purchased_packages): if len(purchased_packages) == 0: if account_name: - primary = _('Are you sure you want to deauthorize this computer ' - 'from the "%s" account?') % account_name + primary = _('Are you sure you want to deauthorize this ' + 'computer from the "%s" account?') % account_name else: - primary = _('Are you sure you want to deauthorize this computer ' - 'for purchases?') + primary = _('Are you sure you want to deauthorize this ' + 'computer for purchases?') button_text = _('Deauthorize') else: if account_name: - primary = _('Deauthorizing this computer from the "%s" account ' - 'will remove this purchased software:') % account_name + primary = _('Deauthorizing this computer from the "%s" ' + 'account will remove this purchased ' + 'software:') % account_name else: primary = _('Deauthorizing this computer for purchases ' 'will remove the following purchased software:') @@ -151,7 +158,7 @@ # generic architecture detection code def get_architecture(self): - return None + pass def _get_distro(): @@ -159,16 +166,18 @@ distro_id = distro_info[0] LOG.debug("get_distro: '%s'", distro_id) # start with a import, this gives us only a softwarecenter module - module = __import__(distro_id, globals(), locals(), [], -1) + module = __import__(distro_id, globals(), locals(), [], -1) # get the right class and instanciate it distro_class = getattr(module, distro_id) instance = distro_class() return instance + def get_distro(): """ factory to return the right Distro object """ return distro_instance + def get_current_arch(): # for tests and similar if "SOFTWARE_CENTER_ARCHITECTURE" in os.environ: @@ -177,11 +186,12 @@ return arch return get_distro().get_architecture() + def get_foreign_architectures(): return get_distro().get_foreign_architectures() # singelton -distro_instance=_get_distro() +distro_instance = _get_distro() if __name__ == "__main__": diff -Nru software-center-5.1.12/softwarecenter/distro/SUSELINUX.py software-center-5.1.13/softwarecenter/distro/SUSELINUX.py --- software-center-5.1.12/softwarecenter/distro/SUSELINUX.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/distro/SUSELINUX.py 2012-03-19 08:35:49.000000000 +0000 @@ -20,28 +20,33 @@ from gettext import gettext as _ from softwarecenter.distro import Distro + class SUSELINUX(Distro): # see __init__.py description DISTROSERIES = ["11.4", ] # screenshot handling - SCREENSHOT_THUMB_URL = "http://screenshots.ubuntu.com/thumbnail-with-version/%(pkgname)s/%(version)s" - SCREENSHOT_LARGE_URL = "http://screenshots.ubuntu.com/screenshot-with-version/%(pkgname)s/%(version)s" + SCREENSHOT_THUMB_URL = ("http://screenshots.ubuntu.com/" + "thumbnail-with-version/%(pkgname)s/%(version)s") + SCREENSHOT_LARGE_URL = ("http://screenshots.ubuntu.com/" + "screenshot-with-version/%(pkgname)s/%(version)s") SCREENSHOT_JSON_URL = "http://screenshots.ubuntu.com/json/package/%s" - + # reviews - REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://reviews.ubuntu.com/reviews/api/1.0" - REVIEWS_URL = REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/" + REVIEWS_SERVER = (os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or + "http://reviews.ubuntu.com/reviews/api/1.0") + REVIEWS_URL = (REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/" + "%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/") - REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats" + REVIEW_STATS_URL = REVIEWS_SERVER + "/review-stats" def get_app_name(self): return _("Software Center") def get_app_description(self): return _("Lets you choose from thousands of applications available.") - + def get_distro_channel_name(self): """ The name in the Release file """ return "openSUSE" @@ -70,17 +75,17 @@ def get_supported_query(self): # FIXME import xapian - query1 = xapian.Query("XOL"+"Ubuntu") - query2a = xapian.Query("XOC"+"main") - query2b = xapian.Query("XOC"+"restricted") + query1 = xapian.Query("XOL" + "Ubuntu") + query2a = xapian.Query("XOC" + "main") + query2b = xapian.Query("XOC" + "restricted") query2 = xapian.Query(xapian.Query.OP_OR, query2a, query2b) return xapian.Query(xapian.Query.OP_AND, query1, query2) - def get_maintenance_status(self, cache, appname, pkgname, component, channelname): + def get_maintenance_status(self, cache, appname, pkgname, component, + channelname): # FIXME - return + pass def get_downloadable_icon_url(self, full_archive_url, icon_filename): # FIXME - return None - + pass diff -Nru software-center-5.1.12/softwarecenter/distro/Ubuntu.py software-center-5.1.13/softwarecenter/distro/Ubuntu.py --- software-center-5.1.12/softwarecenter/distro/Ubuntu.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/distro/Ubuntu.py 2012-03-19 08:35:49.000000000 +0000 @@ -31,6 +31,7 @@ LOG = logging.getLogger(__name__) + class Ubuntu(Debian): # see __init__.py description @@ -43,22 +44,28 @@ IMPORTANT_METAPACKAGES = ("ubuntu-desktop", "kubuntu-desktop") # screenshot handling - SCREENSHOT_THUMB_URL = "http://screenshots.ubuntu.com/thumbnail-with-version/%(pkgname)s/%(version)s" - SCREENSHOT_LARGE_URL = "http://screenshots.ubuntu.com/screenshot-with-version/%(pkgname)s/%(version)s" + SCREENSHOT_THUMB_URL = ("http://screenshots.ubuntu.com/" + "thumbnail-with-version/%(pkgname)s/%(version)s") + SCREENSHOT_LARGE_URL = ("http://screenshots.ubuntu.com/" + "screenshot-with-version/%(pkgname)s/%(version)s") # the json description of the available screenshots SCREENSHOT_JSON_URL = "http://screenshots.ubuntu.com/json/package/%s" # purchase subscription - PURCHASE_APP_URL = BUY_SOMETHING_HOST+"/subscriptions/%s/ubuntu/%s/+new/?%s" + PURCHASE_APP_URL = (BUY_SOMETHING_HOST + "/subscriptions/%s/ubuntu/%s/" + "+new/?%s") # reviews - REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://reviews.ubuntu.com/reviews/api/1.0" - REVIEWS_URL = REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/" + REVIEWS_SERVER = (os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or + "http://reviews.ubuntu.com/reviews/api/1.0") + REVIEWS_URL = (REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/" + "%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/") - #REVIEW_STATS_URL = REVIEWS_SERVER+"/reviews/api/1.0/%(language)s/%(origin)s/%(distroseries)s/review-stats/" + #REVIEW_STATS_URL = (REVIEWS_SERVER + "/reviews/api/1.0/%(language)s/" + # "%(origin)s/%(distroseries)s/review-stats/") # FIXME: does that make sense?!? - REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats" + REVIEW_STATS_URL = REVIEWS_SERVER + "/review-stats" # Starting point for Ubuntu app developers DEVELOPER_URL = "http://developer.ubuntu.com/" @@ -67,8 +74,9 @@ return _("Ubuntu Software Center") def get_app_description(self): - return _("Lets you choose from thousands of applications available for Ubuntu.") - + return _("Lets you choose from thousands of applications available " + "for Ubuntu.") + def get_distro_channel_name(self): """ The name in the Release file """ return "Ubuntu" @@ -87,7 +95,8 @@ if cache[m].section == "metapackages": primary = _("If you uninstall %s, future updates will not " "include new items in %s set. " - "Are you sure you want to continue?") % (appname, cache[m].installed.summary) + "Are you sure you want to continue?") % (appname, + cache[m].installed.summary) button_text = _("Remove Anyway") depends = [] break @@ -110,9 +119,10 @@ elif component == "restricted": return _("Proprietary") else: - # commercial apps provide license info via the software-center-agent, - # but if a given commercial app does not provide this for some reason, - # default to a license type of "Unknown" + # commercial apps provide license info via the + # software-center-agent, but if a given commercial app does not + # provide this for some reason, default to a license type of + # "Unknown" return _("Unknown") def is_supported(self, cache, doc, pkgname): @@ -120,8 +130,8 @@ # section. Looking up in the cache seems just as fast/slow. if pkgname in cache and cache[pkgname].candidate: for origin in cache[pkgname].candidate.origins: - if (origin.origin == "Ubuntu" and - origin.trusted and + if (origin.origin == "Ubuntu" and + origin.trusted and (origin.component == "main" or origin.component == "restricted")): return True @@ -129,25 +139,26 @@ def get_supported_query(self): import xapian - query1 = xapian.Query("XOL"+"Ubuntu") - query2a = xapian.Query("XOC"+"main") - query2b = xapian.Query("XOC"+"restricted") + query1 = xapian.Query("XOL" + "Ubuntu") + query2a = xapian.Query("XOC" + "main") + query2b = xapian.Query("XOC" + "restricted") query2 = xapian.Query(xapian.Query.OP_OR, query2a, query2b) return xapian.Query(xapian.Query.OP_AND, query1, query2) def get_supported_filter_name(self): return _("Canonical-Maintained Software") - def get_maintenance_status(self, cache, appname, pkgname, component, channelname): + def get_maintenance_status(self, cache, appname, pkgname, component, + channelname): # try to figure out the support dates of the release and make # sure to look only for stuff in "Ubuntu" and "distro_codename" - # (to exclude stuff in ubuntu-updates for the support time + # (to exclude stuff in ubuntu-updates for the support time # calculation because the "Release" file time for that gets # updated regularly) if not hasattr(cache, '_cache') or not pkgname: return - releasef = get_release_filename_for_pkg(cache._cache, pkgname, - "Ubuntu", + releasef = get_release_filename_for_pkg(cache._cache, pkgname, + "Ubuntu", self.get_codename()) time_t = get_release_date_from_release_file(releasef) # check the release date and show support information @@ -165,23 +176,27 @@ # see if we have a "Supported" entry in the pkg record if (pkgname in cache and cache[pkgname].candidate): - support_time = cache._cache[pkgname].candidate.record.get("Supported") + support_time = cache._cache[pkgname].candidate.record.get( + "Supported") if support_time: if support_time.endswith("y"): - support_month = 12*int(support_time.strip("y")) + support_month = 12 * int(support_time.strip("y")) elif support_time.endswith("m"): support_month = int(support_time.strip("m")) else: - LOG.warning("unsupported 'Supported' string '%s'" % support_time) + LOG.warning("unsupported 'Supported' string '%s'" % + support_time) # mvo: we do not define the end date very precisely # currently this is why it will just display a end # range # print release_date, support_month - (support_end_year, support_end_month) = get_maintenance_end_date(release_date, support_month) - support_end_month_str = locale.nl_langinfo(getattr(locale,"MON_%d" % support_end_month)) + (support_end_year, support_end_month) = get_maintenance_end_date( + release_date, support_month) + support_end_month_str = locale.nl_langinfo( + getattr(locale, "MON_%d" % support_end_month)) # check if the support has ended - support_ended = (now.year >= support_end_year and + support_ended = (now.year >= support_end_year and now.month > support_end_month) if component == "main": if support_ended: @@ -192,9 +207,10 @@ else: return _("Canonical provides critical updates for " "%(appname)s until %(support_end_month_str)s " - "%(support_end_year)s.") % {'appname' : appname, - 'support_end_month_str' : support_end_month_str, - 'support_end_year' : support_end_year} + "%(support_end_year)s.") % { + 'appname': appname, + 'support_end_month_str': support_end_month_str, + 'support_end_year': support_end_year} elif component == "restricted": if support_ended: return _("Canonical does no longer provide " @@ -205,10 +221,12 @@ return _("Canonical provides critical updates supplied " "by the developers of %(appname)s until " "%(support_end_month_str)s " - "%(support_end_year)s.") % {'appname' : appname, - 'support_end_month_str' : support_end_month_str, - 'support_end_year' : support_end_year} - + "%(support_end_year)s.") % { + 'appname': appname, + 'support_end_month_str': support_end_month_str, + 'support_end_year': support_end_year, + } + # if we couldn't determine a support date, use a generic maintenance # string without the date if (channelname or @@ -223,8 +241,8 @@ return _("Canonical does not provide updates for %s. " "Some updates may be provided by the " "Ubuntu community.") % appname - #return _("Application %s has an unknown maintenance status.") % appname - return + #return (_("Application %s has an unknown maintenance status.") % + # appname) def get_downloadable_icon_url(self, full_archive_url, icon_filename): """ @@ -250,12 +268,16 @@ downloadable_icon_url.append(icon_filename) return "".join(downloadable_icon_url) else: - #raise ValueError, "we currently support downloadable icons in ppa's only" - LOG.warning("downloadable icon is not supported for archive: '%s'" % full_archive_url) + #raise ValueError("we currently support downloadable icons in " + # "ppa's only") + LOG.warning("downloadable icon is not supported for archive: '%s'" + % full_archive_url) return '' if __name__ == "__main__": import apt cache = apt.Cache() - print cache.get_maintenance_status(cache, "synaptic app", "synaptic", "main", None) - print cache.get_maintenance_status(cache, "3dchess app", "3dchess", "universe", None) + print cache.get_maintenance_status(cache, "synaptic app", "synaptic", + "main", None) + print cache.get_maintenance_status(cache, "3dchess app", "3dchess", + "universe", None) diff -Nru software-center-5.1.12/softwarecenter/enums.py software-center-5.1.13/softwarecenter/enums.py --- software-center-5.1.12/softwarecenter/enums.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/enums.py 2012-03-20 08:00:09.000000000 +0000 @@ -22,7 +22,8 @@ # pkgname of this app itself (used for "self-awareness", see spec) SOFTWARE_CENTER_PKGNAME = 'software-center' -# name of the app in the keyring, untranslated, see bug #773214 for the rational +# name of the app in the keyring, untranslated, see bug #773214 for the +# rational SOFTWARE_CENTER_NAME_KEYRING = "Ubuntu Software Center" SOFTWARE_CENTER_SSO_DESCRIPTION = _( "To reinstall previous purchases, sign in to the " @@ -31,18 +32,28 @@ # buy-something base url #BUY_SOMETHING_HOST = "http://localhost:8000/" -BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") or os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "https://software-center.ubuntu.com" -BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") or os.environ.get("SOFTWARE_CENTER_BUY_HOST") or "http://software-center.ubuntu.com" +BUY_SOMETHING_HOST = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") or \ + os.environ.get("SOFTWARE_CENTER_BUY_HOST") or \ + "https://software-center.ubuntu.com" + +BUY_SOMETHING_HOST_ANONYMOUS = os.environ.get("SOFTWARE_CENTER_AGENT_HOST") \ + or os.environ.get("SOFTWARE_CENTER_BUY_HOST") or \ + "http://software-center.ubuntu.com" # recommender -RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or "https://rec.ubuntu.com" -#RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or "https://rec.staging.ubuntu.com" - -# for the sso login +RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or \ + "https://rec.ubuntu.com" +#RECOMMENDER_HOST = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") or \ +# "https://rec.staging.ubuntu.com" + +# for the sso login. ussoc expects the USSOC_SERVICE_URL environment variable +# to be a full path to the service root (including /api/1.0), not just the +# hostname, so we use the same convention for UBUNTU_SSO_SERVICE: UBUNTU_SSO_SERVICE = os.environ.get( - "USSOC_SERVICE_URL", "https://login.ubuntu.com/") -SSO_LOGIN_HOST = UBUNTU_SSO_SERVICE + "USSOC_SERVICE_URL", "https://login.ubuntu.com/api/1.0") +# the terms-of-service link +SOFTWARE_CENTER_TOS_LINK = "http://apps.ubuntu.com/cat/tos" # version of the database, every time something gets added (like # terms for mime-type) increase this (but keep as a string!) @@ -54,13 +65,14 @@ # the server size "page" for ratings&reviews REVIEWS_BATCH_PAGE_SIZE = 10 + # the various "views" that the app has class ViewPages: AVAILABLE = "view-page-available" INSTALLED = "view-page-installed" - HISTORY = "view-page-history" + HISTORY = "view-page-history" SEPARATOR_1 = "view-page-separator-1" - PENDING = "view-page-pending" + PENDING = "view-page-pending" CHANNEL = "view-page-channel" # items considered "permanent", that is, if a item disappears @@ -72,22 +84,25 @@ HISTORY ) + # define ID values for the various buttons found in the navigation bar class NavButtons: CATEGORY = "category" - LIST = "list" - SUBCAT = "subcat" - DETAILS = "details" - SEARCH = "search" + LIST = "list" + SUBCAT = "subcat" + DETAILS = "details" + SEARCH = "search" PURCHASE = "purchase" PREV_PURCHASES = "prev-purchases" + # define ID values for the action bar buttons class ActionButtons: INSTALL = "install" ADD_TO_LAUNCHER = "add_to_launcher" CANCEL_ADD_TO_LAUNCHER = "cancel_add_to_launcher" + # icons class Icons: APP_ICON_SIZE = 48 @@ -98,6 +113,7 @@ GENERIC_MISSING = "gtk-missing-image" INSTALLED_OVERLAY = "software-center-installed" + # sorting class SortMethods: (UNSORTED, @@ -107,10 +123,12 @@ BY_TOP_RATED, ) = range(5) + class ReviewSortMethods: REVIEW_SORT_METHODS = ['helpful', 'newest'] REVIEW_SORT_LIST_ENTRIES = [_('Most helpful first'), _('Newest first')] + # values used in the database class XapianValues: APPNAME = 170 @@ -144,10 +162,12 @@ VERSION_INFO = 198 SC_SUPPORTED_DISTROS = 199 + # fake channels PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME = "for-pay-needs-reinstall" AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME = "available-for-pay" + # custom keys for the new-apps repository, correspond # control file custom fields: # XB-AppName, XB-Icon, XB-Screenshot-Url, XB-Thumbnail-Url, XB-Category @@ -158,6 +178,7 @@ THUMBNAIL_URL = "Thumbnail-Url" CATEGORY = "Category" + # pkg action state constants class PkgStates: ( @@ -180,17 +201,20 @@ FORCE_VERSION, # the package is not found in the DB or cache NOT_FOUND, + # its purchased but not found for the current series + PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES, # this *needs* to be last (for test_appdetails.py) and means # something went wrong and we don't have a state for this PKG UNKNOWN, - PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES, ) = range(17) + # visibility of non applications in the search results class NonAppVisibility: (ALWAYS_VISIBLE, MAYBE_VISIBLE, - NEVER_VISIBLE) = range (3) + NEVER_VISIBLE) = range(3) + # application actions class AppActions: @@ -200,6 +224,7 @@ APPLY = "apply_changes" PURCHASE = "purchase" + # transaction types class TransactionTypes: INSTALL = "install" @@ -208,6 +233,7 @@ APPLY = "apply_changes" REPAIR = "repair_dependencies" + # mouse event codes for back/forward buttons # TODO: consider whether we ought to get these values from gconf so that we # can be sure to use the corresponding values used by Nautilus: @@ -223,26 +249,22 @@ TOP_RATED_CAROUSEL_LIMIT = 12 from .version import VERSION, DISTRO, RELEASE, CODENAME -USER_AGENT="Software Center/%s (N;) %s/%s (%s)" % (VERSION, - DISTRO, - RELEASE, - CODENAME) +USER_AGENT = "Software Center/%s (N;) %s/%s (%s)" % ( + VERSION, DISTRO, RELEASE, CODENAME) # global backend switch, prefer aptdaemon, if that can not be found, use PK USE_PACKAGEKIT_BACKEND = False try: import aptdaemon - aptdaemon # pyflaks + aptdaemon # pyflaks USE_PACKAGEKIT_BACKEND = False except ImportError: try: from gi.repository import PackageKitGlib - PackageKitGlib # pyflakes + PackageKitGlib # pyflakes USE_PACKAGEKIT_BACKEND = True except ImportError: raise Exception("Need either aptdaemon or PackageKitGlib") # allow force via env (useful for testing) if "SOFTWARE_CENTER_FORCE_PACKAGEKIT" in os.environ: USE_PACKAGEKIT_BACKEND = True - - diff -Nru software-center-5.1.12/softwarecenter/gwibber_helper.py software-center-5.1.13/softwarecenter/gwibber_helper.py --- software-center-5.1.12/softwarecenter/gwibber_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/gwibber_helper.py 2012-03-16 08:30:18.000000000 +0000 @@ -25,8 +25,9 @@ import sys from random import random + class GwibberHelper(object): - """ A helper class for gwibber. ideally we would just use + """ A helper class for gwibber. ideally we would just use from gi.repository import Gwibber accounts = Gwibbers.Accounts() accounts.list() @@ -58,9 +59,9 @@ "/com/gwibber/Service") service_iface = dbus.Interface(proxy_obj, "com.Gwibber.Service") if account_id: - json_str = json.dumps({'message' : message, - 'accounts' : [account_id], - }) + json_str = json.dumps({'message': message, + 'accounts': [account_id], + }) service_iface.Send(json_str) else: service_iface.SendMessage(message) @@ -84,10 +85,40 @@ logging.exception("GwibberHelper.has_accounts_in_sqlite() failed") return False + class GwibberHelperMock(object): - - fake_gwibber_accounts_one = [{u'username': u'randomuser', u'user_id': u'2323434224', u'service': u'twitter', u'secret_token': u':some-token', u'color': u'#729FCF', u'receive_enabled': True, u'access_token': u'some_access_token', u'send_enabled': True, u'id': u'twitter-id-random-15af8bddb6'}] - fake_gwibber_accounts_multiple = [{u'username': u'random1 with a very long name', u'user_id': u'2342342313', u'service': u'twitter', u'secret_token': u':some-token', u'color': u'#729FCF', u'receive_enabled': True, u'access_token': u'some_access_token', u'send_enabled': True, u'id': u'twitter-id-rnadomuser-radfsdf'}, {u'username': u'mpt', u'user_id': u'23safdsaf5', u'service': u'twitter', u'secret_token': u':some_otken', u'color': u'#729FCF', u'receive_enabled': True, u'access_token': u'some_access_token', u'send_enabled': True, u'id': u'twitter-id-mpt-afsdfsa'}] + + fake_gwibber_accounts_one = [ + {u'username': u'randomuser', + u'user_id': u'2323434224', + u'service': u'twitter', + u'secret_token': u':some-token', + u'color': u'#729FCF', + u'receive_enabled': True, + u'access_token': u'some_access_token', + u'send_enabled': True, + u'id': u'twitter-id-random-15af8bddb6'} + ] + fake_gwibber_accounts_multiple = [ + {u'username': u'random1 with a very long name', + u'user_id': u'2342342313', + u'service': u'twitter', + u'secret_token': u':some-token', + u'color': u'#729FCF', + u'receive_enabled': True, + u'access_token': u'some_access_token', + u'send_enabled': True, + u'id': u'twitter-id-rnadomuser-radfsdf'}, + {u'username': u'mpt', + u'user_id': u'23safdsaf5', + u'service': u'twitter', + u'secret_token': u':some_otken', + u'color': u'#729FCF', + u'receive_enabled': True, + u'access_token': u'some_access_token', + u'send_enabled': True, + u'id': u'twitter-id-mpt-afsdfsa'} + ] def accounts(self): import copy @@ -101,16 +132,17 @@ def send_message(self, message, account_id="all"): sys.stderr.write("sending '%s' to '%s'\n" % (message, account_id)) - #used for testing purposes, to emulate a gwibber failure for ~1 out of every 5 attempts + # used for testing purposes, to emulate a gwibber failure for ~1 out + # of every 5 attempts r = random() - if r < 0.2 and not "SOFTWARE_CENTER_GWIBBER_MOCK_NO_FAIL" in os.environ: + if (r < 0.2 and + not "SOFTWARE_CENTER_GWIBBER_MOCK_NO_FAIL" in os.environ): return False return True - + def has_accounts_in_sqlite(): return True -GWIBBER_SERVICE_AVAILABLE = GwibberHelper.has_accounts_in_sqlite() and os.path.exists("/usr/bin/gwibber-poster") - - +GWIBBER_SERVICE_AVAILABLE = GwibberHelper.has_accounts_in_sqlite() and \ + os.path.exists("/usr/bin/gwibber-poster") diff -Nru software-center-5.1.12/softwarecenter/hw.py software-center-5.1.13/softwarecenter/hw.py --- software-center-5.1.12/softwarecenter/hw.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/hw.py 2012-03-20 08:39:23.000000000 +0000 @@ -1,4 +1,5 @@ # Copyright (C) 2012 Canonical +# -*- coding: utf-8 -*- # # Authors: # Michael Vogt @@ -17,65 +18,119 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from gettext import gettext as _ +from softwarecenter.utils import utf8 + +# private extension over the debtagshw stuff +OPENGL_DRIVER_BLACKLIST_TAG = "x-hardware::opengl-driver-blacklist:" + TAG_DESCRIPTION = { - 'hardware::webcam' : _('webcam'), - 'hardware::digicam' : _('digicam'), - 'hardware::input:mouse' : _('mouse'), - 'hardware::input:joystick' : _('joystick'), - 'hardware::input:touchscreen' : _('touchscreen'), - 'hardware::gps' : _('GPS'), - 'hardware::laptop' : _('notebook computer'), + # normal tags + 'hardware::webcam': _('webcam'), + 'hardware::digicam': _('digicam'), + 'hardware::input:mouse': _('mouse'), + 'hardware::input:joystick': _('joystick'), + 'hardware::input:touchscreen': _('touchscreen'), + 'hardware::gps': _('GPS'), + 'hardware::laptop': _('notebook computer'), 'hardware::printer': _('printer'), - 'hardware::scanner' : _('scanner'), - 'hardware::storage:cd' : _('CD drive'), - 'hardware::storage:cd-writer' : _('CD burner'), - 'hardware::storage:dvd' : _('DVD drive'), - 'hardware::storage:dvd-writer' : _('DVD burner'), - 'hardware::storage:floppy' : _('floppy disk drive'), - 'hardware::video:opengl' : _('OpenGL hardware acceleration'), - + 'hardware::scanner': _('scanner'), + 'hardware::storage:cd': _('CD drive'), + 'hardware::storage:cd-writer': _('CD burner'), + 'hardware::storage:dvd': _('DVD drive'), + 'hardware::storage:dvd-writer': _('DVD burner'), + 'hardware::storage:floppy': _('floppy disk drive'), + 'hardware::video:opengl': _('OpenGL hardware acceleration'), + # "special" private tag extenstion that needs special handling + OPENGL_DRIVER_BLACKLIST_TAG: _('Graphics driver that is not %s'), } TAG_MISSING_DESCRIPTION = { - 'hardware::digicam' : _('This software requires a digital camera, but none ' + 'hardware::digicam': _('This software requires a digital camera, but none ' 'are currently connected'), - 'hardware::webcam' : _('This software requires a video camera, but none ' + 'hardware::webcam': _('This software requires a video camera, but none ' 'are currently connected'), - 'hardware::input:mouse' : _('This software requires a mouse, ' + 'hardware::input:mouse': _('This software requires a mouse, ' 'but none is currently setup.'), - 'hardware::input:joystick' : _('This software requires a joystick, ' + 'hardware::input:joystick': _('This software requires a joystick, ' 'but none are currently connected.'), - 'hardware::input:touchscreen' : _('This software requires a touchscreen, ' + 'hardware::input:touchscreen': _('This software requires a touchscreen, ' 'but the computer does not have one.'), - 'hardware::gps' : _('This software requires a GPS, ' + 'hardware::gps': _('This software requires a GPS, ' 'but the computer does not have one.'), - 'hardware::laptop' : _('This software is for notebook computers.'), + 'hardware::laptop': _('This software is for notebook computers.'), 'hardware::printer': _('This software requires a printer, but none ' 'are currently set up.'), - 'hardware::scanner' : _('This software requires a scanner, but none are ' + 'hardware::scanner': _('This software requires a scanner, but none are ' 'currently set up.'), - 'hardware::stoarge:cd' : _('This software requires a CD drive, but none ' + 'hardware::stoarge:cd': _('This software requires a CD drive, but none ' 'are currently connected.'), - 'hardware::storage:cd-writer' : _('This software requires a CD burner, ' + 'hardware::storage:cd-writer': _('This software requires a CD burner, ' 'but none are currently connected.'), - 'hardware::storage:dvd' : _('This software requires a DVD drive, but none ' + 'hardware::storage:dvd': _('This software requires a DVD drive, but none ' 'are currently connected.'), - 'hardware::storage:dvd-writer' : _('This software requires a DVD burner, ' + 'hardware::storage:dvd-writer': _('This software requires a DVD burner, ' 'but none are currently connected.'), - 'hardware::storage:floppy' : _('This software requires a floppy disk ' + 'hardware::storage:floppy': _('This software requires a floppy disk ' 'drive, but none are currently connected.'), - 'hardware::video:opengl' : _('This computer does not have graphics fast ' + 'hardware::video:opengl': _('This computer does not have graphics fast ' 'enough for this software.'), + # private extension + OPENGL_DRIVER_BLACKLIST_TAG: _(u'This software does not work with the ' + u'\u201c%s\u201D graphics driver this ' + u'computer is using.'), } + +def get_hw_short_description(tag): + # FIXME: deal with OPENGL_DRIVER_BLACKLIST_TAG as this needs rsplit(":") + # and a view of all available tags + s = TAG_DESCRIPTION.get(tag) + return utf8(s) + + def get_hw_missing_long_description(tags): s = "" # build string for tag, supported in tags.iteritems(): if supported == "no": - s += "%s\n" % TAG_MISSING_DESCRIPTION.get(tag) + descr = TAG_MISSING_DESCRIPTION.get(tag) + if descr: + s += "%s\n" % descr + else: + # deal with generic tags + prefix, sep, postfix = tag.rpartition(":") + descr = TAG_MISSING_DESCRIPTION.get(prefix + sep) + descr = descr % postfix + if descr: + s += "%s\n" % descr # ensure that the last \n is gone if s: s = s[:-1] - return s + return utf8(s) + + +def get_private_extensions_hardware_support_for_tags(tags): + import debtagshw + res = {} + for tag in tags: + if tag.startswith(OPENGL_DRIVER_BLACKLIST_TAG): + prefix, sep, driver = tag.rpartition(":") + if driver == debtagshw.opengl.get_driver(): + res[tag] = debtagshw.enums.HardwareSupported.NO + else: + res[tag] = debtagshw.enums.HardwareSupported.YES + return res + + +def get_hardware_support_for_tags(tags): + """ wrapper around the DebtagsAvailalbeHW to support adding our own + private tag extension (like opengl-driver) + """ + from debtagshw.debtagshw import DebtagsAvailableHW + hw = DebtagsAvailableHW() + support = hw.get_hardware_support_for_tags(tags) + private_extensions = get_private_extensions_hardware_support_for_tags( + tags) + support.update(private_extensions) + return support diff -Nru software-center-5.1.12/softwarecenter/i18n.py software-center-5.1.13/softwarecenter/i18n.py --- software-center-5.1.12/softwarecenter/i18n.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/i18n.py 2012-03-16 08:30:18.000000000 +0000 @@ -25,9 +25,10 @@ FALLBACK = "en" # those languages need the full language-code, the other ones # can be abbreved -FULL = ["pt_BR", +FULL = ["pt_BR", "zh_CN", "zh_TW"] + def init_locale(): try: locale.setlocale(locale.LC_ALL, "") @@ -52,11 +53,13 @@ return [get_language()] return langs + def get_language(): """Helper that returns the current language """ try: - language = locale.getdefaultlocale(('LANGUAGE','LANG','LC_CTYPE','LC_ALL'))[0] + language = locale.getdefaultlocale( + ('LANGUAGE', 'LANG', 'LC_CTYPE', 'LC_ALL'))[0] except Exception as e: LOG.warn("Failed to get language: '%s'" % e) language = "C" @@ -67,11 +70,12 @@ return language return language.split("_")[0] + def langcode_to_name(langcode): import xml.etree.ElementTree from gettext import dgettext for iso in ["iso_639_3", "iso_639"]: - path = os.path.join("/usr/share/xml/iso-codes/", iso+".xml") + path = os.path.join("/usr/share/xml/iso-codes/", iso + ".xml") if os.path.exists(path): root = xml.etree.ElementTree.parse(path) xpath = ".//%s_entry[@part1_code='%s']" % (iso, langcode) @@ -79,4 +83,3 @@ if match is not None: return dgettext(iso, match.attrib["name"]) return langcode - diff -Nru software-center-5.1.12/softwarecenter/__init__.py software-center-5.1.13/softwarecenter/__init__.py --- software-center-5.1.12/softwarecenter/__init__.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/__init__.py 2012-03-16 08:30:18.000000000 +0000 @@ -15,4 +15,3 @@ # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - diff -Nru software-center-5.1.12/softwarecenter/log.py software-center-5.1.13/softwarecenter/log.py --- software-center-5.1.12/softwarecenter/log.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/log.py 2012-03-16 08:30:18.000000000 +0000 @@ -26,6 +26,7 @@ """ setup global logging for software-center """ + class NullFilterThatWarnsAboutRootLoggerUsage(logging.Filter): """ pass all messages through, but warn about messages that come from the root logger (and not from the softwarecenter root) @@ -36,24 +37,29 @@ s = "logs to the root logger: '%s'" % str(fixme_msg) logging.getLogger("softwarecenter.fixme").warn(s) return 1 - + + class OrFilter(logging.Filter): """ A filter that can have multiple filter strings and shows the message if any of the filter strings matches """ + def __init__(self): self.filters = [] + def filter(self, record): - for (fname,flen) in self.filters: - if (flen == 0 or - fname == record.name or - (len(record.name)>flen and record.name[flen] == ".")): + for (fname, flen) in self.filters: + if (flen == 0 or + fname == record.name or + (len(record.name) > flen and record.name[flen] == ".")): return 1 return 0 + def add(self, log_filter): """ add a log_filter string to the list of OR expressions """ self.filters.append((log_filter, len(log_filter))) + def add_filters_from_string(long_filter_str): """ take the string passed from e.g. the commandline and create logging.Filters for it. It will prepend "softwarecenter." @@ -66,7 +72,7 @@ filter_str = filter_str.strip("") if filter_str == "": return - if not (filter_str.startswith("sc") or + if not (filter_str.startswith("sc") or filter_str.startswith("softwarecenter")): filter_str = "sc.%s" % filter_str if filter_str.startswith("sc"): @@ -76,10 +82,10 @@ handler.addFilter(logfilter) - # setup global software-center logging root = logging.getLogger() -fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", None) +fmt = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s", None) handler = logging.StreamHandler() handler.setFormatter(fmt) root.addHandler(handler) @@ -92,7 +98,8 @@ # try to fix inaccessible s-c directory (#688682) if not os.access(SOFTWARE_CENTER_CACHE_DIR, os.W_OK): - logging.warn("found not writable '%s' dir, trying to fix" % SOFTWARE_CENTER_CACHE_DIR) + logging.warn("found not writable '%s' dir, trying to fix" % + SOFTWARE_CENTER_CACHE_DIR) # if we have to do more renames, soemthing else is wrong and its # ok to crash later to learn about the problem for i in range(10): @@ -110,12 +117,9 @@ except: logging.exception("failed to fix non-writeable logfile") -logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) +logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes=100 * 1000, backupCount=5) logfile_handler.setLevel(logging.INFO) logfile_handler.setFormatter(fmt) root.addHandler(logfile_handler) - - root.setLevel(logging.INFO) diff -Nru software-center-5.1.12/softwarecenter/netstatus.py software-center-5.1.13/softwarecenter/netstatus.py --- software-center-5.1.12/softwarecenter/netstatus.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/netstatus.py 2012-03-16 08:30:18.000000000 +0000 @@ -25,58 +25,63 @@ from gi.repository import GObject -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + # enums class NetState(object): """ enums for network manager status """ # Old enum values are for NM 0.7 - # The NetworkManager daemon is in an unknown state. - NM_STATE_UNKNOWN = 0 - NM_STATE_UNKNOWN_LIST = [NM_STATE_UNKNOWN] - # The NetworkManager daemon is asleep and all interfaces managed by it are inactive. - NM_STATE_ASLEEP_OLD = 1 - NM_STATE_ASLEEP = 10 - NM_STATE_ASLEEP_LIST = [NM_STATE_ASLEEP_OLD, - NM_STATE_ASLEEP] + # The NetworkManager daemon is in an unknown state. + NM_STATE_UNKNOWN = 0 + NM_STATE_UNKNOWN_LIST = [NM_STATE_UNKNOWN] + # The NetworkManager daemon is asleep and all interfaces managed by + # it are inactive. + NM_STATE_ASLEEP_OLD = 1 + NM_STATE_ASLEEP = 10 + NM_STATE_ASLEEP_LIST = [NM_STATE_ASLEEP_OLD, + NM_STATE_ASLEEP] # The NetworkManager daemon is connecting a device. - NM_STATE_CONNECTING_OLD = 2 - NM_STATE_CONNECTING = 40 - NM_STATE_CONNECTING_LIST = [NM_STATE_CONNECTING_OLD, - NM_STATE_CONNECTING] - # The NetworkManager daemon is connected. - NM_STATE_CONNECTED_OLD = 3 - NM_STATE_CONNECTED_LOCAL = 50 - NM_STATE_CONNECTED_SITE = 60 - NM_STATE_CONNECTED_GLOBAL = 70 - NM_STATE_CONNECTED_LIST = [NM_STATE_CONNECTED_OLD, - NM_STATE_CONNECTED_LOCAL, - NM_STATE_CONNECTED_SITE, - NM_STATE_CONNECTED_GLOBAL] + NM_STATE_CONNECTING_OLD = 2 + NM_STATE_CONNECTING = 40 + NM_STATE_CONNECTING_LIST = [NM_STATE_CONNECTING_OLD, + NM_STATE_CONNECTING] + # The NetworkManager daemon is connected. + NM_STATE_CONNECTED_OLD = 3 + NM_STATE_CONNECTED_LOCAL = 50 + NM_STATE_CONNECTED_SITE = 60 + NM_STATE_CONNECTED_GLOBAL = 70 + NM_STATE_CONNECTED_LIST = [NM_STATE_CONNECTED_OLD, + NM_STATE_CONNECTED_LOCAL, + NM_STATE_CONNECTED_SITE, + NM_STATE_CONNECTED_GLOBAL] # The NetworkManager daemon is disconnecting. - NM_STATE_DISCONNECTING = 30 + NM_STATE_DISCONNECTING = 30 NM_STATE_DISCONNECTING_LIST = [NM_STATE_DISCONNECTING] # The NetworkManager daemon is disconnected. - NM_STATE_DISCONNECTED_OLD = 4 - NM_STATE_DISCONNECTED = 20 - NM_STATE_DISCONNECTED_LIST = [NM_STATE_DISCONNECTED_OLD, + NM_STATE_DISCONNECTED_OLD = 4 + NM_STATE_DISCONNECTED = 20 + NM_STATE_DISCONNECTED_LIST = [NM_STATE_DISCONNECTED_OLD, NM_STATE_DISCONNECTED] class NetworkStatusWatcher(GObject.GObject): """ simple watcher which notifys subscribers to network events...""" - __gsignals__ = {'changed':(GObject.SIGNAL_RUN_FIRST, - GObject.TYPE_NONE, - (int,)), + __gsignals__ = {'changed': (GObject.SIGNAL_RUN_FIRST, + GObject.TYPE_NONE, + (int,)), } def __init__(self): GObject.GObject.__init__(self) return + # internal helper NETWORK_STATE = 0 + + def __connection_state_changed_handler(state): global NETWORK_STATE @@ -84,15 +89,16 @@ __WATCHER__.emit("changed", NETWORK_STATE) return + # init network state def __init_network_state(): global NETWORK_STATE # honor SOFTWARE_CENTER_NET_{DIS,}CONNECTED in the environment variables import os - env_map = { - 'SOFTWARE_CENTER_NET_DISCONNECTED' : NetState.NM_STATE_DISCONNECTED, - 'SOFTWARE_CENTER_NET_CONNECTED' : NetState.NM_STATE_CONNECTED_GLOBAL, + env_map = { + 'SOFTWARE_CENTER_NET_DISCONNECTED': NetState.NM_STATE_DISCONNECTED, + 'SOFTWARE_CENTER_NET_CONNECTED': NetState.NM_STATE_CONNECTED_GLOBAL, } for envkey, state in env_map.iteritems(): if envkey in os.environ: @@ -104,10 +110,12 @@ bus = dbus.SystemBus(mainloop=dbus_loop) nm = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') - NETWORK_STATE = nm.state(dbus_interface='org.freedesktop.NetworkManager') - bus.add_signal_receiver(__connection_state_changed_handler, - dbus_interface="org.freedesktop.NetworkManager", - signal_name="StateChanged") + NETWORK_STATE = nm.state( + dbus_interface='org.freedesktop.NetworkManager') + bus.add_signal_receiver( + __connection_state_changed_handler, + dbus_interface="org.freedesktop.NetworkManager", + signal_name="StateChanged") return except Exception as e: @@ -121,6 +129,7 @@ thread.start() return + #helper def test_ping(): global NETWORK_STATE @@ -161,15 +170,19 @@ # global watcher __WATCHER__ = NetworkStatusWatcher() + + def get_network_watcher(): return __WATCHER__ + # simply query def get_network_state(): """ get the NetState state """ global NETWORK_STATE return NETWORK_STATE + # simply query even more def network_state_is_connected(): """ get bool if we are connected """ @@ -183,4 +196,3 @@ if __name__ == '__main__': loop = GObject.MainLoop() loop.run() - diff -Nru software-center-5.1.12/softwarecenter/paths.py software-center-5.1.13/softwarecenter/paths.py --- software-center-5.1.12/softwarecenter/paths.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/paths.py 2012-03-20 08:40:44.000000000 +0000 @@ -34,16 +34,14 @@ # xdg_cache_home=os.path.expanduser("~/.cache")) from xdg import BaseDirectory as xdg -from softwarecenter.toolkit import CURRENT_TOOLKIT, UIToolkits - # global datadir, this maybe overriden at startup datadir = "/usr/share/software-center/" # system pathes APP_INSTALL_PATH = "/usr/share/app-install" -APP_INSTALL_DESKTOP_PATH = APP_INSTALL_PATH+"/desktop/" -APP_INSTALL_CHANNELS_PATH = APP_INSTALL_PATH+"/channels/" -ICON_PATH = APP_INSTALL_PATH+"/icons/" +APP_INSTALL_DESKTOP_PATH = APP_INSTALL_PATH + "/desktop/" +APP_INSTALL_CHANNELS_PATH = APP_INSTALL_PATH + "/channels/" +ICON_PATH = APP_INSTALL_PATH + "/icons/" APPSTREAM_BASE_PATH = "/usr/share/app-info" APPSTREAM_XML_PATH = APPSTREAM_BASE_PATH + "/xmls/" @@ -55,41 +53,37 @@ ] # FIXME: use relative paths here -INSTALLED_ICON = "/usr/share/software-center/icons/software-center-installed.png" -IMAGE_LOADING = "/usr/share/icons/hicolor/32x32/animations/softwarecenter-loading.gif" -IMAGE_LOADING_INSTALLED = "/usr/share/icons/hicolor/32x32/animations/softwarecenter-loading-installed.gif" +INSTALLED_ICON = \ + "/usr/share/software-center/icons/software-center-installed.png" # xapian pathes XAPIAN_BASE_PATH = "/var/cache/software-center" XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT = os.path.join( xdg.xdg_cache_home, - "software-center", + "software-center", "software-center-agent.db") -XAPIAN_PATH=os.path.join(XAPIAN_BASE_PATH, "xapian") +XAPIAN_PATH = os.path.join(XAPIAN_BASE_PATH, "xapian") + # ratings&review # relative to datadir class RNRApps: - if CURRENT_TOOLKIT is UIToolkits.GTK2: - SUBMIT_REVIEW = "submit_review.py" - REPORT_REVIEW = "report_review.py" - SUBMIT_USEFULNESS = "submit_usefulness.py" - MODIFY_REVIEW = "modify_review.py" - DELETE_REVIEW = "delete_review.py" - elif CURRENT_TOOLKIT is UIToolkits.GTK3: - SUBMIT_REVIEW = "submit_review_gtk3.py" - REPORT_REVIEW = "report_review_gtk3.py" - SUBMIT_USEFULNESS = "submit_usefulness_gtk3.py" - MODIFY_REVIEW = "modify_review_gtk3.py" - DELETE_REVIEW = "delete_review_gtk3.py" + SUBMIT_REVIEW = "submit_review_gtk3.py" + REPORT_REVIEW = "report_review_gtk3.py" + SUBMIT_USEFULNESS = "submit_usefulness_gtk3.py" + MODIFY_REVIEW = "modify_review_gtk3.py" + DELETE_REVIEW = "delete_review_gtk3.py" + # piston helpers class PistonHelpers: GET_REVIEWS = "piston_get_reviews_helper.py" GENERIC_HELPER = "piston_generic_helper.py" + X2GO_HELPER = "x2go_helper.py" + # there was a bug in maverick 3.0.3 (#652151) that could lead to a empty # root owned directory in ~/.cache/software-center - we remove it here # so that it gets later re-created with the right permissions @@ -102,14 +96,21 @@ logging.exception("failed to fix not writable cache directory") if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: - SOFTWARE_CENTER_CONFIG_DIR = os.path.join(xdg.xdg_config_home, "software-center", "fake-review") - SOFTWARE_CENTER_CACHE_DIR = os.path.join(xdg.xdg_cache_home, "software-center", "fake-review") + SOFTWARE_CENTER_CONFIG_DIR = os.path.join( + xdg.xdg_config_home, "software-center", "fake-review") + SOFTWARE_CENTER_CACHE_DIR = os.path.join( + xdg.xdg_cache_home, "software-center", "fake-review") else: - SOFTWARE_CENTER_CONFIG_DIR = os.path.join(xdg.xdg_config_home, "software-center") - SOFTWARE_CENTER_CACHE_DIR = os.path.join(xdg.xdg_cache_home, "software-center") - + SOFTWARE_CENTER_CONFIG_DIR = os.path.join( + xdg.xdg_config_home, "software-center") + SOFTWARE_CENTER_CACHE_DIR = os.path.join( + xdg.xdg_cache_home, "software-center") + + # FIXUP a brief broken software-center in maverick try_to_fixup_root_owned_dir_via_remove(SOFTWARE_CENTER_CACHE_DIR) -SOFTWARE_CENTER_CONFIG_FILE = os.path.join(SOFTWARE_CENTER_CONFIG_DIR, "softwarecenter.cfg") -SOFTWARE_CENTER_ICON_CACHE_DIR = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "icons") +SOFTWARE_CENTER_CONFIG_FILE = os.path.join( + SOFTWARE_CENTER_CONFIG_DIR, "softwarecenter.cfg") +SOFTWARE_CENTER_ICON_CACHE_DIR = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "icons") diff -Nru software-center-5.1.12/softwarecenter/plugin.py software-center-5.1.13/softwarecenter/plugin.py --- software-center-5.1.12/softwarecenter/plugin.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/plugin.py 2012-03-16 08:30:18.000000000 +0000 @@ -26,14 +26,14 @@ from utils import ExecutionTime + class Plugin(object): """Base class for plugins. - """ def init_plugin(self): """ Init the plugin (UI, connect signals etc) - + This should be overwriten by the individual plugins and should return as fast as possible. if some longer init is required, start a glib timer or a thread @@ -43,12 +43,11 @@ class PluginManager(object): """Class to find and load plugins. - + Plugins are stored in files named '*_plugin.py' in the list of directories given to the initializer. - """ - + def __init__(self, app, plugin_dirs): self._app = app if isinstance(plugin_dirs, basestring): @@ -58,18 +57,16 @@ def get_plugin_files(self): """Return all filenames in which plugins may be stored.""" - names = [] for dirname in self._plugin_dirs: if not os.path.exists(dirname): LOG.debug("no dir '%s'" % dirname) continue - basenames = [x for x in os.listdir(dirname) + basenames = [x for x in os.listdir(dirname) if x.endswith(".py")] - LOG.debug("Plugin modules in %s: %s" % + LOG.debug("Plugin modules in %s: %s" % (dirname, " ".join(basenames))) names += [os.path.join(dirname, x) for x in basenames] - return names def _find_plugins(self, module): @@ -90,8 +87,8 @@ try: module = imp.load_module(module_name, f, filename, (".py", "r", imp.PY_SOURCE)) - except Exception as e: # pragma: no cover - LOG.warning("Failed to load plugin '%s' (%s)" % + except Exception as e: # pragma: no cover + LOG.warning("Failed to load plugin '%s' (%s)" % (module_name, e)) return None f.close() @@ -110,7 +107,8 @@ filenames = self.get_plugin_files() for filename in filenames: if not os.path.exists(filename): - LOG.warn("plugin '%s' does not exists, dangling symlink?" % filename) + LOG.warn("plugin '%s' does not exists, dangling symlink?" % + filename) continue with ExecutionTime("loading plugin: '%s'" % filename): module = self._load_module(filename) diff -Nru software-center-5.1.12/softwarecenter/region.py software-center-5.1.13/softwarecenter/region.py --- software-center-5.1.12/softwarecenter/region.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/region.py 2012-03-16 08:30:18.000000000 +0000 @@ -28,7 +28,7 @@ from gettext import dgettext from gettext import gettext as _ -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) # warning displayed if region does not match REGION_WARNING_STRING = _("Sorry, this software is not available in your " @@ -39,12 +39,13 @@ # blacklist this region REGION_BLACKLIST_TAG = "blacklist-iso3166::" + def get_region_name(countrycode): """ return translated region name from countrycode using iso3166 """ # find translated name if countrycode: for iso in ["iso_3166", "iso_3166_2"]: - path = os.path.join("/usr/share/xml/iso-codes/", iso+".xml") + path = os.path.join("/usr/share/xml/iso-codes/", iso + ".xml") if os.path.exists(path): root = xml.etree.ElementTree.parse(path) xpath = ".//%s_entry[@alpha_2_code='%s']" % (iso, countrycode) @@ -56,22 +57,25 @@ return dgettext(iso, name) return "" + # the first parameter of SetRequirements class AccuracyLevel: - NONE = 0 - COUNTRY = 1 - REGION = 2 - LOCALITY = 3 - POSTALCODE = 4 - STREET = 5 - DETAILED = 6 + NONE = 0 + COUNTRY = 1 + REGION = 2 + LOCALITY = 3 + POSTALCODE = 4 + STREET = 5 + DETAILED = 6 + class AllowedResources: NONE = 0 NETWORK = 1 << 0 CELL = 1 << 1 GPS = 1 << 2 - ALL = (1 << 10) -1 + ALL = (1 << 10) - 1 + class RegionDiscover(object): @@ -93,9 +97,9 @@ def _get_region_dumb(self): """ return dict estimate about the current countrycode/country """ - res = { 'countrycode' : '', - 'country' : '', - } + res = {'countrycode': '', + 'country': '', + } try: # use LC_MONETARY as the best guess loc = locale.getlocale(locale.LC_MONETARY)[0] @@ -111,11 +115,12 @@ def _get_region_geoclue(self): """ return the dict with at least countrycode,country from a geoclue - provider + provider """ bus = dbus.SessionBus() master = bus.get_object( - 'org.freedesktop.Geoclue.Master', '/org/freedesktop/Geoclue/Master') + 'org.freedesktop.Geoclue.Master', + '/org/freedesktop/Geoclue/Master') client = bus.get_object( 'org.freedesktop.Geoclue.Master', master.Create()) client.SetRequirements(AccuracyLevel.COUNTRY, # (i) accuracy_level @@ -128,7 +133,10 @@ time, address_res, accuracy = client.GetAddress() return address_res + my_region = None + + def get_region_cached(): global my_region if my_region is None: diff -Nru software-center-5.1.12/softwarecenter/testutils.py software-center-5.1.13/softwarecenter/testutils.py --- software-center-5.1.12/softwarecenter/testutils.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/softwarecenter/testutils.py 2012-03-19 16:41:16.000000000 +0000 @@ -24,23 +24,25 @@ m_dbus = m_polkit = m_aptd = None + + def start_dummy_backend(): global m_dbus, m_polkit, m_aptd # start private dbus - m_dbus = subprocess.Popen(["dbus-daemon", - "--session", + m_dbus = subprocess.Popen(["dbus-daemon", + "--session", "--nofork", - "--print-address"], + "--print-address"], stdout=subprocess.PIPE) # get and store address bus_address = m_dbus.stdout.readline().strip() os.environ["SOFTWARE_CENTER_APTD_FAKE"] = bus_address # start fake polkit from python-aptdaemon.test - env = { "DBUS_SESSION_BUS_ADDRESS" : bus_address, - "DBUS_SYSTEM_BUS_ADDRESS" : bus_address, + env = {"DBUS_SESSION_BUS_ADDRESS": bus_address, + "DBUS_SYSTEM_BUS_ADDRESS": bus_address, } m_polkit = subprocess.Popen( - ["/usr/share/aptdaemon/tests/fake-polkitd.py", + ["/usr/share/aptdaemon/tests/fake-polkitd.py", "--allowed-actions=all"], env=env) # start aptd in dummy mode @@ -51,6 +53,7 @@ # to ensure that the fake daemon and fake polkit is ready time.sleep(0.5) + def stop_dummy_backend(): global m_dbus, m_polkit, m_aptd m_aptd.terminate() @@ -60,6 +63,7 @@ m_dbus.terminate() m_dbus.wait() + def get_test_gtk3_viewmanager(): from gi.repository import Gtk from softwarecenter.ui.gtk3.session.viewmanager import ( @@ -68,9 +72,10 @@ if not vm: notebook = Gtk.Notebook() vm = ViewManager(notebook) - vm.view_to_pane = {None : None} + vm.view_to_pane = {None: None} return vm + def get_test_db(): from softwarecenter.db.database import StoreDatabase from softwarecenter.db.pkginfo import get_pkg_info @@ -81,34 +86,41 @@ db.open() return db + def get_test_install_backend(): from softwarecenter.backend.installbackend import get_install_backend backend = get_install_backend() return backend + def get_test_gtk3_icon_cache(): from softwarecenter.ui.gtk3.utils import get_sc_icon_theme import softwarecenter.paths icons = get_sc_icon_theme(softwarecenter.paths.datadir) return icons + def get_test_pkg_info(): from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() cache.open() return cache + def get_test_datadir(): import softwarecenter.paths return softwarecenter.paths.datadir + def get_test_categories(db): import softwarecenter.paths from softwarecenter.db.categories import CategoriesParser parser = CategoriesParser(db) - cats = parser.parse_applications_menu(softwarecenter.paths.APP_INSTALL_PATH) + cats = parser.parse_applications_menu( + softwarecenter.paths.APP_INSTALL_PATH) return cats + def get_test_enquirer_matches(db, query=None, limit=20, sortmode=0): from softwarecenter.db.enquire import AppEnquire import xapian @@ -121,12 +133,14 @@ nonblocking_load=False) return enquirer.matches + def do_events(): from gi.repository import GObject main_loop = GObject.main_context_default() while main_loop.pending(): main_loop.iteration() + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified @@ -138,12 +152,14 @@ details = app.get_details(db) details_mock = Mock(details) for a in dir(details): - if a.startswith("_"): continue + if a.startswith("_"): + continue setattr(details_mock, a, getattr(details, a)) app.details = details_mock app.get_details = lambda db: app.details return app + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -163,7 +179,7 @@ # factory stuff for the agent def make_software_center_agent_app_dict(): app_dict = { - u'archive_root' : 'http://private-ppa.launchpad.net/', + u'archive_root': 'http://private-ppa.launchpad.net/', u'archive_id': u'commercial-ppa-uploaders/photobomb', u'description': u"Easy and Social Image Editor\nPhotobomb " u"give you easy access to images in your " @@ -171,18 +187,21 @@ u'name': u'Photobomb', u'package_name': u'photobomb', u'signing_key_id': u'1024R/75254D99', - u'screenshot_url': 'http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot.png', + u'screenshot_url': 'http://software-center.ubuntu.com/site_media/'\ + 'screenshots/2011/08/Screenshot.png', u'license': 'Proprietary', u'support_url': 'mailto:support@example.com', - u'series': { 'oneiric' : ['i386', 'amd64'], - 'natty' : ['i386', 'amd64'], + u'series': {'oneiric': ['i386', 'amd64'], + 'natty': ['i386', 'amd64'], }, - u'channel' : 'For Purchase', - u'icon_url' : 'http://software-center.ubuntu.com/site_media/icons/2011/08/64_Chainz.png', + u'channel': 'For Purchase', + u'icon_url': 'http://software-center.ubuntu.com/site_media/icons/'\ + '2011/08/64_Chainz.png', u'categories': 'Game;LogicGame', } return app_dict + def make_software_center_agent_subscription_dict(app_dict): subscription_dict = { u'application': app_dict, @@ -198,78 +217,90 @@ } return subscription_dict + def make_recommender_agent_recommend_me_dict(): # best to have a list of likely not-installed items app_dict = { u'data': [ { u'package_name': u'clementine' - }, + }, { u'package_name': u'hedgewars' }, { - u'package_name': u'gelemental' - }, + u'package_name': u'mangler' + }, { u'package_name': u'nexuiz' }, { u'package_name': u'fgo' - }, + }, { u'package_name': u'musique' }, { u'package_name': u'pybik' - }, + }, { u'package_name': u'radiotray' }, { u'package_name': u'cherrytree' - }, + }, { u'package_name': u'phlipple' + }, + { + u'package_name': u'psi' + }, + { + u'package_name': u'midori' } - ] -} + ] + } return app_dict - -def make_recommender_profile_upload_data(): + + +def make_recommender_profile_upload_data(): from softwarecenter.utils import get_uuid recommender_uuid = get_uuid() profile_upload_data = [ { - 'uuid': recommender_uuid, + 'uuid': recommender_uuid, 'package_list': [ u'clementine', u'hedgewars', - u'gelemental', + u'mangler', u'nexuiz', u'fgo', u'musique', u'pybik', u'radiotray', u'cherrytree', - u'phlipple' + u'phlipple', + u'psi', + u'midori' ] } ] return profile_upload_data - + + def make_recommend_app_data(): - recommend_app_data = {u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', - u'data': [{u'rating': 4.0, u'package_name': u'kftpgrabber'}, - {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, - {u'rating': 3.0, u'package_name': u'wakeup'}, - {u'rating': 3.0, u'package_name': u'xvidcap'}, - {u'rating': 2.0, u'package_name': u'airstrike'}, - {u'rating': 2.0, u'package_name': u'pixbros'}, - {u'rating': 2.0, u'package_name': u'bomber'}, - {u'rating': 2.0, u'package_name': u'ktron'}, - {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, - {u'rating': 1.5, u'package_name': u'tucan'}], - u'app': u'pitivi'} + recommend_app_data = { + u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', + u'data': [ + {u'rating': 4.0, u'package_name': u'kftpgrabber'}, + {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, + {u'rating': 3.0, u'package_name': u'wakeup'}, + {u'rating': 3.0, u'package_name': u'xvidcap'}, + {u'rating': 2.0, u'package_name': u'airstrike'}, + {u'rating': 2.0, u'package_name': u'pixbros'}, + {u'rating': 2.0, u'package_name': u'bomber'}, + {u'rating': 2.0, u'package_name': u'ktron'}, + {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, + {u'rating': 1.5, u'package_name': u'tucan'}], + u'app': u'pitivi'} return recommend_app_data - diff -Nru software-center-5.1.12/softwarecenter/toolkit.py software-center-5.1.13/softwarecenter/toolkit.py --- software-center-5.1.12/softwarecenter/toolkit.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/toolkit.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -import sys - -class UIToolkits: - GTK2 = 0 - GTK3 = 1 - QML = 2 - FALLBACK = GTK3 - - -if 'software-center' in sys.argv[0]: - CURRENT_TOOLKIT = UIToolkits.GTK3 -elif 'software-center-gtk2' in sys.argv[0]: - CURRENT_TOOLKIT = UIToolkits.GTK2 -elif 'software-center-qml' in sys.argv[0]: - CURRENT_TOOLKIT = UIToolkits.QML -else: - CURRENT_TOOLKIT = UIToolkits.FALLBACK diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/app.py software-center-5.1.13/softwarecenter/ui/gtk3/app.py --- software-center-5.1.12/softwarecenter/ui/gtk3/app.py 2012-03-08 07:48:06.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/app.py 2012-03-20 10:12:24.000000000 +0000 @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (C) 2009 Canonical # # Authors: @@ -59,6 +60,7 @@ DB_SCHEMA_VERSION, MOUSE_EVENT_FORWARD_BUTTON, MOUSE_EVENT_BACK_BUTTON, + SOFTWARE_CENTER_TOS_LINK, SOFTWARE_CENTER_NAME_KEYRING) from softwarecenter.utils import (clear_token_from_ubuntu_sso, get_http_proxy_string_from_gsettings, @@ -71,7 +73,7 @@ from softwarecenter.db.database import StoreDatabase try: from aptd_gtk3 import InstallBackendUI - InstallBackendUI # pyflakes + InstallBackendUI # pyflakes except: from softwarecenter.backend.installbackend import InstallBackendUI @@ -89,6 +91,8 @@ get_appmanager) from softwarecenter.ui.gtk3.session.viewmanager import ( ViewManager, get_viewmanager) +from softwarecenter.ui.gtk3.widgets.recommendations import ( + RecommendationsOptInDialog) from softwarecenter.config import get_config from softwarecenter.backend import get_install_backend @@ -96,7 +100,10 @@ from softwarecenter.backend.channel import AllInstalledChannel from softwarecenter.backend.reviews import get_review_loader, UsefulnessCache -from softwarecenter.backend.oneconfhandler import get_oneconf_handler, is_oneconf_available +from softwarecenter.backend.oneconfhandler import ( + get_oneconf_handler, + is_oneconf_available, +) from softwarecenter.distro import get_distro from softwarecenter.db.pkginfo import get_pkg_info @@ -105,10 +112,12 @@ LOG = logging.getLogger(__name__) + # py3 compat def callable(func): return isinstance(func, collections.Callable) + class SoftwarecenterDbusController(dbus.service.Object): """ This is a helper to provide the SoftwarecenterIFace @@ -164,11 +173,11 @@ #~ self.useful_cache = UsefulnessCache(True) #~ self.setup_database_rebuilding_listener() #~ # open plugin manager and load plugins - #~ self.plugin_manager = PluginManager(self, SOFTWARE_CENTER_PLUGIN_DIRS) + #~ self.plugin_manager = PluginManager(self, + #~ SOFTWARE_CENTER_PLUGIN_DIRS) #~ self.plugin_manager.load_plugins() - class SoftwareCenterAppGtk3(SimpleGtkbuilderApp): WEBLINK_URL = "http://apt.ubuntu.com/p/%s" @@ -183,7 +192,7 @@ self.datadir = datadir SimpleGtkbuilderApp.__init__(self, - datadir+"/ui/gtk3/SoftwareCenter.ui", + datadir + "/ui/gtk3/SoftwareCenter.ui", "software-center") gettext.bindtextdomain("software-center", "/usr/share/locale") gettext.textdomain("software-center") @@ -215,7 +224,7 @@ self._use_axi = not options.disable_apt_xapian_index try: self.db = StoreDatabase(pathname, self.cache) - self.db.open(use_axi = self._use_axi) + self.db.open(use_axi=self._use_axi) if self.db.schema_version() != DB_SCHEMA_VERSION: LOG.warn("database format '%s' expected, but got '%s'" % ( DB_SCHEMA_VERSION, self.db.schema_version())) @@ -224,8 +233,8 @@ except xapian.DatabaseOpeningError: # Couldn't use that folder as a database # This may be because we are in a bzr checkout and that - # folder is empty. If the folder is empty, and we can find the - # script that does population, populate a database in it. + # folder is empty. If the folder is empty, and we can find + # the script that does population, populate a database in it. if os.path.isdir(pathname) and not os.listdir(pathname): self._rebuild_and_reopen_local_db(pathname) except xapian.DatabaseCorruptError: @@ -245,12 +254,14 @@ with ExecutionTime("creating the backend"): self.backend = get_install_backend() self.backend.ui = InstallBackendUI() - self.backend.connect("transaction-finished", self._on_transaction_finished) + self.backend.connect("transaction-finished", + self._on_transaction_finished) self.backend.connect("channels-changed", self.on_channels_changed) # high level app management with ExecutionTime("get the app-manager"): - self.app_manager = ApplicationManager(self.db, self.backend, self.icons) + self.app_manager = ApplicationManager(self.db, self.backend, + self.icons) # misc state self._block_menuitem_view = False @@ -280,7 +291,8 @@ self.view_manager = ViewManager(self.notebook_view, options) with ExecutionTime("building panes"): - self.global_pane = GlobalPane(self.view_manager, self.datadir, self.db, self.cache, self.icons) + self.global_pane = GlobalPane(self.view_manager, self.datadir, + self.db, self.cache, self.icons) self.vbox1.pack_start(self.global_pane, False, False, 0) self.vbox1.reorder_child(self.global_pane, 1) @@ -292,8 +304,10 @@ self.datadir, self.navhistory_back_action, self.navhistory_forward_action) - self.available_pane.connect("available-pane-created", self.on_available_pane_created) - self.view_manager.register(self.available_pane, ViewPages.AVAILABLE) + self.available_pane.connect("available-pane-created", + self.on_available_pane_created) + self.view_manager.register(self.available_pane, + ViewPages.AVAILABLE) # installed pane (view not fully initialized at this point) self.installed_pane = InstalledPane(self.cache, @@ -301,8 +315,10 @@ self.distro, self.icons, self.datadir) - #~ self.installed_pane.connect("installed-pane-created", self.on_installed_pane_created) - self.view_manager.register(self.installed_pane, ViewPages.INSTALLED) + #~ self.installed_pane.connect("installed-pane-created", + #~ self.on_installed_pane_created) + self.view_manager.register(self.installed_pane, + ViewPages.INSTALLED) # history pane (not fully loaded at this point) self.history_pane = HistoryPane(self.cache, @@ -316,8 +332,10 @@ self.pending_pane = PendingPane(self.icons) self.view_manager.register(self.pending_pane, ViewPages.PENDING) - # TRANSLATORS: this is the help menuitem label, e.g. Ubuntu Software Center _Help - self.menuitem_help.set_label(_("%s _Help")%self.distro.get_app_name()) + # TRANSLATORS: this is the help menuitem label, + # e.g. Ubuntu Software Center _Help + self.menuitem_help.set_label(_("%s _Help") % + self.distro.get_app_name()) # specify the smallest allowable window size self.window_main.set_size_request(730, 470) @@ -326,14 +344,16 @@ with ExecutionTime("create review loader"): self.review_loader = get_review_loader(self.cache, self.db) # FIXME: add some kind of throttle, I-M-S here - self.review_loader.refresh_review_stats(self.on_review_stats_loaded) + self.review_loader.refresh_review_stats( + self.on_review_stats_loaded) #load usefulness votes from server when app starts self.useful_cache = UsefulnessCache(True) self.setup_database_rebuilding_listener() with ExecutionTime("create plugin manager"): # open plugin manager and load plugins - self.plugin_manager = PluginManager(self, SOFTWARE_CENTER_PLUGIN_DIRS) + self.plugin_manager = PluginManager(self, + SOFTWARE_CENTER_PLUGIN_DIRS) self.plugin_manager.load_plugins() # setup window name and about information (needs branding) @@ -345,20 +365,23 @@ # about dialog self.aboutdialog.connect("response", lambda dialog, rid: dialog.hide()) - self.aboutdialog.connect("delete_event", lambda w,e: self.aboutdialog.hide_on_delete()) + self.aboutdialog.connect("delete_event", + lambda w, e: self.aboutdialog.hide_on_delete()) # restore state self.config = get_config() self.restore_state() # Adapt menu entries - supported_menuitem = self.builder.get_object("menuitem_view_supported_only") + supported_menuitem = self.builder.get_object( + "menuitem_view_supported_only") supported_menuitem.set_label(self.distro.get_supported_filter_name()) file_menu = self.builder.get_object("menu1") if not self.distro.DEVELOPER_URL: help_menu = self.builder.get_object("menu_help") - developer_separator = self.builder.get_object("separator_developer") + developer_separator = self.builder.get_object( + "separator_developer") help_menu.remove(developer_separator) developer_menuitem = self.builder.get_object("menuitem_developer") help_menu.remove(developer_menuitem) @@ -366,39 +389,41 @@ # Check if oneconf is available och = is_oneconf_available() if not och: - file_menu.remove(self.builder.get_object("menuitem_sync_between_computers")) + file_menu.remove(self.builder.get_object( + "menuitem_sync_between_computers")) - # restore the state of the add to launcher menu item, or remove the menu - # item if Unity is not currently running + # restore the state of the add to launcher menu item, or remove the + # menu item if Unity is not currently running add_to_launcher_menuitem = self.builder.get_object( - "menuitem_add_to_launcher") + "menuitem_add_to_launcher") if is_unity_running(): add_to_launcher_menuitem.set_active( self.available_pane.add_to_launcher_enabled) else: view_menu = self.builder.get_object("menu_view") add_to_launcher_separator = self.builder.get_object( - "add_to_launcher_separator") + "add_to_launcher_separator") view_menu.remove(add_to_launcher_separator) view_menu.remove(add_to_launcher_menuitem) # run s-c-agent update if options.disable_buy or not self.distro.PURCHASE_APP_URL: - file_menu.remove(self.builder.get_object("menuitem_reinstall_purchases")) + file_menu.remove(self.builder.get_object( + "menuitem_reinstall_purchases")) if not (options.enable_lp or och): file_menu.remove(self.builder.get_object("separator_login")) else: # running the agent will trigger a db reload so we do it later GObject.timeout_add_seconds(30, self._run_software_center_agent) - # keep the cache clean GObject.timeout_add_seconds(15, self._run_expunge_cache_helper) # TODO: Remove the following two lines once we have remove repository # support in aptdaemon (see LP: #723911) file_menu = self.builder.get_object("menu1") - file_menu.remove(self.builder.get_object("menuitem_deauthorize_computer")) + file_menu.remove(self.builder.get_object( + "menuitem_deauthorize_computer")) # keep track of the current active pane self.active_pane = self.available_pane @@ -412,7 +437,6 @@ except Exception, e: LOG.debug("launchpad integration error: '%s'" % e) - # helper def _run_software_center_agent(self): """ helper that triggers the update-software-center-agent helper """ @@ -456,19 +480,28 @@ self._gsettings.connect("changed", self._setup_proxy) def _setup_proxy(self, setting=None, key=None): - proxy = get_http_proxy_string_from_gsettings() - if proxy: - os.environ["http_proxy"] = proxy - elif "http_proxy" in os.environ: - del os.environ["http_proxy"] - + try: + proxy = get_http_proxy_string_from_gsettings() + LOG.info("setting up proxy '%s'" % proxy) + if proxy: + os.environ["http_proxy"] = proxy + else: + del os.environ["http_proxy"] + except Exception as e: + # if no gnome settings are installed, do not mess with + # http_proxy + LOG.warn("could not get proxy settings '%s'" % e) + pass # callbacks def on_realize(self, widget): - return + pass def on_available_pane_created(self, widget): self.available_pane.searchentry.grab_focus() + rec_panel = self.available_pane.cat_view.recommended_for_you_panel + self._update_recommendations_menuitem( + opted_in=rec_panel.recommender_agent.is_opted_in()) # connect a signal to monitor the recommendations opt-in state and # persist the recommendations uuid on an opt-in self.available_pane.cat_view.recommended_for_you_panel.connect( @@ -481,16 +514,29 @@ #~ def on_installed_pane_created(self, widget): #~ pass - def _on_recommendations_opt_in(self, agent, recommender_uuid): + def _on_recommendations_opt_in(self, rec_panel, recommender_uuid): self.recommender_uuid = recommender_uuid + self._update_recommendations_menuitem(opted_in=True) - def _on_recommendations_opt_out(self): + def _on_recommendations_opt_out(self, rec_panel): # if the user opts back out of the recommender service, we - # reset the UUID to indicate it + # reset the recommender UUID to indicate it self.recommender_uuid = "" + self._update_recommendations_menuitem(opted_in=False) + + def _update_recommendations_menuitem(self, opted_in): + recommendations_menuitem = self.builder.get_object( + "menuitem_recommendations") + if opted_in: + recommendations_menuitem.set_label( + _(u"Turn Off Recommendations")) + else: + recommendations_menuitem.set_label( + _(u"Turn On Recommendations…")) def _on_update_software_center_agent_finished(self, pid, condition): - LOG.info("software-center-agent finished with status %i" % os.WEXITSTATUS(condition)) + LOG.info("software-center-agent finished with status %i" % + os.WEXITSTATUS(condition)) if os.WEXITSTATUS(condition) == 0: self.db.reopen() @@ -524,7 +570,8 @@ self.menuitem_undo.activate() if (event.keyval == Gdk.keyval_from_name("Z") and - event.state == (Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK)): + event.state == (Gdk.ModifierType.SHIFT_MASK | + Gdk.ModifierType.CONTROL_MASK)): self.menuitem_edit.activate() if self.menuitem_redo.get_sensitive(): self.menuitem_redo.activate() @@ -550,7 +597,8 @@ # copy web link if (event.keyval == Gdk.keyval_from_name("C") and - event.state == (Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK)): + event.state == (Gdk.ModifierType.SHIFT_MASK | + Gdk.ModifierType.CONTROL_MASK)): self.menuitem_edit.activate() if self.menuitem_copy_web_link.get_sensitive(): self.menuitem_copy_web_link.activate() @@ -609,7 +657,8 @@ def _on_lp_login(self, lp, token): self._lp_login_successful = True private_archives = self.glaunchpad.get_subscribed_archives() - self.view_switcher.get_model().channel_manager.feed_in_private_sources_list_entries( + channel_manager = self.view_switcher.get_model().channel_manager + channel_manager.feed_in_private_sources_list_entries( private_archives) def _on_sso_login(self, sso, oauth_result): @@ -621,20 +670,21 @@ def _on_style_updated(self, widget, init_css_callback, *args): init_css_callback(widget, *args) - return def _available_for_me_result(self, scagent, result_list): #print "available_for_me_result", result_list from softwarecenter.db.update import ( add_from_purchased_but_needs_reinstall_data) - self.available_for_me_query = add_from_purchased_but_needs_reinstall_data( - result_list, self.db, self.cache) - self.available_pane.on_previous_purchases_activated(self.available_for_me_query) + available = add_from_purchased_but_needs_reinstall_data(result_list, + self.db, self.cache) + self.available_for_me_query = available + self.available_pane.on_previous_purchases_activated(available) def get_icon_filename(self, iconname, iconsize): iconinfo = self.icons.lookup_icon(iconname, iconsize, 0) if not iconinfo: - iconinfo = self.icons.lookup_icon(Icons.MISSING_APP_ICON, iconsize, 0) + iconinfo = self.icons.lookup_icon(Icons.MISSING_APP_ICON, + iconsize, 0) return iconinfo.get_filename() # File Menu @@ -668,18 +718,20 @@ # update menu items pkg_state = None error = None - # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add Source/Update Now action - # so that all UI controls (menu item, applist view button and appdetails - # view button) are managed centrally: button text, button sensitivity, - # and callback method + # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add Source/Update + # Now action so that all UI controls (menu item, applist view + # button and appdetails view button) are managed centrally: + # button text, button sensitivity, and callback method # FIXME: Add buy support here by implementing the above appdetails = app.get_details(self.db) if appdetails: pkg_state = appdetails.pkg_state error = appdetails.error - if app.pkgname in self.active_pane.app_view.tree_view._action_block_list: + if (app.pkgname in + self.active_pane.app_view.tree_view._action_block_list): return False - elif pkg_state == PkgStates.UPGRADABLE or pkg_state == PkgStates.REINSTALLABLE and not error: + elif (pkg_state == PkgStates.UPGRADABLE or + pkg_state == PkgStates.REINSTALLABLE and not error): self.menuitem_install.set_sensitive(True) self.menuitem_remove.set_sensitive(True) elif pkg_state == PkgStates.INSTALLED: @@ -689,7 +741,8 @@ elif (not pkg_state and not self.active_pane.is_category_view_showing() and app.pkgname in self.cache and - not app.pkgname in self.active_pane.app_view.tree_view._action_block_list and + not app.pkgname in + self.active_pane.app_view.tree_view._action_block_list and not error): # when does this happen? pkg = self.cache[app.pkgname] @@ -712,7 +765,7 @@ #appname = _("Ubuntu Software Center") appname = SOFTWARE_CENTER_NAME_KEYRING help_text = _("To reinstall previous purchases, sign in to the " - "Ubuntu Single Sign-On account you used to pay for them.") + "Ubuntu Single Sign-On account you used to pay for them.") #window = self.window_main.get_window() #xid = self.get_window().xid xid = 0 @@ -729,14 +782,29 @@ if not self.scagent: from softwarecenter.backend.scagent import SoftwareCenterAgent self.scagent = SoftwareCenterAgent() - self.scagent.connect("available-for-me", self._available_for_me_result) + self.scagent.connect("available-for-me", + self._available_for_me_result) + + def on_menuitem_recommendations_activate(self, menu_item): + rec_panel = self.available_pane.cat_view.recommended_for_you_panel + if rec_panel.recommender_agent.is_opted_in(): + rec_panel.opt_out_of_recommendations_service() + else: + # build and show the opt-in dialog + opt_in_dialog = RecommendationsOptInDialog(self.icons) + res = opt_in_dialog.run() + opt_in_dialog.destroy() + if res == Gtk.ResponseType.YES: + rec_panel.opt_in_to_recommendations_service() def on_menuitem_reinstall_purchases_activate(self, menuitem): self.view_manager.set_active_view(ViewPages.AVAILABLE) + self.view_manager.search_entry.clear_with_no_signal() self.available_pane.show_appview_spinner() if self.available_for_me_query: # we already have the list of available items, so just show it - self.available_pane.on_previous_purchases_activated(self.available_for_me_query) + self.available_pane.on_previous_purchases_activated( + self.available_for_me_query) else: # fetch the list of available items and show it self._create_scagent_if_needed() @@ -749,15 +817,12 @@ account_name = None # get a list of installed purchased packages - installed_purchased_packages = self.db.get_installed_purchased_packages() + installed_purchases = self.db.get_installed_purchased_packages() # display the deauthorize computer dialog deauthorize = deauthorize_dialog.deauthorize_computer(None, - self.datadir, - self.db, - self.icons, - account_name, - installed_purchased_packages) + self.datadir, self.db, self.icons, account_name, + installed_purchases) if deauthorize: # clear the ubuntu SSO token for this account clear_token_from_ubuntu_sso(SOFTWARE_CENTER_NAME_KEYRING) @@ -765,16 +830,17 @@ # uninstall the list of purchased packages # TODO: do we need to check for dependencies and show a removal # dialog for that case? seems not since these are purchased apps - for pkgname in installed_purchased_packages: + for pkgname in installed_purchases: app = Application(pkgname=pkgname) appdetails = app.get_details(self.db) self.backend.remove(app, appdetails.icon) # TODO: remove the corresponding private PPA sources - # FIXME: this should really be done using aptdaemon, update this if/when - # remove repository support is added to aptdaemon + # FIXME: this should really be done using aptdaemon, update this + # if/when remove repository support is added to aptdaemon # (private-ppa.launchpad.net_commercial-ppa-uploaders*) - purchased_sources = glob.glob("/etc/apt/sources.list.d/private-ppa.launchpad.net_commercial-ppa-uploaders*") + purchased_sources = glob.glob("/etc/apt/sources.list.d/" + "private-ppa.launchpad.net_commercial-ppa-uploaders*") for source in purchased_sources: print("source: %s" % source) @@ -855,9 +921,9 @@ self.active_pane.is_app_details_view_showing()): self.menuitem_select_all.set_sensitive(True) - sel_text = self.active_pane.app_details_view.desc.get_selected_text() + desc = self.active_pane.app_details_view.desc - if sel_text: + if desc.get_selected_text(): self.menuitem_copy.set_sensitive(True) def on_menuitem_undo_activate(self, menuitem): @@ -898,7 +964,7 @@ app = self.active_pane.get_current_app() if app: display = Gdk.Display.get_default() - selection = Gdk.Atom.intern ("CLIPBOARD", False) + selection = Gdk.Atom.intern("CLIPBOARD", False) clipboard = Gtk.Clipboard.get_for_display(display, selection) clipboard.set_text(self.WEBLINK_URL % app.pkgname, -1) @@ -980,7 +1046,7 @@ self.available_pane.refresh_apps() try: self.installed_pane.refresh_apps() - except: # may not be initialised + except: # may not be initialised pass def on_menuitem_view_supported_only_activate(self, widget): @@ -993,13 +1059,15 @@ self.available_pane.refresh_apps() try: self.installed_pane.refresh_apps() - except: # may not be initialised + except: # may not be initialised pass # navigate up if the details page is no longer available #~ ap = self.active_pane - #~ if (ap and ap.is_app_details_view_showing and ap.app_details_view.app and - #~ not self.distro.is_supported(self.cache, None, ap.app_details_view.app.pkgname)): + #~ if (ap and ap.is_app_details_view_showing and + #~ ap.app_details_view.app and + #~ not self.distro.is_supported(self.cache, None, + #~ ap.app_details_view.app.pkgname)): #~ if len(ap.app_view.get_model()) == 0: #~ ap.navigation_bar.navigate_up_twice() #~ else: @@ -1016,7 +1084,8 @@ vm = get_viewmanager() vm.nav_back() - def on_navhistory_forward_action_activate(self, navhistory_forward_action=None): + def on_navhistory_forward_action_activate(self, + navhistory_forward_action=None): vm = get_viewmanager() vm.nav_forward() @@ -1030,10 +1099,12 @@ self.aboutdialog.show() def on_menuitem_help_activate(self, menuitem): - # run yelp - p = subprocess.Popen(["yelp","ghelp:software-center"]) - # collect the exit status (otherwise we leave zombies) - GObject.timeout_add_seconds(1, lambda p: p.poll() == None, p) + # run browser + (pid, stdin, stdout, stderr) = GObject.spawn_async( + ["yelp", "ghelp:software-center"], flags=GObject.SPAWN_SEARCH_PATH) + + def on_menuitem_tos_activate(self, menuitem): + webbrowser.open_new_tab(SOFTWARE_CENTER_TOS_LINK) def on_menuitem_developer_activate(self, menuitem): webbrowser.open(self.distro.DEVELOPER_URL) @@ -1111,7 +1182,9 @@ res = iface.IsRebuilding() self._on_database_rebuilding_handler(res) except Exception as e: - LOG.debug("query for the update-database exception '%s' (probably ok)" % e) + LOG.debug( + "query for the update-database exception '%s' (probably ok)" % + e) # add signal handler bus.add_signal_receiver(self._on_database_rebuilding_handler, @@ -1140,7 +1213,7 @@ iface.bringToFront('nothing-to-show') sys.exit() except dbus.DBusException: - bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter',bus) + bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter', bus) self.dbusControler = SoftwarecenterDbusController(self, bus_name) def show_available_packages(self, packages): @@ -1209,9 +1282,9 @@ (x, y) = self.config.get("general", "size").split(",") self.window_main.set_default_size(int(x), int(y)) else: - # on first launch, specify the default window size to take advantage - # of the available screen real estate (but set a reasonable limit - # in case of a crazy-huge monitor) + # on first launch, specify the default window size to take + # advantage of the available screen real estate (but set a + # reasonable limit in case of a crazy-huge monitor) screen_height = Gdk.Screen.height() screen_width = Gdk.Screen.width() self.window_main.set_default_size( @@ -1247,7 +1320,7 @@ self.config.set("general", "maximized", "False") # size only matters when non-maximized size = self.window_main.get_size() - self.config.set("general","size", "%s, %s" % (size[0], size[1])) + self.config.set("general", "size", "%s, %s" % (size[0], size[1])) if self.available_pane.add_to_launcher_enabled: self.config.set("general", "add_to_launcher", "True") else: diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/aptd_gtk3.py software-center-5.1.13/softwarecenter/ui/gtk3/aptd_gtk3.py --- software-center-5.1.12/softwarecenter/ui/gtk3/aptd_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/aptd_gtk3.py 2012-03-15 09:28:20.000000000 +0000 @@ -21,10 +21,11 @@ from aptdaemon.gtk3widgets import (AptMediumRequiredDialog, AptConfigFileConflictDialog) -from softwarecenter.backend.installbackend import InstallBackendUI +from softwarecenter.backend.installbackend import InstallBackendUI + class InstallBackendUI(InstallBackendUI): - + def ask_config_file_conflict(self, old, new): dia = AptConfigFileConflictDialog(old, new) res = dia.run() @@ -44,12 +45,13 @@ return True else: return False - - def error(self, parent, primary, secondary, details=None, alternative_action=None): + + def error(self, parent, primary, secondary, details=None, + alternative_action=None): from dialogs import error res = "ok" res = error(parent=parent, - primary=primary, + primary=primary, secondary=secondary, details=details, alternative_action=alternative_action) @@ -62,7 +64,6 @@ from softwarecenter.ui.gtk3.aptd_gtk3 import InstallBackendUI from mock import Mock - aptd = get_install_backend() aptd.ui = InstallBackendUI() # test config file prompt @@ -82,4 +83,3 @@ enum = 101 res = aptd._show_transaction_failed_dialog(trans, enum) print (res) - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py --- software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py 2012-03-15 09:05:38.000000000 +0000 @@ -33,7 +33,9 @@ #FIXME: These need to come from the main app ICON_SIZE = 24 -def deauthorize_computer(parent, datadir, db, icons, account_name, purchased_packages): + +def deauthorize_computer(parent, datadir, db, icons, account_name, + purchased_packages): """ Display a dialog to deauthorize the current computer for purchases """ cache = db._aptcache @@ -41,7 +43,7 @@ (primary, button_text) = distro.get_deauthorize_text(account_name, purchased_packages) - + # build the dialog glade_dialog = SimpleGtkbuilderDialog(datadir, domain="software-center") dialog = glade_dialog.dialog_deauthorize @@ -54,24 +56,26 @@ if (icon_name is None or not icons.has_icon(icon_name)): icon_name = Icons.MISSING_APP - glade_dialog.image_icon.set_from_icon_name(icon_name, + glade_dialog.image_icon.set_from_icon_name(icon_name, Gtk.IconSize.DIALOG) # set the texts - glade_dialog.label_deauthorize_primary.set_text("%s" % primary) + glade_dialog.label_deauthorize_primary.set_text( + "%s" % primary) glade_dialog.label_deauthorize_primary.set_use_markup(True) glade_dialog.button_deauthorize_do.set_label(button_text) # add the list of packages to remove, if there are any if len(purchased_packages) > 0: - view = PackageNamesView(_("Deauthorize"), cache, purchased_packages, icons, ICON_SIZE, db) + view = PackageNamesView(_("Deauthorize"), cache, purchased_packages, + icons, ICON_SIZE, db) view.set_headers_visible(False) # FIXME: work out how not to select?/focus?/activate? first item glade_dialog.scrolledwindow_purchased_packages.add(view) glade_dialog.scrolledwindow_purchased_packages.show_all() else: glade_dialog.viewport_purchased_packages.hide() - + result = dialog.run() dialog.hide() if result == Gtk.ResponseType.ACCEPT: @@ -80,7 +84,8 @@ if __name__ == "__main__": - import sys, os + import sys + import os if len(sys.argv) > 1: datadir = sys.argv[1] @@ -120,7 +125,7 @@ db.open() except xapian.DatabaseCorruptError as e: logging.exception("xapian open failed") - dialogs.error(None, + dialogs.error(None, _("Sorry, can not open the software database"), _("Please re-install the 'software-center' " "package.")) @@ -134,13 +139,12 @@ purchased_packages.add('chromium-browser') purchased_packages.add('cheese') purchased_packages.add('aisleriot') - + account_name = "max.fischer@rushmoreacademy.edu" - deauthorize_computer(None, - "./data", + deauthorize_computer(None, + "./data", db, icons, account_name, purchased_packages) - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py --- software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py 2012-03-15 09:05:38.000000000 +0000 @@ -35,10 +35,12 @@ ICON_SIZE = 24 # for the unittests only -_DIALOG=None +_DIALOG = None + + def confirm_install(parent, datadir, app, db, icons): """Confirm install of the given app - + (currently only shows a dialog if a installed app needs to be removed in order to install the application) """ @@ -51,15 +53,18 @@ depends = cache.get_packages_removed_on_install(appdetails.pkg) if not depends: return True - (primary, button_text) = distro.get_install_warning_text(cache, appdetails.pkg, app.name, depends) - return _confirm_internal(parent, datadir, app, db, icons, primary, button_text, depends, cache) + (primary, button_text) = distro.get_install_warning_text(cache, + appdetails.pkg, app.name, depends) + return _confirm_internal(parent, datadir, app, db, icons, primary, + button_text, depends, cache) + def confirm_remove(parent, datadir, app, db, icons): """ Confirm removing of the given app """ cache = db._aptcache distro = get_distro() appdetails = app.get_details(db) - # FIXME: use + # FIXME: use # backend = get_install_backend() # backend.simulate_remove(app.pkgname) # once it works @@ -70,9 +75,12 @@ return True (primary, button_text) = distro.get_removal_warning_text( db._aptcache, appdetails.pkg, app.name, depends) - return _confirm_internal(parent, datadir, app, db, icons, primary, button_text, depends, cache) + return _confirm_internal(parent, datadir, app, db, icons, primary, + button_text, depends, cache) + -def _get_confirm_internal_dialog(parent, datadir, app, db, icons, primary, button_text, depends, cache): +def _get_confirm_internal_dialog(parent, datadir, app, db, icons, primary, + button_text, depends, cache): glade_dialog = SimpleGtkbuilderDialog(datadir, domain="software-center") dialog = glade_dialog.dialog_dependency_alert dialog.set_resizable(True) @@ -85,26 +93,29 @@ if (icon_name is None or not icons.has_icon(icon_name)): icon_name = Icons.MISSING_APP - glade_dialog.image_package_icon.set_from_icon_name(icon_name, + glade_dialog.image_package_icon.set_from_icon_name(icon_name, Gtk.IconSize.DIALOG) # set the texts - glade_dialog.label_dependency_primary.set_text("%s" % primary) + glade_dialog.label_dependency_primary.set_text( + "%s" % primary) glade_dialog.label_dependency_primary.set_use_markup(True) glade_dialog.button_dependency_do.set_label(button_text) # add the dependencies - view = PackageNamesView(_("Dependency"), cache, depends, icons, ICON_SIZE, db) + view = PackageNamesView(_("Dependency"), cache, depends, icons, ICON_SIZE, + db) view.set_headers_visible(False) # FIXME: work out how not to select?/focus?/activate? first item glade_dialog.scrolledwindow_dependencies.add(view) glade_dialog.scrolledwindow_dependencies.show_all() return dialog + def _confirm_internal(*args): dialog = _get_confirm_internal_dialog(*args) global _DIALOG - _DIALOG=dialog + _DIALOG = dialog result = dialog.run() dialog.hide() if result == Gtk.ResponseType.ACCEPT: @@ -112,8 +123,6 @@ return False - - def get_test_dialog(): import softwarecenter from softwarecenter.db.application import Application @@ -132,7 +141,7 @@ db=db, icons=icons, primary=primary, button_text=button_text, depends=depends, cache=db._aptcache) return dia - + if __name__ == "__main__": diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/dialog_tos.py software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dialog_tos.py --- software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/dialog_tos.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dialog_tos.py 2012-03-20 08:00:09.000000000 +0000 @@ -0,0 +1,84 @@ +# Copyright (C) 2009 Canonical +# +# Authors: +# Michael Vogt +# Andrew Higginson (rugby471) +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk +from gi.repository import WebKit + +from gettext import gettext as _ + +from softwarecenter.ui.gtk3.views.webkit import ScrolledWebkitWindow +from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook + + +class DialogTos(Gtk.Dialog): + + def __init__(self, parent): + Gtk.Dialog.__init__(self) + self.set_default_size(420, 400) + self.set_transient_for(parent) + self.set_title(_("Terms of Use")) + # buttons + self.add_button(_("Decline"), Gtk.ResponseType.NO) + self.add_button(_("Accept"), Gtk.ResponseType.YES) + # label + self.label = Gtk.Label(_(u"One moment, please\u2026")) + self.label.show() + # add the label + box = self.get_action_area() + box.pack_start(self.label, False, False, 0) + box.set_child_secondary(self.label, True) + # hrm, hrm, there really should be a better way + for itm in box.get_children(): + if itm.get_label() == _("Accept"): + self.button_accept = itm + break + self.button_accept.set_sensitive(False) + # webkit + wb = ScrolledWebkitWindow() + wb.show_all() + self.webkit = wb.webkit + self.webkit.connect( + "notify::load-status", self._on_load_status_changed) + # content + content = self.get_content_area() + self.spinner = SpinnerNotebook(wb) + self.spinner.show_all() + content.pack_start(self.spinner, True, True, 0) + + def run(self): + self.spinner.show_spinner() + self.webkit.load_uri("http://apps.ubuntu.com/cat/tos") + return Gtk.Dialog.run(self) + + def _on_load_status_changed(self, view, pspec): + prop = pspec.name + status = view.get_property(prop) + if (status == WebKit.LoadStatus.FINISHED or + status == WebKit.LoadStatus.FAILED): + self.spinner.hide_spinner() + if status == WebKit.LoadStatus.FINISHED: + self.label.set_text(_("Do you accept these terms?")) + self.button_accept.set_sensitive(True) + +if __name__ == "__main__": + d = DialogTos(None) + res = d.run() + print res diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/__init__.py software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/__init__.py --- software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/__init__.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/__init__.py 2012-03-20 08:00:09.000000000 +0000 @@ -21,29 +21,45 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk - from gettext import gettext as _ +import softwarecenter.paths + + class SimpleGtkbuilderDialog(object): def __init__(self, datadir, domain): # setup ui self.builder = Gtk.Builder() self.builder.set_translation_domain(domain) - self.builder.add_from_file(datadir+"/ui/gtk3/dialogs.ui") + self.builder.add_from_file(datadir + "/ui/gtk3/dialogs.ui") self.builder.connect_signals(self) for o in self.builder.get_objects(): if issubclass(type(o), Gtk.Buildable): name = Gtk.Buildable.get_name(o) setattr(self, name, o) + # for unitesting only -_DIALOG=None +_DIALOG = None + + +def show_accept_tos_dialog(parent): + global _DIALOG + from dialog_tos import DialogTos + dialog = DialogTos(parent) + _DIALOG = dialog + result = dialog.run() + dialog.destroy() + if result == Gtk.ResponseType.YES: + return True + return False + def confirm_repair_broken_cache(parent, datadir): glade_dialog = SimpleGtkbuilderDialog(datadir, domain="software-center") dialog = glade_dialog.dialog_broken_cache global _DIALOG - _DIALOG=dialog + _DIALOG = dialog dialog.set_default_size(380, -1) dialog.set_transient_for(parent) result = dialog.run() @@ -52,15 +68,16 @@ return True return False + class DetailsMessageDialog(Gtk.MessageDialog): """Message dialog with optional details expander""" def __init__(self, - parent=None, - title="", - primary=None, - secondary=None, + parent=None, + title="", + primary=None, + secondary=None, details=None, - buttons=Gtk.ButtonsType.OK, + buttons=Gtk.ButtonsType.OK, type=Gtk.MessageType.INFO): Gtk.MessageDialog.__init__(self, parent, 0, type, buttons, primary) self.set_title(title) @@ -71,7 +88,8 @@ textview.get_buffer().set_text(details) scroll = Gtk.ScrolledWindow() scroll.set_size_request(500, 300) - scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scroll.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) scroll.add(textview) expand = Gtk.Expander().new(_("Details")) expand.add(scroll) @@ -81,31 +99,33 @@ self.set_modal(True) self.set_property("skip-taskbar-hint", True) -def messagedialog(parent=None, - title="", - primary=None, - secondary=None, + +def messagedialog(parent=None, + title="", + primary=None, + secondary=None, details=None, - buttons=Gtk.ButtonsType.OK, + buttons=Gtk.ButtonsType.OK, type=Gtk.MessageType.INFO, alternative_action=None): """ run a dialog """ dialog = DetailsMessageDialog(parent=parent, title=title, primary=primary, secondary=secondary, - details=details, type=type, + details=details, type=type, buttons=buttons) global _DIALOG - _DIALOG=dialog + _DIALOG = dialog if alternative_action: dialog.add_button(alternative_action, Gtk.ResponseType.YES) result = dialog.run() dialog.destroy() return result + def error(parent, primary, secondary, details=None, alternative_action=None): """ show a untitled error dialog """ return messagedialog(parent=parent, - primary=primary, + primary=primary, secondary=secondary, details=details, type=Gtk.MessageType.ERROR, @@ -113,18 +133,20 @@ if __name__ == "__main__": - print("Running remove dialog") - + softwarecenter.paths.datadir = "./data" + + print("Showing tos dialog") + res = show_accept_tos_dialog(None) + print "accepted: ", res + print("Running broken apt-cache dialog") confirm_repair_broken_cache(None, "./data") - + print("Showing message dialog") messagedialog(None, primary="first, no second") print("showing error") error(None, "first", "second") error(None, "first", "second", "details ......") - res = error(None, "first", "second", "details ......", alternative_action="Do Something Else") + res = error(None, "first", "second", "details ......", + alternative_action="Do Something Else") print "res: ", res - - - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/drawing.py software-center-5.1.13/softwarecenter/ui/gtk3/drawing.py --- software-center-5.1.12/softwarecenter/ui/gtk3/drawing.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/drawing.py 2012-03-15 09:28:20.000000000 +0000 @@ -1,11 +1,11 @@ from math import pi as PI -PI_OVER_180 = PI/180 +PI_OVER_180 = PI / 180 from gi.repository import Gdk -BLACK = Gdk.RGBA(red=0,green=0,blue=0) -WHITE = Gdk.RGBA(red=1,green=1,blue=1) +BLACK = Gdk.RGBA(red=0, green=0, blue=0) +WHITE = Gdk.RGBA(red=1, green=1, blue=1) def color_floats(spec): @@ -13,16 +13,19 @@ rgba.parse(spec) return rgba.red, rgba.green, rgba.blue + def rgb_to_hex(r, g, b): if isinstance(r, float): r *= 255 g *= 255 b *= 255 - return "#%02X%02X%02X" % (r,g,b) + return "#%02X%02X%02X" % (r, g, b) + def color_to_hex(color): return rgb_to_hex(color.red, color.green, color.blue) + def mix(fgcolor, bgcolor, mix_alpha): """ Creates a composite rgb of a foreground rgba and a background rgb. @@ -41,21 +44,25 @@ b = ((1 - mix_alpha) * bg_b) + (mix_alpha * src_b) return Gdk.RGBA(red=r, green=g, blue=b) + def darken(color, amount=0.3): return mix(BLACK, color, amount) + def lighten(color, amount=0.3): return mix(WHITE, color, amount) + def rounded_rect(cr, x, y, w, h, r): cr.new_sub_path() - cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180) - cr.arc(x+w-r, r+y, r, 270*PI_OVER_180, 0) - cr.arc(x+w-r, y+h-r, r, 0, 90*PI_OVER_180) - cr.arc(r+x, y+h-r, r, 90*PI_OVER_180, PI) + cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180) + cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0) + cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180) + cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI) cr.close_path() return + def rounded_rect2(cr, x, y, w, h, radii): nw, ne, se, sw = radii @@ -72,30 +79,28 @@ else: cr.move_to(0, 0) if ne: - cr.arc(w-ne, ne, ne, 270 * PI_OVER_180, 0) + cr.arc(w - ne, ne, ne, 270 * PI_OVER_180, 0) else: - cr.rel_line_to(w-nw, 0) + cr.rel_line_to(w - nw, 0) if se: - cr.arc(w-se, h-se, se, 0, 90 * PI_OVER_180) + cr.arc(w - se, h - se, se, 0, 90 * PI_OVER_180) else: - cr.rel_line_to(0, h-ne) + cr.rel_line_to(0, h - ne) if sw: - cr.arc(sw, h-sw, sw, 90 * PI_OVER_180, PI) + cr.arc(sw, h - sw, sw, 90 * PI_OVER_180, PI) else: - cr.rel_line_to(-(w-se), 0) + cr.rel_line_to(-(w - se), 0) cr.close_path() cr.restore() - return + def circle(cr, x, y, w, h): cr.new_path() - r = min(w, h)*0.5 - x += int((w-2*r)/2) - y += int((h-2*r)/2) + r = min(w, h) * 0.5 + x += int((w - 2 * r) / 2) + y += int((h - 2 * r) / 2) - cr.arc(r+x, r+y, r, 0, 360*PI_OVER_180) + cr.arc(r + x, r + y, r, 0, 360 * PI_OVER_180) cr.close_path() - return - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/em.py software-center-5.1.13/softwarecenter/ui/gtk3/em.py --- software-center-5.1.12/softwarecenter/ui/gtk3/em.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/em.py 2012-03-15 09:28:20.000000000 +0000 @@ -5,7 +5,8 @@ import logging -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + def get_em(size=""): # calc the height of a character, use as 1em @@ -19,9 +20,11 @@ w, h = l.get_layout().get_size() return h / Pango.SCALE + def get_small_em(): return get_em("small") + def get_big_em(): return get_em("big") @@ -33,19 +36,20 @@ def em(multiplier=1, min=1): - return max(int(min), int(round(EM*multiplier, 0))) + return max(int(min), int(round(EM * multiplier, 0))) + def small_em(multiplier=1, min=1): - return max(int(min), int(round(SMALL_EM*multiplier, 0))) + return max(int(min), int(round(SMALL_EM * multiplier, 0))) + def big_em(multiplier=1, min=1): - return max(int(min), int(round(BIG_EM*multiplier, 0))) + return max(int(min), int(round(BIG_EM * multiplier, 0))) + # common values class StockEms: - XLARGE = em(1.33, 5) - LARGE = em(min=3) - MEDIUM = em(0.666, 2) - SMALL = em(0.333, 1) - - + XLARGE = em(1.33, 5) + LARGE = em(min=3) + MEDIUM = em(0.666, 2) + SMALL = em(0.333, 1) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/gmenusearch.py software-center-5.1.13/softwarecenter/ui/gtk3/gmenusearch.py --- software-center-5.1.12/softwarecenter/ui/gtk3/gmenusearch.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/gmenusearch.py 2012-03-15 09:28:20.000000000 +0000 @@ -22,6 +22,7 @@ LOG = logging.getLogger(__name__) + class GMenuSearcher(object): def __init__(self): @@ -36,29 +37,31 @@ while current_type is not GMenu.TreeItemType.INVALID: if current_type == GMenu.TreeItemType.DIRECTORY: self._search_gmenu_dir( - dirlist+[dir_iter.get_directory()], needle) + dirlist + [dir_iter.get_directory()], needle) elif current_type == GMenu.TreeItemType.ENTRY: item = dir_iter.get_entry() desktop_file_path = item.get_desktop_file_path() # direct match of the desktop file name and the installed # desktop file name if os.path.basename(desktop_file_path) == needle: - self._found = dirlist+[item] + self._found = dirlist + [item] return - # if there is no direct match, take the part of the path after + # if there is no direct match, take the part of the path after # "applications" (e.g. kde4/amarok.desktop) and # change "/" to "__" and do the match again - this is what # the data extractor is doing if "applications/" in desktop_file_path: - path_after_applications = desktop_file_path.split("applications/")[1] - if needle == path_after_applications.replace("/", APP_INSTALL_PATH_DELIMITER): - self._found = dirlist+[item] + path_after_applications = desktop_file_path.split( + "applications/")[1] + if needle == path_after_applications.replace("/", + APP_INSTALL_PATH_DELIMITER): + self._found = dirlist + [item] return current_type = dir_iter.next() - + def get_main_menu_path(self, desktop_file, menu_files_list=None): if not desktop_file: - return None + return from gi.repository import GMenu from gi.repository import GObject # use the system ones by default, but allow override for @@ -74,14 +77,13 @@ tree.load_sync() except GObject.GError as e: LOG.warning("could not load GMenu path: %s" % e) - return None - + return + root = tree.get_root_directory() self._search_gmenu_dir([root], os.path.basename(desktop_file)) if self._found: return self._found - return None # these are the old static bindinds that are no longer required @@ -91,6 +93,7 @@ def __init__(self): self._found = None + def _search_gmenu_dir(self, dirlist, needle): if not dirlist[-1]: return @@ -99,28 +102,29 @@ for item in dirlist[-1].get_contents(): mtype = item.get_type() if mtype == gmenu.TYPE_DIRECTORY: - self._search_gmenu_dir(dirlist+[item], needle) + self._search_gmenu_dir(dirlist + [item], needle) elif item.get_type() == gmenu.TYPE_ENTRY: desktop_file_path = item.get_desktop_file_path() # direct match of the desktop file name and the installed # desktop file name if os.path.basename(desktop_file_path) == needle: - self._found = dirlist+[item] + self._found = dirlist + [item] return - # if there is no direct match, take the part of the path after + # if there is no direct match, take the part of the path after # "applications" (e.g. kde4/amarok.desktop) and # change "/" to "__" and do the match again - this is what # the data extractor is doing if "applications/" in desktop_file_path: - path_after_applications = desktop_file_path.split("applications/")[1] - if needle == path_after_applications.replace("/", APP_INSTALL_PATH_DELIMITER): - self._found = dirlist+[item] + path_after_applications = desktop_file_path.split( + "applications/")[1] + if needle == path_after_applications.replace("/", + APP_INSTALL_PATH_DELIMITER): + self._found = dirlist + [item] return - def get_main_menu_path(self, desktop_file, menu_files_list=None): if not desktop_file: - return None + return import gmenu # use the system ones by default, but allow override for # easier testing @@ -128,8 +132,7 @@ menu_files_list = ["applications.menu", "settings.menu"] for n in menu_files_list: tree = gmenu.lookup_tree(n) - self._search_gmenu_dir([tree.get_root_directory()], + self._search_gmenu_dir([tree.get_root_directory()], os.path.basename(desktop_file)) if self._found: return self._found - return None diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/models/appstore2.py software-center-5.1.13/softwarecenter/ui/gtk3/models/appstore2.py --- software-center-5.1.12/softwarecenter/ui/gtk3/models/appstore2.py 2012-03-08 19:37:37.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/models/appstore2.py 2012-03-19 08:35:49.000000000 +0000 @@ -24,11 +24,15 @@ from gettext import gettext as _ -from softwarecenter.enums import (Icons, +from softwarecenter.enums import (Icons, XapianValues) -from softwarecenter.utils import ExecutionTime, SimpleFileDownloader, split_icon_ext +from softwarecenter.utils import ( + ExecutionTime, + SimpleFileDownloader, + split_icon_ext, + ) from softwarecenter.backend import get_install_backend from softwarecenter.backend.reviews import get_review_loader from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR @@ -44,8 +48,9 @@ LOG = logging.getLogger(__name__) _FREE_AS_IN_BEER = ("0.00", "") + class CategoryRowReference: - """ A simple container for Category properties to be + """ A simple container for Category properties to be displayed in a AppListStore or AppTreeStore """ @@ -55,7 +60,6 @@ #self.subcategories = subcats self.pkg_count = pkg_count self.vis_count = pkg_count - return class UncategorisedRowRef(CategoryRowReference): @@ -70,7 +74,6 @@ untranslated_name, display_name, None, pkg_count) - return class AppPropertiesHelper(GObject.GObject): @@ -79,13 +82,14 @@ """ __gsignals__ = { - "needs-refresh" : (GObject.SignalFlags.RUN_LAST, - None, - (str, ), - ), + "needs-refresh": (GObject.SignalFlags.RUN_LAST, + None, + (str, ), + ), } - def __init__(self, db, cache, icons, icon_size=48, global_icon_cache=False): + def __init__(self, db, cache, icons, icon_size=48, + global_icon_cache=False): GObject.GObject.__init__(self) self.db = db self.cache = cache @@ -102,31 +106,35 @@ self.icons = icons self.icon_size = icon_size - # cache the 'missing icon' used in the treeview for apps without an icon + # cache the 'missing icon' used in the treeview for apps without an + # icon self._missing_icon = icons.load_icon(Icons.MISSING_APP, icon_size, 0) if global_icon_cache: self.icon_cache = _app_icon_cache else: self.icon_cache = {} - return def _download_icon_and_show_when_ready(self, url, pkgname, icon_file_name): - LOG.debug("did not find the icon locally, must download %s" % icon_file_name) + LOG.debug("did not find the icon locally, must download %s" % + icon_file_name) def on_image_download_complete(downloader, image_file_path, pkgname): LOG.debug("download for '%s' complete" % image_file_path) pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file_path, self.icon_size, self.icon_size) - # replace the icon in the icon_cache now that we've got the real one + # replace the icon in the icon_cache now that we've got the real + # one icon_file = split_icon_ext(os.path.basename(image_file_path)) self.icon_cache[icon_file] = pb self.emit("needs-refresh", pkgname) - + if url is not None: - icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, icon_file_name) + icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, + icon_file_name) image_downloader = SimpleFileDownloader() - image_downloader.connect('file-download-complete', on_image_download_complete, pkgname) + image_downloader.connect('file-download-complete', + on_image_download_complete, pkgname) image_downloader.download_file(url, icon_file_path) def update_availability(self, doc): @@ -134,13 +142,13 @@ doc.installed = None doc.purchasable = None self.is_installed(doc) - return def is_available(self, doc): if doc.available is None: pkgname = self.get_pkgname(doc) - doc.available = (pkgname in self.cache or - self.is_purchasable(doc)) + doc.available = ( + (pkgname in self.cache and self.cache[pkgname].candidate) + or self.is_purchasable(doc)) return doc.available def is_installed(self, doc): @@ -152,7 +160,8 @@ def is_purchasable(self, doc): if doc.purchasable is None: - doc.purchasable = doc.get_value(XapianValues.PRICE) not in _FREE_AS_IN_BEER + doc.purchasable = (doc.get_value(XapianValues.PRICE) not in + _FREE_AS_IN_BEER) return doc.purchasable def get_pkgname(self, doc): @@ -195,7 +204,7 @@ # icons.load_icon takes between 0.001 to 0.01s on my # machine, this is a significant burden because get_value # is called *a lot*. caching is the only option - + # look for the icon on the iconpath if self.icons.has_icon(icon_name): icon = self.icons.load_icon(icon_name, self.icon_size, 0) @@ -224,11 +233,11 @@ return -1 def _category_translate(self, catname): - """ helper that will look into the categories we got from the + """ helper that will look into the categories we got from the parser and returns the translated name if it find it, otherwise it resorts to plain gettext """ - # look into parsed categories that use .directory translation + # look into parsed categories that use .directory translation for cat in self.all_categories: if cat.untranslated_name == catname: return cat.name @@ -271,17 +280,20 @@ ICON_SIZE = 32 # the amount of items to initially lo - LOAD_INITIAL = 75 + LOAD_INITIAL = 75 def __init__(self, db, cache, icons, icon_size, global_icon_cache): - AppPropertiesHelper.__init__(self, db, cache, icons, icon_size, + AppPropertiesHelper.__init__(self, db, cache, icons, icon_size, global_icon_cache) # backend stuff self.backend = get_install_backend() - self.backend.connect("transaction-progress-changed", self._on_transaction_progress_changed) - self.backend.connect("transaction-started", self._on_transaction_started) - self.backend.connect("transaction-finished", self._on_transaction_finished) + self.backend.connect("transaction-progress-changed", + self._on_transaction_progress_changed) + self.backend.connect("transaction-started", + self._on_transaction_started) + self.backend.connect("transaction-finished", + self._on_transaction_finished) # keep track of paths for transactions in progress self.transaction_path_map = {} @@ -294,12 +306,12 @@ # other stuff self.active = False - return - # FIXME: port from + # FIXME: port from @property def installable_apps(self): return [] + @property def existing_apps(self): return [] @@ -307,15 +319,15 @@ def notify_action_request(self, doc, path): pkgname = str(self.get_pkgname(doc)) self.transaction_path_map[pkgname] = (path, self.get_iter(path)) - return def set_from_matches(self, matches): # stub raise NotImplementedError # the following methods ensure that the contents data is refreshed - # whenever a transaction potentially changes it: - def _on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): + # whenever a transaction potentially changes it: + def _on_transaction_started(self, backend, pkgname, appname, trans_id, + trans_type): #~ self._refresh_transaction_map() pass @@ -323,7 +335,6 @@ if pkgname in self.transaction_path_map: path, it = self.transaction_path_map[pkgname] self.row_changed(path, it) - return def _on_transaction_finished(self, backend, result): pkgname = str(result.pkgname) @@ -356,16 +367,17 @@ #~ print "Appstore buffered icons in %s seconds" % t_lapsed #from softwarecenter.utils import get_nice_size #~ cache_size = get_nice_size(sys.getsizeof(_app_icon_cache)) - #~ print "Number of icons in cache: %s consuming: %sb" % (len(_app_icon_cache), cache_size) + #~ print "Number of icons in cache: %s consuming: %sb" % ( + #~ len(_app_icon_cache), cache_size) return False # remove from sources on completion if self.current_matches is not None: GObject.idle_add(buffer_icons) - return def load_range(self, indices, step): # stub - return + pass + class AppListStore(Gtk.ListStore, AppGenericStore): """ use for flat applist views. for large lists this appends rows approx @@ -373,18 +385,18 @@ """ __gsignals__ = { - "appcount-changed" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), + "appcount-changed": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), # meh, this is a signal from AppPropertiesHelper - "needs-refresh" : (GObject.SignalFlags.RUN_LAST, - None, - (str, ), - ), + "needs-refresh": (GObject.SignalFlags.RUN_LAST, + None, + (str, ), + ), } - def __init__(self, db, cache, icons, icon_size=AppGenericStore.ICON_SIZE, + def __init__(self, db, cache, icons, icon_size=AppGenericStore.ICON_SIZE, global_icon_cache=True): AppGenericStore.__init__( self, db, cache, icons, icon_size, global_icon_cache) @@ -392,8 +404,6 @@ self.set_column_types(self.COL_TYPES) self.current_matches = None - return - def set_from_matches(self, matches): """ set the content of the liststore based on a list of @@ -401,9 +411,9 @@ """ self.current_matches = matches n_matches = len(matches) - if n_matches == 0: + if n_matches == 0: return - + extent = min(self.LOAD_INITIAL, n_matches) with ExecutionTime("store.append_initial"): @@ -411,7 +421,7 @@ doc.available = doc.installed = doc.purchasable = None self.append((doc,)) - if n_matches == extent: + if n_matches == extent: return with ExecutionTime("store.append_placeholders"): @@ -420,7 +430,6 @@ self.emit('appcount-changed', len(matches)) self.buffer_icons() - return def load_range(self, indices, step): db = self.db.xapiandb @@ -440,39 +449,37 @@ except IndexError: break - if row_content: continue + if row_content: + continue doc = db.get_document(matches[i].docid) doc.available = doc.installed = doc.purchasable = None self[(i,)][0] = doc - return def clear(self): # reset the tranaction map because it will now be invalid self.transaction_path_map = {} self.current_matches = None Gtk.ListStore.clear(self) - return class AppTreeStore(Gtk.TreeStore, AppGenericStore): """ A treestore based application model """ - def __init__(self, db, cache, icons, icon_size=AppGenericStore.ICON_SIZE, + def __init__(self, db, cache, icons, icon_size=AppGenericStore.ICON_SIZE, global_icon_cache=True): AppGenericStore.__init__( self, db, cache, icons, icon_size, global_icon_cache) Gtk.TreeStore.__init__(self) self.set_column_types(self.COL_TYPES) - return def set_documents(self, parent, documents): for doc in documents: - doc.available = None; doc.installed = doc.purchasable = None + doc.available = None + doc.installed = doc.purchasable = None self.append(parent, (doc,)) self.transaction_path_map = {} - return def set_category_documents(self, cat, documents): category = CategoryRowReference(cat.untranslated_name, @@ -484,7 +491,8 @@ self.set_documents(it, documents) return it - def set_nocategory_documents(self, documents, untranslated_name=None, display_name=None): + def set_nocategory_documents(self, documents, untranslated_name=None, + display_name=None): category = UncategorisedRowRef(untranslated_name, display_name, len(documents)) @@ -496,5 +504,3 @@ # reset the tranaction map because it will now be invalid self.transaction_path_map = {} Gtk.TreeStore.clear(self) - return - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/models/navlogstore.py software-center-5.1.13/softwarecenter/ui/gtk3/models/navlogstore.py --- software-center-5.1.12/softwarecenter/ui/gtk3/models/navlogstore.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/models/navlogstore.py 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/models/pendingstore.py software-center-5.1.13/softwarecenter/ui/gtk3/models/pendingstore.py --- software-center-5.1.12/softwarecenter/ui/gtk3/models/pendingstore.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/models/pendingstore.py 2012-03-15 09:05:38.000000000 +0000 @@ -17,25 +17,25 @@ # column names (COL_TID, - COL_ICON, - COL_NAME, - COL_STATUS, + COL_ICON, + COL_NAME, + COL_STATUS, COL_PROGRESS, COL_PULSE, COL_CANCEL) = range(7) # column types - column_types = (str, # COL_TID + column_types = (str, # COL_TID GdkPixbuf.Pixbuf, # COL_ICON - str, # COL_NAME - str, # COL_STATUS - float, # COL_PROGRESS - int, # COL_PULSE - str) # COL_CANCEL + str, # COL_NAME + str, # COL_STATUS + float, # COL_PROGRESS + int, # COL_PULSE + str) # COL_CANCEL # icons PENDING_STORE_ICON_CANCEL = Gtk.STOCK_CANCEL - PENDING_STORE_ICON_NO_CANCEL = "" # Gtk.STOCK_YES + PENDING_STORE_ICON_NO_CANCEL = "" # Gtk.STOCK_YES ICON_SIZE = 24 @@ -46,7 +46,7 @@ self._transactions_watcher = get_transactions_watcher() self._transactions_watcher.connect("lowlevel-transactions-changed", - self._on_lowlevel_transactions_changed) + self._on_lowlevel_transactions_changed) # data self.icons = icons # the apt-daemon stuff @@ -62,8 +62,10 @@ del sig self._signals = [] - def _on_lowlevel_transactions_changed(self, watcher, current_tid, pending_tids): - logging.debug("on_transaction_changed %s (%s)" % (current_tid, len(pending_tids))) + def _on_lowlevel_transactions_changed(self, watcher, current_tid, + pending_tids): + logging.debug("on_transaction_changed %s (%s)" % (current_tid, + len(pending_tids))) self.clear() for tid in [current_tid] + pending_tids: if not tid: @@ -79,11 +81,13 @@ # add pending purchases as pseudo transactions for pkgname in self.backend.pending_purchases: iconname = self.backend.pending_purchases[pkgname].iconname - icon = get_icon_from_theme(self.icons, iconname=iconname, iconsize=self.ICON_SIZE) + icon = get_icon_from_theme(self.icons, iconname=iconname, + iconsize=self.ICON_SIZE) appname = self.backend.pending_purchases[pkgname].appname status_text = self._render_status_text( appname or pkgname, _(u'Installing purchase\u2026')) - self.append([pkgname, icon, pkgname, status_text, float(0), 1, None]) + self.append([pkgname, icon, pkgname, status_text, float(0), 1, + None]) def _pulse_purchase_helper(self): for item in self: @@ -105,7 +109,7 @@ trans.connect("status-changed", self._on_status_changed)) self._signals.append( trans.connect( - "cancellable-changed",self._on_cancellable_changed)) + "cancellable-changed", self._on_cancellable_changed)) if "sc_appname" in trans.meta_data: appname = trans.meta_data["sc_appname"] @@ -121,15 +125,16 @@ except KeyError: icon = get_icon_from_theme(self.icons, iconsize=self.ICON_SIZE) else: - icon = get_icon_from_theme(self.icons, iconname=iconname, iconsize=self.ICON_SIZE) + icon = get_icon_from_theme(self.icons, iconname=iconname, + iconsize=self.ICON_SIZE) if trans.is_waiting(): status = trans.status_details else: status = trans.get_status_description() status_text = self._render_status_text(appname, status) cancel_icon = self._get_cancel_icon(trans.cancellable) - self.append([trans.tid, icon, appname, status_text, float(trans.progress), - -1, cancel_icon]) + self.append([trans.tid, icon, appname, status_text, + float(trans.progress), -1, cancel_icon]) def _on_cancellable_changed(self, trans, cancellable): #print "_on_allow_cancel: ", trans, allow_cancel @@ -161,7 +166,8 @@ total_bytes_str = GLib.format_size(total_bytes) status = _("Downloaded %s of %s") % \ (current_bytes_str, total_bytes_str) - row[self.COL_STATUS] = self._render_status_text(name, status) + row[self.COL_STATUS] = self._render_status_text(name, + status) def _on_progress_changed(self, trans, progress): # print "_on_progress_changed: ", trans, progress @@ -187,4 +193,3 @@ if not name: name = "" return "%s\n%s" % (utf8(name), utf8(status)) - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/models/viewstore.py software-center-5.1.13/softwarecenter/ui/gtk3/models/viewstore.py --- software-center-5.1.12/softwarecenter/ui/gtk3/models/viewstore.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/models/viewstore.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,227 +0,0 @@ -# Copyright (C) 2009 Canonical -# -# Authors: -# Michael Vogt -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - -from gi.repository import GObject -from gi.repository import Gtk, GdkPixbuf -import logging -import xapian - -from gettext import gettext as _ - -from softwarecenter.backend.channel import ChannelsManager -from softwarecenter.backend import get_install_backend -from softwarecenter.distro import get_distro -from softwarecenter.enums import ViewPages - - -LOG = logging.getLogger(__name__) - -class ViewStore(Gtk.TreeStore): - - # columns - (COL_ICON, - COL_NAME, - COL_ACTION, - COL_CHANNEL, - COL_BUBBLE_TEXT, - ) = range(5) - - ICON_SIZE = 24 - - ANIMATION_PATH = "/usr/share/icons/hicolor/24x24/status/softwarecenter-progress.png" - - __gsignals__ = {'channels-refreshed':(GObject.SignalFlags.RUN_FIRST, - None, - ())} - - - def __init__(self, view_manager, datadir, db, cache, icons): - Gtk.TreeStore.__init__(self) - self.set_column_types((GObject.TYPE_PYOBJECT, # COL_ICON - str, # COL_NAME - GObject.TYPE_PYOBJECT, # COL_ACTION - GObject.TYPE_PYOBJECT, # COL_CHANNEL - str, # COL_BUBBLE_TEXT - )) # must match columns above - self.view_manager = view_manager - self.icons = icons - self.datadir = datadir - self.backend = get_install_backend() - self.backend.connect("transactions-changed", self.on_transactions_changed) - self.backend.connect("transaction-finished", self.on_transaction_finished) - self.db = db - self.cache = cache - self.distro = get_distro() - # pending transactions - self._pending = 0 - # setup the normal stuff - - # first, the availablepane items - available_icon = self._get_icon("softwarecenter") - self.available_iter = self.append(None, [available_icon, _("Get Software"), ViewPages.AVAILABLE, None, None]) - - # the installedpane items - icon = self._get_icon("computer") - self.installed_iter = self.append(None, [icon, _("Installed Software"), ViewPages.INSTALLED, None, None]) - - # the channelpane - self.channel_manager = ChannelsManager(db, icons) - # do initial channel list update - self._update_channel_list() - - # the historypane item - icon = self._get_icon("document-open-recent") - self.append(None, [icon, _("History"), ViewPages.HISTORY, None, None]) - icon = None - self.append(None, [icon, " ", ViewPages.SEPARATOR_1, None, None]) - - # the progress pane is build on demand - - # emit a transactions-changed signal to ensure that we display any - # pending transactions - self.backend.emit("transactions-changed", self.backend.pending_transactions) - - def on_transactions_changed(self, backend, total_transactions): - LOG.debug("on_transactions_changed '%s'" % total_transactions) - pending = len(total_transactions) - if pending > 0: - for row in self: - if row[self.COL_ACTION] == ViewPages.PENDING: - row[self.COL_BUBBLE_TEXT] = str(pending) - break - else: - icon = GdkPixbuf.new_from_file(self.ANIMATION_PATH) - #~ icon.start() - self.append(None, [icon, _("In Progress..."), - ViewPages.PENDING, None, str(pending)]) - else: - for (i, row) in enumerate(self): - if row[self.COL_ACTION] == ViewPages.PENDING: - del self[(i,)] - - def on_transaction_finished(self, backend, result): - if result.success: - self._update_channel_list_installed_view() - self.emit("channels-refreshed") - - def get_channel_iter_for_name(self, channel_name, installed_only): - """ get the liststore iterator for the given name, consider - installed-only too because channel names may be duplicated - """ - LOG.debug("get_channel_iter_for_name %s %s" % (channel_name, - installed_only)) - def _get_iter_for_channel_name(it): - """ internal helper """ - while it: - if self.get_value(it, self.COL_NAME) == channel_name: - return it - it = self.iter_next(it) - return None - - # check root iter first - channel_iter_for_name = _get_iter_for_channel_name(self.get_iter_first()) - if channel_iter_for_name: - LOG.debug("found '%s' on root level" % channel_name) - return channel_iter_for_name - - # check children - if installed_only: - parent_iter = self.installed_iter - else: - parent_iter = self.available_iter - LOG.debug("looking at path '%s'" % self.get_path(parent_iter)) - child = self.iter_children(parent_iter) - channel_iter_for_name = _get_iter_for_channel_name(child) - return channel_iter_for_name - - def _get_icon(self, icon_name): - return self.icons.load_icon(icon_name, 22, 0) - - #~ @wait_for_apt_cache_ready - def _update_channel_list(self): - self._update_channel_list_available_view() - self._update_channel_list_installed_view() - self.emit("channels-refreshed") - - # FIXME: this way of updating is really not ideal because it - # will trigger set_cursor signals and that causes the - # UI to behave funny if the user is in a channel view - # and the backend sends a channels-changed signal - def _update_channel_list_available_view(self): - # check what needs to be cleared. we need to append first, kill - # afterward because otherwise a row without children is collapsed - # by the view. - # - # normally GtkTreeIters have a limited life-cycle and are no - # longer valid after the model changed, fortunately with the - # Gtk.TreeStore (that we use) they are persisent - child = self.iter_children(self.available_iter) - iters_to_kill = set() - while child: - iters_to_kill.add(child) - child = self.iter_next(child) - # iterate the channels and add as subnodes of the available node - for channel in self.channel_manager.channels: - self.append(self.available_iter, [ - channel.icon, - channel.display_name, - ViewPages.CHANNEL, - channel, - None]) - # delete the old ones - for child in iters_to_kill: - self.remove(child) - - def _update_channel_list_installed_view(self): - # see comments for _update_channel_list_available_view() method above - child = self.iter_children(self.installed_iter) - iters_to_kill = set() - while child: - iters_to_kill.add(child) - child = self.iter_next(child) - # iterate the channels and add as subnodes of the installed node - for channel in self.channel_manager.channels_installed_only: - # check for no installed items for each channel and do not - # append the channel item in this case - enquire = xapian.Enquire(self.db.xapiandb) - query = channel.query - enquire.set_query(query) - matches = enquire.get_mset(0, len(self.db)) - # only check channels that have a small number of items - add_channel_item = True - if len(matches) < 200: - add_channel_item = False - for m in matches: - doc = m.document - pkgname = self.db.get_pkgname(doc) - if (pkgname in self.cache and - self.cache[pkgname].is_installed): - add_channel_item = True - break - if add_channel_item: - self.append(self.installed_iter, [ - channel.icon, - channel.display_name, - ViewPages.CHANNEL, - channel, - None]) - # delete the old ones - for child in iters_to_kill: - self.remove(child) - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/availablepane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/availablepane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/availablepane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/availablepane.py 2012-03-20 08:00:09.000000000 +0000 @@ -53,6 +53,7 @@ LOG = logging.getLogger(__name__) + class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons @@ -60,10 +61,10 @@ class Pages(): # page names, useful for debugging - NAMES = ('lobby', + NAMES = ('lobby', 'subcategory', 'list', - 'details', + 'details', 'purchase', ) # actual page id's @@ -75,17 +76,17 @@ # the default page HOME = LOBBY - __gsignals__ = {'available-pane-created':(GObject.SignalFlags.RUN_FIRST, - None, - ())} + __gsignals__ = {'available-pane-created': (GObject.SignalFlags.RUN_FIRST, + None, + ())} - def __init__(self, + def __init__(self, cache, - db, - distro, - icons, - datadir, - navhistory_back_action, + db, + distro, + icons, + datadir, + navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons, datadir) @@ -100,21 +101,21 @@ self.current_app_by_category = {} self.current_app_by_subcategory = {} self.pane_name = _("Get Software") - + # views to be created in init_view self.cat_view = None self.subcategories_view = None - + # integrate with the Unity launcher self.unity_launcher = UnityLauncher() - + # flag to indicate whether applications should be added to the # unity launcher when installed (this value is initialized by # the config load in app.py) self.add_to_launcher_enabled = True def init_view(self): - if self.view_initialized: + if self.view_initialized: return self.show_appview_spinner() @@ -134,21 +135,26 @@ #~ self.app_view._append_appcount(appcount) #~ liststore.connect('appcount-changed', on_appcount_changed) self.app_view.set_model(liststore) - liststore.connect( - "needs-refresh", lambda helper, pkgname: self.app_view.queue_draw()) + liststore.connect("needs-refresh", + lambda helper, pkgname: self.app_view.queue_draw()) # purchase view self.purchase_view = PurchaseView() app_manager = get_appmanager() - app_manager.connect("purchase-requested", self.on_purchase_requested) - self.purchase_view.connect("purchase-succeeded", self.on_purchase_succeeded) - self.purchase_view.connect("purchase-failed", self.on_purchase_failed) - self.purchase_view.connect("purchase-cancelled-by-user", self.on_purchase_cancelled_by_user) - self.purchase_view.connect("purchase-needs-spinner", self.on_purchase_needs_spinner) + app_manager.connect("purchase-requested", + self.on_purchase_requested) + self.purchase_view.connect("purchase-succeeded", + self.on_purchase_succeeded) + self.purchase_view.connect("purchase-failed", + self.on_purchase_failed) + self.purchase_view.connect("purchase-cancelled-by-user", + self.on_purchase_cancelled_by_user) + self.purchase_view.connect("purchase-needs-spinner", + self.on_purchase_needs_spinner) # categories, appview and details into the notebook in the bottom self.scroll_categories = Gtk.ScrolledWindow() - self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, + self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.cat_view = LobbyViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, @@ -156,16 +162,17 @@ self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) - self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) + self.notebook.append_page(self.scroll_categories, + Gtk.Label(label="categories")) # sub-categories view self.subcategories_view = SubCategoryViewGtk(self.datadir, - APP_INSTALL_PATH, - self.cache, - self.db, - self.icons, - self.apps_filter, - root_category=self.cat_view.categories[0]) + APP_INSTALL_PATH, + self.cache, + self.db, + self.icons, + self.apps_filter, + root_category=self.cat_view.categories[0]) self.subcategories_view.connect( "category-selected", self.on_subcategory_activated) self.subcategories_view.connect( @@ -196,15 +203,19 @@ "application-activated", self.on_application_activated) # details - self.notebook.append_page(self.scroll_details, Gtk.Label(label=NavButtons.DETAILS)) + self.notebook.append_page(self.scroll_details, + Gtk.Label(label=NavButtons.DETAILS)) # purchase view - self.notebook.append_page(self.purchase_view, Gtk.Label(label=NavButtons.PURCHASE)) + self.notebook.append_page(self.purchase_view, + Gtk.Label(label=NavButtons.PURCHASE)) # install backend - self.backend.connect("transactions-changed", self._on_transactions_changed) - self.backend.connect("transaction-started", self.on_transaction_started) - + self.backend.connect("transactions-changed", + self._on_transactions_changed) + self.backend.connect("transaction-started", + self.on_transaction_started) + # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") @@ -225,12 +236,11 @@ window.set_cursor(None) def on_purchase_requested(self, appmanager, app, iconname, url): - self.purchase_view.initiate_purchase(app, iconname, url) - vm = get_viewmanager() - vm.display_page( - self, AvailablePane.Pages.PURCHASE, self.state, - self.display_purchase) - return + if self.purchase_view.initiate_purchase(app, iconname, url): + vm = get_viewmanager() + vm.display_page( + self, AvailablePane.Pages.PURCHASE, self.state, + self.display_purchase) def on_purchase_needs_spinner(self, appmanager, active): vm = get_viewmanager() @@ -239,14 +249,14 @@ def on_purchase_succeeded(self, widget): # switch to the details page to display the transaction is in progress self._return_to_appdetails_view() - + def on_purchase_failed(self, widget): self._return_to_appdetails_view() dialogs.error(None, _("Failure in the purchase process."), _("Sorry, something went wrong. Your payment " "has been cancelled.")) - + def on_purchase_cancelled_by_user(self, widget): self._return_to_appdetails_view() @@ -307,15 +317,16 @@ #~ def _show_hide_subcategories(self, show_category_applist=False): #~ # check if have subcategories and are not in a subcategory #~ # view - if so, show it - #~ if (self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or - #~ self.notebook.get_current_page() == AvailablePane.Pages.DETAILS): + #~ current_page = self.notebook.get_current_page() + #~ if (current_page == AvailablePane.Pages.LOBBY or + #~ current_page == AvailablePane.Pages.DETAILS): #~ return #~ if (not show_category_applist and #~ self.state.category and #~ self.state.category.subcategories and #~ not (self.state.search_term or self.state.subcategory)): #~ self.subcategories_view.set_subcategory(self.state.category, - #~ num_items=len(self.app_view.get_model())) + #~ num_items=len(self.app_view.get_model())) #~ self.notebook.set_current_page(AvailablePane.Pages.SUBCATEGORY) #~ else: #~ self.notebook.set_current_page(AvailablePane.Pages.LIST) @@ -326,7 +337,8 @@ return None else: if self.state.subcategory: - return self.current_app_by_subcategory.get(self.state.subcategory) + return self.current_app_by_subcategory.get( + self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) @@ -336,11 +348,10 @@ return self.state.subcategory elif self.state.category: return self.state.category - return None def unset_current_category(self): - """ unset the current showing category, but keep e.g. the current - search + """ unset the current showing category, but keep e.g. the current + search """ self.state.category = None @@ -352,14 +363,14 @@ """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() - - def on_transaction_started(self, backend, pkgname, appname, trans_id, + + def on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): self._add_application_to_unity_launcher( backend, pkgname, appname, trans_id, trans_type) - def _add_application_to_unity_launcher(self, backend, pkgname, - appname, trans_id, + def _add_application_to_unity_launcher(self, backend, pkgname, + appname, trans_id, trans_type): if not self.add_to_launcher_enabled: return @@ -370,7 +381,7 @@ # we only care about installs if not trans_type == TransactionTypes.INSTALL: return - + app = Application(pkgname=pkgname, appname=appname) appdetails = app.get_details(self.db) # we only add items to the launcher that have a desktop file @@ -385,9 +396,11 @@ # now gather up the unity launcher info items and send the app to the # launcher service - launcher_info = self._get_unity_launcher_info(app, appdetails, trans_id) - self.unity_launcher.send_application_to_launcher(pkgname, launcher_info) - + launcher_info = self._get_unity_launcher_info(app, appdetails, + trans_id) + self.unity_launcher.send_application_to_launcher(pkgname, + launcher_info) + def _get_unity_launcher_info(self, app, appdetails, trans_id): (icon_size, icon_x, icon_y) = ( self._get_onscreen_icon_details_for_launcher_service(app)) @@ -426,10 +439,10 @@ self._update_action_bar_buttons() def _update_action_bar_buttons(self): - ''' - update buttons in the action bar to implement the custom package lists feature, - see https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists - ''' + """Update buttons in the action bar to implement the custom package + lists feature, see + https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists + """ if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): @@ -439,9 +452,10 @@ not len(self.backend.pending_transactions) > 0): app = Application(pkgname=pkgname) installable.append(app) - button_text = gettext.ngettext("Install %(amount)s Item", - "Install %(amount)s Items", - len(installable)) % { 'amount': len(installable), } + button_text = gettext.ngettext( + "Install %(amount)s Item", + "Install %(amount)s Items", + len(installable)) % {'amount': len(installable)} button = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. @@ -457,7 +471,7 @@ else: # Ensure button is removed. self.action_bar.remove_button(ActionButtons.INSTALL) - + def _install_current_appstore(self): ''' Function that installs all applications displayed in the pane. @@ -478,17 +492,17 @@ self.backend.install_multiple(pkgnames, appnames, iconnames) def set_state(self, nav_item): - return + pass def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" - + def _is_custom_list_search(self, search_term): - return (search_term and + return (search_term and ',' in search_term) - + # callbacks def on_cache_ready(self, cache): """ refresh the application list when the cache is re-opened """ @@ -502,7 +516,7 @@ LOG.debug("on_search_terms_changed: %s" % new_text) self.state.search_term = new_text - + # do not hide technical items for a custom list search if self._is_custom_list_search(self.state.search_term): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE @@ -527,7 +541,7 @@ self.display_app_view_page) return False - elif (self.state.category and + elif (self.state.category and self.state.category.subcategories and not new_text): vm.display_page(self, AvailablePane.Pages.SUBCATEGORY, @@ -632,23 +646,23 @@ SoftwarePane.display_details_page(self, page, view_state) self.cat_view.stop_carousels() return True - + def display_purchase(self, page, view_state): self.notebook.set_current_page(AvailablePane.Pages.PURCHASE) self.action_bar.clear() self.cat_view.stop_carousels() - return - + def display_previous_purchases(self, page, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self.app_view.set_header_labels(_("Previous Purchases"), None) self.notebook.set_current_page(AvailablePane.Pages.LIST) + # clear any search terms + self._clear_search() # do not emit app-list-changed here, this is done async when # the new model is ready self.refresh_apps(query=self.previous_purchases_query) self.action_bar.clear() self.cat_view.stop_carousels() - return def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % ( @@ -713,13 +727,14 @@ """ Return True if we are in the category page or if we display a sub-category page """ - return (self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or \ - self.notebook.get_current_page() == AvailablePane.Pages.SUBCATEGORY) + current_page = self.notebook.get_current_page() + return (current_page == AvailablePane.Pages.LOBBY or + current_page == AvailablePane.Pages.SUBCATEGORY) def is_applist_view_showing(self): """Return True if we are in the applist view """ return self.notebook.get_current_page() == AvailablePane.Pages.LIST - + def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == AvailablePane.Pages.DETAILS @@ -739,12 +754,14 @@ if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) - if category and category.flags and 'available-only' in category.flags: + if (category and category.flags and + 'available-only' in category.flags): self.state.filter.set_available_only(True) else: self.state.filter.set_available_only(False) - if category and category.flags and 'not-installed-only' in category.flags: + if (category and category.flags and + 'not-installed-only' in category.flags): self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) @@ -769,7 +786,7 @@ ) # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() - vm # make pyflakes happy + vm # make pyflakes happy db = get_test_db() cache = get_test_pkg_info() datadir = get_test_datadir() @@ -780,17 +797,20 @@ from softwarecenter.ui.gtk3.session.appmanager import ApplicationManager ApplicationManager(db, backend, icons) - navhistory_back_action = Gtk.Action("navhistory_back_action", "Back", "Back", None) - navhistory_forward_action = Gtk.Action("navhistory_forward_action", "Forward", "Forward", None) + navhistory_back_action = Gtk.Action("navhistory_back_action", "Back", + "Back", None) + navhistory_forward_action = Gtk.Action("navhistory_forward_action", + "Forward", "Forward", None) - w = AvailablePane(cache, db, 'Ubuntu', icons, datadir, navhistory_back_action, navhistory_forward_action) + w = AvailablePane(cache, db, 'Ubuntu', icons, datadir, + navhistory_back_action, navhistory_forward_action) w.init_view() w.show() win = Gtk.Window() win.connect("destroy", Gtk.main_quit) win.add(w) - win.set_size_request(800,600) + win.set_size_request(800, 600) win.show_all() # this is used later in tests @@ -801,4 +821,3 @@ if __name__ == "__main__": win = get_test_window() Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/basepane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/basepane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/basepane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/basepane.py 2012-03-15 09:05:38.000000000 +0000 @@ -18,8 +18,8 @@ class BasePane(object): - """ Base for all the View widgets that can be registered in a - ViewManager + """ Base for all the View widgets that can be registered in a + ViewManager """ def __init__(self): @@ -31,16 +31,16 @@ def is_category_view_showing(self): return False - + def is_applist_view_showing(self): return False - + def is_app_details_view_showing(self): return False def get_current_app(self): - return None - + pass + def init_view(self): """ A callback that is made at the time the pane is selected in the @@ -50,5 +50,3 @@ is to optimize startup time performance. """ pass - - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/globalpane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/globalpane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/globalpane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/globalpane.py 2012-03-15 09:05:38.000000000 +0000 @@ -4,12 +4,12 @@ from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarecenter.ui.gtk3.panes.viewswitcher import ViewSwitcher + def _widget_set_margins(widget, top=0, bottom=0, left=0, right=0): widget.set_margin_top(top) widget.set_margin_bottom(bottom) widget.set_margin_left(left) widget.set_margin_right(right) - return class GlobalPane(Gtk.Toolbar): @@ -20,10 +20,12 @@ context.add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) # add nav history back/forward buttons... - # note: this is hacky, would be much nicer to make the custom self/right - # buttons in BackForwardButton to be Gtk.Activatable/Gtk.Widgets, then wire in the - # actions using e.g. self.navhistory_back_action.connect_proxy(self.back_forward.left), - # but couldn't seem to get this to work..so just wire things up directly + # note: this is hacky, would be much nicer to make the custom + # self/right buttons in BackForwardButton to be + # Gtk.Activatable/Gtk.Widgets, then wire in the actions using e.g. + # self.navhistory_back_action.connect_proxy(self.back_forward.left), + # but couldn't seem to get this to work..so just wire things up + # directly vm = get_viewmanager() self.back_forward = vm.get_global_backforward() self.back_forward.set_vexpand(False) @@ -31,14 +33,17 @@ if self.get_direction() != Gtk.TextDirection.RTL: _widget_set_margins(self.back_forward, - left=StockEms.MEDIUM, right=StockEms.MEDIUM+2) + left=StockEms.MEDIUM, + right=StockEms.MEDIUM + 2) else: _widget_set_margins(self.back_forward, - right=StockEms.MEDIUM, left=StockEms.MEDIUM+2) + right=StockEms.MEDIUM, + left=StockEms.MEDIUM + 2) self._insert_as_tool_item(self.back_forward, 0) # this is what actually draws the All Software, Installed etc buttons - self.view_switcher = ViewSwitcher(view_manager, datadir, db, cache, icons) + self.view_switcher = ViewSwitcher(view_manager, datadir, db, cache, + icons) self._insert_as_tool_item(self.view_switcher, 1) item = Gtk.ToolItem() @@ -58,7 +63,6 @@ _widget_set_margins(self.searchentry, right=StockEms.MEDIUM) else: _widget_set_margins(self.searchentry, left=StockEms.MEDIUM) - return def _insert_as_tool_item(self, widget, pos): item = Gtk.ToolItem() @@ -84,7 +88,7 @@ p = GlobalPane(vm, datadir, db, cache, icons) win = Gtk.Window() - win.set_size_request(400,200) + win.set_size_request(400, 200) win.set_data("pane", p) win.connect("destroy", Gtk.main_quit) win.add(p) @@ -94,5 +98,5 @@ if __name__ == "__main__": win = get_test_window() - + Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/historypane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/historypane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/historypane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/historypane.py 2012-03-15 09:05:38.000000000 +0000 @@ -31,16 +31,17 @@ from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarepane import DisplayState + class HistoryPane(Gtk.VBox, BasePane): __gsignals__ = { - "app-list-changed" : (GObject.SignalFlags.RUN_LAST, - None, - (int, ), - ), - "history-pane-created" : (GObject.SignalFlags.RUN_FIRST, - None, - ()), + "app-list-changed": (GObject.SignalFlags.RUN_LAST, + None, + (int, ), + ), + "history-pane-created": (GObject.SignalFlags.RUN_FIRST, + None, + ()), } (COL_WHEN, COL_ACTION, COL_PKG) = range(3) @@ -50,7 +51,7 @@ ICON_SIZE = 32 PADDING = 6 - + # pages for the spinner notebook (PAGE_HISTORY_VIEW, PAGE_SPINNER) = range(2) @@ -84,12 +85,14 @@ self.toolbar.set_style(Gtk.ToolbarStyle.TEXT) self.pack_start(self.toolbar, False, True, 0) - all_action = Gtk.RadioAction('filter_all', _('All Changes'), None, None, self.ALL) + all_action = Gtk.RadioAction('filter_all', _('All Changes'), None, + None, self.ALL) all_action.connect('changed', self.change_filter) all_button = all_action.create_tool_item() self.toolbar.insert(all_button, 0) - installs_action = Gtk.RadioAction('filter_installs', _('Installations'), None, None, self.INSTALLED) + installs_action = Gtk.RadioAction('filter_installs', + _('Installations'), None, None, self.INSTALLED) installs_action.join_group(all_action) installs_button = installs_action.create_tool_item() self.toolbar.insert(installs_button, 1) @@ -118,11 +121,11 @@ Gtk.PolicyType.AUTOMATIC) self.history_view.show() self.history_view.add(self.view) - + # make a spinner to display while history is loading self.spinner_notebook = SpinnerNotebook( self.history_view, _('Loading history')) - + self.pack_start(self.spinner_notebook, True, True, 0) self.store = Gtk.TreeStore(*self.COL_TYPES) @@ -132,7 +135,7 @@ self.view.set_model(self.store_filter) all_action.set_active(True) self.last = None - + # to save (a lot of) time at startup we load history later, only when # it is selected to be viewed self.history = None @@ -145,14 +148,14 @@ self.cell_text = Gtk.CellRendererText() self.column.pack_start(self.cell_text, True) self.column.set_cell_data_func(self.cell_text, self.render_cell_text) - + # busy cursor self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH) def init_view(self): if self.history == None: - # if the history is not yet initialized we have to load and parse it - # show a spinner while we do that + # if the history is not yet initialized we have to load and parse + # it show a spinner while we do that self.realize() window = self.get_window() window.set_cursor(self.busy_cursor) @@ -171,9 +174,8 @@ cr.set_source_rgba(color.red, color.green, color.blue, 0.5) cr.set_line_width(1) cr.move_to(0.5, a.height - 0.5) - cr.rel_line_to(a.width-1, 0) + cr.rel_line_to(a.width - 1, 0) cr.stroke() - return def _get_emblems(self, icons): from softwarecenter.enums import USE_PACKAGEKIT_BACKEND @@ -189,7 +191,6 @@ for i, emblem in enumerate(emblem_names): pb = icons.load_icon(emblem, self.ICON_SIZE, 0) self._emblems[i + 1] = pb - return def _set_actions_sensitive(self, sensitive): for action in self._actions_list: @@ -198,11 +199,12 @@ def _reset_icon_cache(self, theme=None): self._app_icon_cache.clear() try: - missing = self.icons.load_icon(Icons.MISSING_APP, self.ICON_SIZE, 0) + missing = self.icons.load_icon(Icons.MISSING_APP, self.ICON_SIZE, + 0) except GObject.GError: missing = None self._app_icon_cache[Icons.MISSING_APP] = missing - + def load_and_parse_history(self): from softwarecenter.db.history import get_pkg_history self.history = get_pkg_history() @@ -234,7 +236,7 @@ date = when.date() day = self.store.append(None, (date, self.ALL, None)) last_row = None - actions = {self.INSTALLED: trans.install, + actions = {self.INSTALLED: trans.install, self.REMOVED: trans.remove, self.UPGRADED: trans.upgrade, } @@ -247,11 +249,11 @@ def get_current_page(self): # single page views can return None here - return None + pass def get_callback_for_page(self, page, state): # single page views can return None here - return None + pass def on_search_terms_changed(self, entry, terms): self.update_view() @@ -277,20 +279,20 @@ # Compute the number of visible changes # don't do this atm - the spec doesn't mention that the history pane - # should have a status text and it gives us a noticable performance gain - # if we don't calculate this + # should have a status text and it gives us a noticable performance + # gain if we don't calculate this # self.visible_changes = 0 # day = self.store_filter.get_iter_first() # while day is not None: # self.visible_changes += self.store_filter.iter_n_children(day) # day = self.store_filter.iter_next(day) - + # Expand the most recent day day = self.store.get_iter_first() if day is not None: - path = self.store.get_path(day) - self.view.expand_row(path, False) - self.view.scroll_to_cell(path) + path = self.store.get_path(day) + self.view.expand_row(path, False) + self.view.scroll_to_cell(path) # self.emit('app-list-changed', self.visible_changes) @@ -327,7 +329,6 @@ action = store.get_value(iter, self.COL_ACTION) cell.set_property('pixbuf', self._emblems[action]) - #~ icon_name = Icons.MISSING_APP #~ for m in self.db.xapiandb.postlist("AP" + pkg): #~ doc = self.db.xapiandb.get_document(m.docid) @@ -339,11 +340,11 @@ #~ icon = self._app_icon_cache[icon_name] #~ else: #~ try: - #~ icon = self.icons.load_icon(icon_name, self.ICON_SIZE, 0) + #~ icon = self.icons.load_icon(icon_name, self.ICON_SIZE, + #~ 0) #~ except GObject.GError: #~ icon = self._app_icon_cache[Icons.MISSING_APP] #~ self._app_icon_cache[icon_name] = icon - def render_cell_text(self, column, cell, store, iter, user_data): when = store.get_value(iter, self.COL_WHEN) @@ -352,15 +353,20 @@ pkg = store.get_value(iter, self.COL_PKG) subs = {'pkgname': pkg, 'color': '#8A8A8A', - # Translators : time displayed in history, display hours (0-12), minutes and AM/PM. %H should be used instead of %I to display hours 0-24 + # Translators : time displayed in history, display hours + # (0-12), minutes and AM/PM. %H should be used instead + # of %I to display hours 0-24 'time': when.time().strftime(_('%I:%M %p')), } if action == self.INSTALLED: - text = _('%(pkgname)s installed %(time)s') % subs + text = _('%(pkgname)s ' + 'installed %(time)s') % subs elif action == self.REMOVED: - text = _('%(pkgname)s removed %(time)s') % subs + text = _('%(pkgname)s ' + 'removed %(time)s') % subs elif action == self.UPGRADED: - text = _('%(pkgname)s updated %(time)s') % subs + text = _('%(pkgname)s ' + 'updated %(time)s') % subs elif isinstance(when, datetime.date): today = datetime.date.today() monday = today - datetime.timedelta(days=today.weekday()) @@ -388,7 +394,7 @@ ) # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() - vm # make pyflakes happy + vm # make pyflakes happy db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() @@ -409,4 +415,3 @@ if __name__ == '__main__': win = get_test_window() Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/installedpane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/installedpane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/installedpane.py 2012-03-08 15:15:30.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/installedpane.py 2012-03-15 09:05:38.000000000 +0000 @@ -45,7 +45,8 @@ from softwarecenter.db.appfilter import AppFilter from softwarecenter.paths import APP_INSTALL_PATH -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + def interrupt_build_and_wait(f): """ decorator that ensures that a build of the categorised installed apps @@ -79,19 +80,20 @@ DETAILS) = range(2) # the default page HOME = LIST - + # pages for the installed view spinner notebook (PAGE_SPINNER, PAGE_INSTALLED) = range(2) - __gsignals__ = {'installed-pane-created':(GObject.SignalFlags.RUN_FIRST, - None, - ())} + __gsignals__ = {'installed-pane-created': (GObject.SignalFlags.RUN_FIRST, + None, + ())} def __init__(self, cache, db, distro, icons, datadir): # parent - SoftwarePane.__init__(self, cache, db, distro, icons, datadir, show_ratings=False) + SoftwarePane.__init__(self, cache, db, distro, icons, datadir, + show_ratings=False) CategoriesParser.__init__(self, db) self.current_appview_selection = None @@ -111,36 +113,42 @@ self._halt_build = False self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE - + self.visible_docids = None self.visible_cats = {} - + self.installed_spinner_notebook = None def init_view(self): - if self.view_initialized: + if self.view_initialized: return SoftwarePane.init_view(self) - - # show a busy cursor and display the main spinner while we build the view + + # show a busy cursor and display the main spinner while we build the + # view window = self.get_window() if window: window.set_cursor(self.busy_cursor) self.show_appview_spinner() - + self.oneconf_viewpickler = OneConfViews(self.icons) - self.oneconf_viewpickler.register_computer(None, _("This computer (%s)") % platform.node()) + self.oneconf_viewpickler.register_computer(None, + _("This computer (%s)") % platform.node()) self.oneconf_viewpickler.select_first() - self.oneconf_viewpickler.connect('computer-changed', self._selected_computer_changed) - self.oneconf_viewpickler.connect('current-inventory-refreshed', self._current_inventory_need_refresh) - + self.oneconf_viewpickler.connect('computer-changed', + self._selected_computer_changed) + self.oneconf_viewpickler.connect('current-inventory-refreshed', + self._current_inventory_need_refresh) + # Start OneConf self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler) if self.oneconf_handler: - self.oneconf_handler.connect('show-oneconf-changed', self._show_oneconf_changed) - self.oneconf_handler.connect('last-time-sync-changed', self._last_time_sync_oneconf_changed) - + self.oneconf_handler.connect('show-oneconf-changed', + self._show_oneconf_changed) + self.oneconf_handler.connect('last-time-sync-changed', + self._last_time_sync_oneconf_changed) + # OneConf pane self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL) self.oneconfcontrol = Gtk.Box() @@ -154,14 +162,18 @@ scroll.set_shadow_type(Gtk.ShadowType.IN) scroll.add(self.oneconf_viewpickler) self.oneconfcontrol.pack_start(scroll, True, True, 0) - + oneconftoolbar = Gtk.Box() oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL) oneconfpropertymenu = Gtk.Menu() - self.oneconfproperty = MenuButton(oneconfpropertymenu, Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, Gtk.IconSize.BUTTON)) + self.oneconfproperty = MenuButton(oneconfpropertymenu, + Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, + Gtk.IconSize.BUTTON)) self.stopsync_label = _(u"Stop Syncing “%s”") - stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label % platform.node()) - stop_oneconf_share_menuitem.connect("activate", self._on_stop_oneconf_hostshare_clicked) + stop_oneconf_share_menuitem = Gtk.MenuItem( + label=self.stopsync_label % platform.node()) + stop_oneconf_share_menuitem.connect("activate", + self._on_stop_oneconf_hostshare_clicked) stop_oneconf_share_menuitem.show() oneconfpropertymenu.append(stop_oneconf_share_menuitem) self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1) @@ -173,7 +185,8 @@ self.notebook.append_page(self.box_app_list, Gtk.Label(label="list")) # details - self.notebook.append_page(self.scroll_details, Gtk.Label(label="details")) + self.notebook.append_page(self.scroll_details, + Gtk.Label(label="details")) # initial refresh self.state.search_term = "" @@ -184,25 +197,26 @@ self.treefilter.set_visible_func(self._row_visibility_func, AppTreeStore.COL_ROW_DATA) self.app_view.set_model(self.treefilter) - self.app_view.tree_view.connect("row-collapsed", self._on_row_collapsed) + self.app_view.tree_view.connect("row-collapsed", + self._on_row_collapsed) self._all_cats = self.parse_applications_menu(APP_INSTALL_PATH) self._all_cats = categories_sorted_by_name(self._all_cats) - + # we do not support the search aid feature in the installedview self.box_app_list.remove(self.search_aid) # remove here - self.box_app_list.remove(self.app_view) + self.box_app_list.remove(self.app_view) # create a local spinner notebook for the installed view self.installed_spinner_notebook = SpinnerNotebook(self.app_view) - + self.computerpane.pack2(self.installed_spinner_notebook, True, True) self.show_installed_view_spinner() - + self.show_all() - + # initialize view to hide the oneconf computer selector self.oneconf_viewpickler.select_first() self.oneconfcontrol.hide() @@ -214,15 +228,15 @@ # now we are initialized self.emit("installed-pane-created") - + self.view_initialized = True return False - + def show_installed_view_spinner(self): """ display the local spinner for the installed view panel """ if self.installed_spinner_notebook: self.installed_spinner_notebook.show_spinner() - + def hide_installed_view_spinner(self): """ hide the local spinner for the installed view panel """ if self.installed_spinner_notebook: @@ -236,15 +250,17 @@ self.current_hostname = hostname menuitem = self.oneconfproperty.get_menu().get_children()[0] if self.current_hostid: - (self.oneconf_additional_pkg, self.oneconf_missing_pkg) = self.oneconf_handler.oneconf.diff(self.current_hostid, '') + diff = self.oneconf_handler.oneconf.diff(self.current_hostid, '') + self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff stopsync_hostname = self.current_hostname # FIXME for P: oneconf views don't support search - if self.state.search_term: + if self.state.search_term: self._search() else: stopsync_hostname = platform.node() self.searchentry.show() - menuitem.set_label(self.stopsync_label % stopsync_hostname.encode('utf-8')) + menuitem.set_label(self.stopsync_label % + stopsync_hostname.encode('utf-8')) self.refresh_apps() def _last_time_sync_oneconf_changed(self, oneconf_handler, msg): @@ -266,14 +282,15 @@ if self.current_hostid: self.oneconf_viewpickler.remove_computer(self.current_hostid) self.oneconf_viewpickler.select_first() - + def _current_inventory_need_refresh(self, oneconfviews): if self.current_hostid: - (self.oneconf_additional_pkg, self.oneconf_missing_pkg) = self.oneconf_handler.oneconf.diff(self.current_hostid, '') + diff = self.oneconf_handler.oneconf.diff(self.current_hostid, '') + self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff self.refresh_apps() def _on_row_collapsed(self, view, it, path): - return + pass def _row_visibility_func(self, model, it, col): row = model.get_value(it, col) @@ -285,16 +302,18 @@ elif isinstance(row, CategoryRowReference): return row.untranslated_name in self.visible_cats.keys() - elif row is None: return False + elif row is None: + return False return row.get_docid() in self.visible_docids def _use_category(self, cat): # System cat is large and slow to search, filter it in default mode - if ('carousel-only' in cat.flags or + if ('carousel-only' in cat.flags or ((self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE) - and cat.untranslated_name == 'System')): return False + and cat.untranslated_name == 'System')): + return False return True @@ -306,14 +325,14 @@ #~ @interrupt_build_and_wait def _build_categorised_installedview(self): LOG.debug('Rebuilding categorised installedview...') - + # display the busy cursor and a local spinner while we build the view window = self.get_window() if window: window.set_cursor(self.busy_cursor) self.show_installed_view_spinner() - - model = self.base_model # base model not treefilter + + model = self.base_model # base model not treefilter model.clear() def profiled_rebuild_categorised_view(): @@ -323,28 +342,30 @@ def rebuild_categorised_view(): self.cat_docid_map = {} enq = self.enquirer - + i = 0 - + while Gtk.events_pending(): Gtk.main_iteration() xfilter = AppFilter(self.db, self.cache) xfilter.set_installed_only(True) - + for cat in self._all_cats: # for each category do category query and append as a new # node to tree_view - if not self._use_category(cat): continue + if not self._use_category(cat): + continue query = self.get_query_for_cat(cat) - LOG.debug("xfilter.installed_only: %s" % xfilter.installed_only) + LOG.debug("xfilter.installed_only: %s" % + xfilter.installed_only) enq.set_query(query, sortmode=SortMethods.BY_ALPHABET, nonapps_visible=self.nonapps_visible, filter=xfilter, nonblocking_load=False, - persistent_duplicate_filter=(i>0)) - + persistent_duplicate_filter=(i > 0)) + L = len(enq.matches) if L: i += L @@ -352,13 +373,13 @@ self.cat_docid_map[cat.untranslated_name] = \ set([doc.get_docid() for doc in docs]) model.set_category_documents(cat, docs) - + while Gtk.events_pending(): Gtk.main_iteration() # check for uncategorised pkgs if self.state.channel: - self._run_channel_enquirer(persistent_duplicate_filter=(i>0)) + self._run_channel_enquirer(persistent_duplicate_filter=(i > 0)) L = len(enq.matches) if L: # some foo for channels @@ -369,7 +390,8 @@ channel_name = self.state.channel.display_name docs = enq.get_documents() tag = channel_name or 'Uncategorized' - self.cat_docid_map[tag] = set([doc.get_docid() for doc in docs]) + self.cat_docid_map[tag] = set( + [doc.get_docid() for doc in docs]) model.set_nocategory_documents(docs, untranslated_name=tag, display_name=channel_name) i += L @@ -382,14 +404,15 @@ # cache the installed app count self.installed_count = i - self.app_view._append_appcount(self.installed_count, mode=AppView.INSTALLED_MODE) - + self.app_view._append_appcount(self.installed_count, + mode=AppView.INSTALLED_MODE) + # hide the local spinner self.hide_installed_view_spinner() - + if window: window.set_cursor(None) - + # reapply search if needed if self.state.search_term: self._do_search(self.state.search_term) @@ -398,18 +421,17 @@ return GObject.idle_add(profiled_rebuild_categorised_view) - return - + def _build_oneconfview(self): LOG.debug('Rebuilding oneconfview for %s...' % self.current_hostid) - + # display the busy cursor and the local spinner while we build the view window = self.get_window() if window: window.set_cursor(self.busy_cursor) self.show_installed_view_spinner() - - model = self.base_model # base model not treefilter + + model = self.base_model # base model not treefilter model.clear() def profiled_rebuild_oneconfview(): @@ -417,10 +439,10 @@ rebuild_oneconfview() def rebuild_oneconfview(): - + # FIXME for P: hide the search entry self.searchentry.hide() - + self.cat_docid_map = {} enq = self.enquirer query = xapian.Query("") @@ -430,30 +452,33 @@ self.state.channel.query) i = 0 - + # First search: missing apps only xfilter = AppFilter(self.db, self.cache) xfilter.set_restricted_list(self.oneconf_additional_pkg) xfilter.set_not_installed_only(True) - + enq.set_query(query, sortmode=SortMethods.BY_ALPHABET, nonapps_visible=self.nonapps_visible, filter=xfilter, - nonblocking_load=True, # we don't block this one for better oneconf responsiveness - persistent_duplicate_filter=(i>0)) + nonblocking_load=True, # we don't block this one for + # better oneconf responsiveness + persistent_duplicate_filter=(i > 0)) L = len(enq.matches) if L: - cat_title = utf8(ngettext(u'%(amount)s item on “%(machine)s” not on this computer', - u'%(amount)s items on “%(machine)s” not on this computer', - L)) % { 'amount' : L, 'machine': utf8(self.current_hostname)} + cat_title = utf8(ngettext( + u'%(amount)s item on “%(machine)s” not on this computer', + u'%(amount)s items on “%(machine)s” not on this computer', + L)) % {'amount': L, 'machine': utf8(self.current_hostname)} i += L docs = enq.get_documents() - self.cat_docid_map["missingpkg"] = set([doc.get_docid() for doc in docs]) - model.set_nocategory_documents(docs, untranslated_name="additionalpkg", - display_name=cat_title) + self.cat_docid_map["missingpkg"] = set( + [doc.get_docid() for doc in docs]) + model.set_nocategory_documents(docs, + untranslated_name="additionalpkg", display_name=cat_title) # Second search: additional apps xfilter.set_restricted_list(self.oneconf_missing_pkg) @@ -464,18 +489,20 @@ nonapps_visible=self.nonapps_visible, filter=xfilter, nonblocking_load=False, - persistent_duplicate_filter=(i>0)) + persistent_duplicate_filter=(i > 0)) L = len(enq.matches) if L: - cat_title = utf8(ngettext(u'%(amount)s item on this computer not on “%(machine)s”', - '%(amount)s items on this computer not on “%(machine)s”', - L)) % { 'amount' : L, 'machine': utf8(self.current_hostname)} + cat_title = utf8(ngettext( + u'%(amount)s item on this computer not on “%(machine)s”', + '%(amount)s items on this computer not on “%(machine)s”', + L)) % {'amount': L, 'machine': utf8(self.current_hostname)} i += L docs = enq.get_documents() - self.cat_docid_map["additionalpkg"] = set([doc.get_docid() for doc in docs]) - model.set_nocategory_documents(docs, untranslated_name="additionalpkg", - display_name=cat_title) + self.cat_docid_map["additionalpkg"] = set( + [doc.get_docid() for doc in docs]) + model.set_nocategory_documents(docs, + untranslated_name="additionalpkg", display_name=cat_title) if i: self.app_view.tree_view.set_cursor(Gtk.TreePath(), @@ -485,31 +512,30 @@ # cache the installed app count self.installed_count = i - self.app_view._append_appcount(self.installed_count, mode=AppView.DIFF_MODE) - + self.app_view._append_appcount(self.installed_count, + mode=AppView.DIFF_MODE) + # hide the local spinner self.hide_installed_view_spinner() - + if window: window.set_cursor(None) - + self.emit("app-list-changed", i) return GObject.idle_add(profiled_rebuild_oneconfview) - return def _check_expand(self): it = self.treefilter.get_iter_first() while it: path = self.treefilter.get_path(it) - if self.state.search_term:# or path in self._user_expanded_paths: + if self.state.search_term: # or path in self._user_expanded_paths: self.app_view.tree_view.expand_row(path, False) else: self.app_view.tree_view.collapse_row(path) it = self.treefilter.iter_next(it) - return def _do_search(self, terms): self.state.search_term = terms @@ -519,7 +545,7 @@ nonapps_visible=self.nonapps_visible, filter=xfilter, nonblocking_load=True) - + self.visible_docids = self.enquirer.get_docids() self.visible_cats = self._get_vis_cats(self.visible_docids) self.treefilter.refilter() @@ -551,7 +577,6 @@ self.emit("app-list-changed", 0) elif self.state.search_term != terms: self._do_search(terms) - return def get_query(self): # search terms @@ -575,7 +600,6 @@ self._build_oneconfview() else: self._build_categorised_installedview() - return def _clear_search(self): # remove the details and clear the search @@ -589,7 +613,6 @@ self.state.search_term = terms self.notebook.set_current_page(InstalledPane.Pages.LIST) self.hide_installed_view_spinner() - return def _get_vis_cats(self, visids): vis_cats = {} @@ -626,7 +649,7 @@ def display_overview_page(self, page, view_state): LOG.debug("view_state: %s" % view_state) if self.current_hostid: - # FIXME for P: oneconf views don't support search + # FIXME for P: oneconf views don't support search # this one ensure that even when switching between pane, we # don't have the search item if self.state.search_term: @@ -643,16 +666,16 @@ """return the current active application object applicable to the context""" return self.current_appview_selection - + def is_category_view_showing(self): # there is no category view in the installed pane return False - + def is_applist_view_showing(self): """Return True if we are in the applist view """ return (self.notebook.get_current_page() == InstalledPane.Pages.LIST) - + def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == InstalledPane.Pages.DETAILS @@ -667,7 +690,7 @@ ) # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() - vm # make pyflakes happy + vm # make pyflakes happy db = get_test_db() cache = get_test_pkg_info() datadir = get_test_datadir() @@ -696,4 +719,3 @@ if __name__ == "__main__": win = get_test_window() Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/pendingpane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/pendingpane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/pendingpane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/pendingpane.py 2012-03-15 09:05:38.000000000 +0000 @@ -30,7 +30,7 @@ class PendingPane(Gtk.ScrolledWindow, BasePane): - + CANCEL_XPAD = StockEms.MEDIUM CANCEL_YPAD = StockEms.MEDIUM @@ -64,14 +64,14 @@ tp.set_property("xpad", self.CANCEL_XPAD) tp.set_property("ypad", self.CANCEL_YPAD) tp.set_property("text", "") - column = Gtk.TreeViewColumn("Progress", tp, + column = Gtk.TreeViewColumn("Progress", tp, value=PendingStore.COL_PROGRESS, pulse=PendingStore.COL_PULSE) column.set_min_width(200) self.tv.append_column(column) # cancel icon tpix = Gtk.CellRendererPixbuf() - column = Gtk.TreeViewColumn("Cancel", tpix, + column = Gtk.TreeViewColumn("Cancel", tpix, stock_id=PendingStore.COL_CANCEL) self.tv.append_column(column) # fake columns that eats the extra space at the end @@ -81,7 +81,7 @@ # add it store = PendingStore(icons) self.tv.set_model(store) - + def _on_button_pressed(self, widget, event): """button press handler to capture clicks on the cancel button""" #print "_on_clicked: ", event @@ -100,7 +100,7 @@ # not cancelable (no icon) model = self.tv.get_model() if model[path][PendingStore.COL_CANCEL] == "": - return + return # get tid tid = model[path][PendingStore.COL_TID] trans = model._transactions_watcher.get_transaction(tid) @@ -125,6 +125,7 @@ def get_callback_for_page(self, page_id, view_state): return None + def get_test_window(): from softwarecenter.testutils import get_test_gtk3_icon_cache @@ -139,7 +140,7 @@ win = Gtk.Window() win.add(scroll) view.grab_focus() - win.set_size_request(500,200) + win.set_size_request(500, 200) win.show_all() win.connect("destroy", Gtk.main_quit) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/softwarepane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/softwarepane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/softwarepane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/softwarepane.py 2012-03-20 08:22:01.000000000 +0000 @@ -78,15 +78,16 @@ self.filter = None self.previous_purchases_query = None self.vadjustment = 0.0 - return def __setattr__(self, name, val): attrs = self._attrs if name not in attrs: - raise AttributeError("The attr name \"%s\" is not permitted" % name) + raise AttributeError("The attr name \"%s\" is not permitted" % + name) Gtk.main_quit() if not isinstance(val, attrs[name]): - msg = "Attribute %s expects %s, got %s" % (name, attrs[name], type(val)) + msg = "Attribute %s expects %s, got %s" % (name, attrs[name], + type(val)) raise TypeError(msg) Gtk.main_quit() return object.__setattr__(self, name, val) @@ -124,7 +125,7 @@ self.limit = 0 #~ self.filter = None self.vadjustment = 0.0 - return + class SoftwarePane(Gtk.VBox, BasePane): """ Common base class for AvailablePane and InstalledPane""" @@ -136,10 +137,10 @@ SPINNER = 2 __gsignals__ = { - "app-list-changed" : (GObject.SignalFlags.RUN_LAST, - None, - (int,), - ), + "app-list-changed": (GObject.SignalFlags.RUN_LAST, + None, + (int,), + ), } PADDING = 6 @@ -148,7 +149,7 @@ Gtk.VBox.__init__(self) BasePane.__init__(self) - # other classes we need + # other classes we need self.enquirer = AppEnquire(cache, db) self._query_complete_handler = self.enquirer.connect( "query-complete", self.on_query_complete) @@ -189,7 +190,7 @@ # cursor self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH) - + # views to be created in init_view self.app_view = None self.app_details_view = None @@ -200,7 +201,7 @@ SoftwarePane. Note that this method is intended to be called by the subclass itself at the start of its own init_view() implementation. """ - # common UI elements (applist and appdetails) + # common UI elements (applist and appdetails) # its the job of the Child class to put it into a good location # list self.box_app_list = Gtk.VBox() @@ -211,23 +212,24 @@ self.app_view = AppView(self.db, self.cache, self.icons, self.show_ratings) - self.app_view.connect("sort-method-changed", self.on_app_view_sort_method_changed) + self.app_view.connect("sort-method-changed", + self.on_app_view_sort_method_changed) self.init_atk_name(self.app_view, "app_view") self.box_app_list.pack_start(self.app_view, True, True, 0) - self.app_view.connect("application-selected", + self.app_view.connect("application-selected", self.on_application_selected) - self.app_view.connect("application-activated", + self.app_view.connect("application-activated", self.on_application_activated) - + # details self.scroll_details = Gtk.ScrolledWindow() - self.scroll_details.set_policy(Gtk.PolicyType.AUTOMATIC, + self.scroll_details.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - self.app_details_view = AppDetailsView(self.db, + self.app_details_view = AppDetailsView(self.db, self.distro, - self.icons, - self.cache, + self.icons, + self.cache, self.datadir) self.app_details_view.connect( "different-application-selected", self.on_application_activated) @@ -246,7 +248,7 @@ """ init the atk name for a given gtk widget based on parent-pane and variable name (used for the mago tests) """ - name = self.__class__.__name__ + "." + name + name = self.__class__.__name__ + "." + name Atk.Object.set_name(widget.get_accessible(), name) def on_cache_ready(self, cache): @@ -254,7 +256,7 @@ LOG.debug("on_cache_ready") # it only makes sense to refresh if there is something to # refresh, otherwise we create a bunch of (not yet needed) - # AppStore objects on startup when the cache sends its + # AppStore objects on startup when the cache sends its # initial "cache-ready" signal model = self.app_view.tree_view.get_model() if model is None: @@ -270,7 +272,8 @@ self.state.application = app vm = get_viewmanager() - vm.display_page(self, SoftwarePane.Pages.DETAILS, self.state, self.display_details_page) + vm.display_page(self, SoftwarePane.Pages.DETAILS, self.state, + self.display_details_page) def show_app(self, app): self.on_application_activated(None, app) @@ -288,7 +291,6 @@ self.app_view.display_matches(enquirer.matches, self._is_in_search_mode()) self.hide_appview_spinner() - return def on_app_view_sort_method_changed(self, app_view, combo): if app_view.get_sort_mode() == self.enquirer.sortmode: @@ -298,7 +300,6 @@ app_view.clear_model() query = self.get_query() self._refresh_apps_with_apt_cache(query) - return def _is_in_search_mode(self): return (self.state.search_term and @@ -311,7 +312,7 @@ if not self.state.search_term: self.action_bar.clear() self.spinner_notebook.show_spinner() - + def hide_appview_spinner(self): """ hide the spinner and display the appview in the panel """ LOG.debug("hide_appview_spinner") @@ -320,11 +321,9 @@ def set_section(self, section): self.section = section self.app_details_view.set_section(section) - return def section_sync(self): self.app_details_view.set_section(self.section) - return def on_app_list_changed(self, pane, length): """internal helper that keeps the the action bar up-to-date by @@ -333,12 +332,10 @@ self.show_nonapps_if_required(len(self.enquirer.matches)) self.search_aid.update_search_help(self.state) - return def hide_nonapps(self): """ hide non-applications control in the action bar """ self.action_bar.unset_label() - return def show_nonapps_if_required(self, length): """ @@ -360,7 +357,7 @@ return LOG.debug("nonapps_visible value=%s (always visible: %s)" % ( - self.nonapps_visible, + self.nonapps_visible, self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE)) self.action_bar.unset_label() @@ -370,13 +367,13 @@ # In most/all languages you will want the whole string as a link label = gettext.ngettext("_Hide %(amount)i technical item_", "_Hide %(amount)i technical items_", - n_pkgs) % { 'amount': n_pkgs, } + n_pkgs) % {'amount': n_pkgs} self.action_bar.set_label( - label, link_result=self._hide_nonapp_pkgs) + label, link_result=self._hide_nonapp_pkgs) else: label = gettext.ngettext("_Show %(amount)i technical item_", "_Show %(amount)i technical items_", - n_pkgs) % { 'amount': n_pkgs, } + n_pkgs) % {'amount': n_pkgs} self.action_bar.set_label( label, link_result=self._show_nonapp_pkgs) @@ -388,7 +385,7 @@ self.unset_current_category() self.refresh_apps() elif uri.startswith("search-parent:"): - self.apps_subcategory = None; + self.apps_subcategory = None self.refresh_apps() elif uri.startswith("search-unsupported:"): self.apps_filter.set_supported_only(False) @@ -424,7 +421,7 @@ return channel_query # ... otherwise show all return xapian.Query("") - + def refresh_apps(self, query=None): """refresh the applist and update the navigation bar """ LOG.debug("refresh_apps") @@ -433,6 +430,9 @@ if query is None: query = self.get_query() + # this can happen e.g. when opening a deb file, see bug #951238 + if not self.app_view: + return self.app_view.clear_model() self.search_aid.reset() self.show_appview_spinner() @@ -461,7 +461,6 @@ exact=self.is_custom_list(), nonapps_visible=self.nonapps_visible, filter=self.state.filter) - return def display_details_page(self, page, view_state): self.app_details_view.show_app(view_state.application) @@ -486,11 +485,11 @@ def get_sort_mode(self): # if the category sets a custom sort order, that wins, this # is required for top-rated and whats-new - if (self.state.category and + if (self.state.category and self.state.category.sortmode != SortMethods.BY_ALPHABET): return self.state.category.sortmode # searches are always by ranking unless the user decided differently - if (self._is_in_search_mode() and + if (self._is_in_search_mode() and not self.app_view.user_defined_sort_method): return SortMethods.BY_SEARCH_RANKING # use the appview combo @@ -520,15 +519,15 @@ def is_category_view_showing(self): " stub implementation " pass - + def is_applist_view_showing(self): " stub implementation " pass - + def is_app_details_view_showing(self): " stub implementation " pass - + def get_current_app(self): " stub implementation " pass diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/unused__channelpane.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/unused__channelpane.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/unused__channelpane.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/unused__channelpane.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,313 +0,0 @@ -# Copyright (C) 2010 Canonical -# -# Authors: -# Michael Vogt -# Gary Lasker -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import gettext -from gi.repository import Gtk -import logging -import os -import sys -import xapian -from gi.repository import GObject - -from gettext import gettext as _ - -from softwarecenter.backend import get_install_backend -from softwarecenter.distro import get_distro -from softwarecenter.enums import NavButtons, NonAppVisibility -from softwarecenter.paths import XAPIAN_BASE_PATH -from softwarepane import SoftwarePane -from softwarecenter.ui.gtk3.views.appview import AppViewFilter -import softwarecenter.ui.gtk3.dialogs as dialogs - -LOG = logging.getLogger(__name__) - -class ChannelPane(SoftwarePane): - """Widget that represents the channel pane for display of - individual channels (PPAs, partner repositories, etc.) - in software-center. - It contains a search entry and navigation buttons. - """ - - (PAGE_APPLIST, - PAGE_APP_DETAILS, - PAGE_APP_PURCHASE) = range(3) - - __gsignals__ = {'channel-pane-created':(GObject.SignalFlags.RUN_FIRST, - None, - ())} - - def __init__(self, cache, db, distro, icons, datadir): - # parent - SoftwarePane.__init__(self, cache, db, distro, icons, datadir, - show_ratings=False) - self.channel = None - self.apps_filter = None - self.apps_search_term = "" - self.current_appview_selection = None - self.distro = get_distro() - self.pane_name = _("Software Channels") - - def init_view(self): - if not self.view_initialized: - SoftwarePane.init_view(self) - self.notebook.append_page(self.box_app_list, Gtk.Label(label="channel")) - # details - self.notebook.append_page(self.scroll_details, Gtk.Label(label="details")) - # purchase view - self.notebook.append_page(self.purchase_view, Gtk.Label(label="purchase")) - # now we are initialized - self.emit("channel-pane-created") - self.show_all() - self.view_initialized = True - - def _show_channel_overview(self): - " helper that goes back to the overview page " - self.navigation_bar.remove_ids(NavButtons.DETAILS) - self.navigation_bar.remove_ids(NavButtons.PURCHASE) - self.notebook.set_current_page(self.PAGE_APPLIST) - self.searchentry.show() - - def _clear_search(self): - # remove the details and clear the search - self.searchentry.clear() - self.navigation_bar.remove_ids(NavButtons.SEARCH) - - def set_channel(self, channel): - """ - set the current software channel object for display in the channel pane - and set up the AppViewFilter if required - """ - self.channel = channel - # check to see if there is any section info that needs to be applied - # FIXME - #~ if channel._channel_color: - #~ self.section.set_color(channel._channel_color) - #~ if channel._channel_view_id: - #~ self.section.set_view_id(channel._channel_view_id) - #~ self.section_sync() - - # check if the channel needs to added - if channel.needs_adding and channel._source_entry: - dialog = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL, - type=Gtk.MessageType.QUESTION) - dialog.set_title("") - dialog.set_markup("%s" % _("Add channel")) - dialog.format_secondary_text(_("The selected channel is not yet " - "added. Do you want to add it now?")) - dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_ADD, Gtk.ResponseType.YES) - res = dialog.run() - dialog.destroy() - if res == Gtk.ResponseType.YES: - channel.needs_adding = False - backend = get_install_backend() - backend.add_sources_list_entry(channel._source_entry) - backend.emit("channels-changed", True) - backend.reload() - return - # normal operation - self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE - self.apps_filter = None - if self.channel.installed_only: - if self.apps_filter is None: - self.apps_filter = AppViewFilter(self.db, self.cache) - self.apps_filter.set_installed_only(True) - # switch to applist, this will clear searches too - self.display_list() - - def on_search_terms_changed(self, searchentry, terms): - """callback when the search entry widget changes""" - LOG.debug("on_search_terms_changed: '%s'" % terms) - self.apps_search_term = terms - if not self.apps_search_term: - self._clear_search() - self.refresh_apps() - self.notebook.set_current_page(self.PAGE_APPLIST) - - def on_db_reopen(self, db): - LOG.debug("got db-reopen signal") - self.refresh_apps() - self.app_details_view.refresh_app() - - def on_navigation_search(self, button, part): - """ callback when the navigation button with id 'search' is clicked""" - self.display_search() - - def on_navigation_list(self, button, part): - """callback when the navigation button with id 'list' is clicked""" - if not button.get_active(): - return - self.display_list() - - def display_list(self): - self._clear_search() - self._show_channel_overview() - # only emit something if the model is there - model = self.app_view.get_model() - if model: - self.emit("app-list-changed", len(model)) - - def on_navigation_details(self, button, part): - """callback when the navigation button with id 'details' is clicked""" - if not button.get_active(): - return - self.display_details() - - def display_details(self): - self.navigation_bar.remove_ids(NavButtons.PURCHASE) - self.notebook.set_current_page(self.PAGE_APP_DETAILS) - self.searchentry.hide() - self.action_bar.clear() - # we want to re-enable the buy button if this is an app for purchase - # FIXME: hacky, find a better approach - if self.app_details_view.pkg_statusbar.button.get_label() == _(u'Buy\u2026'): - self.app_details_view.pkg_statusbar.button.set_sensitive(True) - - def on_navigation_purchase(self, button, part): - """callback when the navigation button with id 'purchase' is clicked""" - if not button.get_active(): - return - self.display_purchase() - - def display_purchase(self): - self.notebook.set_current_page(self.PAGE_APP_PURCHASE) - self.searchentry.hide() - self.action_bar.clear() - - def on_application_selected(self, appview, app): - """callback when an app is selected""" - LOG.debug("on_application_selected: '%s'" % app) - self.current_appview_selection = app - - def display_search(self): - self.navigation_bar.remove_ids(NavButtons.DETAILS) - self.navigation_bar.remove_ids(NavButtons.PURCHASE) - self.notebook.set_current_page(self.PAGE_APPLIST) - model = self.app_view.get_model() - if model: - length = len(self.app_view.get_model()) - self.emit("app-list-changed", length) - self.searchentry.show() - - def get_status_text(self): - """return user readable status text suitable for a status bar""" - # no status text in the details page - if self.notebook.get_current_page() == self.PAGE_APP_DETAILS: - return "" - # otherwise, show status based on search or not - model = self.app_view.get_model() - if not model: - return "" - length = len(self.app_view.get_model()) - if self.channel.installed_only: - if len(self.searchentry.get_text()) > 0: - return gettext.ngettext("%(amount)s matching item", - "%(amount)s matching items", - length) % { 'amount' : length, } - else: - return gettext.ngettext("%(amount)s item installed", - "%(amount)s items installed", - length) % { 'amount' : length, } - else: - if len(self.searchentry.get_text()) > 0: - return gettext.ngettext("%(amount)s matching item", - "%(amount)s matching items", - length) % { 'amount' : length, } - else: - return gettext.ngettext("%(amount)s item available", - "%(amount)s items available", - length) % { 'amount' : length, } - - def get_current_app(self): - """return the current active application object applicable - to the context""" - return self.current_appview_selection - - def is_category_view_showing(self): - # there is no category view in the channel pane - return False - - def is_applist_view_showing(self): - """Return True if we are in the applist view """ - return self.notebook.get_current_page() == self.PAGE_APPLIST - - def is_app_details_view_showing(self): - """Return True if we are in the app_details view """ - return self.notebook.get_current_page() == self.PAGE_APP_DETAILS - -if __name__ == "__main__": - #logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) > 1: - datadir = sys.argv[1] - elif os.path.exists("./data"): - datadir = "./data" - else: - datadir = "/usr/share/software-center" - - from softwarecenter.ui.gtk3.utils import get_sc_icon_theme - icons = get_sc_icon_theme(datadir) - - from softwarecenter.db.database import StoreDatabase - from softwarecenter.db.pkginfo import get_pkg_info - cache = get_pkg_info() - cache.open() - - # xapian - xapian_base_path = XAPIAN_BASE_PATH - pathname = os.path.join(xapian_base_path, "xapian") - try: - db = StoreDatabase(pathname, cache) - db.open() - except xapian.DatabaseOpeningError: - # Couldn't use that folder as a database - # This may be because we are in a bzr checkout and that - # folder is empty. If the folder is empty, and we can find the - # script that does population, populate a database in it. - if os.path.isdir(pathname) and not os.listdir(pathname): - from softwarecenter.db.update import rebuild_database - logging.info("building local database") - rebuild_database(pathname) - db = StoreDatabase(pathname, cache) - db.open() - except xapian.DatabaseCorruptError as e: - logging.exception("xapian open failed") - dialogs.error(None, - _("Sorry, can not open the software database"), - _("Please re-install the 'software-center' " - "package.")) - # FIXME: force rebuild by providing a dbus service for this - sys.exit(1) - - - import softwarecenter.distro - distro = softwarecenter.distro.get_distro() - - w = ChannelPane(cache, db, distro, icons, datadir) - w.show() - - win = Gtk.Window() - win.add(w) - w.init_view() - win.set_size_request(400, 600) - win.show_all() - win.connect("destroy", Gtk.main_quit) - - Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/panes/viewswitcher.py software-center-5.1.13/softwarecenter/ui/gtk3/panes/viewswitcher.py --- software-center-5.1.12/softwarecenter/ui/gtk3/panes/viewswitcher.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/panes/viewswitcher.py 2012-03-19 19:43:39.000000000 +0000 @@ -38,6 +38,8 @@ _last_button = None + + class ViewSwitcher(Gtk.Box): ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR @@ -45,6 +47,7 @@ def __init__(self, view_manager, datadir, db, cache, icons): # boring stuff self.view_manager = view_manager + def on_view_changed(widget, view_id): self.view_buttons[view_id].set_active(True) self.view_manager.connect('view-changed', on_view_changed) @@ -70,7 +73,6 @@ self._prev_item = None # track the previous active menu-item self._handlers = [] - # order is important here! # first, the availablepane items icon = SymbolicIcon("available") @@ -112,22 +114,19 @@ else: self.stop_icon_animation() pending_btn = self.view_buttons[ViewPages.PENDING] - from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager + from softwarecenter.ui.gtk3.session.viewmanager import ( + get_viewmanager, + ) vm = get_viewmanager() if vm.get_active_view() == 'view-page-pending': vm.nav_back() vm.clear_forward_history() pending_btn.set_visible(False) - return def start_icon_animation(self): - # the pending button ProgressImage is special, see: - # softwarecenter/ui/gtk3/widgets/animatedimage.py self.view_buttons[ViewPages.PENDING].image.start() def stop_icon_animation(self): - # the pending button ProgressImage is special, see: - # softwarecenter/ui/gtk3/widgets/animatedimage.py self.view_buttons[ViewPages.PENDING].image.stop() def notify_icon_of_pending_count(self, count): @@ -135,9 +134,8 @@ image.set_transaction_count(count) def on_transaction_finished(self, backend, result): - if result.success: + if result.success: self.on_channels_changed() - return def on_section_sel_clicked(self, button, event, view_id): # mvo: this check causes bug LP: #828675 @@ -166,7 +164,6 @@ self._prev_view = view_id GObject.idle_add(config_view) - return def on_get_available_channels(self, popup): return self.build_channel_list(popup, ViewPages.AVAILABLE) @@ -179,7 +176,6 @@ # setting popup to None will cause a rebuild of the popup # menu the next time the selector is clicked sel.popup = None - return def append_section(self, view_id, label, icon): btn = SectionSelector(label, icon, self.ICON_SIZE) @@ -196,7 +192,8 @@ # to the group, toggled & clicked gets emitted... causing # all panes to fully initialise on USC startup, which is # undesirable! - btn.connect("button-release-event", self.on_section_sel_clicked, view_id) + btn.connect("button-release-event", self.on_section_sel_clicked, + view_id) return btn def append_channel_selector(self, section_btn, view_id, build_func): @@ -206,7 +203,8 @@ self.pack_start(sel, False, False, 0) return sel - def append_section_with_channel_sel(self, view_id, label, icon, build_func): + def append_section_with_channel_sel(self, view_id, label, icon, + build_func): btn = self.append_section(view_id, label, icon) btn.draw_hint_has_channel_selector = True sel = self.append_channel_selector(btn, view_id, build_func) @@ -227,15 +225,16 @@ for i, channel in enumerate(channels): # only calling it with a explicit new() makes it a really # empty one, otherwise the following error is raised: - # """Attempting to add a widget with type GtkBox to a + # """Attempting to add a widget with type GtkBox to a # GtkCheckMenuItem, but as a GtkBin subclass a - # GtkCheckMenuItem can only contain one widget at a time; + # GtkCheckMenuItem can only contain one widget at a time; # it already contains a widget of type GtkAccelLabel """ item = Gtk.MenuItem.new() label = Gtk.Label.new(channel.display_name) - image = Gtk.Image.new_from_icon_name(channel.icon, Gtk.IconSize.MENU) + image = Gtk.Image.new_from_icon_name(channel.icon, + Gtk.IconSize.MENU) box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, StockEms.SMALL) box.pack_start(image, False, False, 0) @@ -252,8 +251,7 @@ view_id ) ) - popup.attach(item, 0, 1, i, i+1) - return + popup.attach(item, 0, 1, i, i + 1) def on_channel_selected(self, item, event, channel, view_id): vm = self.view_manager @@ -279,7 +277,6 @@ return False GObject.idle_add(config_view) - return def get_test_window_viewswitcher(): @@ -305,7 +302,7 @@ scroll.add_with_viewport(view) win.add(box) - win.set_size_request(400,200) + win.set_size_request(400, 200) win.connect("destroy", Gtk.main_quit) win.show_all() return win @@ -317,5 +314,4 @@ softwarecenter.paths.datadir = "./data" win = get_test_window_viewswitcher() - Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/review_gui_helper.py software-center-5.1.13/softwarecenter/ui/gtk3/review_gui_helper.py --- software-center-5.1.12/softwarecenter/ui/gtk3/review_gui_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/review_gui_helper.py 2012-03-20 08:00:09.000000000 +0000 @@ -30,20 +30,17 @@ import time import threading -# py3 +# py3 try: from urllib.request import urlopen - urlopen # pyflakes + urlopen # pyflakes from queue import Queue - Queue # pyflakes + Queue # pyflakes except ImportError: # py2 fallbacks from urllib import urlopen from Queue import Queue - - - from gettext import gettext as _ from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend @@ -51,7 +48,7 @@ import piston_mini_client from softwarecenter.paths import SOFTWARE_CENTER_CONFIG_DIR -from softwarecenter.enums import Icons +from softwarecenter.enums import Icons, SOFTWARE_CENTER_NAME_KEYRING from softwarecenter.config import get_config from softwarecenter.distro import get_distro, get_current_arch from softwarecenter.backend.login_sso import get_sso_backend @@ -69,40 +66,42 @@ # get current distro and set default server root distro = get_distro() -SERVER_ROOT=distro.REVIEWS_SERVER +SERVER_ROOT = distro.REVIEWS_SERVER # server status URL -SERVER_STATUS_URL = SERVER_ROOT+"/server-status/" +SERVER_STATUS_URL = SERVER_ROOT + "/server-status/" + class UserCancelException(Exception): """ user pressed cancel """ pass -TRANSMIT_STATE_NONE="transmit-state-none" -TRANSMIT_STATE_INPROGRESS="transmit-state-inprogress" -TRANSMIT_STATE_DONE="transmit-state-done" -TRANSMIT_STATE_ERROR="transmit-state-error" +TRANSMIT_STATE_NONE = "transmit-state-none" +TRANSMIT_STATE_INPROGRESS = "transmit-state-inprogress" +TRANSMIT_STATE_DONE = "transmit-state-done" +TRANSMIT_STATE_ERROR = "transmit-state-error" + class GRatingsAndReviews(GObject.GObject): """ Access ratings&reviews API as a gobject """ __gsignals__ = { # send when a transmit is started - "transmit-start" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, ), - ), + "transmit-start": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, ), + ), # send when a transmit was successful - "transmit-success" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, ), - ), + "transmit-success": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, ), + ), # send when a transmit failed - "transmit-failure" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, str), - ), + "transmit-failure": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, str), + ), } def __init__(self, token): @@ -110,39 +109,48 @@ # piston worker thread self.worker_thread = Worker(token) self.worker_thread.start() - GObject.timeout_add(500, + GObject.timeout_add(500, self._check_thread_status, None) + def submit_review(self, review): self.emit("transmit-start", review) self.worker_thread.pending_reviews.put(review) + def report_abuse(self, review_id, summary, text): self.emit("transmit-start", review_id) self.worker_thread.pending_reports.put((int(review_id), summary, text)) + def submit_usefulness(self, review_id, is_useful): self.emit("transmit-start", review_id) self.worker_thread.pending_usefulness.put((int(review_id), is_useful)) + def modify_review(self, review_id, review): self.emit("transmit-start", review_id) self.worker_thread.pending_modify.put((int(review_id), review)) + def delete_review(self, review_id): self.emit("transmit-start", review_id) self.worker_thread.pending_delete.put(int(review_id)) + def server_status(self): self.worker_thread.pending_server_status() + def shutdown(self): self.worker_thread.shutdown() + # internal def _check_thread_status(self, data): if self.worker_thread._transmit_state == TRANSMIT_STATE_DONE: self.emit("transmit-success", "") self.worker_thread._transmit_state = TRANSMIT_STATE_NONE elif self.worker_thread._transmit_state == TRANSMIT_STATE_ERROR: - self.emit("transmit-failure", "", + self.emit("transmit-failure", "", self.worker_thread._transmit_error_str) self.worker_thread._transmit_state = TRANSMIT_STATE_NONE return True + class Worker(threading.Thread): def __init__(self, token): @@ -161,9 +169,8 @@ self._transmit_error_str = "" self.display_name = "No display name" auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], - token["token_secret"], - token["consumer_key"], - token["consumer_secret"]) + token["token_secret"], token["consumer_key"], + token["consumer_secret"]) # change default server to the SSL one distro = get_distro() service_root = distro.REVIEWS_SERVER @@ -229,7 +236,7 @@ review_text = review['review_text'] rating = review['rating'] try: - res = self.rnrclient.modify_review(review_id=review_id, + res = self.rnrclient.modify_review(review_id=review_id, summary=summary, review_text=review_text, rating=rating) @@ -241,7 +248,7 @@ self._write_exception_html_log_if_needed(e) self._transmit_state = TRANSMIT_STATE_ERROR self._transmit_error_str = err_str - self.pending_modify.task_done() + self.pending_modify.task_done() def _submit_delete_if_pending(self): """ the actual deletion """ @@ -281,9 +288,9 @@ self.pending_reports.task_done() def _write_exception_html_log_if_needed(self, e): - # write out a "oops.html" + # write out a "oops.html" if type(e) is piston_mini_client.APIError: - f=tempfile.NamedTemporaryFile( + f = tempfile.NamedTemporaryFile( prefix="sc_submit_oops_", suffix=".html", delete=False) # new piston-mini-client has only the body of the returned data # older just pushes it into a big string @@ -315,7 +322,7 @@ piston_review.language = review.language piston_review.arch_tag = get_current_arch() piston_review.origin = review.origin - piston_review.distroseries=distro.get_codename() + piston_review.distroseries = distro.get_codename() try: res = self.rnrclient.submit_review(review=piston_review) self._transmit_state = TRANSMIT_STATE_DONE @@ -329,7 +336,7 @@ self._transmit_state = TRANSMIT_STATE_ERROR self._transmit_error_str = err_str self.pending_reviews.task_done() - + def _get_error_messages(self, e): if type(e) is piston_mini_client.APIError: try: @@ -340,15 +347,16 @@ for err in errs: err_str = _("%s\n%s") % (err_str, err) except: - err_str = _("Unknown error communicating with server. Check your log " - "and consider raising a bug report if this problem persists") + err_str = _("Unknown error communicating with server. " + "Check your log and consider raising a bug report " + "if this problem persists") logging.warning(e) else: - err_str = _("Unknown error communicating with server. Check your log " - "and consider raising a bug report if this problem persists") + err_str = _("Unknown error communicating with server. Check " + "your log and consider raising a bug report if this " + "problem persists") logging.warning(e) return err_str - def verify_server_status(self): """ verify that the server we want to talk to can be reached @@ -369,11 +377,8 @@ def __init__(self, datadir, uifile): SimpleGtkbuilderApp.__init__( - self, os.path.join(datadir,"ui/gtk3", uifile), "software-center") + self, os.path.join(datadir, "ui/gtk3", uifile), "software-center") # generic data - # see bug #773214 for the rational - #self.appname = _("Ubuntu Software Center") - self.appname = "Ubuntu Software Center" self.token = None self.display_name = None self._login_successful = False @@ -384,24 +389,30 @@ self.config = get_config(configfile) # status spinner self.status_spinner = Gtk.Spinner() - self.status_spinner.set_size_request(32,32) - self.login_spinner_vbox.pack_start(self.status_spinner, False, False, 0) + self.status_spinner.set_size_request(32, 32) + self.login_spinner_vbox.pack_start(self.status_spinner, False, False, + 0) self.login_spinner_vbox.reorder_child(self.status_spinner, 0) self.status_spinner.show() #submit status spinner self.submit_spinner = Gtk.Spinner() - self.submit_spinner.set_size_request(*Gtk.icon_size_lookup(Gtk.IconSize.SMALL_TOOLBAR)[:2]) + self.submit_spinner.set_size_request(*Gtk.icon_size_lookup( + Gtk.IconSize.SMALL_TOOLBAR)[:2]) #submit error image self.submit_error_img = Gtk.Image() - self.submit_error_img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.SMALL_TOOLBAR) + self.submit_error_img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, + Gtk.IconSize.SMALL_TOOLBAR) #submit success image self.submit_success_img = Gtk.Image() - self.submit_success_img.set_from_stock(Gtk.STOCK_APPLY, Gtk.IconSize.SMALL_TOOLBAR) + self.submit_success_img.set_from_stock(Gtk.STOCK_APPLY, + Gtk.IconSize.SMALL_TOOLBAR) #submit warn image self.submit_warn_img = Gtk.Image() - self.submit_warn_img.set_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.SMALL_TOOLBAR) + self.submit_warn_img.set_from_stock(Gtk.STOCK_DIALOG_INFO, + Gtk.IconSize.SMALL_TOOLBAR) #label size to prevent image or spinner from resizing - self.label_transmit_status.set_size_request(-1, Gtk.icon_size_lookup(Gtk.IconSize.SMALL_TOOLBAR)[1]) + self.label_transmit_status.set_size_request(-1, + Gtk.icon_size_lookup(Gtk.IconSize.SMALL_TOOLBAR)[1]) def _get_parent_xid_for_login_window(self): # no get_xid() yet in gir world @@ -422,7 +433,7 @@ def _add_spellcheck_to_textview(self, textview): """ adds a spellchecker (if available) to the given Gtk.textview """ - return None + pass #~ try: #~ import gtkspell #~ # mvo: gtkspell.get_from_text_view() is broken, so we use this @@ -430,7 +441,7 @@ #~ # use (that is directly passed to pspell) #~ spell = gtkspell.Spell(textview, None) #~ except: - #~ return None + #~ return #~ return spell def login(self, show_register=True): @@ -439,7 +450,7 @@ help_text = _("To review software or to report abuse you need to " "sign in to a Ubuntu Single Sign-On account.") self.sso = get_sso_backend(login_window_xid, - self.appname, help_text) + SOFTWARE_CENTER_NAME_KEYRING, help_text) self.sso.connect("login-successful", self._maybe_login_successful) self.sso.connect("login-canceled", self._login_canceled) if show_register: @@ -449,16 +460,19 @@ def _login_canceled(self, sso): self.status_spinner.hide() - self.login_status_label.set_markup('%s' % _("Login was canceled")) + self.login_status_label.set_markup( + '%s' % _("Login was canceled")) def _maybe_login_successful(self, sso, oauth_result): - """ called after we have the token, then we go and figure out our name """ + """called after we have the token, then we go and figure out our + name + """ logging.debug("_maybe_login_successful") self.token = oauth_result self.ssoapi = get_ubuntu_sso_backend() self.ssoapi.connect("whoami", self._whoami_done) self.ssoapi.connect("error", self._whoami_error) - # this will automatically verify the token and retrigger login + # this will automatically verify the token and retrigger login # if its expired self.ssoapi.whoami() @@ -472,7 +486,8 @@ logging.error("whoami error '%s'" % e) # show error self.status_spinner.hide() - self.login_status_label.set_markup('%s' % _("Failed to log in")) + self.login_status_label.set_markup( + '%s' % _("Failed to log in")) def login_successful(self, display_name): """ callback when the login was successful """ @@ -495,23 +510,26 @@ def on_transmit_start(self, api, trans): self.button_post.set_sensitive(False) self.button_cancel.set_sensitive(False) - self._change_status("progress", _(self.SUBMIT_MESSAGE)) + self._change_status("progress", _(self.SUBMIT_MESSAGE)) def on_transmit_success(self, api, trans): self.api.shutdown() self.quit() def on_transmit_failure(self, api, trans, error): - self._change_status("fail", error) + self._change_status("fail", error) self.button_post.set_sensitive(True) self.button_cancel.set_sensitive(True) - - def _change_status(self, type, message): - """method to separate the updating of status icon/spinner and message in the submit review window, - takes a type (progress, fail, success, clear, warning) as a string and a message string then updates status area accordingly""" + + def _change_status(self, type, message): + """method to separate the updating of status icon/spinner and + message in the submit review window, takes a type (progress, + fail, success, clear, warning) as a string and a message + string then updates status area accordingly + """ self._clear_status_imagery() self.label_transmit_status.set_text("") - + if type == "progress": self.status_hbox.pack_start(self.submit_spinner, False, False, 0) self.status_hbox.reorder_child(self.submit_spinner, 0) @@ -523,10 +541,11 @@ self.status_hbox.reorder_child(self.submit_error_img, 0) self.submit_error_img.show() self.label_transmit_status.set_text(_(self.FAILURE_MESSAGE)) - self.error_textview.get_buffer().set_text(_(message)) + self.error_textview.get_buffer().set_text(_(message)) self.detail_expander.show() elif type == "success": - self.status_hbox.pack_start(self.submit_success_img, False, False, 0) + self.status_hbox.pack_start(self.submit_success_img, False, False, + 0) self.status_hbox.reorder_child(self.submit_success_img, 0) self.submit_success_img.show() self.label_transmit_status.set_text(message) @@ -540,7 +559,7 @@ self.detail_expander.hide() self.detail_expander.set_expanded(False) - #clears spinner or error image from dialog submission label + #clears spinner or error image from dialog submission label # before trying to display one or the other if self.submit_spinner.get_parent(): self.status_hbox.remove(self.submit_spinner) @@ -550,7 +569,6 @@ self.status_hbox.remove(self.submit_success_img) if self.submit_warn_img.get_window(): self.status_hbox.remove(self.submit_warn_img) - return class SubmitReviewsApp(BaseApp): @@ -558,8 +576,9 @@ STAR_SIZE = (32, 32) APP_ICON_SIZE = 48 - #character limits for text boxes and hurdles for indicator changes - # (overall field maximum, limit to display warning, limit to change colour) + #character limits for text boxes and hurdles for indicator changes + # (overall field maximum, limit to display warning, limit to change + # colour) SUMMARY_CHAR_LIMITS = (80, 60, 70) REVIEW_CHAR_LIMITS = (5000, 4900, 4950) #alert colours for character warning labels @@ -569,12 +588,18 @@ FAILURE_MESSAGE = _("Failed to submit review") SUCCESS_MESSAGE = _("Review submitted") - def __init__(self, app, version, iconname, origin, parent_xid, datadir, action="submit", review_id=0): + def __init__(self, app, version, iconname, origin, parent_xid, datadir, + action="submit", review_id=0): BaseApp.__init__(self, datadir, "submit_review.ui") self.datadir = datadir # legal fineprint, do not change without consulting a lawyer - msg = _("By submitting this review, you agree not to include anything defamatory, infringing, or illegal. Canonical may, at its discretion, publish your name and review in Ubuntu Software Center and elsewhere, and allow the software or content author to publish it too.") - self.label_legal_fineprint.set_markup('%s' % msg) + msg = _("By submitting this review, you agree not to include " + "anything defamatory, infringing, or illegal. Canonical " + "may, at its discretion, publish your name and review in " + "Ubuntu Software Center and elsewhere, and allow the " + "software or content author to publish it too.") + self.label_legal_fineprint.set_markup( + '%s' % msg) # additional icons come from app-install-data self.icons = Gtk.IconTheme.get_default() @@ -584,7 +609,7 @@ self.star_rating = ReactiveStar() alignment = Gtk.Alignment.new(0.0, 0.5, 1.0, 1.0) - alignment.set_padding(3,3,3,3) + alignment.set_padding(3, 3, 3, 3) alignment.add(self.star_rating) self.star_rating.set_size_as_pixel_value(36) self.star_caption = Gtk.Label() @@ -598,7 +623,7 @@ self.review_buffer = self.textview_review.get_buffer() self.detail_expander.hide() - + self.retrieve_api = RatingsAndReviewsAPI() # data @@ -625,10 +650,11 @@ self._displaying_cancel_confirmation = False self.submit_window.connect("key-press-event", self._on_key_press_event) - self.review_summary_entry.connect('changed', self._on_mandatory_text_entry_changed) + self.review_summary_entry.connect('changed', + self._on_mandatory_text_entry_changed) self.star_rating.connect('changed', self._on_mandatory_fields_changed) self.review_buffer.connect('changed', self._on_text_entry_changed) - + # gwibber stuff self.gwibber_combo = Gtk.ComboBoxText.new() #cells = self.gwibber_combo.get_cells() @@ -638,10 +664,11 @@ self.gwibber_helper = GwibberHelperMock() else: self.gwibber_helper = GwibberHelper() - - #get a dict with a saved gwibber_send (boolean) and gwibber account_id for persistent state + + # get a dict with a saved gwibber_send (boolean) and gwibber + # account_id for persistent state self.gwibber_prefs = self._get_gwibber_prefs() - + # gwibber stuff self._setup_gwibber_gui() @@ -651,10 +678,10 @@ elif self.action == "modify": self._init_modify() - def _init_submit(self): - self.submit_window.set_title(_("Review %s") % gettext.dgettext("app-install-data", self.app.name)) - + self.submit_window.set_title(_("Review %s") % + gettext.dgettext("app-install-data", self.app.name)) + def _init_modify(self): self._populate_review() self.submit_window.set_title(_("Modify Your %(appname)s Review") % { @@ -664,24 +691,28 @@ self.FAILURE_MESSAGE = _("Failed to edit review") self.SUCCESS_MESSAGE = _("Review updated") self._enable_or_disable_post_button() - + def _populate_review(self): try: - review_data = self.retrieve_api.get_review(review_id=self.review_id) - app = Application(appname=review_data.app_name, pkgname=review_data.package_name) + review_data = self.retrieve_api.get_review( + review_id=self.review_id) + app = Application(appname=review_data.app_name, + pkgname=review_data.package_name) self.app = app self.review_summary_entry.set_text(review_data.summary) self.star_rating.set_rating(review_data.rating) self.review_buffer.set_text(review_data.review_text) - #save original review field data, for comparison purposes when user makes changes to fields + # save original review field data, for comparison purposes when + # user makes changes to fields self.orig_summary_text = review_data.summary self.orig_star_rating = review_data.rating self.orig_review_text = review_data.review_text self.version = review_data.version self.origin = review_data.origin - return except piston_mini_client.APIError: - logging.warn('Unable to retrieve review id %s for editing. Exiting' % self.review_id) + logging.warn( + 'Unable to retrieve review id %s for editing. Exiting' % + self.review_id) self.quit(2) def _setup_details(self, widget, app, iconname, version, display_name): @@ -689,7 +720,8 @@ try: icon = self.icons.load_icon(iconname, self.APP_ICON_SIZE, 0) except: - icon = self.icons.load_icon(Icons.MISSING_APP, self.APP_ICON_SIZE, 0) + icon = self.icons.load_icon(Icons.MISSING_APP, self.APP_ICON_SIZE, + 0) self.review_appicon.set_from_pixbuf(icon) # title @@ -698,16 +730,17 @@ gettext.dgettext("app-install-data", app.name), version)) # review label - self.review_label.set_markup(_('Review by: %s') % display_name.encode('utf8')) + self.review_label.set_markup(_('Review by: %s') % + display_name.encode('utf8')) # review summary label self.review_summary_label.set_markup(_('Summary:')) - + #rating label self.rating_label.set_markup(_('Rating:')) #error detail link label - self.label_expander.set_markup('%s' % (_('Error Details'))) - return + self.label_expander.set_markup('%s' % + (_('Error Details'))) def _has_user_started_reviewing(self): summary_chars = self.review_summary_entry.get_text_length() @@ -716,15 +749,15 @@ def _on_mandatory_fields_changed(self, *args): self._enable_or_disable_post_button() - + def _on_mandatory_text_entry_changed(self, widget): self._check_summary_character_count() self._on_mandatory_fields_changed(widget) - + def _on_text_entry_changed(self, widget): self._check_review_character_count() self._on_mandatory_fields_changed(widget) - + def _enable_or_disable_post_button(self): summary_chars = self.review_summary_entry.get_text_length() review_chars = self.review_buffer.get_char_count() @@ -736,34 +769,42 @@ else: self.button_post.set_sensitive(False) self._change_status("clear", "") - - #set post button insensitive, if review being modified is the same as what is currently in the UI fields - #checks if 'original' review attributes exist to avoid exceptions when this method has been called prior to review being retrieved + + # set post button insensitive, if review being modified is the same + # as what is currently in the UI fields checks if 'original' review + # attributes exist to avoid exceptions when this method has been + # called prior to review being retrieved if self.action == 'modify' and hasattr(self, "orig_star_rating"): if self._modify_review_is_the_same(): self.button_post.set_sensitive(False) self._change_status("warning", _("Can't submit unmodified")) else: self._change_status("clear", "") - + def _modify_review_is_the_same(self): - '''checks if review fields are the same as the review being modified and returns true if so''' - - #perform an initial check on character counts to return False if any don't match, avoids doing unnecessary string comparisons - if (self.review_summary_entry.get_text_length() != len(self.orig_summary_text) or - self.review_buffer.get_char_count() != len(self.orig_review_text)): + """checks if review fields are the same as the review being modified + and returns True if so + """ + + # perform an initial check on character counts to return False if any + # don't match, avoids doing unnecessary string comparisons + if (self.review_summary_entry.get_text_length() != + len(self.orig_summary_text) or + self.review_buffer.get_char_count() != len(self.orig_review_text)): return False #compare rating if self.star_rating.get_rating() != self.orig_star_rating: return False #compare summary text - if self.review_summary_entry.get_text().decode('utf-8') != self.orig_summary_text: + if (self.review_summary_entry.get_text().decode('utf-8') != + self.orig_summary_text): return False #compare review text - if self.review_buffer.get_text( + if (self.review_buffer.get_text( self.review_buffer.get_start_iter(), self.review_buffer.get_end_iter(), - include_hidden_chars=False).decode('utf-8') != self.orig_review_text: + include_hidden_chars=False).decode('utf-8') != + self.orig_review_text): return False return True @@ -771,24 +812,24 @@ summary_chars = self.review_summary_entry.get_text_length() if summary_chars > self.SUMMARY_CHAR_LIMITS[1] - 1: markup = self._get_fade_colour_markup( - self.NORMAL_COLOUR, self.ERROR_COLOUR, - self.SUMMARY_CHAR_LIMITS[2], self.SUMMARY_CHAR_LIMITS[0], + self.NORMAL_COLOUR, self.ERROR_COLOUR, + self.SUMMARY_CHAR_LIMITS[2], self.SUMMARY_CHAR_LIMITS[0], summary_chars) self.summary_char_label.set_markup(markup) else: self.summary_char_label.set_text('') - - def _check_review_character_count(self): + + def _check_review_character_count(self): review_chars = self.review_buffer.get_char_count() if review_chars > self.REVIEW_CHAR_LIMITS[1] - 1: markup = self._get_fade_colour_markup( - self.NORMAL_COLOUR, self.ERROR_COLOUR, - self.REVIEW_CHAR_LIMITS[2], self.REVIEW_CHAR_LIMITS[0], - review_chars) + self.NORMAL_COLOUR, self.ERROR_COLOUR, + self.REVIEW_CHAR_LIMITS[2], self.REVIEW_CHAR_LIMITS[0], + review_chars) self.review_char_label.set_markup(markup) else: self.review_char_label.set_text('') - + def _get_fade_colour_markup(self, full_col, empty_col, cmin, cmax, curr): """takes two colours as well as a minimum and maximum value then fades one colour into the other based on the proportion of the @@ -797,37 +838,38 @@ """ markup = '%s' if curr > cmax: - return markup % (empty_col, str(cmax-curr)) - elif curr <= cmin: #saves division by 0 later if same value was passed as min and max - return markup % (full_col, str(cmax-curr)) + return markup % (empty_col, str(cmax - curr)) + elif curr <= cmin: # saves division by 0 later if cmin == cmax + return markup % (full_col, str(cmax - curr)) else: #distance between min and max values to fade colours scale = cmax - cmin #percentage to fade colour by, based on current number of chars percentage = (curr - cmin) / float(scale) - + full_rgb = self._convert_html_to_rgb(full_col) empty_rgb = self._convert_html_to_rgb(empty_col) - + #calc changes to each of the r g b values to get the faded colour red_change = full_rgb[0] - empty_rgb[0] green_change = full_rgb[1] - empty_rgb[1] blue_change = full_rgb[2] - empty_rgb[2] - + new_red = int(full_rgb[0] - (percentage * red_change)) new_green = int(full_rgb[1] - (percentage * green_change)) new_blue = int(full_rgb[2] - (percentage * blue_change)) - return_color = self._convert_rgb_to_html(new_red, new_green, new_blue) - - return markup % (return_color, str(cmax-curr)) - + return_color = self._convert_rgb_to_html(new_red, new_green, + new_blue) + + return markup % (return_color, str(cmax - curr)) + def _convert_html_to_rgb(self, html): r = html[0:2] g = html[2:4] b = html[4:6] - return (int(r,16), int(g,16), int(b,16)) - + return (int(r, 16), int(g, 16), int(b, 16)) + def _convert_rgb_to_html(self, r, g, b): return "%s%s%s" % ("%02X" % r, "%02X" % g, @@ -839,20 +881,20 @@ text_buffer = self.textview_review.get_buffer() review.text = text_buffer.get_text(text_buffer.get_start_iter(), text_buffer.get_end_iter(), - False) # include_hidden_chars + False) # include_hidden_chars review.summary = self.review_summary_entry.get_text() review.date = datetime.datetime.now() review.language = get_language() review.rating = int(self.star_rating.get_rating()) review.package_version = self.version review.origin = self.origin - + if self.action == "submit": self.api.submit_review(review) elif self.action == "modify": - changes = {'review_text':review.text, - 'summary':review.summary, - 'rating':review.rating} + changes = {'review_text': review.text, + 'summary': review.summary, + 'rating': review.rating} self.api.modify_review(self.review_id, changes) def login_successful(self, display_name): @@ -860,8 +902,7 @@ self._setup_details(self.submit_window, self.app, self.iconname, self.version, display_name) self.textview_review.grab_focus() - return - + def _setup_gwibber_gui(self): self.gwibber_accounts = self.gwibber_helper.accounts() list_length = len(self.gwibber_accounts) @@ -871,39 +912,41 @@ self._on_one_gwibber_account() else: self._on_multiple_gwibber_accounts() - + def _get_gwibber_prefs(self): - if self.config.has_option("reviews", "gwibber_send"): + if self.config.has_option("reviews", "gwibber_send"): send = self.config.getboolean("reviews", "gwibber_send") else: send = False - + if self.config.has_option("reviews", "account_id"): account_id = self.config.get("reviews", "account_id") else: account_id = False - - return { "gwibber_send" : send, - "account_id" : account_id } - + + return { + "gwibber_send": send, + "account_id": account_id + } + def _on_no_gwibber_accounts(self): self.gwibber_hbox.hide() self.gwibber_checkbutton.set_active(False) - + def _on_one_gwibber_account(self): account = self.gwibber_accounts[0] self.gwibber_hbox.show() self.gwibber_combo.hide() from softwarecenter.utils import utf8 - acct_text = utf8(_("Also post this review to %s (@%s)")) % ( - utf8(account['service'].capitalize()), utf8(account['username']) ) + acct_text = utf8(_("Also post this review to %s (@%s)")) % ( + utf8(account['service'].capitalize()), utf8(account['username'])) self.gwibber_checkbutton.set_label(acct_text) # simplifies on_transmit_successful later self.gwibber_combo.append_text(acct_text) self.gwibber_combo.set_active(0) # auto select submit via gwibber checkbutton if saved prefs say True self.gwibber_checkbutton.set_active(self.gwibber_prefs['gwibber_send']) - + def _on_multiple_gwibber_accounts(self): self.gwibber_hbox.show() self.gwibber_combo.show() @@ -911,14 +954,13 @@ # setup accounts combo self.gwibber_checkbutton.set_label(_("Also post this review to: ")) for account in self.gwibber_accounts: - acct_text = "%s (@%s)" % ( - account['service'].capitalize(), account['username'] ) + acct_text = "%s (@%s)" % ( + account['service'].capitalize(), account['username']) self.gwibber_combo.append_text(acct_text) # add "all" to both combo and accounts (the later is only pseudo) self.gwibber_combo.append_text(_("All my Gwibber services")) - self.gwibber_accounts.append({ "id" : "pseudo-sc-all", - }) + self.gwibber_accounts.append({"id": "pseudo-sc-all"}) # reapply preferences self.gwibber_checkbutton.set_active(self.gwibber_prefs['gwibber_send']) @@ -934,16 +976,17 @@ """ status_text = _("Posting to %s") % account['service'].capitalize() self._change_status("progress", status_text) - return self.gwibber_helper.send_message(msg,account['id']) + return self.gwibber_helper.send_message(msg, account['id']) def on_transmit_success(self, api, trans): - """on successful submission of a review, try to send to gwibber as well""" - self._run_gwibber_submits(api,trans) + """on successful submission of a review, try to send to gwibber as + well + """ + self._run_gwibber_submits(api, trans) def _on_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: self._confirm_cancellation() - return def _confirm_cancellation(self): if (self._has_user_started_reviewing() and not @@ -952,6 +995,7 @@ def do_cancel(widget): self.submit_window.destroy() self.quit() + def undo_cancel(widget): self._displaying_cancel_confirmation = False self.response_hbuttonbox.set_visible(True) @@ -971,23 +1015,27 @@ else: self.submit_window.destroy() self.quit() - return def _get_send_accounts(self, sel_index): - """return the account referenced by the passed in index, or all accounts - if the index of the combo points to the pseudo-sc-all string""" + """return the account referenced by the passed in index, or all + accounts if the index of the combo points to the pseudo-sc-all + string + """ if self.gwibber_accounts[sel_index]["id"] == "pseudo-sc-all": return self.gwibber_accounts else: return [self.gwibber_accounts[sel_index]] - - def _submit_to_gwibber(self,msg,send_accounts): + + def _submit_to_gwibber(self, msg, send_accounts): """for each send_account passed in, try to submit to gwibber - then return a list of accounts that failed to submit (empty list if all succeeded""" - #list of gwibber accounts that failed to submit, used later to allow selective re-send if user desires - failed_accounts=[] + then return a list of accounts that failed to submit (empty list + if all succeeded) + """ + #list of gwibber accounts that failed to submit, used later to allow + # selective re-send if user desires + failed_accounts = [] for account in send_accounts: - if account["id"]!= "pseudo-sc-all": + if account["id"] != "pseudo-sc-all": if not self._post_to_one_gwibber_account(msg, account): failed_accounts.append(account) return failed_accounts @@ -1002,10 +1050,11 @@ send_accounts = self._get_send_accounts(i) self._save_gwibber_state(True, self.gwibber_accounts[i]['id']) #tries to send to gwibber, and gets back any failed accounts - failed_accounts = self._submit_to_gwibber(msg,send_accounts) + failed_accounts = self._submit_to_gwibber(msg, send_accounts) if len(failed_accounts) > 0: gwibber_success = False - #FIXME: send an error string to this method instead of empty string + #FIXME: send an error string to this method instead of empty + # string self._on_gwibber_fail(api, trans, failed_accounts, "") else: # prevent _save_gwibber_state from overwriting the account id @@ -1024,39 +1073,43 @@ gwibber_success = True failed_accounts = [] msg = (self._gwibber_message()) - + for account in accounts: if not self._post_to_one_gwibber_account(msg, account): failed_accounts.append(account) gwibber_success = False - + if not gwibber_success: #FIXME: send an error string to this method instead of empty string self._on_gwibber_fail(api, trans, failed_accounts, "") else: self._success_status() BaseApp.on_transmit_success(self, api, trans) - + def _success_status(self): - """Updates status area to show success for 2 seconds then allows window to proceed""" + """Updates status area to show success for 2 seconds then allows + window to proceed + """ self._change_status("success", _(self.SUCCESS_MESSAGE)) while Gtk.events_pending(): Gtk.main_iteration() time.sleep(2) def _on_gwibber_fail(self, api, trans, failed_accounts, error): - self._change_status("fail",_("Problems posting to Gwibber")) + self._change_status("fail", _("Problems posting to Gwibber")) #list to hold service strings in the format: "Service (@username)" failed_services = [] for account in failed_accounts: - failed_services.append("%s (@%s)" % (account['service'].capitalize(), account['username'])) - - glade_dialog = SimpleGtkbuilderDialog(self.datadir, domain="software-center") + failed_services.append("%s (@%s)" % ( + account['service'].capitalize(), account['username'])) + + glade_dialog = SimpleGtkbuilderDialog(self.datadir, + domain="software-center") dialog = glade_dialog.dialog_gwibber_error dialog.set_transient_for(self.submit_window) # build the failure string # TRANSLATORS: the part in %s can either be a single entry - # like "facebook" or a string like + # like "facebook" or a string like # "factbook and twister" error_str = gettext.ngettext( "There was a problem posting this review to %s.", @@ -1071,40 +1124,38 @@ self._gwibber_retry_some(api, trans, failed_accounts) else: BaseApp.on_transmit_success(self, api, trans) - + def _save_gwibber_state(self, gwibber_send, account_id): if not self.config.has_section("reviews"): self.config.add_section("reviews") - + self.config.set("reviews", "gwibber_send", str(gwibber_send)) if account_id: self.config.set("reviews", "account_id", account_id) - + self.config.write() - return - - + def _gwibber_message(self, max_len=140): """ build a gwibber message of max_len""" def _gwibber_message_string_from_data(appname, rating, summary, link): """ helper so that we do not duplicate the "reviewed..." string """ return _("reviewed %(appname)s in Ubuntu: %(rating)s " "%(summary)s %(link)s") % { - 'appname' : appname, - 'rating' : rating, - 'summary' : summary, - 'link' : link } - + 'appname': appname, + 'rating': rating, + 'summary': summary, + 'link': link} + rating = self.star_rating.get_rating() rating_string = '' - + #fill star ratings for string - for i in range(1,6): + for i in range(1, 6): if i <= rating: rating_string = rating_string + u"\u2605" else: rating_string = rating_string + u"\u2606" - + review_summary_text = self.review_summary_entry.get_text() # FIXME: currently the link is not useful (at all) for most # people not runnig ubuntu @@ -1112,22 +1163,24 @@ app_link = "" gwib_msg = _gwibber_message_string_from_data( self.app.name, rating_string, review_summary_text, app_link) - + #check char count and ellipsize review summary if larger than 140 chars if len(gwib_msg) > max_len: - chars_to_reduce = len(gwib_msg) - (max_len-1) + chars_to_reduce = len(gwib_msg) - (max_len - 1) new_char_count = len(review_summary_text) - chars_to_reduce - review_summary_text = review_summary_text[:new_char_count] + u"\u2026" + review_summary_text = (review_summary_text[:new_char_count] + + u"\u2026") gwib_msg = _gwibber_message_string_from_data( self.app.name, rating_string, review_summary_text, app_link) - + return gwib_msg + class ReportReviewApp(BaseApp): """ report a given application or package """ APP_ICON_SIZE = 48 - + SUBMIT_MESSAGE = _(u"Sending report\u2026") FAILURE_MESSAGE = _("Failed to submit report") @@ -1157,33 +1210,38 @@ self.submit_window.set_position(Gtk.WindowPosition.MOUSE) # simple APIs ftw! self.combobox_report_summary = Gtk.ComboBoxText.new() - self.report_body_vbox.pack_start(self.combobox_report_summary, False, False, 0) + self.report_body_vbox.pack_start(self.combobox_report_summary, False, + False, 0) self.report_body_vbox.reorder_child(self.combobox_report_summary, 2) self.combobox_report_summary.show() - for term in [ _(u"Please make a selection\u2026"), + for term in [_(u"Please make a selection\u2026"), # TRANSLATORS: The following is one entry in a combobox that is - # located directly beneath a label asking 'Why is this review inappropriate?'. + # located directly beneath a label asking 'Why is this review + # inappropriate?'. # This text refers to a possible reason for why the corresponding # review is being flagged as inappropriate. - _("Offensive language"), + _("Offensive language"), # TRANSLATORS: The following is one entry in a combobox that is - # located directly beneath a label asking 'Why is this review inappropriate?'. + # located directly beneath a label asking 'Why is this review + # inappropriate?'. # This text refers to a possible reason for why the corresponding # review is being flagged as inappropriate. - _("Infringes copyright"), + _("Infringes copyright"), # TRANSLATORS: The following is one entry in a combobox that is - # located directly beneath a label asking 'Why is this review inappropriate?'. + # located directly beneath a label asking 'Why is this review + # inappropriate?'. # This text refers to a possible reason for why the corresponding - # review is being flagged as inappropriate. - _("Contains inaccuracies"), + # review is being flagged as inappropriate. + _("Contains inaccuracies"), # TRANSLATORS: The following is one entry in a combobox that is - # located directly beneath a label asking 'Why is this review inappropriate?'. + # located directly beneath a label asking 'Why is this review + # inappropriate?'. # This text refers to a possible reason for why the corresponding # review is being flagged as inappropriate. - _("Other") ]: + _("Other")]: self.combobox_report_summary.append_text(term) self.combobox_report_summary.set_active(0) - + self.combobox_report_summary.connect( "changed", self._enable_or_disable_report_button) @@ -1200,12 +1258,12 @@ self.report_label.set_markup(_('Please give details:')) # review summary label - self.report_summary_label.set_markup(_('Why is this review inappropriate?')) - + self.report_summary_label.set_markup( + _('Why is this review inappropriate?')) + #error detail link label - self.label_expander.set_markup('%s' % (_('Error Details'))) - - return + self.label_expander.set_markup('%s' + % (_('Error Details'))) def on_button_post_clicked(self, button): logging.debug("report_abuse ok button") @@ -1225,7 +1283,7 @@ class SubmitUsefulnessApp(BaseApp): SUBMIT_MESSAGE = _(u"Sending usefulness\u2026") - + def __init__(self, review_id, parent_xid, is_useful, datadir): BaseApp.__init__(self, datadir, "submit_usefulness.ui") # data @@ -1243,7 +1301,7 @@ logging.debug("submit usefulness") self.main_notebook.set_current_page(1) self.api.submit_usefulness(self.review_id, self.is_useful) - + def on_transmit_failure(self, api, trans, error): logging.warn("exiting - error: %s" % error) self.api.shutdown() @@ -1254,21 +1312,22 @@ # stub ui that can be useful for testing def run(self): self.login() - - # override UI update methods from BaseApp to prevent them + + # override UI update methods from BaseApp to prevent them # causing errors if called when UI is hidden def _clear_status_imagery(self): pass - + def _change_status(self, type, message): pass + class DeleteReviewApp(BaseApp): SUBMIT_MESSAGE = _(u"Deleting review\u2026") FAILURE_MESSAGE = _("Failed to delete review") - + def __init__(self, review_id, parent_xid, datadir): - # uses same UI as submit usefulness because + # uses same UI as submit usefulness because # (a) it isn't shown and (b) it's similar in usage BaseApp.__init__(self, datadir, "submit_usefulness.ui") # data @@ -1284,8 +1343,8 @@ def login_successful(self, display_name): logging.debug("delete review") self.main_notebook.set_current_page(1) - self.api.delete_review(self.review_id) - + self.api.delete_review(self.review_id) + def on_transmit_failure(self, api, trans, error): logging.warn("exiting - error: %s" % error) self.api.shutdown() @@ -1296,11 +1355,11 @@ # stub ui that can be useful for testing def run(self): self.login() - - # override UI update methods from BaseApp to prevent them + + # override UI update methods from BaseApp to prevent them # causing errors if called when UI is hidden def _clear_status_imagery(self): pass - + def _change_status(self, type, message): pass diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/session/appmanager.py software-center-5.1.13/softwarecenter/ui/gtk3/session/appmanager.py --- software-center-5.1.12/softwarecenter/ui/gtk3/session/appmanager.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/session/appmanager.py 2012-03-15 09:05:38.000000000 +0000 @@ -31,10 +31,14 @@ from softwarecenter.distro import get_current_arch, get_distro from softwarecenter.i18n import get_language from softwarecenter.ui.gtk3.dialogs import dependency_dialogs -from softwarecenter.backend.transactionswatcher import TransactionFinishedResult +from softwarecenter.backend.transactionswatcher import ( + TransactionFinishedResult, +) import softwarecenter.paths _appmanager = None # the global AppManager instance + + def get_appmanager(): """ get a existing appmanager instance (or None if none is created yet) """ return _appmanager @@ -43,10 +47,10 @@ class ApplicationManager(GObject.GObject): __gsignals__ = { - "purchase-requested" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, str, str,) - ), + "purchase-requested": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, str, str,) + ), } def __init__(self, db, backend, icons): @@ -72,25 +76,26 @@ if action is "remove", must check if other dependencies have to be removed as well and show a dialog in that case """ - #~ LOG.debug("on_application_action_requested: '%s' %s" % (app, action)) + #~ LOG.debug("on_application_action_requested: '%s' %s" + #~ % (app, action)) appdetails = app.get_details(self.db) if action == AppActions.REMOVE: if not dependency_dialogs.confirm_remove( None, self.datadir, app, self.db, self.icons): - # craft an instance of TransactionFinishedResult to send with the - # transaction-stopped signal + # craft an instance of TransactionFinishedResult to send + # with the transaction-stopped signal result = TransactionFinishedResult(None, False) result.pkgname = app.pkgname self.backend.emit("transaction-stopped", result) return elif action == AppActions.INSTALL: - # If we are installing a package, check for dependencies that will + # If we are installing a package, check for dependencies that will # also be removed and show a dialog for confirmation # generic removal text (fixing LP bug #554319) if not dependency_dialogs.confirm_install( None, self.datadir, app, self.db, self.icons): - # craft an instance of TransactionFinishedResult to send with the - # transaction-stopped signal + # craft an instance of TransactionFinishedResult to send + # with the transaction-stopped signal result = TransactionFinishedResult(None, False) result.pkgname = app.pkgname self.backend.emit("transaction-stopped", result) @@ -100,8 +105,9 @@ if (action == AppActions.UPGRADE and app.request and isinstance(app, DebFileApplication)): action = AppActions.INSTALL - - # action_func is one of: "install", "remove", "upgrade", "apply_changes" + + # action_func is one of: + # "install", "remove", "upgrade", "apply_changes" action_func = getattr(self.backend, action) if action == AppActions.INSTALL: # the package.deb path name is in the request @@ -117,7 +123,8 @@ addons_install=addons_install, addons_remove=addons_remove) #~ else: - #~ LOG.error("Not a valid action in AptdaemonBackend: '%s'" % action) + #~ LOG.error("Not a valid action in AptdaemonBackend: '%s'" % + #~ action) # public interface def reload(self): @@ -149,10 +156,10 @@ lang = get_language() appdetails = app.get_details(self.db) url = self.distro.PURCHASE_APP_URL % ( - lang, self.distro.get_codename(), urlencode( - {'archive_id' : appdetails.ppaname, - 'arch' : get_current_arch(),} - ) + lang, self.distro.get_codename(), urlencode({ + 'archive_id': appdetails.ppaname, + 'arch': get_current_arch() + }) ) self.emit("purchase-requested", app, appdetails.icon, url) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/session/navhistory.py software-center-5.1.13/softwarecenter/ui/gtk3/session/navhistory.py --- software-center-5.1.12/softwarecenter/ui/gtk3/session/navhistory.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/session/navhistory.py 2012-03-15 09:05:38.000000000 +0000 @@ -20,7 +20,8 @@ import logging -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + class NavigationHistory(object): """ @@ -34,7 +35,6 @@ self.in_replay_history_mode = False self._nav_back_set_sensitive(False) self._nav_forward_set_sensitive(False) - return def append(self, nav_item): """ @@ -81,7 +81,7 @@ if self.back_forward.left.has_focus(): self.back_forward.right.grab_focus() self._nav_back_set_sensitive(False) - + def clear_forward_history(self): """ clear the forward history and set the corresponding forward @@ -100,7 +100,7 @@ self.stack.reset() self._nav_back_set_sensitive(False) self._nav_forward_set_sensitive(False) - + def _nav_back_set_sensitive(self, is_sensitive): self.back_forward.left.set_sensitive(is_sensitive) #~ self.navhistory_back_action.set_sensitive(is_sensitive) @@ -121,7 +121,6 @@ self.page = page self.view_state = view_state self.callback = callback - return def __str__(self): facet = self.pane.pane_name.replace(' ', '')[:6] @@ -154,7 +153,7 @@ self.stack = [] self.cursor = 0 - if not options or not options.display_navlog: + if not options or not options.display_navlog: return import softwarecenter.ui.gtk3.widgets.navlog as navlog @@ -186,7 +185,8 @@ last = self[-1] last_vs = last.view_state item_vs = item.view_state - # FIXME: This is getting messy, this stuff should be cleaned up somehow... + # FIXME: This is getting messy, this stuff should be cleaned up + # somehow... # hacky: special case, check for subsequent searches # if subsequent search, update previous item_vs.search_term # to current. @@ -212,11 +212,12 @@ if len(self) == 1: self.history._nav_back_set_sensitive(False) self.history._nav_forward_set_sensitive(False) - LOG.debug("also remove 'spurious' list navigation item (Bug #854047)") + LOG.debug("also remove 'spurious' list navigation item " + "(Bug #854047)") return False elif (item.page != AvailablePane.Pages.LIST and - last.page == AvailablePane.Pages.LIST and + last.page == AvailablePane.Pages.LIST and not item_vs.search_term and last_vs.search_term): LOG.debug("search has been cleared. ignoring history item") if len(self) > 1: @@ -224,7 +225,8 @@ if len(self) == 1: self.history._nav_back_set_sensitive(False) self.history._nav_forward_set_sensitive(False) - LOG.debug("also remove 'spurious' list navigation item (Bug #854047)") + LOG.debug("also remove 'spurious' list navigation item " + "(Bug #854047)") return False if item.__str__() == last.__str__(): @@ -234,19 +236,18 @@ def append(self, item): if not self._isok(item): - self.cursor = len(self.stack)-1 + self.cursor = len(self.stack) - 1 LOG.debug('A:%s' % repr(self)) #~ print ('A:%s' % repr(self)) return if len(self.stack) + 1 > self.max_length: self.stack.pop(1) self.stack.append(item) - self.cursor = len(self.stack)-1 + self.cursor = len(self.stack) - 1 LOG.debug('A:%s' % repr(self)) #~ print ('A:%s' % repr(self)) if hasattr(self, "navlog"): self.navlog.log.notify_append(item) - return def step_back(self): did_step = False @@ -263,11 +264,11 @@ def step_forward(self): did_step = False - if self.cursor < len(self.stack)-1: + if self.cursor < len(self.stack) - 1: self.cursor += 1 did_step = True else: - self.cursor = len(self.stack)-1 + self.cursor = len(self.stack) - 1 LOG.debug('B:%s' % repr(self)) #~ print ('B:%s' % repr(self)) if hasattr(self, "navlog") and did_step: @@ -280,7 +281,7 @@ self.navlog.log.notify_clear_forward_items() def at_end(self): - return self.cursor == len(self.stack)-1 + return self.cursor == len(self.stack) - 1 def at_start(self): return self.cursor == 0 diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/session/viewmanager.py software-center-5.1.13/softwarecenter/ui/gtk3/session/viewmanager.py --- software-center-5.1.12/softwarecenter/ui/gtk3/session/viewmanager.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/session/viewmanager.py 2012-03-15 09:05:38.000000000 +0000 @@ -22,17 +22,21 @@ from softwarecenter.ui.gtk3.widgets.backforward import BackForwardButton from softwarecenter.ui.gtk3.widgets.searchentry import SearchEntry -_viewmanager = None # the global Viewmanager instance + +_viewmanager = None # the global Viewmanager instance + + def get_viewmanager(): return _viewmanager + class ViewManager(GObject.GObject): __gsignals__ = { - "view-changed" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), + "view-changed": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), } def __init__(self, notebook_view, options=None): @@ -69,30 +73,27 @@ pane = self.get_current_view_widget() if hasattr(pane, "on_search_terms_changed"): pane.on_search_terms_changed(widget, new_text) - return def on_nav_back_clicked(self, widget): pane = self.get_current_view_widget() if hasattr(pane, "on_nav_back_clicked"): pane.on_nav_back_clicked(widget) - return def on_nav_forward_clicked(self, widget): pane = self.get_current_view_widget() if hasattr(pane, "on_nav_forward_clicked"): pane.on_nav_forward_clicked(widget) - return - + def on_search_entry_key_press_event(self, widget, event): - + pane = self.get_current_view_widget() if hasattr(pane, "on_search_entry_key_press_event"): pane.on_search_entry_key_press_event(event) - return def register(self, pane, view_id): page_id = self.notebook_view.append_page( - pane, Gtk.Label.new("View %s" % view_id)) # label is for debugging only + pane, + Gtk.Label.new("View %s" % view_id)) # label is for debugging only self.all_views[view_id] = page_id self.view_to_pane[view_id] = pane @@ -115,7 +116,7 @@ def set_active_view(self, view_id): # no views yet - if not self.all_views: + if not self.all_views: return # if the view switches, ensure that the global spinner is hidden self.spinner.hide() @@ -135,7 +136,7 @@ view_widget.state.search_term): self.search_entry.set_text_with_no_signal( view_widget.state.search_term) - + # callback = view_widget.get_callback_for_page(view_page, # view_state) @@ -157,7 +158,7 @@ def get_notebook_page_from_view_id(self, view_id): return self.all_views[view_id] - + def get_view_widget(self, view_id): return self.view_to_pane.get(view_id, None) @@ -194,8 +195,9 @@ if self.get_current_view_widget() != pane: view_id = None for view_id, widget in self.view_to_pane.items(): - if widget == pane: break - + if widget == pane: + break + self.set_active_view(view_id) if (not pane.searchentry or @@ -209,14 +211,13 @@ else: self.search_entry.show() self.spinner.hide() - return def nav_back(self): self.navhistory.nav_back() def nav_forward(self): self.navhistory.nav_forward() - + def clear_forward_history(self): self.navhistory.clear_forward_history() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/shapes.py software-center-5.1.13/softwarecenter/ui/gtk3/shapes.py --- software-center-5.1.12/softwarecenter/ui/gtk3/shapes.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/shapes.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,273 +0,0 @@ -import gi -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk - - -from math import sin, cos - -# pi constants -from math import pi as PI -PI_OVER_180 = PI/180 - - -def radian(deg): - return PI_OVER_180 * deg - -# directional shapes - -class Shape: - - """ Base class for a Shape implementation. - - Currently implements a single method which is called - to layout the shape using cairo paths. It can also store the - 'direction' of the shape which should be on of the Gtk.TEXT_DIR - constants. Default 'direction' is Gtk.TextDirection.LTR. - - When implementing a Shape, there are two options available. - - If the Shape is direction dependent, the Shape MUST - implement <_layout_ltr> and <_layout_rtl> methods. - - If the Shape is not direction dependent, then it simply can - override the method. - - methods must take the following as arguments: - - cr : a CairoContext - x : x coordinate - y : y coordinate - w : width value - h : height value - - methods can then be passed Shape specific - keyword arguments which can be used as draw-time modifiers. - """ - - def __init__(self, direction): - self.direction = direction - return - - def layout(self, cr, x, y, w, h, *args, **kwargs): - if self.direction != Gtk.TextDirection.RTL: - self._layout_ltr(cr, x, y, w, h, *args, **kwargs) - else: - self._layout_rtl(cr, x, y, w, h, *args, **kwargs) - return - - -class ShapeRoundedRectangle(Shape): - - """ - RoundedRectangle lays out a rectangle with all four corners - rounded as specified at the layout call by the keyword argument: - - radius : an integer or float specifying the corner radius. - The radius must be > 0. - - RoundedRectangle is not direction sensitive. - """ - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - def layout(self, cr, x, y, w, h, *args, **kwargs): - r = kwargs['radius'] - - cr.new_sub_path() - cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180) - cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0) - cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180) - cr.arc(r+x, h-r, r, 90*PI_OVER_180, PI) - cr.close_path() - return - - -class ShapeRoundedRectangleIrregular(Shape): - - """ - RoundedRectangleIrregular lays out a rectangle for which each - individual corner can be rounded by a specific radius, - as specified at the layout call by the keyword argument: - - radii : a 4-tuple of ints or floats specifying the radius for - each corner. A value of 0 is acceptable as a radius, it - will result in a squared corner. - - RoundedRectangleIrregular is not direction sensitive. - """ - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - def layout(self, cr, x, y, w, h, *args, **kwargs): - nw, ne, se, sw = kwargs['radii'] - - cr.save() - cr.translate(x, y) - if nw: - cr.new_sub_path() - cr.arc(nw, nw, nw, PI, 270 * PI_OVER_180) - else: - cr.move_to(0, 0) - if ne: - cr.arc(w-ne, ne, ne, 270 * PI_OVER_180, 0) - else: - cr.rel_line_to(w-nw, 0) - if se: - cr.arc(w-se, h-se, se, 0, 90 * PI_OVER_180) - else: - cr.rel_line_to(0, h-ne) - if sw: - cr.arc(sw, h-sw, sw, 90 * PI_OVER_180, PI) - else: - cr.rel_line_to(-(w-se), 0) - - cr.close_path() - cr.restore() - return - - -class ShapeStartArrow(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs): - aw = kwargs['arrow_width'] - r = kwargs['radius'] - - cr.new_sub_path() - cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180) - # arrow head - cr.line_to(w-aw, y) - cr.line_to(w-x+1, (h+y)/2) - cr.line_to(w-aw, h) - cr.arc(r+x, h-r, r, 90*PI_OVER_180, PI) - cr.close_path() - return - - def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs): - aw = kwargs['arrow_width'] - r = kwargs['radius'] - - cr.new_sub_path() - cr.move_to(x, (h+y)/2) - cr.line_to(aw, y) - cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0) - cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180) - cr.line_to(aw, h) - cr.close_path() - return - - -class ShapeMidArrow(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs): - aw = kwargs['arrow_width'] - - cr.move_to(x, y) - # arrow head - cr.line_to(w-aw, y) - cr.line_to(w-x+1, (h+y)/2) - cr.line_to(w-aw, h) - cr.line_to(x, h) - cr.close_path() - return - - def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs): - aw = kwargs['arrow_width'] - - cr.move_to(x, (h+y)/2) - cr.line_to(aw, y) - cr.line_to(w, y) - cr.line_to(w, h) - cr.line_to(aw, h) - cr.close_path() - return - - -class ShapeEndCap(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs): - r = kwargs['radius'] - aw = kwargs['arrow_width'] - - cr.move_to(x-1, y) - cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0) - cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180) - cr.line_to(x-1, h) - cr.line_to(x+aw, (h+y)/2) - cr.close_path() - return - - def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs): - r = kwargs['radius'] - - cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180) - cr.line_to(w, y) - cr.line_to(w, h) - cr.arc(r+x, h-r, r, 90*PI_OVER_180, PI) - cr.close_path() - return - - -class Circle(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - return - - @staticmethod - def layout(cr, x, y, w, h, *args, **kwargs): - cr.new_path() - - r = min(w, h)*0.5 - x += int((w-2*r)/2) - y += int((h-2*r)/2) - - cr.arc(r+x, r+y, r, 0, 360*PI_OVER_180) - cr.close_path() - return - - -class ShapeStar(Shape): - - def __init__(self, points, indent=0.61, direction=Gtk.TextDirection.LTR): - self.coords = self._calc_coords(points, 1-indent) - - def _calc_coords(self, points, indent): - coords = [] - step = radian(180.0/points) - - for i in range(2*points): - if i%2: - x = (sin(step*i)+1)*0.5 - y = (cos(step*i)+1)*0.5 - else: - x = (sin(step*i)*indent+1)*0.5 - y = (cos(step*i)*indent+1)*0.5 - - coords.append((x,y)) - return coords - - def layout(self, cr, x, y, w, h): - points = [ (sx_sy[0]*w+x,sx_sy[1]*h+y) for sx_sy in self.coords ] - cr.move_to(*points[0]) - - for p in points[1:]: - cr.line_to(*p) - - cr.close_path() - return diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py software-center-5.1.13/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py --- software-center-5.1.12/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py 2012-03-15 09:28:20.000000000 +0000 @@ -21,6 +21,7 @@ from gi.repository import Gtk + # based on SimpleGladeApp class SimpleGtkbuilderApp: @@ -54,4 +55,3 @@ after a program is finished by pressing Control-C. """ pass - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/utils.py software-center-5.1.13/softwarecenter/ui/gtk3/utils.py --- software-center-5.1.12/softwarecenter/ui/gtk3/utils.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/utils.py 2012-03-20 08:00:09.000000000 +0000 @@ -28,10 +28,25 @@ LOG = logging.getLogger(__name__) +def get_parent(widget): + while widget.get_parent(): + widget = widget.get_parent() + return widget + + +def get_parent_xid(widget): + window = get_parent(widget).get_window() + #print dir(window) + if hasattr(window, 'xid'): + return window.xid + return 0 # cannot figure out how to get the xid of gdkwindow under pygi + + def point_in(rect, px, py): return (rect.x <= px <= rect.x + rect.width and rect.y <= py <= rect.y + rect.height) + def init_sc_css_provider(toplevel, settings, screen, datadir): context = toplevel.get_style_context() theme_name = settings.get_property("gtk-theme-name").lower() @@ -41,7 +56,7 @@ # style provider if toplevel._css_provider._theme_name == theme_name: return - else: # clean up old css provider if exixts + else: # clean up old css provider if exixts context.remove_provider_for_screen(screen, toplevel._css_provider) # munge css path for theme-name @@ -57,9 +72,10 @@ # check fallback exists as well... if not return None but warn # its not the end of the world if there is no fallback, just some # styling will be derived from the plain ol' Gtk theme - msg = "Could not set software-center CSS provider. File '%s' does not exist!" + msg = ("Could not set software-center CSS provider. File '%s' does " + "not exist!") LOG.warn(msg % css_path) - return None + return # things seem ok, now set the css provider for softwarecenter msg = "Softwarecenter style provider for %s Gtk theme: %s" @@ -73,18 +89,19 @@ context.add_provider_for_screen(screen, provider, 800) return css_path + def get_sc_icon_theme(datadir): # additional icons come from app-install-data icons = Gtk.IconTheme.get_default() icons.append_search_path(ICON_PATH) - icons.append_search_path(os.path.join(datadir,"icons")) - icons.append_search_path(os.path.join(datadir,"emblems")) + icons.append_search_path(os.path.join(datadir, "icons")) + icons.append_search_path(os.path.join(datadir, "emblems")) # uninstalled run if os.path.exists('./data/app-stream/icons'): icons.append_search_path('./data/app-stream/icons') - # add the humanity icon theme to the iconpath, as not all icon + # add the humanity icon theme to the iconpath, as not all icon # themes contain all the icons we need # this *shouldn't* lead to any performance regressions path = '/usr/share/icons/Humanity' @@ -107,4 +124,3 @@ os.makedirs(icon_cache_dir) icons.append_search_path(icon_cache_dir) return icons - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/appdetailsview.py software-center-5.1.13/softwarecenter/ui/gtk3/views/appdetailsview.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/appdetailsview.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/appdetailsview.py 2012-03-20 08:40:44.000000000 +0000 @@ -37,12 +37,11 @@ from softwarecenter.db.application import Application from softwarecenter.db import DebFileApplication from softwarecenter.backend.reviews import ReviewStats -#from softwarecenter.backend.zeitgeist_simple import zeitgeist_singleton -from softwarecenter.enums import (AppActions, +from softwarecenter.enums import (AppActions, PkgStates, - Icons, + Icons, SOFTWARE_CENTER_PKGNAME) -from softwarecenter.utils import (is_unity_running, +from softwarecenter.utils import (is_unity_running, upstream_version, get_exec_line_from_desktop, SimpleFileDownloader, @@ -82,10 +81,11 @@ from softwarecenter.backend import get_install_backend -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + class StatusBar(Gtk.Alignment): - """ Subclass of Gtk.Alignment that draws a small dash border + """ Subclass of Gtk.Alignment that draws a small dash border around the rectangle. """ @@ -102,7 +102,6 @@ self._bg = [1, 1, 1, 0.3] self.connect("style-updated", self.on_style_updated) - return def on_style_updated(self, widget): context = self.get_style_context() @@ -110,7 +109,6 @@ context = widget.get_style_context() border = context.get_border(Gtk.StateFlags.NORMAL) self._border_width = max(1, max(border.top, border.bottom)) - return def do_draw(self, cr): cr.save() @@ -118,7 +116,7 @@ width = self._border_width # fill bg - cr.rectangle(-width, 0, a.width+2*width, a.height) + cr.rectangle(-width, 0, a.width + 2 * width, a.height) cr.set_source_rgba(*self._bg) cr.fill_preserve() @@ -130,14 +128,15 @@ context.restore() Gdk.cairo_set_source_rgba(cr, bc) - cr.set_dash((width, 2*width), 1) - cr.set_line_width(2*width) + cr.set_dash((width, 2 * width), 1) + cr.set_line_width(2 * width) cr.stroke() cr.restore() - for child in self: + for child in self: self.propagate_draw(child, cr) + class WarningStatusBar(StatusBar): def __init__(self, view): @@ -153,6 +152,7 @@ # override _bg self._bg = [1, 1, 0, 0.3] + class PackageStatusBar(StatusBar): """ Package specific status bar that contains a state label, a action button and a progress bar. @@ -160,7 +160,7 @@ def __init__(self, view): StatusBar.__init__(self, view) - self.installed_icon = Gtk.Image.new_from_icon_name( + self.installed_icon = Gtk.Image.new_from_icon_name( Icons.INSTALLED_OVERLAY, Gtk.IconSize.DIALOG) self.label = Gtk.Label() self.label.set_line_wrap(True) @@ -242,20 +242,17 @@ app, addons_to_install, addons_to_remove) elif state == PkgStates.NEEDS_SOURCE: app_manager.enable_software_source(app) - return def set_label(self, label): m = '%s' % label self.label.set_markup(m) - return def get_label(self): return self.label.get_text() def set_button_label(self, label): self.button.set_label(label) - return - + def get_button_label(self): return self.button.get_label() @@ -263,14 +260,14 @@ # the currently forced archive_suite for the given app app_version = self.app_details.version # all available not-automatic (version, archive_suits) - not_automatic_suites = self.app_details.get_not_automatic_archive_versions() - # populat the combobox if - if not_automatic_suites: + not_auto_suites = self.app_details.get_not_automatic_archive_versions() + # populat the combobox if + if not_auto_suites: combo = self.combo_multiple_versions combo.disconnect_by_func(self._on_combo_multiple_versions_changed) model = self.combo_multiple_versions.get_model() model.clear() - for i, archive_suite in enumerate(not_automatic_suites): + for i, archive_suite in enumerate(not_auto_suites): # get the version, archive_suite ver, archive_suite = archive_suite # the string to display is something like: @@ -278,9 +275,9 @@ displayed_archive_suite = archive_suite if i == 0: displayed_archive_suite = _("default") - s = "v%s (%s)" % (upstream_version(ver), + s = "v%s (%s)" % (upstream_version(ver), displayed_archive_suite) - model.append( (s, archive_suite) ) + model.append((s, archive_suite)) if app_version == ver: self.combo_multiple_versions.set_active(i) # if nothing is found, set to default @@ -304,13 +301,11 @@ PkgStates.INSTALLING_PURCHASED, PkgStates.REMOVING, PkgStates.UPGRADING, + PkgStates.ERROR, AppActions.APPLY): self.show() elif state == PkgStates.NOT_FOUND: self.hide() - elif state == PkgStates.ERROR: - # error details are set below - self.show() else: # mvo: why do we override state here again? state = app_details.pkg_state @@ -321,9 +316,9 @@ self.progress.hide() self.installed_icon.hide() - # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add + # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add # Source/Update Now action so that all UI controls - # (menu item, applist view button and appdetails view button) + # (menu item, applist view button and appdetails view button) # are managed centrally: button text, button sensitivity, # and the associated callback. if state == PkgStates.INSTALLING: @@ -340,32 +335,34 @@ self.set_label(_(u'Upgrading\u2026')) self.button.set_sensitive(False) elif state == PkgStates.INSTALLED or state == PkgStates.REINSTALLABLE: - #special label only if the app being viewed is software centre itself + # special label only if the app being viewed is software centre + # itself self.installed_icon.show() - if app_details.pkgname== SOFTWARE_CENTER_PKGNAME: - self.set_label(_(u'Installed (you\u2019re using it right now)')) + if app_details.pkgname == SOFTWARE_CENTER_PKGNAME: + self.set_label( + _(u'Installed (you\u2019re using it right now)')) else: if app_details.purchase_date: - # purchase_date is a string, must first convert to + # purchase_date is a string, must first convert to # datetime.datetime pdate = self._convert_purchase_date_str_to_datetime( app_details.purchase_date) - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, - # please specify a format per your locale (if you prefer, - # %x can be used to provide a default locale-specific date + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, + # please specify a format per your locale (if you prefer, + # %x can be used to provide a default locale-specific date # representation) self.set_label(pdate.strftime(_('Purchased on %Y-%m-%d'))) elif app_details.installation_date: - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, - # please specify a format per your locale (if you prefer, - # %x can be used to provide a default locale-specific date + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, + # please specify a format per your locale (if you prefer, + # %x can be used to provide a default locale-specific date # representation) template = _('Installed on %Y-%m-%d') self.set_label(app_details.installation_date.strftime( template)) else: self.set_label(_('Installed')) - if state == PkgStates.REINSTALLABLE: # only deb files atm + if state == PkgStates.REINSTALLABLE: # only deb files atm self.set_button_label(_('Reinstall')) elif state == PkgStates.INSTALLED: self.set_button_label(_('Remove')) @@ -374,7 +371,7 @@ # get that info from the software-center-agent/payments # service. # NOTE: the currency string for this label is purposely not - # translatable when hardcoded, since it (currently) + # translatable when hardcoded, since it (currently) # won't vary based on locale and as such we don't want # it translated self.set_label("US$ %s" % app_details.price) @@ -389,11 +386,12 @@ PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES): - # purchase_date is a string, must first convert to datetime.datetime + # purchase_date is a string, must first convert to + # datetime.datetime pdate = self._convert_purchase_date_str_to_datetime( app_details.purchase_date) - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please - # specify a format per your locale (if you prefer, %x can be used + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please + # specify a format per your locale (if you prefer, %x can be used # to provide a default locale-specific date representation) label = pdate.strftime(_('Purchased on %Y-%m-%d')) self.set_button_label(_('Install')) @@ -408,7 +406,7 @@ elif state == PkgStates.UNINSTALLED: #special label only if the app being viewed is software centre # itself - if app_details.pkgname== SOFTWARE_CENTER_PKGNAME: + if app_details.pkgname == SOFTWARE_CENTER_PKGNAME: self.set_label(_(u'Removed (close it and it\u2019ll be gone)')) else: # TRANSLATORS: Free here means Gratis @@ -425,7 +423,7 @@ self.set_label(_(u'Changing Add-ons\u2026')) self.button.set_sensitive(False) elif state == PkgStates.UNKNOWN: - self.set_button_label("") + self.button.hide() self.set_label(_("Error")) elif state == PkgStates.ERROR: # this is used when the pkg can not be installed @@ -436,12 +434,12 @@ self.set_label(_("Error")) elif state == PkgStates.NOT_FOUND: # this is used when the pkg is not in the cache and there is no - # request we display the error in the summary field and hide the + # request we display the error in the summary field and hide the # rest - pass + self.button.hide() elif state == PkgStates.NEEDS_SOURCE: channelfile = self.app_details.channelfile - # it has a price and is not available + # it has a price and is not available if channelfile: self.set_button_label(_("Use This Source")) # check if it comes from a non-enabled component @@ -453,20 +451,26 @@ # components that are not enabled or that just # lack the "Packages" files (but are in sources.list) self.set_button_label(_("Update Now")) - if (self.app_details.warning and not self.app_details.error and - not state in (PkgStates.INSTALLING, PkgStates.INSTALLING_PURCHASED, - PkgStates.REMOVING, PkgStates.UPGRADING, AppActions.APPLY)): + + # this maybe a region or hw compatibility warning + if (self.app_details.warning and + not self.app_details.error and + not state in (PkgStates.INSTALLING, + PkgStates.INSTALLING_PURCHASED, + PkgStates.REMOVING, + PkgStates.UPGRADING, + AppActions.APPLY)): self.set_label(self.app_details.warning) sensitive = network_state_is_connected() self.button.set_sensitive(sensitive) - return - + def _convert_purchase_date_str_to_datetime(self, purchase_date): if purchase_date is not None: return datetime.datetime.strptime( purchase_date, "%Y-%m-%d %H:%M:%S") + class PackageInfo(Gtk.HBox): """ Box with labels for package specific information like version info """ @@ -485,14 +489,13 @@ self.a11y = self.get_accessible() self.connect('realize', self._on_realize) - return def _on_realize(self, widget): # key k = Gtk.Label() k.set_name("subtle-label") key_markup = '%s' - k.set_markup(key_markup % self.key) + k.set_markup(key_markup % self.key) k.set_alignment(1, 0) # determine max width of all keys @@ -516,22 +519,23 @@ self.set_property("can-focus", True) self.show_all() - return - + def set_width(self, width): - return + pass def set_value(self, value): self.value_label.set_markup(value) self.a11y.set_name(utf8(self.key) + ' ' + utf8(value)) + class PackageInfoHW(PackageInfo): - """ special version of packageinfo that uses the custom + """ special version of packageinfo that uses the custom HardwareRequirementsBox as the "label" """ def __init__(self, *args): super(PackageInfoHW, self).__init__(*args) self.value_label = HardwareRequirementsBox() + def set_value(self, value): self.value_label.set_hardware_requirements(value) @@ -553,7 +557,6 @@ self.checkbutton.pkgname = self.app.pkgname self.pack_start(self.checkbutton, False, False, 12) self.connect('realize', self._on_realize, icons, pkgname) - return def _on_realize(self, widget, icons, pkgname): # icon @@ -610,15 +613,15 @@ self.checkbutton.set_active(is_active) def set_width(self, width): - return + pass class AddonsTable(Gtk.VBox): """ Widget to display a table of addons. """ - __gsignals__ = {'table-built' : (GObject.SignalFlags.RUN_FIRST, - None, - ()), + __gsignals__ = {'table-built': (GObject.SignalFlags.RUN_FIRST, + None, + ()), } def __init__(self, addons_manager): @@ -687,18 +690,18 @@ This will become visible if any addons are scheduled for install or remove. """ - + def __init__(self, addons_manager): StatusBar.__init__(self, addons_manager.view) self.addons_manager = addons_manager self.cache = self.addons_manager.view.cache self.applying = False - - # TRANSLATORS: Free here means Gratis + + # TRANSLATORS: Free here means Gratis self.label_price = Gtk.Label(_("Free")) self.hbox.pack_start(self.label_price, False, False, 0) - + self.hbuttonbox = Gtk.HButtonBox() self.hbuttonbox.set_layout(Gtk.ButtonBoxStyle.END) self.button_apply = Gtk.Button(_("Apply Changes")) @@ -711,9 +714,9 @@ def configure(self): LOG.debug("AddonsStatusBarConfigure") - # FIXME: addons are not always free, but the old implementation + # FIXME: addons are not always free, but the old implementation # of determining price was buggy - if (not self.addons_manager.addons_to_install and + if (not self.addons_manager.addons_to_install and not self.addons_manager.addons_to_remove): self.hide() else: @@ -721,7 +724,7 @@ self.button_apply.set_sensitive(sensitive) self.button_cancel.set_sensitive(sensitive) self.show_all() - + def _on_button_apply_clicked(self, button): self.applying = True self.button_apply.set_sensitive(False) @@ -791,27 +794,28 @@ _asset_cache = {} + + class AppDetailsView(Viewport): """ The view that shows the application details """ # the size of the icon on the left side - APP_ICON_SIZE = 96 # Gtk.IconSize.DIALOG ? + APP_ICON_SIZE = 96 # Gtk.IconSize.DIALOG ? # art stuff BACKGROUND = os.path.join(softwarecenter.paths.datadir, "ui/gtk3/art/itemview-background.png") - - # need to include application-request-action here also since we are + # need to include application-request-action here also since we are # multiple-inheriting - __gsignals__ = {'selected':(GObject.SignalFlags.RUN_FIRST, - None, - (GObject.TYPE_PYOBJECT,)), - "different-application-selected" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, )), + __gsignals__ = {'selected': (GObject.SignalFlags.RUN_FIRST, + None, + (GObject.TYPE_PYOBJECT,)), + "different-application-selected": ( + GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, )), } - def __init__(self, db, distro, icons, cache, datadir): Viewport.__init__(self) # basic stuff @@ -856,7 +860,7 @@ self.backend.connect( "transaction-finished", self._on_transaction_finished) self.backend.connect( - "transaction-progress-changed", + "transaction-progress-changed", self._on_transaction_progress_changed) # network status watcher @@ -874,7 +878,7 @@ self._reviews_server_language = None self._reviews_relaxed = False self._review_sort_method = 0 - + # switches self._show_overlay = False @@ -883,14 +887,14 @@ self._cache_art_assets() self.connect('realize', self._on_realize) self.loaded = True - return def _on_destroy(self, widget): self.cache.disconnect_by_func(self._on_cache_ready) def _cache_art_assets(self): global _asset_cache - if _asset_cache: return _asset_cache + if _asset_cache: + return _asset_cache assets = _asset_cache # cache the bg pattern surf = cairo.ImageSurface.create_from_png(self.BACKGROUND) @@ -911,8 +915,7 @@ self.addon_view.addons_set_sensitive(sensitive) self.addons_statusbar.button_apply.set_sensitive(sensitive) self.addons_statusbar.button_cancel.set_sensitive(sensitive) - return - + def _update_recommendations(self, pkgname): self.recommended_for_app_panel.set_pkgname(pkgname) @@ -920,10 +923,9 @@ def _update_reviews(self, app_details): self.reviews.clear() self._check_for_reviews() - return def _check_for_reviews(self): - # self.app may be undefined on network state change events + # self.app may be undefined on network state change events # (LP: #742635) if not self.app: return @@ -936,7 +938,7 @@ def _on_more_reviews_clicked(self, uilist): self._reviews_server_page += 1 self._do_load_reviews() - + def _on_review_sort_method_changed(self, uilist, sort_method): self._reviews_server_page = 1 self._reviews_relaxed = False @@ -954,7 +956,7 @@ def _do_load_reviews(self): self.reviews.show_spinner_with_message(_('Checking for reviews...')) self.review_loader.get_reviews( - self.app, self._reviews_ready_callback, + self.app, self._reviews_ready_callback, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, @@ -965,11 +967,10 @@ self.reviews.replace_review(review) elif action == 'remove': self.reviews.remove_review(review) - return def _update_review_stats_widget(self, stats): if stats: - # ensure that the review UI knows about the stats + # ensure that the review UI knows about the stats self.reviews.global_review_stats = stats # update the widget self.review_stats_widget.set_avg_rating(stats.ratings_average) @@ -988,7 +989,7 @@ """ LOG.debug("_review_ready_callback: %s" % app) # avoid possible race if we already moved to a new app when - # the reviews become ready + # the reviews become ready # (we only check for pkgname currently to avoid breaking on # software-center totem) if self.app.pkgname != app.pkgname: @@ -1000,7 +1001,7 @@ self._reviews_relaxed = True self._reviews_server_page = 1 self.review_loader.get_reviews( - self.app, self._reviews_ready_callback, + self.app, self._reviews_ready_callback, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, @@ -1020,15 +1021,16 @@ if stats.ratings_total == 0: stats.ratings_average = 0 else: - stats.ratings_average = sum([x.rating for x in reviews_data]) / float(stats.ratings_total) + stats.ratings_average = (sum([x.rating for x in reviews_data]) + / float(stats.ratings_total)) # update UI self._update_review_stats_widget(stats) # update global stats cache as well self.review_loader.update_review_stats(app, stats) - + if my_votes: self.reviews.update_useful_votes(my_votes) - + if action: self._review_update_single(action, single_review) else: @@ -1042,7 +1044,7 @@ # We retrieved data, but nothing new. Keep going. self._reviews_server_page += 1 self.review_loader.get_reviews( - self.app, self._reviews_ready_callback, + self.app, self._reviews_ready_callback, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, @@ -1069,13 +1071,13 @@ def on_weblive_exception(self, weblive, exception): """ When receiving an exception, reset button and show the error """ - error(None,"WebLive exception", exception) + error(None, "WebLive exception", exception) self.test_drive.set_label(_("Test drive")) self.test_drive.set_sensitive(True) def on_weblive_warning(self, weblive, warning): """ When receiving a warning, just show it """ - error(None,"WebLive warning", warning) + error(None, "WebLive warning", warning) def on_test_drive_clicked(self, button): if self.weblive.client.state == "disconnected": @@ -1090,20 +1092,20 @@ if len(servers) == 0: error(None, - "No available server", + "No available server", "There is currently no available WebLive server " "for this application.\nPlease try again later.") elif len(servers) == 1: self.weblive.create_automatic_user_and_run_session( - session=cmd,serverid=servers[0].name) + session=cmd, serverid=servers[0].name) button.set_sensitive(False) else: d = ShowWebLiveServerChooserDialog(servers, self.app.pkgname) - serverid=None + serverid = None if d.run() == Gtk.ResponseType.OK: for server in d.servers_vbox: if server.get_active(): - serverid=server.serverid + serverid = server.serverid break d.destroy() @@ -1122,13 +1124,11 @@ self.info_vb.reorder_child(table, 0) if not table.get_property('visible'): table.show_all() - return def _on_realize(self, widget): self.addons_statusbar.hide() # the install button gets initial focus self.pkg_statusbar.button.grab_focus() - return def _on_homepage_clicked(self, label, link): import webbrowser @@ -1160,17 +1160,16 @@ self.title.set_selectable(True) self.subtitle.set_line_wrap(True) self.subtitle.set_selectable(True) - vb_inner=Gtk.VBox() + vb_inner = Gtk.VBox() vb_inner.pack_start(self.title, False, False, 0) vb_inner.pack_start(self.subtitle, False, False, 0) - # usage - #~ self.usage = mkit.BubbleLabel() - #~ vb_inner.pack_start(self.usage, True, True, 0) - - # star rating widget + # star rating box/widget self.review_stats_widget = StarRatingsWidget() + self.review_stats = Gtk.HBox() vb_inner.pack_start( + self.review_stats, False, False, 0) + self.review_stats.pack_start( self.review_stats_widget, False, False, StockEms.SMALL) #~ vb_inner.set_property("can-focus", True) @@ -1189,10 +1188,11 @@ # installed where widget self.installed_where_hbox = Gtk.HBox() self.installed_where_hbox.set_spacing(6) - self.installed_where_hbox.a11y = self.installed_where_hbox.get_accessible() + hbox_a11y = self.installed_where_hbox.get_accessible() + self.installed_where_hbox.a11y = hbox_a11y vb.pack_start(self.installed_where_hbox, False, False, 0) - # the hbox that hold the description on the left and the screenshot + # the hbox that hold the description on the left and the screenshot # thumbnail on the right body_hb = Gtk.HBox() body_hb.set_spacing(12) @@ -1240,7 +1240,7 @@ self.homepage_btn = Gtk.Label() self.homepage_btn.set_name("subtle-label") self.homepage_btn.connect('activate-link', self._on_homepage_clicked) - + # support site self.support_btn = Gtk.Label() self.support_btn.set_name("subtle-label") @@ -1275,10 +1275,12 @@ self.datadir, None, self.cache, self.db, self.icons, None) self.recommended_for_app_panel = RecommendationsPanelDetails(catview) self.recommended_for_app_panel.connect( - "application-activated", self._on_recommended_application_activated) + "application-activated", + self._on_recommended_application_activated) self.recommended_for_app_panel.show_all() - self.info_vb.pack_start(self.recommended_for_app_panel, False, False, 0) - + self.info_vb.pack_start(self.recommended_for_app_panel, False, + False, 0) + # package info self.info_keys = [] @@ -1310,14 +1312,15 @@ # reviews cascade self.reviews.connect("new-review", self._on_review_new) self.reviews.connect("report-abuse", self._on_review_report_abuse) - self.reviews.connect("submit-usefulness", self._on_review_submit_usefulness) + self.reviews.connect("submit-usefulness", + self._on_review_submit_usefulness) self.reviews.connect("modify-review", self._on_review_modify) self.reviews.connect("delete-review", self._on_review_delete) self.reviews.connect("more-reviews-clicked", self._on_more_reviews_clicked) - self.reviews.connect("different-review-language-clicked", + self.reviews.connect("different-review-language-clicked", self._on_reviews_in_different_language_clicked) - self.reviews.connect("review-sort-changed", + self.reviews.connect("review-sort-changed", self._on_review_sort_method_changed) if get_distro().REVIEWS_SERVER: vb.pack_start(self.reviews, False, False, 0) @@ -1325,15 +1328,14 @@ self.show_all() # signals! - self.connect('size-allocate', lambda w,a: w.queue_draw()) - return + self.connect('size-allocate', lambda w, a: w.queue_draw()) def _on_recommended_application_activated(self, recwidget, app): self.emit("different-application-selected", app) def _on_review_new(self, button): self._review_write_new() - + def _on_review_modify(self, button, review_id): self._review_modify(review_id) @@ -1347,7 +1349,7 @@ self._review_submit_usefulness(review_id, is_useful) def _update_title_markup(self, appname, summary): - # make title font size fixed as they should look good compared to the + # make title font size fixed as they should look good compared to the # icon (also fixed). font_size = em(1.6) * Pango.SCALE markup = '%s' @@ -1355,7 +1357,6 @@ self.title.set_markup(markup) self.title.a11y.set_name(appname + '. ' + summary) self.subtitle.set_markup(summary) - return def _update_app_icon(self, app_details): pb = self._get_icon_as_pixbuf(app_details) @@ -1363,13 +1364,12 @@ # self._show_overlay = app_details.pkg_state == PkgStates.INSTALLED w, h = pb.get_width(), pb.get_height() - tw = self.APP_ICON_SIZE # target width + tw = self.APP_ICON_SIZE # target width if pb.get_width() < tw: pb = pb.scale_simple(tw, tw, GdkPixbuf.InterpType.TILES) self.icon.set_from_pixbuf(pb) self.icon.set_size_request(self.APP_ICON_SIZE, self.APP_ICON_SIZE) - return def _update_layout_error_status(self, pkg_error): # if we have an error or if we need to enable a source @@ -1377,6 +1377,7 @@ if pkg_error: self.addon_view.hide() self.reviews.hide() + self.review_stats.hide() self.screenshot.hide() #~ self.info_header.hide() self.info_vb.hide() @@ -1385,12 +1386,12 @@ else: self.addon_view.show() self.reviews.show() + self.review_stats.show() self.screenshot.show() #~ self.info_header.show() self.info_vb.show() for hbar in self._hbars: hbar.show() - return def _update_app_description(self, app_details, appname): # format new app description @@ -1401,9 +1402,8 @@ # a11y for description self.desc.description.a11y.set_name(description) - return - def _update_description_footer_links(self, app_details): + def _update_description_footer_links(self, app_details): # show or hide the homepage button and set uri if homepage specified if app_details.website: self.homepage_btn.show() @@ -1420,7 +1420,6 @@ self.support_btn.set_tooltip_text(app_details.supportsite) else: self.support_btn.hide() - return def _update_app_video(self, app_details): self.videoplayer.uri = app_details.video_url @@ -1433,20 +1432,20 @@ # get screenshot urls and configure the ScreenshotView... if app_details.thumbnail and app_details.screenshot: self.screenshot.fetch_screenshots(app_details) - return def _update_weblive(self, app_details): - if self.weblive.client is None: return + if self.weblive.client is None: + return self.desktop_file = app_details.desktop_file # only enable test drive if we have a desktop file and exec line if (not self.weblive.ready or - not self.weblive.is_pkgname_available_on_server(app_details.pkgname) or + not self.weblive.is_pkgname_available_on_server( + app_details.pkgname) or not os.path.exists(self.desktop_file) or not get_exec_line_from_desktop(self.desktop_file)): self.test_drive.hide() else: self.test_drive.show() - return def _update_warning_bar(self, app_details): # generic error wins over HW issue @@ -1462,7 +1461,7 @@ app_details.hardware_requirements) if not app_details.region_requirements_satisfied: if len(s) > 0: - s += "\n"+REGION_WARNING_STRING + s += "\n" + REGION_WARNING_STRING else: s = REGION_WARNING_STRING self.pkg_warningbar.label.set_text(s) @@ -1494,7 +1493,6 @@ self.hardware_info.show() else: self.hardware_info.hide() - return def _update_addons(self, app_details): # refresh addons interface @@ -1511,7 +1509,6 @@ # Update addons state bar self.addons_statusbar.configure() - return def _update_all(self, app_details, skip_update_addons=False): # reset view to top left @@ -1537,9 +1534,6 @@ if not summary: summary = "" - # hide stuff - #~ self.usage.hide() - # depending on pkg install state set action labels self.pkg_statusbar.configure(app_details, app_details.pkg_state) @@ -1568,10 +1562,6 @@ # show where it is self._configure_where_is_it() - # async query zeitgeist and rnr - self._update_usage_counter() - return - def _update_minimal(self, app_details): self._update_app_icon(app_details) self._update_pkg_info_table(app_details) @@ -1583,12 +1573,11 @@ # # show where it is self._configure_where_is_it() - return def _add_where_is_it_commandline(self, pkgname): cmdfinder = CmdFinder(self.cache) cmds = cmdfinder.find_cmds_from_pkgname(pkgname) - if not cmds: + if not cmds: return vb = Gtk.VBox() vb.set_spacing(12) @@ -1604,8 +1593,8 @@ #~ title.set_size_request(self.get_allocation().width-24, -1) vb.pack_start(title, False, False, 0) cmds_str = ", ".join(cmds) - cmd_label = Gtk.Label(label= - '%s' % cmds_str) + cmd_label = Gtk.Label( + label='%s' % cmds_str) cmd_label.set_selectable(True) cmd_label.set_use_markup(True) cmd_label.set_alignment(0, 0.5) @@ -1650,9 +1639,10 @@ label_name.set_text(app_info.get_name()) self.installed_where_hbox.pack_start(label_name, False, False, 0) - if i+1 < len(where): - right_arrow = Gtk.Arrow.new(Gtk.ArrowType.RIGHT, Gtk.ShadowType.NONE) - self.installed_where_hbox.pack_start(right_arrow, + if i + 1 < len(where): + right_arrow = Gtk.Arrow.new(Gtk.ArrowType.RIGHT, + Gtk.ShadowType.NONE) + self.installed_where_hbox.pack_start(right_arrow, False, False, 0) # create our a11y text @@ -1694,7 +1684,7 @@ # see if we have the location if its installed if self.app_details.pkg_state == PkgStates.INSTALLED: # first try the desktop file from the DB, then see if - # there is a local desktop file with the same name as + # there is a local desktop file with the same name as # the package # try to show menu location if there is a desktop file, but # never show commandline programs for apps with a desktop file @@ -1705,7 +1695,6 @@ # if there is no desktop file, show commandline else: self._add_where_is_it_commandline(self.app_details.pkgname) - return # public API def show_app(self, app, force=False): @@ -1714,8 +1703,8 @@ LOG.debug("no app selected") return - same_app = (self.app and - self.app.pkgname and + same_app = (self.app and + self.app.pkgname and self.app.appname == app.appname and self.app.pkgname == app.pkgname) #print 'SameApp:', same_app @@ -1745,8 +1734,9 @@ # update all (but skip the addons calculation if this is a # DebFileApplication as this is not useful for this case and it # increases the view load time dramatically) + skip_update_addons = type(self.app) == DebFileApplication self._update_all(self.app_details, - skip_update_addons=(type(self.app)==DebFileApplication)) + skip_update_addons=skip_update_addons) # this is a bit silly, but without it and self.title being selectable # gtk will select the entire title (which looks ugly). this grab works @@ -1754,18 +1744,16 @@ self.pkg_statusbar.button.grab_focus() self.emit("selected", self.app) - return def refresh_app(self): self.show_app(self.app) - # common code def _review_write_new(self): if (not self.app or not self.app.pkgname in self.cache or not self.cache[self.app.pkgname].candidate): - dialogs.error(None, + dialogs.error(None, _("Version unknown"), _("The version of the application can not " "be detected. Entering a review is not " @@ -1778,7 +1766,7 @@ # FIXME: probably want to not display the ui if we can't review it if not origin: - dialogs.error(None, + dialogs.error(None, _("Origin unknown"), _("The origin of the application can not " "be detected. Entering a review is not " @@ -1787,7 +1775,8 @@ if pkg.installed: version = pkg.installed.version - # call the loader to do call out the right helper and collect the result + # call the loader to do call out the right helper and collect the + # result parent_xid = '' #parent_xid = get_parent_xid(self) self.reviews.new_review.disable() @@ -1796,7 +1785,7 @@ parent_xid, self.datadir, self._reviews_ready_callback, done_callback=self._submit_reviews_done_callback) - + def _review_report_abuse(self, review_id): parent_xid = '' #parent_xid = get_parent_xid(self) @@ -1809,7 +1798,7 @@ self.review_loader.spawn_submit_usefulness_ui( review_id, is_useful, parent_xid, self.datadir, self._reviews_ready_callback) - + def _review_modify(self, review_id): parent_xid = '' #parent_xid = get_parent_xid(self) @@ -1834,11 +1823,12 @@ state = self.pkg_statusbar.pkg_state # handle purchase: install purchased has multiple steps - if (state == PkgStates.INSTALLING_PURCHASED and + if (state == PkgStates.INSTALLING_PURCHASED and result and not result.pkgname): - self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING_PURCHASED) - elif (state == PkgStates.INSTALLING_PURCHASED and + self.pkg_statusbar.configure(self.app_details, + PkgStates.INSTALLING_PURCHASED) + elif (state == PkgStates.INSTALLING_PURCHASED and result and result.pkgname): self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) @@ -1846,7 +1836,8 @@ self.reviews.configure_reviews_ui() # normal states elif state == PkgStates.REMOVING: - self.pkg_statusbar.configure(self.app_details, PkgStates.UNINSTALLED) + self.pkg_statusbar.configure(self.app_details, + PkgStates.UNINSTALLED) elif state == PkgStates.INSTALLING: self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) elif state == PkgStates.UPGRADING: @@ -1862,15 +1853,16 @@ # reset the reviews UI now that we have installed the package self.reviews.configure_reviews_ui() elif state == PkgStates.UNINSTALLED: - self.pkg_statusbar.configure(self.app_details, PkgStates.UNINSTALLED) + self.pkg_statusbar.configure(self.app_details, + PkgStates.UNINSTALLED) self.adjustment_value = None - + if self.addons_statusbar.applying: self.addons_statusbar.applying = False return False - def _on_transaction_started(self, backend, pkgname, appname, trans_id, + def _on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): if self.addons_statusbar.applying: self.pkg_statusbar.configure(self.app_details, AppActions.APPLY) @@ -1879,32 +1871,32 @@ state = self.pkg_statusbar.pkg_state LOG.debug("_on_transaction_started %s" % state) if state == PkgStates.NEEDS_PURCHASE: - self.pkg_statusbar.configure(self.app_details, + self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING_PURCHASED) elif (state == PkgStates.UNINSTALLED or state == PkgStates.FORCE_VERSION): - self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING) + self.pkg_statusbar.configure(self.app_details, + PkgStates.INSTALLING) elif state == PkgStates.INSTALLED: self.pkg_statusbar.configure(self.app_details, PkgStates.REMOVING) elif state == PkgStates.UPGRADABLE: self.pkg_statusbar.configure(self.app_details, PkgStates.UPGRADING) elif state == PkgStates.REINSTALLABLE: - self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING) + self.pkg_statusbar.configure(self.app_details, + PkgStates.INSTALLING) # FIXME: is there a way to tell if we are installing/removing? # we will assume that it is being installed, but this means that # during removals we get the text "Installing.." - # self.pkg_statusbar.configure(self.app_details, PkgStates.REMOVING) - return + # self.pkg_statusbar.configure(self.app_details, + # PkgStates.REMOVING) def _on_transaction_stopped(self, backend, result): self.pkg_statusbar.progress.hide() self._update_interface_on_trans_ended(result) - return def _on_transaction_finished(self, backend, result): self.pkg_statusbar.progress.hide() self._update_interface_on_trans_ended(result) - return def _on_transaction_progress_changed(self, backend, pkgname, progress): if (self.app_details and @@ -1915,11 +1907,10 @@ self.pkg_statusbar.combo_multiple_versions.hide() self.pkg_statusbar.progress.show() if pkgname in backend.pending_transactions: - self.pkg_statusbar.progress.set_fraction(progress/100.0) + self.pkg_statusbar.progress.set_fraction(progress / 100.0) if progress >= 100: self.pkg_statusbar.progress.set_fraction(1) self.adjustment_value = self.get_vadjustment().get_value() - return def get_app_icon_details(self): """ helper for unity dbus support to provide details about the @@ -1941,7 +1932,7 @@ else: icon_size = pb.get_height() return icon_size - + def _get_app_icon_xy_position_on_screen(self): """ helper for unity dbus support to get the x,y position of the application icon as it is displayed on-screen. if the icon's @@ -1954,15 +1945,15 @@ parent = parent.get_parent() # get x, y relative to toplevel try: - (x,y) = self.icon.translate_coordinates(parent, 0, 0) + (x, y) = self.icon.translate_coordinates(parent, 0, 0) except Exception as e: LOG.warning("couldn't translate icon coordinates on-screen " "for unity dbus message: %s" % e) - return (0,0) + return (0, 0) # get toplevel window position (px, py) = parent.get_position() - return (px+x, py+y) - + return (px + x, py + y) + def _get_icon_as_pixbuf(self, app_details): if app_details.icon: if self.icons.has_icon(app_details.icon): @@ -1978,9 +1969,10 @@ LOG.debug("did not find the icon locally, must download it") def on_image_download_complete(downloader, image_file_path): - # when the download is complete, replace the icon in the + # when the download is complete, replace the icon in the # view with the downloaded one - logging.debug("_get_icon_as_pixbuf:image_downloaded() %s" % image_file_path) + logging.debug("_get_icon_as_pixbuf:image_downloaded() %s" % + image_file_path) try: pb = GdkPixbuf.Pixbuf.new_from_file(image_file_path) # fixes crash in testsuite if window is destroyed @@ -1989,15 +1981,17 @@ if self.icon.get_property("visible"): self.icon.set_from_pixbuf(pb) except Exception as e: - LOG.warning("couldn't load downloadable icon file '%s': %s" % (image_file_path, e)) - + LOG.warning( + "couldn't load downloadable icon file '%s': %s" % + (image_file_path, e)) + image_downloader = SimpleFileDownloader() image_downloader.connect( 'file-download-complete', on_image_download_complete) image_downloader.download_file( app_details.icon_url, app_details.cached_icon_file_path) return self.icons.load_icon(Icons.MISSING_APP, self.APP_ICON_SIZE, 0) - + def update_totalsize(self): if not self.totalsize_info.get_property('visible'): return False @@ -2013,7 +2007,7 @@ self.addons_manager.addons_to_remove, self.app.archive_suite) total_download_size, total_install_size = res - if res==(0,0) and type(self.app)==DebFileApplication: + if res == (0, 0) and type(self.app) == DebFileApplication: total_install_size = self.app_details.installed_size if total_download_size > 0: download_size = GLib.format_size(total_download_size) @@ -2032,43 +2026,14 @@ elif total_install_size < 0: remove_size = GLib.format_size(-total_install_size) label_string += _("%s to be freed") % (remove_size) - + self.totalsize_info.set_value(label_string or _("Unknown")) # self.totalsize_info.show_all() return False def set_section(self, section): self.section = section - return - - def _update_usage_counter(self): - """ try to get the usage counter from zeitgeist """ - def _zeitgeist_callback(counter): - LOG.debug("zeitgeist usage: %s" % counter) - if counter == 0: - # this probably means we just have no idea about it, - # so instead of saying "Used: never" we just return - # this can go away when zeitgeist captures more events - # --there are still cases when we really do want to hide this - self.usage.hide() - return - if counter <= 100: - label_string = gettext.ngettext("Used: one time", - "Used: %(amount)s times", - counter) % { 'amount' : counter, } - else: - label_string = _("Used: over 100 times") - self.usage.set_text('%s' % label_string) - self.usage.show() - - # try to get it - # FIXME - # try: - # zeitgeist_singleton.get_usage_counter( - # self.app_details.desktop_file, _zeitgeist_callback) - # except Exception, e: - # LOG.warning("could not update the usage counter: %s " % e) - # self.usage.hide() + def get_test_window_appdetails(): @@ -2082,7 +2047,7 @@ db = StoreDatabase(pathname, cache) db.open() - import softwarecenter.paths + import softwarecenter.paths datadir = softwarecenter.paths.datadir from softwarecenter.ui.gtk3.utils import get_sc_icon_theme @@ -2090,7 +2055,7 @@ import softwarecenter.distro distro = softwarecenter.distro.get_distro() - + # gui win = Gtk.Window() scroll = Gtk.ScrolledWindow() @@ -2118,7 +2083,7 @@ scroll.add(view) scroll.show() win.add(scroll) - win.set_size_request(600,800) + win.set_size_request(600, 800) win.show() win.connect('destroy', Gtk.main_quit) win.set_data("view", view) @@ -2132,7 +2097,7 @@ else: view.show_app(Application("Movie Player", "totem")) return True - + win = get_test_window_appdetails() # keep it spinning to test for re-draw issues and memleaks diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/appview.py software-center-5.1.13/softwarecenter/ui/gtk3/views/appview.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/appview.py 2012-03-08 15:15:30.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/appview.py 2012-03-15 09:05:38.000000000 +0000 @@ -29,7 +29,8 @@ from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.utils import ExecutionTime -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) + class AppView(Gtk.VBox): @@ -38,16 +39,16 @@ None, (GObject.TYPE_PYOBJECT, ), ), - "application-activated" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), - "application-selected" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), + "application-activated": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), + "application-selected": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), } - + (INSTALLED_MODE, AVAILABLE_MODE, DIFF_MODE) = range(3) _SORT_METHOD_INDEX = (SortMethods.BY_ALPHABET, @@ -97,14 +98,13 @@ self._handler = self.sort_methods_combobox.connect( "changed", self.on_sort_method_changed) - return #~ def on_draw(self, w, cr): #~ cr.set_source_rgb(1,1,1) #~ cr.paint() def _append_appcount(self, appcount, mode=AVAILABLE_MODE): -#~ +#~ #~ if mode == self.INSTALLED_MODE: #~ text = gettext.ngettext("%(amount)s item installed", #~ "%(amount)s items installed", @@ -112,12 +112,12 @@ #~ elif mode == self.DIFF_MODE: #~ text = gettext.ngettext("%(amount)s item", #~ "%(amount)s items", - #~ appcount) % { 'amount' : appcount, } + #~ appcount) % { 'amount' : appcount, } #~ else: #~ text = gettext.ngettext("%(amount)s item available", #~ "%(amount)s items available", #~ appcount) % { 'amount' : appcount, } -#~ +#~ #~ if not self.appcount: #~ self.appcount = Gtk.Label() #~ self.appcount.set_alignment(0.5, 0.5) @@ -127,13 +127,12 @@ #~ self.vbox.pack_start(self.appcount, False, False, 0) #~ self.appcount.set_text(text) #~ self.appcount.show() - return + pass def on_sort_method_changed(self, *args): self.user_defined_sort_method = True self.vadj = 0.0 self.emit("sort-method-changed", self.sort_methods_combobox) - return def _get_sort_methods_combobox(self): combo = Gtk.ComboBoxText.new() @@ -151,25 +150,21 @@ if self._get_combo_children() == 4: return self.sort_methods_combobox.append_text(_("By Relevance")) - return def _use_combobox_without_sort_by_search_ranking(self): if self._get_combo_children() == 3: return self.sort_methods_combobox.remove(self._SORT_BY_SEARCH_RANKING) self.set_sort_method_with_no_signal(self._SORT_BY_TOP_RATED) - return def set_sort_method_with_no_signal(self, sort_method): combo = self.sort_methods_combobox combo.handler_block(self._handler) combo.set_active(sort_method) combo.handler_unblock(self._handler) - return def set_allow_user_sorting(self, do_allow): self.sort_methods_combobox.set_visible(do_allow) - return def set_header_labels(self, first_line, second_line): if second_line: @@ -180,7 +175,6 @@ def set_model(self, model): self.tree_view.set_model(model) - return def display_matches(self, matches, is_search=False): # FIXME: installedpane handles display of the trees intimately, @@ -210,7 +204,6 @@ self.tree_view_scroll.get_vadjustment().set_lower(self.vadj) self.tree_view_scroll.get_vadjustment().set_value(self.vadj) - return def clear_model(self): return self.tree_view.clear_model() @@ -218,7 +211,7 @@ def get_sort_mode(self): active_index = self.sort_methods_combobox.get_active() return self._SORT_METHOD_INDEX[active_index] - + def get_app_icon_details(self): """ helper for unity dbus support to provide details about the application icon as it is displayed on-screen @@ -239,7 +232,7 @@ else: icon_size = pb.get_height() return icon_size - + def _get_app_icon_xy_position_on_screen(self): """ helper for unity dbus support to get the x,y position of the application icon as it is displayed on-screen @@ -251,16 +244,14 @@ # get toplevel window position (px, py) = parent.get_position() # and return the coordinate values - return (px+self.tree_view.selected_row_renderer.icon_x_offset, - py+self.tree_view.selected_row_renderer.icon_y_offset) - - - + return (px + self.tree_view.selected_row_renderer.icon_x_offset, + py + self.tree_view.selected_row_renderer.icon_y_offset) # ----------------------------------------------- testcode from softwarecenter.enums import NonAppVisibility + def get_query_from_search_entry(search_term): import xapian if not search_term: @@ -269,6 +260,7 @@ user_query = parser.parse_query(search_term) return user_query + def on_entry_changed(widget, data): def _work(): @@ -278,8 +270,8 @@ with ExecutionTime("total time"): with ExecutionTime("enquire.set_query()"): enquirer.set_query(get_query_from_search_entry(new_text), - limit=100*1000, - nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE) + limit=100 * 1000, + nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE) store = view.tree_view.get_model() with ExecutionTime("store.clear()"): @@ -293,10 +285,11 @@ Gtk.main_iteration() return - if widget.stamp: + if widget.stamp: GObject.source_remove(widget.stamp) widget.stamp = GObject.timeout_add(250, _work) + def get_test_window(): import softwarecenter.log softwarecenter.log.root.setLevel(level=logging.DEBUG) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/catview_gtk.py software-center-5.1.13/softwarecenter/ui/gtk3/views/catview_gtk.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/catview_gtk.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/catview_gtk.py 2012-03-19 17:03:56.000000000 +0000 @@ -37,7 +37,8 @@ from softwarecenter.ui.gtk3.widgets.containers import ( FramedHeaderBox, FramedBox, FlowableGrid) from softwarecenter.ui.gtk3.widgets.recommendations import ( - RecommendationsPanelLobby) + RecommendationsPanelLobby, + RecommendationsPanelCategory) from softwarecenter.ui.gtk3.widgets.exhibits import ( ExhibitBanner, FeaturedExhibit) from softwarecenter.ui.gtk3.widgets.buttons import (LabelTile, @@ -55,31 +56,33 @@ from softwarecenter.backend.scagent import SoftwareCenterAgent from softwarecenter.backend.reviews import get_review_loader -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) _asset_cache = {} + + class CategoriesViewGtk(Viewport, CategoriesParser): __gsignals__ = { - "category-selected" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), - - "application-selected" : (GObject.SignalFlags.RUN_LAST, + "category-selected": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), + + "application-selected": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT, ), + ), + + "application-activated": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, ), ), - "application-activated" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT, ), - ), - - "show-category-applist" : (GObject.SignalFlags.RUN_LAST, - None, - (),) + "show-category-applist": (GObject.SignalFlags.RUN_LAST, + None, + (),) } SPACING = PADDING = 3 @@ -88,17 +91,17 @@ STIPPLE = os.path.join(softwarecenter.paths.datadir, "ui/gtk3/art/stipple.png") - def __init__(self, + def __init__(self, datadir, - desktopdir, + desktopdir, cache, db, icons, - apps_filter=None, # FIXME: kill this, its not needed anymore? + apps_filter=None, # FIXME: kill this, its not needed anymore? apps_limit=0): """ init the widget, takes - + datadir - the base directory of the app-store data desktopdir - the dir where the applications.menu file can be found db - a Database object @@ -172,7 +175,8 @@ def _cache_art_assets(self): global _asset_cache - if _asset_cache: return _asset_cache + if _asset_cache: + return _asset_cache assets = _asset_cache # cache the bg pattern surf = cairo.ImageSurface.create_from_png(self.STIPPLE) @@ -189,7 +193,6 @@ return False GObject.timeout_add(50, timeout_emit) - return def on_category_clicked(self, btn, cat): """emit the category-selected signal when a category was clicked""" @@ -198,7 +201,6 @@ return False GObject.timeout_add(50, timeout_emit) - return def build(self, desktopdir): pass @@ -206,14 +208,14 @@ def do_draw(self, cr): cr.set_source(_asset_cache["stipple"]) cr.paint_with_alpha(0.5) - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) def set_section(self, section): self.section = section def refresh_apps(self): - raise NotImplemented + raise NotImplementedError class LobbyViewGtk(CategoriesViewGtk): @@ -251,7 +253,8 @@ self.top_hbox = Gtk.HBox(spacing=StockEms.SMALL) top_hbox_alignment = Gtk.Alignment() - top_hbox_alignment.set_padding(0, 0, StockEms.MEDIUM-2, StockEms.MEDIUM-2) + top_hbox_alignment.set_padding(0, 0, StockEms.MEDIUM - 2, + StockEms.MEDIUM - 2) top_hbox_alignment.add(self.top_hbox) self.vbox.pack_start(top_hbox_alignment, False, False, 0) @@ -271,33 +274,33 @@ #~ def _append_top_of_the_pops(self): #~ self.totp_hbox = Gtk.HBox(spacing=self.SPACING) -#~ +#~ #~ alignment = Gtk.Alignment() #~ alignment.set_padding(0, 0, self.PADDING, self.PADDING) #~ alignment.add(self.totp_hbox) -#~ +#~ #~ frame = FramedHeaderBox() #~ frame.header_implements_more_button() #~ frame.set_header_label(_("Most Popular")) -#~ +#~ #~ label = Gtk.Label.new("Soda pop!!!") #~ label.set_name("placeholder") #~ label.set_size_request(-1, 200) -#~ +#~ #~ frame.add(label) #~ self.totp_hbox.add(frame) -#~ +#~ #~ frame = FramedHeaderBox() #~ frame.header_implements_more_button() #~ frame.set_header_label(_("Top Rated")) -#~ +#~ #~ label = Gtk.Label.new("Demos ftw(?)") #~ label.set_name("placeholder") #~ label.set_size_request(-1, 200) -#~ +#~ #~ frame.add(label) #~ self.totp_hbox.add(frame) -#~ +#~ #~ self.vbox.pack_start(alignment, False, False, 0) #~ return @@ -306,17 +309,17 @@ #~ frame.set_header_expand(False) #~ frame.set_header_position(HeaderPosition.LEFT) #~ frame.set_header_label(_("Latest Demo Videos")) -#~ +#~ #~ label = Gtk.Label.new("Videos go here") #~ label.set_name("placeholder") #~ label.set_size_request(-1, 200) -#~ +#~ #~ frame.add(label) -#~ +#~ #~ alignment = Gtk.Alignment() #~ alignment.set_padding(0, 0, self.PADDING, self.PADDING) #~ alignment.add(frame) -#~ +#~ #~ self.vbox.pack_start(alignment, False, False, 0) #~ return @@ -343,18 +346,19 @@ # query using the agent scagent = SoftwareCenterAgent() scagent.connect( - "exhibits", lambda sca,l: exhibit_banner.set_exhibits(l)) + "exhibits", lambda sca, l: exhibit_banner.set_exhibits(l)) scagent.query_exhibits() a = Gtk.Alignment() - a.set_padding(0,StockEms.SMALL,0,0) + a.set_padding(0, StockEms.SMALL, 0, 0) a.add(exhibit_banner) self.vbox.pack_start(a, False, False, 0) return def _append_departments(self): - # set the departments section to use the label markup we have just defined + # set the departments section to use the label markup we have just + # defined cat_vbox = FramedBox(Gtk.Orientation.VERTICAL) self.top_hbox.pack_start(cat_vbox, False, False, 0) @@ -363,7 +367,8 @@ mrkup = "%s" for cat in sorted_cats: - if 'carousel-only' in cat.flags: continue + if 'carousel-only' in cat.flags: + continue category_name = mrkup % GObject.markup_escape_text(cat.name) label = LabelTile(category_name, None) label.label.set_margin_left(StockEms.SMALL) @@ -400,8 +405,8 @@ # only display the 'More' LinkButton if we have top_rated content if top_rated_cat is not None: self.top_rated_frame.header_implements_more_button() - self.top_rated_frame.more.connect('clicked', - self.on_category_clicked, top_rated_cat) + self.top_rated_frame.more.connect('clicked', + self.on_category_clicked, top_rated_cat) return def _update_whats_new_content(self): @@ -409,7 +414,7 @@ self.whats_new.remove_all() # get top_rated category and docs whats_new_cat = get_category_by_name( - self.categories, u"What\u2019s New") # untranslated name + self.categories, u"What\u2019s New") # untranslated name if whats_new_cat: docs = whats_new_cat.get_documents(self.db) self._add_tiles_to_flowgrid(docs, self.whats_new, 8) @@ -428,33 +433,33 @@ self.right_column.pack_start(self.whats_new_frame, True, True, 0) self.whats_new_frame.header_implements_more_button() self.whats_new_frame.more.connect( - 'clicked', self.on_category_clicked, whats_new_cat) - return - + 'clicked', self.on_category_clicked, whats_new_cat) + def _update_recommended_for_you_content(self): if (self.recommended_for_you_panel and self.recommended_for_you_panel.get_parent()): self.bottom_hbox.remove(self.recommended_for_you_panel) self.recommended_for_you_panel = RecommendationsPanelLobby(self) - self.bottom_hbox.pack_start(self.recommended_for_you_panel, + self.bottom_hbox.pack_start(self.recommended_for_you_panel, True, True, 0) - + def _append_recommended_for_you(self): # TODO: This space will initially contain an opt-in screen, and this # will update to the tile view of recommended apps when ready # see https://wiki.ubuntu.com/SoftwareCenter#Home_screen self.bottom_hbox = Gtk.HBox(spacing=StockEms.SMALL) bottom_hbox_alignment = Gtk.Alignment() - bottom_hbox_alignment.set_padding(0, 0, StockEms.MEDIUM-2, StockEms.MEDIUM-2) + bottom_hbox_alignment.set_padding(0, 0, StockEms.MEDIUM - 2, + StockEms.MEDIUM - 2) bottom_hbox_alignment.add(self.bottom_hbox) self.vbox.pack_start(bottom_hbox_alignment, False, False, 0) - - # TODO: During development, place the "Recommended for You" panel + + # TODO: During development, place the "Recommended For You" panel # at the bottom, but swap this with the Top Rated panel once # the recommended for you pieces are done and deployed # see https://wiki.ubuntu.com/SoftwareCenter#Home_screen self.recommended_for_you_panel = RecommendationsPanelLobby(self) - self.bottom_hbox.pack_start(self.recommended_for_you_panel, + self.bottom_hbox.pack_start(self.recommended_for_you_panel, True, True, 0) def _update_appcount(self): @@ -468,7 +473,7 @@ length = enq.get_estimated_matches_count(query) text = gettext.ngettext("%(amount)s item", "%(amount)s items", length - ) % { 'amount' : length, } + ) % {'amount': length} self.appcount.set_text(text) def _append_appcount(self): @@ -499,7 +504,7 @@ self._update_appcount() return - # stubs for the time being, we may reuse them if we get dynamic content + # stubs for the time being, we may reuse them if we get dynamic content # again def stop_carousels(self): pass @@ -507,6 +512,7 @@ def start_carousels(self): pass + class SubCategoryViewGtk(CategoriesViewGtk): def __init__(self, datadir, desktopdir, cache, db, icons, @@ -525,12 +531,12 @@ self.current_category = None self.departments = None self.top_rated = None - self.recommended_for_you = None + self.recommended_for_you_in_cat = None self.appcount = None # widgetry - self.vbox.set_margin_left(StockEms.MEDIUM-2) - self.vbox.set_margin_right(StockEms.MEDIUM-2) + self.vbox.set_margin_left(StockEms.MEDIUM - 2) + self.vbox.set_margin_right(StockEms.MEDIUM - 2) self.vbox.set_margin_top(StockEms.MEDIUM) return @@ -544,15 +550,17 @@ nonblocking_load=False) return self.enquire.get_documents() - @wait_for_apt_cache_ready # be consistent with new apps + @wait_for_apt_cache_ready # be consistent with new apps def _update_sub_top_rated_content(self, category): self.top_rated.remove_all() # FIXME: should this be m = "%s %s" % (_(gettext text), header text) ?? - # TRANSLATORS: %s is a category name, like Internet or Development Tools - m = _('Top Rated %(category)s') % { 'category' : GObject.markup_escape_text(self.header)} + # TRANSLATORS: %s is a category name, like Internet or Development + # Tools + m = _('Top Rated %(category)s') % { + 'category': GObject.markup_escape_text(self.header)} self.top_rated_frame.set_header_label(m) docs = self._get_sub_top_rated_content(category) - self._add_tiles_to_flowgrid(docs, self.top_rated, + self._add_tiles_to_flowgrid(docs, self.top_rated, TOP_RATED_CAROUSEL_LIMIT) return @@ -565,12 +573,28 @@ self.vbox.pack_start(self.top_rated_frame, False, True, 0) return + def _update_recommended_for_you_in_cat_content(self, category): + if (self.recommended_for_you_in_cat and + self.recommended_for_you_in_cat.get_parent()): + self.vbox.remove(self.recommended_for_you_in_cat) + self.recommended_for_you_in_cat = RecommendationsPanelCategory( + self, + category) + # only show the panel in the categories view when the user + # is opted in to the recommender service + # FIXME: this is needed vs. a simple hide() on the widget because + # we do a show_all on the view + if self.recommended_for_you_in_cat.recommender_agent.is_opted_in(): + self.vbox.pack_start(self.recommended_for_you_in_cat, + False, False, 0) + def _update_subcat_departments(self, category, num_items): self.departments.remove_all() # set the subcat header m = "%s" - self.subcat_label.set_markup(m % GObject.markup_escape_text(self.header)) + self.subcat_label.set_markup(m % GObject.markup_escape_text( + self.header)) # sort Category.name's alphabetically sorted_cats = categories_sorted_by_name(self.categories) @@ -580,14 +604,14 @@ # add the subcategory if and only if it is non-empty enquire.set_query(cat.query) - if len(enquire.get_mset(0,1)): + if len(enquire.get_mset(0, 1)): tile = CategoryTile(cat.name, cat.iconname) tile.connect('clicked', self.on_category_clicked, cat) self.departments.add_child(tile) # partialy work around a (quite rare) corner case if num_items == 0: - enquire.set_query(xapian.Query(xapian.Query.OP_AND, + enquire.set_query(xapian.Query(xapian.Query.OP_AND, category.query, xapian.Query("ATapplication"))) # assuming that we only want apps is not always correct ^^^ @@ -595,7 +619,8 @@ num_items = tmp_matches.get_matches_estimated() # append an additional button to show all of the items in the category - all_cat = Category("All", _("All"), "category-show-all", category.query) + all_cat = Category("All", _("All"), "category-show-all", + category.query) name = GObject.markup_escape_text('%s %s' % (_("All"), num_items)) tile = CategoryTile(name, "category-show-all") tile.connect('clicked', self.on_category_clicked, all_cat) @@ -622,7 +647,7 @@ def _update_appcount(self, appcount): text = gettext.ngettext("%(amount)s item available", "%(amount)s items available", - appcount) % { 'amount' : appcount, } + appcount) % {'amount': appcount} self.appcount.set_text(text) return @@ -639,6 +664,9 @@ # changing order of methods changes order that they appear in the page self._append_subcat_departments() self._append_sub_top_rated() + # NOTE that the recommended for you in category view is built and added + # in the _update_recommended_for_you_in_cat method (and so is not + # needed here) self._append_appcount() self._built = True return @@ -646,6 +674,7 @@ def _update_subcat_view(self, category, num_items=0): num_items = self._update_subcat_departments(category, num_items) self._update_sub_top_rated_content(category) + self._update_recommended_for_you_in_cat_content(category) self._update_appcount(num_items) self.show_all() return @@ -660,7 +689,8 @@ self.header = root_category.name self.categories = root_category.subcategories - if not self._built: self._build_subcat_view() + if not self._built: + self._build_subcat_view() self._update_subcat_view(root_category, num_items) GObject.idle_add(self.queue_draw) @@ -673,7 +703,8 @@ return self._supported_only = supported_only - if not self._built: self._build_subcat_view() + if not self._built: + self._build_subcat_view() self._update_subcat_view(self.current_category) GObject.idle_add(self.queue_draw) return @@ -683,10 +714,12 @@ #self.set_subcategory(self.root_category) #return + def get_test_window_catview(): def on_category_selected(view, cat): - print("on_category_selected %s %s" % view, cat) + print "on_category_selected view: ", view + print "on_category_selected cat: ", cat from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() @@ -711,7 +744,7 @@ # gui win = Gtk.Window() - n = Gtk.Notebook() + notebook = Gtk.Notebook() from softwarecenter.paths import APP_INSTALL_PATH view = LobbyViewGtk(datadir, APP_INSTALL_PATH, @@ -720,7 +753,7 @@ scroll = Gtk.ScrolledWindow() scroll.add(view) - n.append_page(scroll, Gtk.Label(label="Lobby")) + notebook.append_page(scroll, Gtk.Label(label="Lobby")) # find a cat in the LobbyView that has subcategories subcat_cat = None @@ -737,14 +770,15 @@ scroll = Gtk.ScrolledWindow() scroll.add(view) - n.append_page(scroll, Gtk.Label(label="Subcats")) + notebook.append_page(scroll, Gtk.Label(label="Subcats")) - win.add(n) - win.set_size_request(800,800) + win.add(notebook) + win.set_size_request(800, 800) win.show_all() win.connect('destroy', Gtk.main_quit) return win - + + def get_test_catview(): def on_category_selected(view, cat): @@ -784,5 +818,3 @@ # run it Gtk.main() - - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/pkgnamesview.py software-center-5.1.13/softwarecenter/ui/gtk3/views/pkgnamesview.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/pkgnamesview.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/pkgnamesview.py 2012-03-15 09:05:38.000000000 +0000 @@ -27,6 +27,7 @@ LOG = logging.getLogger(__name__) + class PackageNamesView(Gtk.TreeView): """ A simple widget that presents a list of packages, with associated icons, in a treeview. Note the for current @@ -52,7 +53,7 @@ continue s = "%s \n%s" % ( cache[pkgname].installed.summary.capitalize(), pkgname) - + app_details = Application("", pkgname).get_details(db) proposed_icon = app_details.icon if not proposed_icon or not icons.has_icon(proposed_icon): @@ -65,11 +66,11 @@ LOG.warn("cant set icon for '%s' " % pkgname) pb = icons.load_icon(Icons.MISSING_APP, icon_size, - Gtk.IconLookupFlags.GENERIC_FALLBACK) - pb = pb.scale_simple(icon_size, + Gtk.IconLookupFlags.GENERIC_FALLBACK) + pb = pb.scale_simple(icon_size, icon_size, GdkPixbuf.InterpType.BILINEAR) model.append([pb, s]) - + # finally, we don't allow selection, it's just a simple display list tree_selection = self.get_selection() tree_selection.set_mode(Gtk.SelectionMode.NONE) @@ -87,19 +88,19 @@ db = StoreDatabase(pathname, cache) db.open() - import softwarecenter.paths + import softwarecenter.paths datadir = softwarecenter.paths.datadir from softwarecenter.ui.gtk3.utils import get_sc_icon_theme icons = get_sc_icon_theme(datadir) - + pkgs = ["apt", "software-center"] view = PackageNamesView("header", cache, pkgs, icons, 32, db) view.show() win = Gtk.Window() win.add(view) - win.set_size_request(600,400) + win.set_size_request(600, 400) win.show() win.connect('destroy', Gtk.main_quit) return win diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/purchaseview.py software-center-5.1.13/softwarecenter/ui/gtk3/views/purchaseview.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/purchaseview.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/purchaseview.py 2012-03-20 08:00:09.000000000 +0000 @@ -20,113 +20,32 @@ from gi.repository import GObject from gi.repository import Gtk from gi.repository import Gdk -from gi.repository import Pango + +import ConfigParser import logging import os import json import sys import urllib -import urlparse + from gi.repository import WebKit as webkit from gettext import gettext as _ from softwarecenter.backend import get_install_backend -from softwarecenter.i18n import get_language +from softwarecenter.ui.gtk3.dialogs import show_accept_tos_dialog +from softwarecenter.config import get_config +from softwarecenter.ui.gtk3.utils import get_parent +from softwarecenter.ui.gtk3.views.webkit import ScrolledWebkitWindow LOG = logging.getLogger(__name__) -class LocaleAwareWebView(webkit.WebView): - - def __init__(self): - # actual webkit init - webkit.WebView.__init__(self) - self.connect("resource-request-starting", - self._on_resource_request_starting) - - def _on_resource_request_starting(self, view, frame, res, req, resp): - lang = get_language() - if lang: - message = req.get_message() - if message: - headers = message.get_property("request-headers") - headers.append("Accept-Language", lang) - #def _show_header(name, value, data): - # print name, value - #headers.foreach(_show_header, None) - - -class ScrolledWebkitWindow(Gtk.VBox): - - def __init__(self, include_progress_ui=False): - super(ScrolledWebkitWindow, self).__init__() - # get webkit - self.webkit = LocaleAwareWebView() - settings = self.webkit.get_settings() - settings.set_property("enable-plugins", False) - # add progress UI if needed - if include_progress_ui: - self._add_progress_ui() - # create main webkitview - self.scroll = Gtk.ScrolledWindow() - self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, - Gtk.PolicyType.AUTOMATIC) - self.pack_start(self.scroll, True, True, 0) - # embed the webkit view in a scrolled window - self.scroll.add(self.webkit) - self.show_all() - def _add_progress_ui(self): - # create toolbar box - self.header = Gtk.HBox() - # add spinner - self.spinner = Gtk.Spinner() - self.header.pack_start(self.spinner, False, False, 6) - # add a url to the toolbar - self.url = Gtk.Label() - self.url.set_ellipsize(Pango.EllipsizeMode.END) - self.url.set_alignment(0.0, 0.5) - self.url.set_text("") - self.header.pack_start(self.url, True, True, 0) - # frame around the box - self.frame = Gtk.Frame() - self.frame.set_border_width(3) - self.frame.add(self.header) - self.pack_start(self.frame, False, False, 6) - # connect the webkit stuff - self.webkit.connect("notify::uri", self._on_uri_changed) - self.webkit.connect("notify::load-status", self._on_load_status_changed) - def _on_uri_changed(self, view, pspec): - prop = pspec.name - uri = view.get_property(prop) - # the full uri is irellevant for the purchase view, but it is - # interessting to know what protocol/netloc is in use so that the - # user can verify its https on sites he is expecting - scheme, netloc, path, params, query, frag = urlparse.urlparse(uri) - if scheme == "file" and netloc == "": - self.url.set_text("") - else: - self.url.set_text("%s://%s" % (scheme, netloc)) - # start spinner when the uri changes - #self.spinner.start() - def _on_load_status_changed(self, view, pspec): - prop = pspec.name - status = view.get_property(prop) - #print status - if status == webkit.LoadStatus.PROVISIONAL: - self.spinner.start() - self.spinner.show() - if (status == webkit.LoadStatus.FINISHED or - status == webkit.LoadStatus.FAILED): - self.spinner.stop() - self.spinner.hide() - - class PurchaseView(Gtk.VBox): - """ - View that displays the webkit-based UI for purchasing an item. """ - + View that displays the webkit-based UI for purchasing an item. + """ + LOADING_HTML = """ @@ -151,7 +70,8 @@ vertical-align: middle; } h1 { - background: url(file:///usr/share/software-center/images/spinner.gif) top center no-repeat; + background: url(file:///usr/share/software-center/images/spinner.gif) top \ +center no-repeat; padding-top: 48px; /* leaves room for the spinner above */ font-size: 100%%; font-weight: normal; @@ -164,19 +84,19 @@ """ % _("Connecting to payment service...") __gsignals__ = { - 'purchase-succeeded' : (GObject.SignalFlags.RUN_LAST, - None, - ()), - 'purchase-failed' : (GObject.SignalFlags.RUN_LAST, - None, - ()), - 'purchase-cancelled-by-user' : (GObject.SignalFlags.RUN_LAST, - None, - ()), - 'purchase-needs-spinner' : (GObject.SignalFlags.RUN_LAST, - None, - (bool, )), - + 'purchase-succeeded': (GObject.SignalFlags.RUN_LAST, + None, + ()), + 'purchase-failed': (GObject.SignalFlags.RUN_LAST, + None, + ()), + 'purchase-cancelled-by-user': (GObject.SignalFlags.RUN_LAST, + None, + ()), + 'purchase-needs-spinner': (GObject.SignalFlags.RUN_LAST, + None, + (bool, )), + } def __init__(self): @@ -184,11 +104,13 @@ self.wk = None self._wk_handlers_blocked = False self._oauth_token = None + self.config = get_config() def init_view(self): if self.wk is None: self.wk = ScrolledWebkitWindow() - #self.wk.webkit.connect("new-window-policy-decision-requested", self._on_new_window) + #self.wk.webkit.connect("new-window-policy-decision-requested", + # self._on_new_window) self.wk.webkit.connect("create-web-view", self._on_create_web_view) self.wk.webkit.connect("close-web-view", self._on_close_web_view) self.wk.webkit.connect("console-message", self._on_console_message) @@ -196,16 +118,35 @@ # a possible way to do IPC (script or title change) self.wk.webkit.connect("script-alert", self._on_script_alert) self.wk.webkit.connect("title-changed", self._on_title_changed) - self.wk.webkit.connect("notify::load-status", self._on_load_status_changed) - # unblock signal handlers if needed when showing the purchase webkit view in - # case they were blocked after a previous purchase was completed or canceled + self.wk.webkit.connect("notify::load-status", + self._on_load_status_changed) + # unblock signal handlers if needed when showing the purchase webkit + # view in case they were blocked after a previous purchase was + # completed or canceled self._unblock_wk_handlers() + def _ask_for_tos_acceptance_if_needed(self): + try: + accepted_tos = self.config.getboolean("general", "accepted_tos") + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + accepted_tos = False + if not accepted_tos: + # show the dialog and ensure the user accepts it + res = show_accept_tos_dialog(get_parent(self)) + if not res: + return False + self.config.set("general", "accepted_tos", "yes") + return True + return True + def initiate_purchase(self, app, iconname, url=None, html=None): """ initiates the purchase workflow inside the embedded webkit window - for the item specified + for the item specified """ + if not self._ask_for_tos_acceptance_if_needed(): + return False + self.init_view() self.app = app self.iconname = iconname @@ -223,7 +164,8 @@ # only for debugging if os.environ.get("SOFTWARE_CENTER_DEBUG_BUY"): GObject.timeout_add_seconds(1, _generate_events, self) - + return True + def _on_new_window(self, view, frame, request, action, policy): LOG.debug("_on_new_window") import subprocess @@ -234,7 +176,7 @@ win = view.get_data("win") win.destroy() return True - + def _on_create_web_view(self, view, frame): win = Gtk.Window() win.set_size_request(400, 400) @@ -261,7 +203,8 @@ pass for k in ["token_key", "token_secret", "consumer_secret"]: if k in message: - LOG.debug("skipping console message that contains sensitive data") + LOG.debug( + "skipping console message that contains sensitive data") return True LOG.debug("_on_console_message '%s'" % message) return False @@ -312,7 +255,8 @@ return # this is what the agent implements elif "failures" in res: - LOG.error("the server returned a error: '%s'" % res["failures"]) + LOG.error("the server returned a error: '%s'" % + res["failures"]) # show a generic error, the "failures" string we get from the # server is way too technical to show, but we do log it self.emit("purchase-failed") @@ -329,9 +273,9 @@ # add repo and key backend = get_install_backend() backend.add_repo_add_key_and_install_app( - deb_line, signing_key_id, self.app, self.iconname, + deb_line, signing_key_id, self.app, self.iconname, license_key, license_key_path, json.dumps(self._oauth_token)) - + def _block_wk_handlers(self): # we need to block webkit signal handlers when we hide the # purchase webkit view, this prevents e.g. handling of signals on @@ -341,14 +285,15 @@ self.wk.webkit.handler_block_by_func(self._on_title_changed) self.wk.webkit.handler_block_by_func(self._on_load_status_changed) self._wk_handlers_blocked = True - + def _unblock_wk_handlers(self): if self._wk_handlers_blocked: self.wk.webkit.handler_unblock_by_func(self._on_script_alert) self.wk.webkit.handler_unblock_by_func(self._on_title_changed) - self.wk.webkit.handler_unblock_by_func(self._on_load_status_changed) + self.wk.webkit.handler_unblock_by_func( + self._on_load_status_changed) self._wk_handlers_blocked = False - + # just used for testing -------------------------------------------- DUMMY_HTML = """ @@ -361,11 +306,11 @@ @@ -375,11 +320,11 @@

- - @@ -387,30 +332,31 @@ """ + # synthetic key event generation def _send_keys(view, s): print("_send_keys %s" % s) - MAPPING = { '@' : 'at', - '.' : 'period', - '\t' : 'Tab', - '\n' : 'Return', - '?' : 'question', - '\a' : 'Down', # fake - ' ' : 'space', - '\v' : 'Page_Down', # fake + MAPPING = {'@': 'at', + '.': 'period', + '\t': 'Tab', + '\n': 'Return', + '?': 'question', + '\a': 'Down', # fake + ' ': 'space', + '\v': 'Page_Down', # fake } - + for key in s: event = Gdk.Event(Gdk.KEY_PRESS) event.window = view.window if key.isdigit(): - key = "_"+key + key = "_" + key if hasattr(Gdk, key): event.keyval = getattr(Gdk, key) else: event.keyval = getattr(Gdk, MAPPING[key]) Gtk.main_do_event(event) - + # \a means down key - its a just a fake to get it working LOGIN = os.environ.get("SOFTWARE_CENTER_LOGIN") or "michael.vogt@ubuntu.com" @@ -419,12 +365,14 @@ PAYMENT_DETAILS = "\tstreet1\tstreet2\tcity\tstate\t1234\t\a\t\a\a\t"\ "ACCEPTED\t4111111111111111\t1234\t\a\t\a\a\t\t\t \v" # state-name, window title, keys -STATES = [ ('login', 'Log in', LOGIN+"\t"), - ('confirm-sso', 'Authenticate to', '\n'), - ('enter-payment', 'Confirm Payment Details', PAYMENT_DETAILS), - ('confirm-payment', 'title-the-same-as-before', '\t\n'), - ('end-state', 'no-title', ''), +STATES = [('login', 'Log in', LOGIN + "\t"), + ('confirm-sso', 'Authenticate to', '\n'), + ('enter-payment', 'Confirm Payment Details', PAYMENT_DETAILS), + ('confirm-payment', 'title-the-same-as-before', '\t\n'), + ('end-state', 'no-title', ''), ] + + def _generate_events(view): global STATES @@ -440,19 +388,20 @@ return True -# # for debugging only +# # for debugging only # def _on_key_press(dialog, event): # print event, event.keyval + def get_test_window_purchaseview(): #url = "http://www.animiertegifs.de/java-scripts/alertbox.php" url = "http://www.ubuntu.cohtml=DUMMY_m" #d = PurchaseDialog(app=None, url="http://spiegel.de") from softwarecenter.enums import BUY_SOMETHING_HOST - url = BUY_SOMETHING_HOST+"/subscriptions/en/ubuntu/maverick/+new/?%s" % ( + url = BUY_SOMETHING_HOST + "/subscriptions/en/ubuntu/maverick/+new/?%s" % ( urllib.urlencode({ - 'archive_id' : "mvo/private-test", - 'arch' : "i386", + 'archive_id': "mvo/private-test", + 'arch': "i386", })) # use cmdline if available if len(sys.argv) > 1: @@ -460,10 +409,10 @@ # useful for debugging #d.connect("key-press-event", _on_key_press) #GObject.timeout_add_seconds(1, _generate_events, d) - + widget = PurchaseView() - widget.initiate_purchase(app=None, iconname=None, url=url) - #widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML) + from mock import Mock + widget.config = Mock() win = Gtk.Window() win.set_data("view", widget) @@ -472,9 +421,14 @@ win.set_position(Gtk.WindowPosition.CENTER) win.show_all() win.connect('destroy', Gtk.main_quit) + + widget.initiate_purchase(app=None, iconname=None, url=url) + #widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML) + return win if __name__ == "__main__": + import softwarecenter.paths + softwarecenter.paths.datadir = "./data" win = get_test_window_purchaseview() Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/views/webkit.py software-center-5.1.13/softwarecenter/ui/gtk3/views/webkit.py --- software-center-5.1.12/softwarecenter/ui/gtk3/views/webkit.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/views/webkit.py 2012-03-20 08:00:09.000000000 +0000 @@ -0,0 +1,114 @@ +# Copyright (C) 2010 Canonical +# +# Authors: +# Michael Vogt +# Gary Lasker +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; version 3. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from gi.repository import WebKit as webkit +from gi.repository import Gtk +from gi.repository import Pango +import urlparse + +from softwarecenter.i18n import get_language + + +class LocaleAwareWebView(webkit.WebView): + + def __init__(self): + # actual webkit init + webkit.WebView.__init__(self) + self.connect("resource-request-starting", + self._on_resource_request_starting) + + def _on_resource_request_starting(self, view, frame, res, req, resp): + lang = get_language() + if lang: + message = req.get_message() + if message: + headers = message.get_property("request-headers") + headers.append("Accept-Language", lang) + #def _show_header(name, value, data): + # print name, value + #headers.foreach(_show_header, None) + + +class ScrolledWebkitWindow(Gtk.VBox): + + def __init__(self, include_progress_ui=False): + super(ScrolledWebkitWindow, self).__init__() + # get webkit + self.webkit = LocaleAwareWebView() + settings = self.webkit.get_settings() + settings.set_property("enable-plugins", False) + # add progress UI if needed + if include_progress_ui: + self._add_progress_ui() + # create main webkitview + self.scroll = Gtk.ScrolledWindow() + self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) + self.pack_start(self.scroll, True, True, 0) + # embed the webkit view in a scrolled window + self.scroll.add(self.webkit) + self.show_all() + + def _add_progress_ui(self): + # create toolbar box + self.header = Gtk.HBox() + # add spinner + self.spinner = Gtk.Spinner() + self.header.pack_start(self.spinner, False, False, 6) + # add a url to the toolbar + self.url = Gtk.Label() + self.url.set_ellipsize(Pango.EllipsizeMode.END) + self.url.set_alignment(0.0, 0.5) + self.url.set_text("") + self.header.pack_start(self.url, True, True, 0) + # frame around the box + self.frame = Gtk.Frame() + self.frame.set_border_width(3) + self.frame.add(self.header) + self.pack_start(self.frame, False, False, 6) + # connect the webkit stuff + self.webkit.connect("notify::uri", self._on_uri_changed) + self.webkit.connect("notify::load-status", + self._on_load_status_changed) + + def _on_uri_changed(self, view, pspec): + prop = pspec.name + uri = view.get_property(prop) + # the full uri is irellevant for the purchase view, but it is + # interessting to know what protocol/netloc is in use so that the + # user can verify its https on sites he is expecting + scheme, netloc, path, params, query, frag = urlparse.urlparse(uri) + if scheme == "file" and netloc == "": + self.url.set_text("") + else: + self.url.set_text("%s://%s" % (scheme, netloc)) + # start spinner when the uri changes + #self.spinner.start() + + def _on_load_status_changed(self, view, pspec): + prop = pspec.name + status = view.get_property(prop) + #print status + if status == webkit.LoadStatus.PROVISIONAL: + self.spinner.start() + self.spinner.show() + if (status == webkit.LoadStatus.FINISHED or + status == webkit.LoadStatus.FAILED): + self.spinner.stop() + self.spinner.hide() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/actionbar.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/actionbar.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/actionbar.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/actionbar.py 2012-03-14 16:03:50.000000000 +0000 @@ -22,24 +22,26 @@ LOG = logging.getLogger(__name__) + class ActionBar(Gtk.HBox): """ This is a gtk box wrapped so that all child widgets, save one, align to the right. The widget does not show by default when its parent calls show_all(), but rather autoshows and autohides when buttons - (or a left-aligned label) are added and removed. + (or a left-aligned label) are added and removed. The widget is only intended to hold buttons and a label, for which it has specific methods; general add and pack are not implemented. - See: https://wiki.ubuntu.com/SoftwareCenter#Main%20window - https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists - https://wiki.ubuntu.com/SoftwareCenter#software-list-view-disclosure - https://wiki.ubuntu.com/SoftwareCenter#Learning_how_to_launch_an_application + See: +https://wiki.ubuntu.com/SoftwareCenter#Main%20window +https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists +https://wiki.ubuntu.com/SoftwareCenter#software-list-view-disclosure +https://wiki.ubuntu.com/SoftwareCenter#Learning_how_to_launch_an_application """ - + PADDING = 4 - + ANIMATE_START_DELAY = 50 ANIMATE_STEP_INTERVAL = 10 ANIMATE_STEP = 8 @@ -52,7 +54,7 @@ self._label.set_border_width(self.PADDING) # So that all buttons children right align self._btn_bin = Gtk.Alignment.new(1.0, 0.0, 1.0, 1.0) - self._btn_bin.set_padding(0,0,0,10) + self._btn_bin.set_padding(0, 0, 0, 10) self._btn_bin.add(self._btns) # Buttons go on the right, labels on the left (in LTR mode) super(ActionBar, self).pack_start(self._label, False, False, 10) @@ -63,7 +65,7 @@ self._label.show_all() self._btn_bin.show_all() self._visible = False - + # listen for size allocation events, used for implementing the # action bar slide in/out animation effect self.connect('size-allocate', self._on_size_allocate) @@ -100,8 +102,9 @@ children = self._btns.get_children() for child in children: if child.id == id: - # lock the height of the action bar when removing buttons to prevent - # an unsightly resize if a label remains but all buttons are removed + # lock the height of the action bar when removing buttons to + # prevent an unsightly resize if a label remains but all + # buttons are removed self.set_size_request(-1, self.get_allocation().height) self._btns.remove(child) if len(children) == 1 and not len(self._label): @@ -234,7 +237,7 @@ self._slide_in() else: super(ActionBar, self).show() - + def _hide(self, animate): if not self._visible or self._is_sliding_out: return @@ -244,8 +247,7 @@ self.set_size_request(-1, -1) self._visible = False super(ActionBar, self).hide() - return - + def _slide_in(self): self._is_sliding_in = True self._target_height = self.get_size_request()[1] @@ -254,7 +256,6 @@ super(ActionBar, self).show() GObject.timeout_add(self.ANIMATE_START_DELAY, self._slide_in_cb) - return def _slide_out(self): self._is_sliding_out = True @@ -262,8 +263,7 @@ self._current_height = self.get_size_request()[1] GObject.timeout_add(self.ANIMATE_START_DELAY, self._slide_out_cb) - return - + def _slide_in_cb(self): if (self._is_sliding_in and self._current_height < self._target_height): @@ -273,8 +273,7 @@ self.set_size_request(-1, new_height) else: self._is_sliding_in = False - return - + def _slide_out_cb(self): if (self._is_sliding_out and self._current_height > self._target_height): @@ -287,8 +286,7 @@ super(ActionBar, self).hide() else: self.set_size_request(-1, new_height) - return - + def _on_size_allocate(self, widget, allocation): # FIXME: mvo: allocation.height is no longer accessable with gtk3 # THIS SEEMS TO BREAK THE ANIMATION @@ -306,7 +304,6 @@ priority=100) else: self.queue_draw() - return def _on_draw(self, widget, cr): a = widget.get_allocation() @@ -320,9 +317,8 @@ cr.set_source_rgba(color.red, color.green, color.blue, 0.5) cr.set_line_width(1) cr.move_to(0.5, 0.5) - cr.rel_line_to(a.width-1, 0) + cr.rel_line_to(a.width - 1, 0) cr.stroke() - return def _callback(self, function, args): # Disposes of the 'widget' argument that diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/animatedimage.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/animatedimage.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/animatedimage.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/animatedimage.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,157 +0,0 @@ -# Copyright (C) 2009 Canonical -# -# Authors: -# Michael Vogt -# Andrew Higginson (rugby471) -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from gi.repository import Gtk, Gdk, GObject, GdkPixbuf - -from softwarecenter.ui.gtk3.drawing import rounded_rect - - -class ProgressImage(Gtk.Image): - - ANIMATION_PATH = "/usr/share/icons/hicolor/24x24/status/softwarecenter-progress.png" - FRAME_DELAY = 50 # msec - - BUBBLE_BORDER_RADIUS = 6 - BUBBLE_XPADDING = 4 - BUBBLE_YPADDING = 1 - - _frame_cache = {} - - def __init__(self): - Gtk.Image.__init__(self) - self.pixbuf = GdkPixbuf.Pixbuf.new_from_file( - self.ANIMATION_PATH) - - # attrs specific to the pending animation image - self.col_count = 6 - self.row_count = 3 - self.h_stride = self.pixbuf.get_width() / self.col_count - self.v_stride = self.pixbuf.get_height() / self.row_count - self.position = (0, 0) - - # transaction count num - self.transaction_count = 0 - # the signal tag for the animation timeout - self.handler = None - # the first frame - self.frame = self.get_idle_frame() - - # a Pango.Layout for rendering the transaction count - layout = self.create_pango_layout("") - self.connect("draw", self.on_draw, layout) - return - - # overrides - def do_get_preferred_width(self): - return self.h_stride, self.h_stride - - def do_get_preferred_height(self): - return self.v_stride, self.v_stride - - # handlers - def on_new_frame(self): - i, j = self.position - x = i * self.h_stride - y = j * self.v_stride - - if (i,j) in self._frame_cache: - self.frame = self._frame_cache[(i,j)] - else: - self.frame = GdkPixbuf.Pixbuf.new_subpixbuf(self.pixbuf, - x, y, - self.h_stride, - self.v_stride) - self._frame_cache[(i,j)] = self.frame - - if i < self.col_count-1: - i += 1 - else: - i = 0 - if j < self.row_count-1: - j += 1 - else: - j = 0 - self.position = (i, j) - - self.queue_draw() - return True - - # public - def get_idle_frame(self): - i, j = self.position - x = i * self.h_stride - y = j * self.v_stride - - self.frame = GdkPixbuf.Pixbuf.new_subpixbuf(self.pixbuf, - x, y, - self.h_stride, - self.v_stride) - self._frame_cache[(i,j)] = self.frame - return self.frame - - def set_transaction_count(self, transaction_count): - self.transaction_count = transaction_count - self.queue_draw() - return - - def on_draw(self, widget, cr, layout): - a = widget.get_allocation() - # paint the current animation frame - x = (a.width - self.h_stride) * 0.5 - y = (a.height - self.v_stride) * 0.5 - Gdk.cairo_set_source_pixbuf(cr, self.frame, x, y) - cr.paint() - if self.transaction_count <= 0: return - # paint a bubble with the transaction count - layout.set_markup('%i' % self.transaction_count, -1) - # determine bubble size - extents = layout.get_pixel_extents()[1] - width = extents.width + (2 * self.BUBBLE_XPADDING) - height = extents.height + (2 * self.BUBBLE_YPADDING) - # now render the bubble and layout - context = self.get_style_context() - x += self.h_stride + self.BUBBLE_XPADDING - y += (self.v_stride - height) / 2 - rounded_rect(cr, x, y, width, height, self.BUBBLE_BORDER_RADIUS) - cr.set_source_rgba(0,0,0,0.2) - cr.fill() - Gtk.render_layout(context, cr, - x + self.BUBBLE_XPADDING, - y + self.BUBBLE_YPADDING, - layout) - return - - def is_playing(self): - return self.handler is not None - - def start(self): - if self.is_playing(): return - self.position = (0, 0) - self.handler = GObject.timeout_add(self.FRAME_DELAY, - self.on_new_frame) - return - - def stop(self): - if self.handler: - GObject.source_remove(self.handler) - self.handler = None - - self.position = (0, 0) - self.queue_draw() - return diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/apptreeview.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/apptreeview.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/apptreeview.py 2012-03-08 19:48:54.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/apptreeview.py 2012-03-20 10:12:24.000000000 +0000 @@ -43,7 +43,7 @@ self._action_block_list = [] self._needs_collapse = [] self.expanded_path = None - + # pixbuf for the icon that is displayed in the selected row self.selected_row_icon = None @@ -54,7 +54,8 @@ self.set_property("ubuntu-almost-fixed-height-mode", True) self.set_fixed_height_mode(True) except: - self._logger.warn("ubuntu-almost-fixed-height-mode extension not available") + self._logger.warn( + "ubuntu-almost-fixed-height-mode extension not available") self.set_headers_visible(False) @@ -122,7 +123,7 @@ if isinstance(model, Gtk.TreeModelFilter): return model.get_model() return model - + def clear_model(self): vadjustment = self.get_scrolled_window_vadjustment() if vadjustment: @@ -134,7 +135,7 @@ def expand_path(self, path): if path is not None and not isinstance(path, Gtk.TreePath): - raise TypeError, ("Expects Gtk.TreePath or None, got %s" % + raise TypeError("Expects Gtk.TreePath or None, got %s" % type(path)) model = self.get_model() @@ -142,7 +143,9 @@ self.expanded_path = path if old is not None: - ok, start, end = self.get_visible_range() + res = self.get_visible_range() + if res: + start, end = res if (start and start.compare(old) != -1) or \ (end and end.compare(old) != 1): self._needs_collapse.append(old) @@ -154,34 +157,35 @@ "path is an invalid tree path: '%s'" % old) logging.debug(msg) - if path == None: return + if path == None: + return model.row_changed(path, model.get_iter(path)) - return def get_scrolled_window_vadjustment(self): ancestor = self.get_ancestor(Gtk.ScrolledWindow) if ancestor: return ancestor.get_vadjustment() - return None def get_rowref(self, model, path): - if path == None: - return None - return model[path][AppGenericStore.COL_ROW_DATA] + if path is not None: + return model[path][AppGenericStore.COL_ROW_DATA] def rowref_is_category(self, rowref): return isinstance(rowref, CategoryRowReference) def _on_realize(self, widget, tr): - # connect to backend events once self is realized so handlers + # connect to backend events once self is realized so handlers # have access to the TreeView's initialised Gdk.Window - if self._transactions_connected: return - self.backend.connect("transaction-started", self._on_transaction_started, tr) - self.backend.connect("transaction-finished", self._on_transaction_finished, tr) - self.backend.connect("transaction-stopped", self._on_transaction_stopped, tr) + if self._transactions_connected: + return + self.backend.connect("transaction-started", + self._on_transaction_started, tr) + self.backend.connect("transaction-finished", + self._on_transaction_finished, tr) + self.backend.connect("transaction-stopped", + self._on_transaction_stopped, tr) self._transactions_connected = True - return def _calc_row_heights(self, tr): ypad = StockEms.SMALL @@ -194,13 +198,11 @@ btn_h = btn.height - tr.normal_height = max(32 + 2*ypad, em(2.5) + ypad) + tr.normal_height = max(32 + 2 * ypad, em(2.5) + ypad) tr.selected_height = tr.normal_height + btn_h + StockEms.MEDIUM - return def _on_style_updated(self, widget, tr): self._calc_row_heights(tr) - return def _on_motion(self, tree, event, tr): window = self.get_window() @@ -215,7 +217,8 @@ return rowref = self.get_rowref(tree.get_model(), path[0]) - if not rowref: return + if not rowref: + return if self.rowref_is_category(rowref): window.set_cursor(None) @@ -248,7 +251,6 @@ window.set_cursor(self._cursor_hand) else: window.set_cursor(None) - return def _on_cursor_changed(self, view, tr): model = view.get_model() @@ -256,9 +258,11 @@ path = view.get_cursor()[0] rowref = self.get_rowref(model, path) - if not rowref: return + if not rowref: + return - if self.has_focus(): self.grab_focus() + if self.has_focus(): + self.grab_focus() if self.rowref_is_category(rowref): self.expand_path(None) @@ -266,7 +270,6 @@ sel.select_path(path) self._update_selected_row(view, tr, path) - return def _update_selected_row(self, view, tr, path=None): # keep track of the currently selected row renderer for use when @@ -278,7 +281,7 @@ if not sel: return False model, rows = sel.get_selected_rows() - if not rows: + if not rows: return False row = rows[0] if self.rowref_is_category(row): @@ -343,53 +346,60 @@ x, y = self.get_pointer() for btn in tr.get_buttons(): - if btn.point_in(x, y): + if btn.point_in(x, y): return app = self.appmodel.get_application(rowref) if app: self.app_view.emit("application-activated", app) - return def _on_button_event_get_path(self, view, event): - if event.button != 1: return False + if event.button != 1: + return False res = view.get_path_at_pos(int(event.x), int(event.y)) - if not res: return False + if not res: + return False # check the path is valid and is not a category row path = res[0] - is_cat = self.rowref_is_category(self.get_rowref(view.get_model(), path)) - if path is None or is_cat: return False + is_cat = self.rowref_is_category(self.get_rowref(view.get_model(), + path)) + if path is None or is_cat: + return False # only act when the selection is already there selection = view.get_selection() - if not selection.path_is_selected(path): return False + if not selection.path_is_selected(path): + return False return path def _on_button_press_event(self, view, event, tr): - if not self._on_button_event_get_path(view, event): return + if not self._on_button_event_get_path(view, event): + return self.pressed = True x, y = int(event.x), int(event.y) for btn in tr.get_buttons(): - if btn.point_in(x, y) and (btn.state != Gtk.StateFlags.INSENSITIVE): + if (btn.point_in(x, y) and + (btn.state != Gtk.StateFlags.INSENSITIVE)): self.focal_btn = btn btn.set_state(Gtk.StateFlags.ACTIVE) view.queue_draw() return self.focal_btn = None - return def _on_button_release_event(self, view, event, tr): path = self._on_button_event_get_path(view, event) - if not path: return + if not path: + return self.pressed = False x, y = int(event.x), int(event.y) for btn in tr.get_buttons(): - if btn.point_in(x, y) and (btn.state != Gtk.StateFlags.INSENSITIVE): + if (btn.point_in(x, y) and + (btn.state != Gtk.StateFlags.INSENSITIVE)): btn.set_state(Gtk.StateFlags.NORMAL) self.get_window().set_cursor(self._cursor_hand) if self.focal_btn is not btn: @@ -398,22 +408,23 @@ view.queue_draw() break self.focal_btn = None - return def _on_key_press_event(self, widget, event, tr): kv = event.keyval #print kv r = False - if kv == Gdk.KEY_Right: # right-key + if kv == Gdk.KEY_Right: # right-key btn = tr.get_button_by_name(CellButtonIDs.ACTION) - if btn is None: return # Bug #846779 + if btn is None: + return # Bug #846779 if btn.state != Gtk.StateFlags.INSENSITIVE: btn.has_focus = True btn = tr.get_button_by_name(CellButtonIDs.INFO) btn.has_focus = False - elif kv == Gdk.KEY_Left: # left-key + elif kv == Gdk.KEY_Left: # left-key btn = tr.get_button_by_name(CellButtonIDs.ACTION) - if btn is None: return # Bug #846779 + if btn is None: + return # Bug #846779 btn.has_focus = False btn = tr.get_button_by_name(CellButtonIDs.INFO) btn.has_focus = True @@ -462,7 +473,6 @@ app, model, path) - return def _cell_data_func_cb(self, col, cell, model, it, user_data): @@ -488,12 +498,11 @@ return cell.set_property('isactive', path == self.expanded_path) - return def _app_activated_cb(self, btn, btn_id, app, store, path): - if self.rowref_is_category(app): + if self.rowref_is_category(app): return - + # FIXME: would be nice if that would be more elegant # because we use a treefilter we need to get the "real" # model first @@ -526,7 +535,7 @@ action = AppActions.INSTALL store.notify_action_request(app, path) - + app_manager.request_action( self.appmodel.get_application(app), [], [], action) @@ -540,15 +549,20 @@ if btn.point_in(x, y): window.set_cursor(cursor) - def _on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type, tr): - """ callback when an application install/remove transaction has started """ + def _on_transaction_started(self, backend, pkgname, appname, trans_id, + trans_type, tr): + """callback when an application install/remove transaction has + started + """ action_btn = tr.get_button_by_name(CellButtonIDs.ACTION) if action_btn: action_btn.set_sensitive(False) self._set_cursor(action_btn, None) def _on_transaction_finished(self, backend, result, tr): - """ callback when an application install/remove transaction has finished """ + """callback when an application install/remove transaction has + finished + """ # need to send a cursor-changed so the row button is properly updated self.emit("cursor-changed") # remove pkg from the block list @@ -560,13 +574,15 @@ self._set_cursor(action_btn, self._cursor_hand) def _on_transaction_stopped(self, backend, result, tr): - """ callback when an application install/remove transaction has stopped """ + """callback when an application install/remove transaction has + stopped + """ # remove pkg from the block list self._check_remove_pkg_from_blocklist(result.pkgname) action_btn = tr.get_button_by_name(CellButtonIDs.ACTION) if action_btn: - # this should be a function that decides action button state label... + # this should be a function that decides action button state label if action_btn.current_variant == self.VARIANT_INSTALL: action_btn.set_markup(self.VARIANT_REMOVE) action_btn.set_sensitive(True) @@ -576,7 +592,6 @@ self._update_selected_row(self, tr) # queue a draw just to be sure the view is looking right self.queue_draw() - return def _check_remove_pkg_from_blocklist(self, pkgname): if pkgname in self._action_block_list: @@ -591,7 +606,6 @@ return self.get_path_at_pos(x, y)[0] == self.get_cursor()[0] - def get_test_window(): import softwarecenter.log softwarecenter.log.root.setLevel(level=logging.DEBUG) @@ -623,7 +637,7 @@ with ExecutionTime("query cat '%s'" % cat.name): docs = db.get_docs_from_query(cat.query) store.set_category_documents(cat, docs) - + # ok, this is confusing - the AppView contains the AppTreeView that # is a tree or list depending on the model from softwarecenter.ui.gtk3.views.appview import AppView @@ -640,7 +654,7 @@ win.show_all() return win - + if __name__ == "__main__": win = get_test_window() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/backforward.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/backforward.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/backforward.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/backforward.py 2012-03-14 16:03:50.000000000 +0000 @@ -24,15 +24,16 @@ DEFAULT_PART_SIZE = (28, -1) + class BackForwardButton(Gtk.HBox): - __gsignals__ = {'left-clicked':(GObject.SignalFlags.RUN_LAST, - None, - ()), - - 'right-clicked':(GObject.SignalFlags.RUN_LAST, - None, - ())} + __gsignals__ = {'left-clicked': (GObject.SignalFlags.RUN_LAST, + None, + ()), + + 'right-clicked': (GObject.SignalFlags.RUN_LAST, + None, + ())} def __init__(self, part_size=None): Gtk.HBox.__init__(self) @@ -49,13 +50,14 @@ self.left.connect("clicked", self.on_clicked) self.right.connect("clicked", self.on_clicked) - return def _build_left_right_buttons(self): if self.get_direction() != Gtk.TextDirection.RTL: # ltr - self.left = ButtonPart('left-clicked', arrow_type=Gtk.ArrowType.LEFT) - self.right = ButtonPart('right-clicked', arrow_type=Gtk.ArrowType.RIGHT) + self.left = ButtonPart('left-clicked', + arrow_type=Gtk.ArrowType.LEFT) + self.right = ButtonPart('right-clicked', + arrow_type=Gtk.ArrowType.RIGHT) context = self.left.get_style_context() context.add_class("backforward-left-button") @@ -65,8 +67,10 @@ self.set_button_atk_info_ltr() else: # rtl - self.left = ButtonPart('left-clicked', arrow_type=Gtk.ArrowType.RIGHT) - self.right = ButtonPart('right-clicked', arrow_type=Gtk.ArrowType.LEFT) + self.left = ButtonPart('left-clicked', + arrow_type=Gtk.ArrowType.RIGHT) + self.right = ButtonPart('right-clicked', + arrow_type=Gtk.ArrowType.LEFT) context = self.left.get_style_context() context.add_class("backforward-right-button") @@ -74,11 +78,9 @@ context.add_class("backforward-left-button") self.set_button_atk_info_rtl() - return def on_clicked(self, button): self.emit(button.signal_name) - return def set_button_atk_info_ltr(self): # left button @@ -90,7 +92,6 @@ atk_obj = self.right.get_accessible() atk_obj.set_name(_('Forward Button')) atk_obj.set_description(_('Navigates forward.')) - return def set_button_atk_info_rtl(self): # right button @@ -104,11 +105,9 @@ atk_obj.set_name(_('Forward Button')) atk_obj.set_description(_('Navigates forward.')) atk_obj.set_role(Atk.Role.PUSH_BUTTON) - return def set_use_hand_cursor(self, use_hand): self.use_hand = use_hand - return class ButtonPart(Gtk.Button): @@ -129,7 +128,6 @@ self.arrow.set_margin_right(2) self.add(self.arrow) self.signal_name = signal_name - return def do_draw(self, cr): context = self.get_style_context() @@ -161,8 +159,8 @@ context.restore() - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) # this is used in the automatic tests as well @@ -170,7 +168,7 @@ win = Gtk.Window() win.set_border_width(20) win.connect("destroy", lambda x: Gtk.main_quit()) - win.set_default_size(300,100) + win.set_default_size(300, 100) backforward = BackForwardButton() win.add(backforward) return win diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/buttons.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/buttons.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/buttons.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/buttons.py 2012-03-14 16:03:50.000000000 +0000 @@ -46,7 +46,7 @@ class _Tile(object): - MIN_WIDTH = em(7) + MIN_WIDTH = em(7) def __init__(self): self.set_focus_on_click(False) @@ -54,7 +54,6 @@ self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) self.box.set_size_request(self.MIN_WIDTH, -1) self.add(self.box) - return def build_default(self, label, icon, icon_size): if icon is not None: @@ -67,7 +66,6 @@ self.label = Gtk.Label.new(label) self.box.pack_start(self.label, True, True, 0) - return class TileButton(Gtk.Button, _Tile): @@ -75,7 +73,6 @@ def __init__(self): Gtk.Button.__init__(self) _Tile.__init__(self) - return class TileToggleButton(Gtk.RadioButton, _Tile): @@ -84,7 +81,6 @@ Gtk.RadioButton.__init__(self) self.set_mode(False) _Tile.__init__(self) - return class LabelTile(TileButton): @@ -101,7 +97,6 @@ self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) - return def do_draw(self, cr): cr.save() @@ -111,22 +106,20 @@ Gtk.render_focus(self.get_style_context(), cr, 3, 3, - A.width-6, A.height-6) + A.width - 6, A.height - 6) - for child in self: self.propagate_draw(child, cr) + for child in self: + self.propagate_draw(child, cr) cr.restore() - return def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) - return def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) - return class CategoryTile(TileButton): @@ -145,7 +138,6 @@ self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) - return def do_draw(self, cr): cr.save() @@ -155,25 +147,25 @@ Gtk.render_focus(self.get_style_context(), cr, 3, 3, - A.width-6, A.height-6) + A.width - 6, A.height - 6) - for child in self: self.propagate_draw(child, cr) + for child in self: + self.propagate_draw(child, cr) cr.restore() - return def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) - return def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) - return _global_featured_tile_width = em(11) + + class FeaturedTile(TileButton): INSTALLED_OVERLAY_SIZE = 22 @@ -191,12 +183,13 @@ self.is_installed = helper.is_installed(doc) self._overlay = helper.icons.load_icon(Icons.INSTALLED_OVERLAY, self.INSTALLED_OVERLAY_SIZE, - 0) # flags + 0) # flags self.box.set_orientation(Gtk.Orientation.HORIZONTAL) self.box.set_spacing(StockEms.SMALL) - self.content_left = Gtk.Box.new(Gtk.Orientation.VERTICAL, StockEms.MEDIUM) + self.content_left = Gtk.Box.new(Gtk.Orientation.VERTICAL, + StockEms.MEDIUM) self.content_right = Gtk.Box.new(Gtk.Orientation.VERTICAL, 1) self.box.pack_start(self.content_left, False, False, 0) self.box.pack_start(self.content_right, False, False, 0) @@ -204,7 +197,8 @@ _update_icon(self.image, icon, icon_size) self.content_left.pack_start(self.image, False, False, 0) - self.title = Gtk.Label.new(self._MARKUP % GObject.markup_escape_text(label)) + self.title = Gtk.Label.new(self._MARKUP % + GObject.markup_escape_text(label)) self.title.set_alignment(0.0, 0.5) self.title.set_use_markup(True) self.title.set_ellipsize(Pango.EllipsizeMode.END) @@ -212,7 +206,8 @@ categories = helper.get_categories(doc) if categories is not None: - self.category = Gtk.Label.new('%s' % (em(0.6), GObject.markup_escape_text(categories))) + self.category = Gtk.Label.new('%s' % + (em(0.6), GObject.markup_escape_text(categories))) self.category.set_use_markup(True) self.category.set_alignment(0.0, 0.5) self.category.set_ellipsize(Pango.EllipsizeMode.END) @@ -223,10 +218,11 @@ self.stars = Star(size=StarSize.SMALL) self.stars.render_outline = True self.stars.set_rating(stats.ratings_average) - self.rating_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, StockEms.SMALL) + self.rating_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, + StockEms.SMALL) self.rating_box.pack_start(self.stars, False, False, 0) self.n_ratings = Gtk.Label.new( - ' (%i)' % ( + ' (%i)' % ( em(0.45), stats.ratings_total)) self.n_ratings.set_use_markup(True) self.n_ratings.set_name("subtle-label") @@ -237,8 +233,9 @@ # is not visible in the ui stats_a11y = _('%(stars)d stars - %(reviews)d reviews') % { 'stars': stats.ratings_average, 'reviews': stats.ratings_total} - - #work out width tile needs to be to ensure ratings text is all visible + + # work out width tile needs to be to ensure ratings text is all + # visible req_width = (self.stars.size_request().width + self.image.size_request().width + self.n_ratings.size_request().width + @@ -254,7 +251,7 @@ if price == '0.00': # TRANSLATORS: Free here means Gratis price = _("Free") - # TRANSLATORS: Free here means Gratis + # TRANSLATORS: Free here means Gratis if price != _("Free"): price = 'US$ ' + price self.price = Gtk.Label.new( @@ -266,7 +263,8 @@ self.set_name("featured-tile") - a11y_name = '. '.join([t for t in [label, categories, stats_a11y, price] if t]) + a11y_name = '. '.join([t + for t in [label, categories, stats_a11y, price] if t]) self.get_accessible().set_name(a11y_name) backend = get_install_backend() @@ -278,12 +276,10 @@ self.connect("leave-notify-event", self.on_leave) self.connect("button-press-event", self.on_press) self.connect("button-release-event", self.on_release) - return def _on_needs_refresh(self, helper, pkgname, doc, icon_size): icon = helper.get_icon_at_size(doc, icon_size, icon_size) _update_icon(self.image, icon, icon_size) - def do_get_preferred_width(self): w = _global_featured_tile_width @@ -299,9 +295,10 @@ Gtk.render_focus(self.get_style_context(), cr, 3, 3, - A.width-6, A.height-6) + A.width - 6, A.height - 6) - for child in self: self.propagate_draw(child, cr) + for child in self: + self.propagate_draw(child, cr) if self.is_installed: # paint installed tick overlay @@ -315,18 +312,17 @@ cr.paint() cr.restore() - return def on_transaction_finished(self, backend, result, helper, doc): trans_pkgname = str(result.pkgname) pkgname = helper.get_pkgname(doc) - if trans_pkgname != pkgname: return + if trans_pkgname != pkgname: + return # update installed state helper.update_availability(doc) self.is_installed = helper.is_installed(doc) self.queue_draw() - return def on_enter(self, widget, event): window = self.get_window() @@ -341,13 +337,12 @@ def on_press(self, widget, event): self._pressed = True - return def on_release(self, widget, event): - if not self._pressed: return + if not self._pressed: + return self.emit("clicked") self._pressed = False - return class ChannelSelector(Gtk.Button): @@ -368,7 +363,6 @@ self.section_button = section_button self.popup = None self.connect("button-press-event", self.on_button_press) - return def do_draw(self, cr): cr.save() @@ -381,7 +375,7 @@ cr.set_line_width(1) a = self.get_allocation() - lin = cairo.LinearGradient(0,0,0,a.height) + lin = cairo.LinearGradient(0, 0, 0, a.height) lin.add_color_stop_rgba(0.1, color.red, color.green, @@ -409,24 +403,22 @@ cr.restore() - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) def on_button_press(self, button, event): if self.popup is None: self.build_channel_selector() self.show_channel_sel_popup(self, event) - return -#~ +#~ #~ def on_style_updated(self, widget): #~ context = widget.get_style_context() #~ context.save() #~ context.add_class("menu") #~ bgcolor = context.get_background_color(Gtk.StateFlags.NORMAL) #~ context.restore() -#~ +#~ #~ self._dark_color = darken(bgcolor, 0.5) - #~ return def show_channel_sel_popup(self, widget, event): @@ -443,23 +435,20 @@ window = self.section_button.get_window() self.popup.popup(None, None, position_func, (window, a), event.button, event.time) - return def set_build_func(self, build_func): self.build_func = build_func - return def build_channel_selector(self): self.popup = Gtk.Menu() self.popup.set_name('toolbar-popup') # to set 'padding: 0;' self.popup.get_style_context().add_class('primary-toolbar') self.build_func(self.popup) - return class SectionSelector(TileToggleButton): - MIN_WIDTH = em(5) + MIN_WIDTH = em(5) _MARKUP = '%s' def __init__(self, label, icon, icon_size=Gtk.IconSize.DIALOG): @@ -471,7 +460,7 @@ context = self.get_style_context() context.add_class("section-sel-bg") - + context = self.label.get_style_context() context.add_class("section-sel") @@ -481,23 +470,20 @@ self.connect('size-allocate', self.on_size_allocate) self.connect('style-updated', self.on_style_updated) - return def on_size_allocate(self, *args): alloc = self.get_allocation() - + if (self._alloc is None or self._alloc.width != alloc.width or self._alloc.height != alloc.height): self._alloc = alloc # reset the bg cache self._bg_cache = {} - return def on_style_updated(self, *args): # also reset the bg cache self._bg_cache = {} - return def _cache_bg_for_state(self, state): a = self.get_allocation() @@ -512,9 +498,9 @@ context.set_state(state) Gtk.render_background(context, cr, - -5, -5, a.width+10, a.height+10) + -5, -5, a.width + 10, a.height + 10) Gtk.render_frame(context, cr, - -5, -5, a.width+10, a.height+10) + -5, -5, a.width + 10, a.height + 10) del cr # new surface which will be cached which @@ -524,11 +510,11 @@ # gradient for masking lin = cairo.LinearGradient(0, 0, 0, a.height) - lin.add_color_stop_rgba(0.0, 1,1,1, 0.1) - lin.add_color_stop_rgba(0.25, 1,1,1, 0.7) - lin.add_color_stop_rgba(0.5, 1,1,1, 1.0) - lin.add_color_stop_rgba(0.75, 1,1,1, 0.7) - lin.add_color_stop_rgba(1.0, 1,1,1, 0.1) + lin.add_color_stop_rgba(0.0, 1, 1, 1, 0.1) + lin.add_color_stop_rgba(0.25, 1, 1, 1, 0.7) + lin.add_color_stop_rgba(0.5, 1, 1, 1, 1.0) + lin.add_color_stop_rgba(0.75, 1, 1, 1, 0.7) + lin.add_color_stop_rgba(1.0, 1, 1, 1, 0.1) cr.set_source_surface(_surf, 0, 0) cr.mask(lin) @@ -536,7 +522,6 @@ # cache the resulting surf... self._bg_cache[state] = surf - return def do_draw(self, cr): state = self.get_state_flags() @@ -547,30 +532,29 @@ cr.set_source_surface(self._bg_cache[state], 0, 0) cr.paint() - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) class Link(Gtk.Label): __gsignals__ = { - "clicked" : (GObject.SignalFlags.RUN_LAST, - None, - (),) + "clicked": (GObject.SignalFlags.RUN_LAST, + None, + (),) } def __init__(self, markup="", uri="none"): Gtk.Label.__init__(self) self._handler = 0 self.set_markup(markup, uri) - return def set_markup(self, markup="", uri="none"): markup = '%s' % (uri, markup) Gtk.Label.set_markup(self, markup) if self._handler == 0: - self._handler = self.connect("activate-link", self.on_activate_link) - return + self._handler = self.connect("activate-link", + self.on_activate_link) # synonyms for set_markup def set_label(self, label): @@ -581,7 +565,6 @@ def on_activate_link(self, uri, data): self.emit("clicked") - return def disable(self): self.set_sensitive(False) @@ -591,6 +574,7 @@ self.set_sensitive(True) self.set_name("label") + class MoreLink(Gtk.Button): _MARKUP = '%s' @@ -605,7 +589,6 @@ self._init_event_handling() context = self.get_style_context() context.add_class("more-link") - return def _init_event_handling(self): self.connect("enter-notify-event", self.on_enter) @@ -619,21 +602,20 @@ e = layout.get_pixel_extents()[1] xo, yo = self.label.get_layout_offsets() Gtk.render_focus(self.get_style_context(), cr, - xo-a.x-3, yo-a.y-1, - e.width+6, e.height+2) + xo - a.x - 3, yo - a.y - 1, + e.width + 6, e.height + 2) - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) - return def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) - return + def _build_channels_list(popup): for i in range(3): @@ -643,11 +625,12 @@ box.pack_start(label, False, False, 0) item.add(box) item.show_all() - popup.attach(item, 0, 1, i, i+1) + popup.attach(item, 0, 1, i, i + 1) + def get_test_buttons_window(): win = Gtk.Window() - win.set_size_request(200,200) + win.set_size_request(200, 200) vb = Gtk.VBox(spacing=12) win.add(vb) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/cellrenderers.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/cellrenderers.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/cellrenderers.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/cellrenderers.py 2012-03-14 16:03:50.000000000 +0000 @@ -39,24 +39,23 @@ # size of the install overlay icon OVERLAY_SIZE = 16 - + # ratings MAX_STARS = 5 STAR_SIZE = EM __gproperties__ = { - 'application' : (GObject.TYPE_PYOBJECT, 'document', - 'a xapian document containing pkg information', - GObject.PARAM_READWRITE), - - 'isactive' : (bool, 'isactive', 'is cell active/selected', False, - GObject.PARAM_READWRITE), - } - + 'application': (GObject.TYPE_PYOBJECT, 'document', + 'a xapian document containing pkg information', + GObject.PARAM_READWRITE), + + 'isactive': (bool, 'isactive', 'is cell active/selected', False, + GObject.PARAM_READWRITE), + } def __init__(self, icons, layout, show_ratings, overlay_icon_name): GObject.GObject.__init__(self) - + # the icon pixbuf to be displayed in the row self.icon = None @@ -70,8 +69,10 @@ # button packing self.button_spacing = 0 - self._buttons = {Gtk.PackType.START: [], - Gtk.PackType.END: []} + self._buttons = { + Gtk.PackType.START: [], + Gtk.PackType.END: [] + } self._all_buttons = {} # cache a layout @@ -88,7 +89,6 @@ # icon not present in theme, probably because running uninstalled self._installed = icons.load_icon('emblem-system', self.OVERLAY_SIZE, 0) - return def _layout_get_pixel_width(self, layout): return layout.get_size()[0] / Pango.SCALE @@ -109,12 +109,12 @@ x = cell_area.x else: x = cell_area.x + cell_area.width - lw - y = cell_area.y + (cell_area.height - lh)/2 + y = cell_area.y + (cell_area.height - lh) / 2 Gtk.render_layout(context, cr, x, y, layout) - return - def _render_price(self, context, cr, app, layout, cell_area, xpad, ypad, is_rtl): + def _render_price(self, context, cr, app, layout, cell_area, xpad, ypad, + is_rtl): layout.set_markup("US$ %s" % self.model.get_price(app), -1) if is_rtl: @@ -125,15 +125,14 @@ Gtk.render_layout(context, cr, x, ypad + cell_area.y, layout) - return def _render_icon(self, cr, app, cell_area, xpad, ypad, is_rtl): # calc offsets so icon is nicely centered self.icon = self.model.get_icon(app) self.icon_x_offset = xpad + cell_area.x self.icon_y_offset = ypad + cell_area.y - xo = (self.pixbuf_width - self.icon.get_width())/2 - + xo = (self.pixbuf_width - self.icon.get_width()) / 2 + if not is_rtl: x = cell_area.x + xo + xpad else: @@ -143,7 +142,7 @@ # draw appicon pixbuf Gdk.cairo_set_source_pixbuf(cr, self.icon, x, y) cr.paint() - + # draw overlay if application is installed if self.model.is_installed(app): if not is_rtl: @@ -153,7 +152,6 @@ y += (self.pixbuf_width - self.OVERLAY_SIZE + self.OVERLAY_YO) Gdk.cairo_set_source_pixbuf(cr, self._installed, x, y) cr.paint() - return def _render_summary(self, context, cr, app, cell_area, layout, xpad, ypad, @@ -165,20 +163,21 @@ layout.set_width(-1) lw = self._layout_get_pixel_width(layout) max_layout_width = (cell_area.width - self.pixbuf_width - - 3*xpad - star_width) - - max_layout_width = cell_area.width - self.pixbuf_width - 3*xpad - + 3 * xpad - star_width) + + max_layout_width = cell_area.width - self.pixbuf_width - 3 * xpad + stats = self.model.get_review_stats(app) if self.show_ratings and stats: - max_layout_width -= star_width+6*xpad - - if self.props.isactive and self.model.get_transaction_progress(app) > 0: + max_layout_width -= star_width + 6 * xpad + + if (self.props.isactive and + self.model.get_transaction_progress(app) > 0): action_btn = self.get_button_by_name(CellButtonIDs.ACTION) - max_layout_width -= (xpad + action_btn.width) + max_layout_width -= (xpad + action_btn.width) if lw >= max_layout_width: - layout.set_width((max_layout_width)*Pango.SCALE) + layout.set_width((max_layout_width) * Pango.SCALE) layout.set_ellipsize(Pango.EllipsizeMode.MIDDLE) lw = max_layout_width @@ -187,21 +186,22 @@ self.apptitle_height = apptitle_extents.height if not is_rtl: - x = cell_area.x+2*xpad+self.pixbuf_width + x = cell_area.x + 2 * xpad + self.pixbuf_width else: - x = cell_area.x+cell_area.width-lw-self.pixbuf_width-2*xpad + x = (cell_area.x + cell_area.width - lw - self.pixbuf_width - + 2 * xpad) - y = cell_area.y+ypad + y = cell_area.y + ypad Gtk.render_layout(context, cr, x, y, layout) - return def _render_rating(self, context, cr, app, cell_area, layout, xpad, ypad, star_width, star_height, is_rtl): stats = self.model.get_review_stats(app) - if not stats: return + if not stats: + return sr = self._stars @@ -212,14 +212,14 @@ x = (cell_area.x + cell_area.width - 3 * xpad - self.pixbuf_width - - self.apptitle_width + - self.apptitle_width - star_width) - y = cell_area.y + ypad + (self.apptitle_height-self.STAR_SIZE)/2 + y = cell_area.y + ypad + (self.apptitle_height - self.STAR_SIZE) / 2 sr.rating = stats.ratings_average sr.render_star(context, cr, x, y) - + # and nr-reviews in parenthesis to the right of the title nreviews = stats.ratings_total s = "(%i)" % nreviews @@ -227,19 +227,19 @@ layout.set_markup("%s" % s, -1) if not is_rtl: - x += xpad+star_width + x += xpad + star_width else: - x -= xpad+self._layout_get_pixel_width(layout) + x -= xpad + self._layout_get_pixel_width(layout) context.save() context.add_class("cellrenderer-avgrating-label") Gtk.render_layout(context, cr, x, y, layout) context.restore() - return def _render_progress(self, context, cr, progress, cell_area, ypad, is_rtl): percent = progress * 0.01 - # per the spec, the progressbar should be the width of the action button + # per the spec, the progressbar should be the width of the action + # button action_btn = self.get_button_by_name(CellButtonIDs.ACTION) x, _, w, h = action_btn.allocation @@ -252,26 +252,25 @@ Gtk.render_background(context, cr, x, y, w, h) Gtk.render_frame(context, cr, x, y, w, h) - context.restore () + context.restore() bar_size = w * percent - context.save () - context.add_class ("progressbar") + context.save() + context.add_class("progressbar") if (bar_size > 0): if is_rtl: x += (w - bar_size) Gtk.render_activity(context, cr, x, y, bar_size, h) - context.restore () - return + context.restore() - def _render_buttons( - self, context, cr, cell_area, layout, xpad, ypad, is_rtl): + def _render_buttons(self, context, cr, cell_area, layout, xpad, ypad, + is_rtl): # layout buttons and paint - y = cell_area.y+cell_area.height-ypad + y = cell_area.y + cell_area.height - ypad spacing = self.button_spacing if not is_rtl: @@ -286,30 +285,26 @@ xb = cell_area.x + cell_area.width - 2 * xpad - self.pixbuf_width for btn in self._buttons[start]: - btn.set_position(xs, y-btn.height) + btn.set_position(xs, y - btn.height) btn.render(context, cr, layout) xs += btn.width + spacing for btn in self._buttons[end]: xb -= btn.width - btn.set_position(xb, y-btn.height) + btn.set_position(xb, y - btn.height) btn.render(context, cr, layout) xb -= spacing - return def set_pixbuf_width(self, w): self.pixbuf_width = w - return def set_button_spacing(self, spacing): self.button_spacing = spacing - return def get_button_by_name(self, name): if name in self._all_buttons: return self._all_buttons[name] - return None def get_buttons(self): btns = () @@ -320,15 +315,12 @@ def button_pack(self, btn, pack_type=Gtk.PackType.START): self._buttons[pack_type].append(btn) self._all_buttons[btn.name] = btn - return def button_pack_start(self, btn): self.button_pack(btn, Gtk.PackType.START) - return def button_pack_end(self, btn): self.button_pack(btn, Gtk.PackType.END) - return def do_set_property(self, pspec, value): setattr(self, pspec.name, value) @@ -345,7 +337,8 @@ def do_render(self, cr, widget, bg_area, cell_area, flags): app = self.props.application - if not app: return + if not app: + return self.model = widget.appmodel @@ -356,9 +349,11 @@ is_rtl = widget.get_direction() == Gtk.TextDirection.RTL layout = self._layout - # important! ensures correct text rendering, esp. when using hicolor theme + # important! ensures correct text rendering, esp. when using hicolor + # theme #~ if (flags & Gtk.CellRendererState.SELECTED) != 0: - #~ # this follows the behaviour that gtk+ uses for states in treeviews + #~ # this follows the behaviour that gtk+ uses for states in + #~ # treeviews #~ if widget.has_focus(): #~ state = Gtk.StateFlags.SELECTED #~ else: @@ -388,7 +383,7 @@ xpad, ypad, star_width, is_rtl) - + # only show ratings if we have one if self.show_ratings: self._render_rating(context, cr, app, @@ -421,7 +416,6 @@ is_rtl) context.restore() - return class CellButtonRenderer(object): @@ -441,28 +435,30 @@ self.visible = True self.widget = widget - return def _layout_reset(self, layout): layout.set_width(-1) layout.set_ellipsize(Pango.EllipsizeMode.NONE) - return @property - def x(self): return self.allocation[0] + def x(self): + return self.allocation[0] @property - def y(self): return self.allocation[1] + def y(self): + return self.allocation[1] @property - def width(self): return self.allocation[2] + def width(self): + return self.allocation[2] @property - def height(self): return self.allocation[3] + def height(self): + return self.allocation[3] def configure_geometry(self, layout): self._layout_reset(layout) - max_size = (0,0) + max_size = (0, 0) for k, variant in self.markup_variants.items(): safe_markup = GObject.markup_escape_text(utf8(variant)) @@ -474,8 +470,7 @@ w /= Pango.SCALE h /= Pango.SCALE - self.set_size(w+2*self.xpad, h+2*self.ypad) - return + self.set_size(w + 2 * self.xpad, h + 2 * self.ypad) def point_in(self, px, py): x, y, w, h = self.allocation @@ -487,22 +482,21 @@ def set_position(self, x, y): self.allocation[:2] = int(x), int(y) - return def set_size(self, w, h): self.allocation[2:] = int(w), int(h) - return def set_state(self, state): if not isinstance(state, Gtk.StateFlags): - msg = "state should be of type Gtk.StateFlags, got %s" % type(state) + msg = ("state should be of type Gtk.StateFlags, got %s" % + type(state)) raise TypeError(msg) - elif state == self.state: return + elif state == self.state: + return self.state = state self.widget.queue_draw_area(*self.allocation) - return def set_sensitive(self, is_sensitive): if is_sensitive: @@ -510,7 +504,6 @@ else: state = Gtk.StateFlags.INSENSITIVE self.set_state(state) - return def show(self): self.visible = True @@ -520,22 +513,20 @@ def set_markup(self, markup): self.markup_variant = (markup,) - return def set_markup_variants(self, markup_variants): if not isinstance(markup_variants, dict): msg = type(markup_variants) raise TypeError("Expects a dict object, got %s" % msg) - elif not markup_variants: return + elif not markup_variants: + return self.markup_variants = markup_variants self.current_variant = markup_variants.keys()[0] - return def set_variant(self, current_var): self.current_variant = current_var - return def is_sensitive(self): return self.state == Gtk.StateFlags.INSENSITIVE @@ -576,4 +567,3 @@ context.restore() context.restore() - return diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/containers.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/containers.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/containers.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/containers.py 2012-03-19 19:43:39.000000000 +0000 @@ -1,7 +1,7 @@ import cairo import os from math import pi as PI -PI_OVER_180 = PI/180 +PI_OVER_180 = PI / 180 import softwarecenter.paths from gi.repository import Gtk, Gdk @@ -25,7 +25,6 @@ self.n_rows = 0 self.paint_grid_pattern = paint_grid_pattern self._cell_size = None - return # private def _get_n_columns_for_width(self, width, cell_w, col_spacing): @@ -33,7 +32,8 @@ return n_cols def _layout_children(self, a): - if not self.get_visible(): return + if not self.get_visible(): + return children = self.get_children() width = a.width @@ -45,7 +45,8 @@ cell_w, cell_h = self.get_cell_size() n_cols = self._get_n_columns_for_width(width, cell_w, col_spacing) - if n_cols == 0: return + if n_cols == 0: + return cell_w = width / n_cols self.n_columns = n_cols @@ -54,11 +55,11 @@ #~ xo = h_overhang / (n_cols-1) #~ else: #~ xo = h_overhang - + if len(children) % n_cols: - self.n_rows = len(children)/n_cols + 1 + self.n_rows = len(children) / n_cols + 1 else: - self.n_rows = len(children)/n_cols + self.n_rows = len(children) / n_cols y = 0 for i, child in enumerate(children): @@ -67,7 +68,7 @@ #~ x = a.x + (i % n_cols) * (cell_w + col_spacing + xo) #~ if n_cols == 1: #~ x += xo/2 - if (i%n_cols) == 0: + if (i % n_cols) == 0: y = a.y + (i / n_cols) * (cell_h + row_spacing) child_alloc = child.get_allocation() @@ -76,7 +77,6 @@ child_alloc.width = cell_w child_alloc.height = cell_h child.size_allocate(child_alloc) - return # overrides def do_get_request_mode(self): @@ -84,13 +84,15 @@ def do_get_preferred_height_for_width(self, width): old = self.get_allocation() - if width == old.width: old.height, old.height + if width == old.width: + old.height, old.height cell_w, cell_h = self.get_cell_size() n_cols = self._get_n_columns_for_width( width, cell_w, self.column_spacing) - if not n_cols: return self.MIN_HEIGHT, self.MIN_HEIGHT + if not n_cols: + return self.MIN_HEIGHT, self.MIN_HEIGHT children = self.get_children() n_rows = len(children) / n_cols @@ -99,7 +101,7 @@ if len(children) % n_cols: n_rows += 1 - pref_h = n_rows*cell_h + (n_rows-1)*self.row_spacing + 1 + pref_h = n_rows * cell_h + (n_rows - 1) * self.row_spacing + 1 pref_h = max(self.MIN_HEIGHT, pref_h) return pref_h, pref_h @@ -107,16 +109,16 @@ def do_size_allocate(self, allocation): self.set_allocation(allocation) self._layout_children(allocation) - return def do_draw(self, cr): - if not (self.n_columns and self.n_rows): return + if not (self.n_columns and self.n_rows): + return if self.paint_grid_pattern: self.render_grid(cr) - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) # public def render_grid(self, cr): @@ -135,24 +137,22 @@ w = a.width / self.n_columns for i in range(self.n_columns): - cr.move_to(i*w+0.5, 0) - cr.rel_line_to(0, a.height-3) + cr.move_to(i * w + 0.5, 0) + cr.rel_line_to(0, a.height - 3) cr.stroke() w = a.height / self.n_rows for i in range(self.n_rows): - cr.move_to(2, i*w+0.5) - cr.rel_line_to(a.width-4, 0) + cr.move_to(2, i * w + 0.5) + cr.rel_line_to(a.width - 4, 0) cr.stroke() cr.restore() - return def add_child(self, child): self._cell_size = None self.put(child, 0, 0) - return def get_cell_size(self): if self._cell_size is not None: @@ -170,59 +170,46 @@ def set_row_spacing(self, value): self.row_spacing = value - return def set_column_spacing(self, value): self.column_spacing = value self._layout_children(self.get_allocation()) - return def remove_all(self): self._cell_size = None for child in self: self.remove(child) - return + # first tier of caching, cache component assets from which frames are # rendered _frame_asset_cache = {} + + class Frame(Gtk.Alignment): BORDER_RADIUS = 8 ASSET_TAG = "default" BORDER_IMAGE = os.path.join( softwarecenter.paths.datadir, "ui/gtk3/art/frame-border-image.png") - #~ CORNER_LABEL = os.path.join( - #~ softwarecenter.paths.datadir, "ui/gtk3/art/corner-label.png") def __init__(self, padding=0): Gtk.Alignment.__init__(self) # set padding + some additional padding in the bottom, left and # right edges to factor in the dropshadow width, and ensure even # visual border - self.set_padding(padding, padding+2, padding+1, padding+1) - - # corner lable jazz - #~ self.show_corner_label = False - #~ self.layout = self.create_pango_layout("") - #~ self.layout.set_width(40960) - #~ self.layout.set_ellipsize(Pango.EllipsizeMode.END) + self.set_padding(padding, padding + 2, padding + 1, padding + 1) - #~ assets = self._cache_art_assets() self._cache_art_assets() # second tier of caching, cache resultant surface of # fully composed and rendered frame self._frame_surface_cache = None - #~ self.connect_after("draw", self.on_draw_after, - #~ assets, self.layout) self._allocation = Gdk.Rectangle() self.connect("size-allocate", self.on_size_allocate) self.connect("style-updated", self.on_style_updated) - return def on_style_updated(self, widget): self._frame_surface_cache = None - return def on_size_allocate(self, *args): old = self._allocation @@ -237,7 +224,8 @@ global _frame_asset_cache at = self.ASSET_TAG assets = _frame_asset_cache - if at in assets: return assets + if at in assets: + return assets def cache_corner_surface(tag, xo, yo): sw = sh = cnr_slice @@ -247,7 +235,6 @@ cr.paint() assets[tag] = surf del cr - return def cache_edge_pattern(tag, xo, yo, sw, sh): surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh) @@ -258,7 +245,6 @@ ptrn.set_extend(cairo.EXTEND_REPEAT) assets[tag] = ptrn del cr - return # register the asset tag within the asset_cache assets[at] = 'loaded' @@ -275,24 +261,24 @@ # northern edge pattern cache_edge_pattern("%s-n" % at, -cnr_slice, 0, - w-2*cnr_slice, cnr_slice) + w - 2 * cnr_slice, cnr_slice) # north-east corner - cache_corner_surface("%s-ne" % at, -(w-cnr_slice), 0) + cache_corner_surface("%s-ne" % at, -(w - cnr_slice), 0) # eastern edge pattern cache_edge_pattern("%s-e" % at, - -(w-cnr_slice), -cnr_slice, - cnr_slice, h-2*cnr_slice) + -(w - cnr_slice), -cnr_slice, + cnr_slice, h - 2 * cnr_slice) # south-east corner - cache_corner_surface("%s-se" % at, -(w-cnr_slice), -(h-cnr_slice)) + cache_corner_surface("%s-se" % at, -(w - cnr_slice), -(h - cnr_slice)) # southern edge pattern cache_edge_pattern("%s-s" % at, - -cnr_slice, -(h-cnr_slice), - w-2*cnr_slice, cnr_slice) + -cnr_slice, -(h - cnr_slice), + w - 2 * cnr_slice, cnr_slice) # south-west corner - cache_corner_surface("%s-sw" % at, 0, -(h-cnr_slice)) + cache_corner_surface("%s-sw" % at, 0, -(h - cnr_slice)) # western edge pattern cache_edge_pattern("%s-w" % at, 0, -cnr_slice, - cnr_slice, h-2*cnr_slice) + cnr_slice, h - 2 * cnr_slice) # all done! return assets @@ -300,63 +286,13 @@ cr.save() self.on_draw(cr) cr.restore() - return def on_draw(self, cr): a = self.get_allocation() self.render_frame(cr, a, self.BORDER_RADIUS, _frame_asset_cache) - for child in self: self.propagate_draw(child, cr) - return - - #~ def on_draw_after(self, widget, cr, assets, layout): - #~ if not self.show_corner_label: return - #~ cr.save() - #~ surf = assets["corner-label"] - #~ w = surf.get_width() - #~ h = surf.get_height() - #~ cr.reset_clip() - #~ # the following arbitrary adjustments are specific to the - #~ # corner-label.png image... - - #~ # alter the to allow drawing outside of the widget bounds - #~ cr.rectangle(-10, -10, w+4, h+4) - #~ cr.clip() - #~ cr.set_source_surface(surf, -7, -8) - #~ cr.paint() - #~ # render label - #~ ex = layout.get_pixel_extents()[1] - #~ # transalate to the visual center of the corner-label - #~ cr.translate(19, 18) - #~ # rotate counter-clockwise - #~ cr.rotate(-pi*0.25) - #~ # paint normal markup - #~ Gtk.render_layout(widget.get_style_context(), - #~ cr, -ex.width/2, -ex.height/2, layout) - #~ cr.restore() - #~ return - - def set_show_corner_label(self, show_label): - if (not self.layout.get_text() and - self.show_corner_label == show_label): return - global _frame_asset_cache - assets = _frame_asset_cache - - if "corner-label" not in assets: - # cache corner label - surf = cairo.ImageSurface.create_from_png(self.CORNER_LABEL) - assets["corner-label"] = surf - - self.show_corner_label = show_label - self.queue_draw() - return - - #~ def set_corner_label(self, markup): - #~ markup = '%s' % markup - #~ self.set_show_corner_label(True) - #~ self.layout.set_markup(markup, -1) - #~ self.queue_draw() - #~ return + for child in self: + self.propagate_draw(child, cr) def render_frame(self, cr, a, border_radius, assets): # we cache as much of the drawing as possible @@ -379,56 +315,56 @@ # paint north length _cr.save() _cr.set_source(assets["%s-n" % at]) - _cr.rectangle(cnr_slice, 0, width-2*cnr_slice, cnr_slice) + _cr.rectangle(cnr_slice, 0, width - 2 * cnr_slice, cnr_slice) _cr.clip() _cr.paint() _cr.restore() # paint north-east corner _cr.set_source_surface(assets["%s-ne" % at], - width-cnr_slice, 0) + width - cnr_slice, 0) _cr.paint() # paint east length _cr.save() - _cr.translate(width-cnr_slice, cnr_slice) + _cr.translate(width - cnr_slice, cnr_slice) _cr.set_source(assets["%s-e" % at]) - _cr.rectangle(0, 0, cnr_slice, height-2*cnr_slice) + _cr.rectangle(0, 0, cnr_slice, height - 2 * cnr_slice) _cr.clip() _cr.paint() _cr.restore() # paint south-east corner _cr.set_source_surface(assets["%s-se" % at], - width-cnr_slice, - height-cnr_slice) + width - cnr_slice, + height - cnr_slice) _cr.paint() # paint south length _cr.save() - _cr.translate(cnr_slice, height-cnr_slice) + _cr.translate(cnr_slice, height - cnr_slice) _cr.set_source(assets["%s-s" % at]) - _cr.rectangle(0, 0, width-2*cnr_slice, cnr_slice) + _cr.rectangle(0, 0, width - 2 * cnr_slice, cnr_slice) _cr.clip() _cr.paint() _cr.restore() # paint south-west corner _cr.set_source_surface(assets["%s-sw" % at], - 0, height-cnr_slice) + 0, height - cnr_slice) _cr.paint() # paint west length _cr.save() _cr.translate(0, cnr_slice) _cr.set_source(assets["%s-w" % at]) - _cr.rectangle(0, 0, cnr_slice, height-2*cnr_slice) + _cr.rectangle(0, 0, cnr_slice, height - 2 * cnr_slice) _cr.clip() _cr.paint() _cr.restore() # fill interior - rounded_rect(_cr, 3, 2, a.width-6, a.height-6, border_radius) + rounded_rect(_cr, 3, 2, a.width - 6, a.height - 6, border_radius) context = self.get_style_context() bg = context.get_background_color(self.get_state_flags()) @@ -447,14 +383,13 @@ # paint the cached surface and apply a rounded rect clip to # child draw ops A = self.get_allocation() - xo, yo = a.x-A.x, a.y-A.y + xo, yo = a.x - A.x, a.y - A.y cr.set_source_surface(self._frame_surface_cache, xo, yo) cr.paint() #~ rounded_rect(cr, xo+3, yo+2, a.width-6, a.height-6, border_radius) #~ cr.clip() - return class SmallBorderRadiusFrame(Frame): @@ -462,20 +397,20 @@ BORDER_RADIUS = 3 ASSET_TAG = "small" BORDER_IMAGE = os.path.join( - softwarecenter.paths.datadir, "ui/gtk3/art/frame-border-image-2px-border-radius.png") + softwarecenter.paths.datadir, + "ui/gtk3/art/frame-border-image-2px-border-radius.png") def __init__(self, padding=3): Frame.__init__(self, padding) - return class FramedBox(Frame): - def __init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0, padding=0): + def __init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0, + padding=0): Frame.__init__(self, padding) self.box = Gtk.Box.new(orientation, spacing) Gtk.Alignment.add(self, self.box) - return def add(self, *args, **kwargs): return self.box.add(*args, **kwargs) @@ -496,7 +431,7 @@ class FramedHeaderBox(FramedBox): MARKUP = '%s' - + # pages for the spinner notebook (CONTENT, SPINNER) = range(2) @@ -517,7 +452,6 @@ # finally, a notebook for the spinner and the content box to share self.spinner_notebook = SpinnerNotebook(self.content_box) self.box.add(self.spinner_notebook) - return def on_draw(self, cr): a = self.get_allocation() @@ -525,8 +459,8 @@ a = self.header_alignment.get_allocation() self.render_header(cr, a, Frame.BORDER_RADIUS, _frame_asset_cache) - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) def add(self, *args, **kwargs): return self.content_box.add(*args, **kwargs) @@ -536,7 +470,7 @@ def pack_end(self, *args, **kwargs): return self.content_box.pack_end(*args, **kwargs) - + # XXX: non-functional with current code... #~ def set_header_expand(self, expand): #~ alignment = self.header_alignment @@ -564,13 +498,16 @@ self.title.show() self.title.set_markup(self.MARKUP % label) - return def header_implements_more_button(self): if not hasattr(self, "more"): self.more = MoreLink() self.header.pack_end(self.more, False, False, 0) - return + + def remove_more_button(self): + if hasattr(self, "more"): + self.header.remove(self.more) + del self.more def render_header(self, cr, a, border_radius, assets): @@ -596,35 +533,35 @@ w = ta.width + StockEms.MEDIUM cr.new_sub_path() - cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180) - cr.line_to(x+w, y) - cr.line_to(x+w - StockEms.MEDIUM, y + h/2) - cr.line_to(x+w, y+h) - cr.line_to(x, y+h) + cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180) + cr.line_to(x + w, y) + cr.line_to(x + w - StockEms.MEDIUM, y + h / 2) + cr.line_to(x + w, y + h) + cr.line_to(x, y + h) cr.close_path() cr.fill() - cr.move_to(x+w, y) - cr.line_to(x+w - StockEms.MEDIUM, y + h/2) - cr.line_to(x+w, y+h) + cr.move_to(x + w, y) + cr.line_to(x + w - StockEms.MEDIUM, y + h / 2) + cr.line_to(x + w, y + h) else: x = ta.x - a.x - StockEms.MEDIUM w = ta.width + StockEms.MEDIUM - 1 cr.move_to(x, y) - cr.arc(x+w-r, y+r, r, 270*PI_OVER_180, 0) - cr.line_to(x+w, y+h) - cr.line_to(x, y+h) - cr.line_to(x + StockEms.MEDIUM, y + h/2) + cr.arc(x + w - r, y + r, r, 270 * PI_OVER_180, 0) + cr.line_to(x + w, y + h) + cr.line_to(x, y + h) + cr.line_to(x + StockEms.MEDIUM, y + h / 2) cr.close_path() cr.fill() cr.move_to(x, y) - cr.line_to(x + StockEms.MEDIUM, y + h/2) - cr.line_to(x, y+h) + cr.line_to(x + StockEms.MEDIUM, y + h / 2) + cr.line_to(x, y + h) bc = context.get_border_color(self.get_state_flags()) Gdk.cairo_set_source_rgba(cr, bc) @@ -634,8 +571,8 @@ cr.restore() # paint the containers children - for child in self: self.propagate_draw(child, cr) - return + for child in self: + self.propagate_draw(child, cr) # this is used in the automatic tests diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/description.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/description.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/description.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/description.py 2012-03-14 16:03:50.000000000 +0000 @@ -68,7 +68,6 @@ def __init__(self): dict.__init__(self) self.new_press(None, None, None, False) - return def __setitem__(self, k, v): if k not in EventHelper.VALID_KEYS: @@ -80,7 +79,7 @@ if event is None: self['event'] = None else: - # this should be simply event.copy() but that appears broken + # this should be simply event.copy() but that appears broken # currently(?) self['event'] = EventHelper.ButtonEvent(event) @@ -89,7 +88,6 @@ self['within-selection'] = within_sel self['drag-active'] = False self['drag-context'] = None - return class PangoLayoutProxy(object): @@ -99,7 +97,6 @@ def __init__(self, context): self._layout = Pango.Layout.new(context) - return def xy_to_index(self, x, y): return self._layout.xy_to_index(x, y) @@ -110,7 +107,7 @@ # setter proxies def set_attributes(self, attrs): return self._layout.set_attributes(attrs) - + def set_markup(self, markup): return self._layout.set_markup(markup, -1) @@ -154,7 +151,6 @@ self.allocation = Gdk.Rectangle() self._default_attrs = True self.set_markup(text) - return def __len__(self): return self.length @@ -169,7 +165,6 @@ a.y = y a.width = w a.height = h - return def get_position(self): return self.allocation.x, self.allocation.y @@ -182,7 +177,7 @@ if target_x >= 0: x = target_x - y -= _PS*self.widget.line_height + y -= _PS * self.widget.line_height return layout.xy_to_index(x, y), (x, y) def cursor_down(self, cursor, target_x=-1): @@ -193,36 +188,37 @@ if target_x >= 0: x = target_x - y += _PS*self.widget.line_height + y += _PS * self.widget.line_height return layout.xy_to_index(x, y), (x, y) def index_at(self, px, py): #wa = self.widget.get_allocation() - x, y = self.get_position() # layout allocation - (_, index, k) = self.xy_to_index((px-x)*_PS, (py-y)*_PS) + x, y = self.get_position() # layout allocation + (_, index, k) = self.xy_to_index((px - x) * _PS, (py - y) * _PS) return point_in(self.allocation, px, py), index + k def reset_attrs(self): #~ self.set_attributes(Pango.AttrList()) self.set_markup(self.get_text()) self._default_attrs = True - return def highlight(self, start, end, bg, fg): # FIXME: AttrBackground doesnt seem to be expose by gi yet?? #~ attrs = Pango.AttrList() - #~ attrs.insert(Pango.AttrBackground(bg.red, bg.green, bg.blue, start, end)) - #~ attrs.insert(Pango.AttrForeground(fg.red, fg.green, fg.blue, start, end)) + #~ attrs.insert(Pango.AttrBackground(bg.red, bg.green, bg.blue, start, + #~ end)) + #~ attrs.insert(Pango.AttrForeground(fg.red, fg.green, fg.blue, start, + #~ end)) #~ self.set_attributes(attrs) # XXX: workaround text = self.get_text() - new_text = text[:start] + '' % (bg, fg) + new_text = (text[:start] + '' % + (bg, fg)) new_text += text[start:end] new_text += '' + text[end:] self.set_markup(new_text) self._default_attrs = False - return def highlight_all(self, bg, fg): # FIXME: AttrBackground doesnt seem to be expose by gi yet?? @@ -233,9 +229,9 @@ # XXX: workaround text = self.get_text() - self.set_markup('%s' % (bg, fg, text)) + self.set_markup('%s' % + (bg, fg, text)) self._default_attrs = False - return class Cursor(object): @@ -258,7 +254,6 @@ other_pos = cursor.get_position() self.set_position(*other_pos) cursor.set_position(*this_pos) - return def same_line(self, cursor): return self.get_current_line()[0] == cursor.get_current_line()[0] @@ -266,7 +261,7 @@ def get_current_line(self): keep_going = True i, it = self.index, self.parent.order[self.paragraph].get_iter() - ln = 0 + ln = 0 while keep_going: l = it.get_line() ls = l.start_index @@ -292,7 +287,7 @@ return self.paragraph, (start, j) elif text[j] in self.WORD_TERMINATORS: - start = j+1 + start = j + 1 keep_going = it.next_char() return self.paragraph, (start, len(layout)) @@ -311,7 +306,7 @@ Cursor.__init__(self, parent) def __repr__(self): - return 'Cursor: '+str((self.paragraph, self.index)) + return 'Cursor: ' + str((self.paragraph, self.index)) def get_rectangle(self, layout, a): if self.index < len(layout): @@ -319,15 +314,14 @@ else: pos = layout.get_cursor_pos(len(layout))[1] - x = layout.allocation.x + pos.x/_PS - y = layout.allocation.y + pos.y/_PS - return x, y, 1, pos.height/_PS + x = layout.allocation.x + pos.x / _PS + y = layout.allocation.y + pos.y / _PS + return x, y, 1, pos.height / _PS def draw(self, cr, layout, a): - cr.set_source_rgb(0,0,0) + cr.set_source_rgb(0, 0, 0) cr.rectangle(*self.get_rectangle(layout, a)) cr.fill() - return def zero(self): self.index = 0 @@ -344,7 +338,7 @@ self.restore_point = None def __repr__(self): - return 'Selection: '+str(self.get_range()) + return 'Selection: ' + str(self.get_range()) def __nonzero__(self): c = self.cursor @@ -372,7 +366,6 @@ def set_target_x(self, x, indent): self.target_x = x self.target_x_indent = indent - return def get_range(self): return self.min, self.max @@ -401,10 +394,10 @@ self.set_size_request(200, -1) self.set_can_focus(True) - self.set_events(Gdk.EventMask.KEY_PRESS_MASK| - Gdk.EventMask.ENTER_NOTIFY_MASK| - Gdk.EventMask.LEAVE_NOTIFY_MASK| - Gdk.EventMask.BUTTON_RELEASE_MASK| + self.set_events(Gdk.EventMask.KEY_PRESS_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | + Gdk.EventMask.LEAVE_NOTIFY_MASK | + Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK) self._is_new = False @@ -431,7 +424,8 @@ self.menu.append(self.select_all_menuitem) self.menu.show_all() self.copy_menuitem.connect('select', self._menu_do_copy, sel) - self.select_all_menuitem.connect('select', self._menu_do_select_all, cur, sel) + self.select_all_menuitem.connect('select', self._menu_do_select_all, + cur, sel) #~ Gtk.drag_source_set(self, Gdk.ModifierType.BUTTON1_MASK, #~ None, Gdk.DragAction.COPY) @@ -441,9 +435,12 @@ event_helper = EventHelper() - self.connect('button-press-event', self._on_press, event_helper, cur, sel) - self.connect('button-release-event', self._on_release, event_helper, cur, sel) - self.connect('motion-notify-event', self._on_motion, event_helper, cur, sel) + self.connect('button-press-event', self._on_press, event_helper, cur, + sel) + self.connect('button-release-event', self._on_release, event_helper, + cur, sel) + self.connect('motion-notify-event', self._on_motion, event_helper, + cur, sel) self.connect('key-press-event', self._on_key_press, cur, sel) self.connect('key-release-event', self._on_key_release, cur, sel) self.connect('focus-in-event', self._on_focus_in) @@ -451,7 +448,6 @@ self.connect("size-allocate", self.on_size_allocate) self.connect('style-updated', self._on_style_updated) - return def on_size_allocate(self, *args): allocation = self.get_allocation() @@ -459,20 +455,20 @@ x = y = 0 for layout in self.order: - layout.set_width(_PS*(width-layout.indent)) + layout.set_width(_PS * (width - layout.indent)) if layout.index > 0: y += (layout.vspacing or self.line_height) e = layout.get_pixel_extents() if self.get_direction() != Gtk.TextDirection.RTL: - layout.set_allocation(e.x+layout.indent, y+e.y, - width-layout.indent, e.height) + layout.set_allocation(e.x + layout.indent, y + e.y, + width - layout.indent, e.height) else: - layout.set_allocation(x+width-e.x-e.width-layout.indent-1, y+e.y, - width-layout.indent, e.height) + layout.set_allocation(x + width - e.x - e.width - + layout.indent - 1, y + e.y, width - layout.indent, + e.height) y += e.y + e.height - return # overrides def do_get_request_mode(self): @@ -483,7 +479,7 @@ layout = self._test_layout for l in self.order: layout.set_text(l.get_text(), -1) - layout.set_width(_PS*(width-l.indent)) + layout.set_width(_PS * (width - l.indent)) lh = layout.get_pixel_extents()[1].height height += lh + (l.vspacing or self.line_height) @@ -492,7 +488,6 @@ def do_draw(self, cr): self.render(self, cr) - return def _config_colors(self): context = self.get_style_context() @@ -505,40 +500,37 @@ self._bg = color_to_hex(context.get_background_color(state)) self._fg = color_to_hex(context.get_color(state)) context.restore() - return def _on_style_updated(self, widget): self._config_colors() self._update_cached_layouts() - return # def _on_drag_begin(self, widgets, context, event_helper): # print 'drag: begin' -# return - def _on_drag_data_get(self, widget, context, selection, info, timestamp, sel): + def _on_drag_data_get(self, widget, context, selection, info, timestamp, + sel): # print 'drag: get data' text = self.get_selected_text(sel) selection.set_text(text, -1) - return def _on_focus_in(self, widget, event): self._config_colors() - return def _on_focus_out(self, widget, event): self._config_colors() - return def _on_motion(self, widget, event, event_helper, cur, sel): - if not (event.state == Gdk.ModifierType.BUTTON1_MASK):# or not self.has_focus(): + if not (event.state == Gdk.ModifierType.BUTTON1_MASK): + # or not self.has_focus(): return # check if we have moved enough to count as a drag press = event_helper['event'] # mvo: how can this be? - if not press: return + if not press: + return start_x, start_y = int(press.x), int(press.y) cur_x, cur_y = int(event.x), int(event.y) @@ -547,18 +539,18 @@ self.drag_check_threshold(start_x, start_y, cur_x, cur_y)): event_helper['drag-active'] = True - if not event_helper['drag-active']: + if not event_helper['drag-active']: return - #~ if (event_helper['within-selection'] and + #~ if (event_helper['within-selection'] and #~ not event_helper['drag-context']): #~ target_list = Gtk.TargetList() #~ target_list.add_text_targets(80) - #~ ctx = self.drag_begin(target_list, # target list - #~ Gdk.DragAction.COPY, # action - #~ 1, # initiating button - #~ event) # event -#~ + #~ ctx = self.drag_begin(target_list, # target list + #~ Gdk.DragAction.COPY, # action + #~ 1, # initiating button + #~ event) # event +#~ #~ event_helper['drag-context'] = ctx #~ return @@ -596,13 +588,14 @@ cur.set_position(layout.index, index) sel.clear() - #~ event_helper.new_press(event.copy(), layout, index, within_sel) + #~ event_helper.new_press(event.copy(), layout, index, + #~ within_sel) event_helper.new_press(event, layout, index, within_sel) break - return def _on_release(self, widget, event, event_helper, cur, sel): - if not event_helper['event']: return + if not event_helper['event']: + return # check if a drag occurred if event_helper['drag-active']: @@ -622,7 +615,6 @@ self._3click_select(cur, sel) self.queue_draw() - return def _menu_do_copy(self, item, sel): self._copy_text(sel) @@ -639,16 +631,15 @@ if not sel: self.copy_menuitem.set_sensitive(False) elif start == (0, 0) and \ - end == (len(self.order)-1, len(self.order[-1])): + end == (len(self.order) - 1, len(self.order[-1])): self.select_all_menuitem.set_sensitive(False) - self.menu.popup(None, # parent_menu_shell, - None, # parent_menu_item, - None, # GtkMenuPositionFunc func, - None, # data, + self.menu.popup(None, # parent_menu_shell, + None, # parent_menu_item, + None, # GtkMenuPositionFunc func, + None, # data, event.button, event.time) - return def _on_key_press(self, widget, event, cur, sel): kv = event.keyval @@ -676,7 +667,7 @@ pos = layout.index_to_pos(cur.index) sel.set_target_x(pos.x, layout.indent) - elif kv == Gdk.KEY_Right: + elif kv == Gdk.KEY_Right: if ctrl: self._select_right_word(cur, sel, s, i) else: @@ -701,7 +692,7 @@ elif kv == Gdk.KEY_Down: if ctrl: if i == len(self._get_layout(cur)): - if s+1 < len(self.order): + if s + 1 < len(self.order): cur.paragraph += 1 i = len(self._get_layout(cur)) cur.set_position(cur.paragraph, i) @@ -720,7 +711,7 @@ if shift: self._select_end(cur, sel, self.order[cur.paragraph]) else: - cur.paragraph = len(self.order)-1 + cur.paragraph = len(self.order) - 1 cur.index = len(self._get_layout(cur)) else: @@ -742,7 +733,6 @@ self._copy_text(sel) self.queue_draw() - return def _select_up(self, cur, sel): #~ if sel and not cur.is_min(sel) and cur.same_line(sel): @@ -767,13 +757,13 @@ cur.set_position(s, j) elif s > 0: - cur.paragraph = s-1 + cur.paragraph = s - 1 layout = self._get_layout(cur) if sel.target_x_indent: x += (sel.target_x_indent - layout.indent) * _PS y = layout.get_extents()[0].height (_, j, k) = layout.xy_to_index(x, y) - cur.set_position(s-1, j+k) + cur.set_position(s - 1, j + k) else: return False @@ -802,13 +792,13 @@ cur.set_position(s, j) elif s < len(self.order) - 1: - cur.paragraph = s+1 + cur.paragraph = s + 1 layout = self._get_layout(cur) if sel.target_x_indent: x += (sel.target_x_indent - layout.indent) * _PS y = 0 (_, j, k) = layout.xy_to_index(x, y) - cur.set_position(s+1, j+k) + cur.set_position(s + 1, j + k) else: return False @@ -816,7 +806,6 @@ def _2click_select(self, cursor, sel): self._select_word(cursor, sel) - return def _3click_select(self, cursor, sel): # XXX: @@ -827,7 +816,6 @@ # ... which can result in a segfault #~ self._select_line(cursor, sel) self._select_all(cursor, sel) - return def _copy_text(self, sel): @@ -840,7 +828,6 @@ self.clipboard.clear() self.clipboard.set_text(text.strip(), -1) - return def _select_end(self, cur, sel, layout): if not cur.is_max(sel): @@ -849,7 +836,7 @@ n, r, line = cur.get_current_line() cur_pos = cur.get_position() - if cur_pos == (len(self.order)-1, len(self.order[-1])): # absolute end + if cur_pos == (len(self.order) - 1, len(self.order[-1])): # abs end if sel.restore_point: # reinstate restore point cur.set_position(*sel.restore_point) @@ -860,7 +847,7 @@ elif cur_pos[1] == len(self.order[n[0]]): # para end # select abs end - cur.set_position(len(self.order)-1, len(self.order[-1])) + cur.set_position(len(self.order) - 1, len(self.order[-1])) elif cur_pos == (n[0], r[1]): # line end # select para end @@ -871,7 +858,6 @@ if sel: sel.restore_point = cur_pos cur.set_position(n[0], r[1]) - return def _select_home(self, cur, sel, layout): if not cur.is_min(sel): @@ -888,7 +874,7 @@ cur.set_position(n[0], r[0]) elif cur_pos[1] == 0: # para home - cur.set_position(0,0) + cur.set_position(0, 0) elif cur_pos == (n[0], r[0]): # line home cur.set_position(n[0], 0) @@ -897,28 +883,25 @@ if sel: sel.restore_point = cur_pos cur.set_position(n[0], r[0]) - return def _select_left(self, cur, sel, s, i, shift): if not shift and not cur.is_min(sel): cur.switch(sel) return if i > 0: - cur.set_position(s, i-1) + cur.set_position(s, i - 1) elif cur.paragraph > 0: cur.paragraph -= 1 - cur.set_position(s-1, len(self._get_layout(cur))) - return + cur.set_position(s - 1, len(self._get_layout(cur))) def _select_right(self, cur, sel, s, i, shift): if not shift and not cur.is_max(sel): cur.switch(sel) return if i < len(self._get_layout(cur)): - cur.set_position(s, i+1) - elif s < len(self.order)-1: - cur.set_position(s+1, 0) - return + cur.set_position(s, i + 1) + elif s < len(self.order) - 1: + cur.set_position(s + 1, 0) def _select_left_word(self, cur, sel, s, i): if i > 0: @@ -928,31 +911,30 @@ cur.index = len(self._get_layout(cur)) paragraph, word = cur.get_current_word() - if not word: return - cur.set_position(paragraph, max(0, word[0]-1)) - return + if not word: + return + cur.set_position(paragraph, max(0, word[0] - 1)) def _select_right_word(self, cur, sel, s, i): ll = len(self._get_layout(cur)) if i < ll: cur.index += 1 - elif s+1 < len(self.order): + elif s + 1 < len(self.order): cur.paragraph += 1 cur.index = 0 paragraph, word = cur.get_current_word() - if not word: return - cur.set_position(paragraph, min(word[1]+1, ll)) - return + if not word: + return + cur.set_position(paragraph, min(word[1] + 1, ll)) def _select_word(self, cursor, sel): paragraph, word = cursor.get_current_word() if word: - cursor.set_position(paragraph, word[1]+1) + cursor.set_position(paragraph, word[1] + 1) sel.set_position(paragraph, word[0]) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) - return def _select_line(self, cursor, sel): n, r = self.cursor.get_current_line() @@ -960,7 +942,6 @@ cursor.set_position(n[0], r[1]) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) - return def _select_all(self, cursor, sel): layout = self.order[-1] @@ -968,7 +949,6 @@ cursor.set_position(layout.index, len(layout)) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) - return def _selection_copy(self, layout, sel, new_para=True): i = layout.index @@ -983,18 +963,18 @@ if i == start[0]: if end[0] > i: - return text+layout.get_text()[start[1]: len(layout)] + return text + layout.get_text()[start[1]: len(layout)] else: - return text+layout.get_text()[start[1]: end[1]] + return text + layout.get_text()[start[1]: end[1]] elif i == end[0]: if start[0] < i: - return text+layout.get_text()[0: end[1]] + return text + layout.get_text()[0: end[1]] else: - return text+layout.get_text()[start[1]: end[1]] + return text + layout.get_text()[start[1]: end[1]] else: - return text+layout.get_text() + return text + layout.get_text() return '' def _new_layout(self, text=''): @@ -1011,8 +991,7 @@ self._bullet.set_font_description(font_desc) e = self._bullet.get_pixel_extents() - self.indent, self.line_height = e.width, e.height - return + self.indent, self.line_height = e.width, e.height def _selection_highlight(self, layout, sel, bg, fg): i = layout.index @@ -1035,7 +1014,6 @@ elif not layout._default_attrs: layout.reset_attrs() - return def _paint_bullet_point(self, cr, x, y): # draw the layout @@ -1055,7 +1033,7 @@ return self.order[self.selection.paragraph] def render(self, widget, cr): - if not self.order: + if not self.order: return a = self.get_allocation() @@ -1076,7 +1054,7 @@ if self.DEBUG_PAINT_BBOXES: la = layout.allocation cr.rectangle(la.x, la.y, la.width, la.height) - cr.set_source_rgb(1,0,0) + cr.set_source_rgb(1, 0, 0) cr.stroke() # draw the layout @@ -1089,7 +1067,6 @@ # draw the cursor if self.PAINT_PRIMARY_CURSOR and self.has_focus(): self.cursor.draw(cr, self._get_layout(self.cursor), a) - return def append_paragraph(self, p, vspacing=None): l = self._new_layout() @@ -1097,7 +1074,6 @@ l.vspacing = vspacing l.set_text(p) self.order.append(l) - return def append_bullet(self, point, indent_level, vspacing=None): l = self._new_layout() @@ -1108,11 +1084,9 @@ l.set_text(point) self.order.append(l) - return def copy_clipboard(self): self._copy_text(self.selection) - return def get_selected_text(self, sel=None): text = '' @@ -1125,23 +1099,20 @@ def select_all(self): self._select_all(self.cursor, self.selection) self.queue_draw() - return def finished(self): self.queue_resize() - return def clear(self, key=None): self.cursor.zero() self.selection.clear(key) self.order = [] - return class AppDescription(Gtk.VBox): TYPE_PARAGRAPH = 0 - TYPE_BULLET = 1 + TYPE_BULLET = 1 _preparser = _SpecialCasePreParsers() @@ -1150,7 +1121,6 @@ self.description = TextBlock() self.pack_start(self.description, False, False, 0) self._prev_type = None - return def _part_is_bullet(self, part): # normalize_description() ensures that we only have "* " bullets @@ -1158,8 +1128,8 @@ return i > -1, i def _parse_desc(self, desc, pkgname): - """ Attempt to maintain original fixed width layout, while - reconstructing the description into text blocks + """ Attempt to maintain original fixed width layout, while + reconstructing the description into text blocks (either paragraphs or bullets) which are line-wrap friendly. """ # pre-parse descrition if special case exists for the given pkgname @@ -1167,7 +1137,8 @@ parts = normalize_package_description(desc).split('\n') for part in parts: - if not part: continue + if not part: + continue is_bullet, indent = self._part_is_bullet(part) if is_bullet: self.append_bullet(part, indent) @@ -1175,28 +1146,24 @@ self.append_paragraph(part) self.description.finished() - return def clear(self): self.description.clear() - return def append_paragraph(self, p): vspacing = self.description.line_height self.description.append_paragraph(p.strip(), vspacing) self._prev_type = self.TYPE_PARAGRAPH - return def append_bullet(self, point, indent_level): if self._prev_type == self.TYPE_BULLET: - vspacing = int(0.4*self.description.line_height) + vspacing = int(0.4 * self.description.line_height) else: vspacing = self.description.line_height self.description.append_bullet( - point[indent_level+2:], indent_level, vspacing) + point[indent_level + 2:], indent_level, vspacing) self._prev_type = self.TYPE_BULLET - return def set_description(self, raw_desc, pkgname): self.clear() @@ -1207,7 +1174,6 @@ self._text = GObject.markup_escape_text(encoded_desc) self._parse_desc(self._text, pkgname) self.show_all() - return # easy access to some TextBlock methods def copy_clipboard(self): @@ -1221,21 +1187,29 @@ def get_test_description_window(): - EXAMPLE0 = """ -p7zip is the Unix port of 7-Zip, a file archiver that archives with very high compression ratios. + EXAMPLE0 = """p7zip is the Unix port of 7-Zip, a file archiver that \ +archives with very high compression ratios. p7zip-full provides: - - /usr/bin/7za a standalone version of the 7-zip tool that handles - 7z archives (implementation of the LZMA compression algorithm) and some other formats. - - - /usr/bin/7z not only does it handle 7z but also ZIP, Zip64, CAB, RAR, ARJ, GZIP, - BZIP2, TAR, CPIO, RPM, ISO and DEB archives. 7z compression is 30-50% better than ZIP compression. - -p7zip provides 7zr, a light version of 7za, and p7zip a gzip like wrapper around 7zr.""".strip() - - - EXAMPLE1 = """Transmageddon supports almost any format as its input and can generate a very large host of output files. The goal of the application was to help people to create the files they need to be able to play on their mobile devices and for people not hugely experienced with multimedia to generate a multimedia file without having to resort to command line tools with ungainly syntaxes. + - /usr/bin/7za a standalone version of the 7-zip tool that handles + 7z archives (implementation of the LZMA compression algorithm) and some \ +other formats. + + - /usr/bin/7z not only does it handle 7z but also ZIP, Zip64, CAB, RAR, \ +ARJ, GZIP, + BZIP2, TAR, CPIO, RPM, ISO and DEB archives. 7z compression is 30-50% \ +better than ZIP compression. + +p7zip provides 7zr, a light version of 7za, and p7zip a gzip like wrapper \ +around 7zr.""" + + EXAMPLE1 = """Transmageddon supports almost any format as its input and \ +can generate a very large host of output files. The goal of the application \ +was to help people to create the files they need to be able to play on their \ +mobile devices and for people not hugely experienced with multimedia to \ +generate a multimedia file without having to resort to command line tools \ +with ungainly syntaxes. The currently supported codecs are: * Containers: - Ogg @@ -1265,8 +1239,8 @@ - DNxHD It also provide the support for the GStreamer's plugins auto-search.""" - - EXAMPLE2 = """File-roller is an archive manager for the GNOME environment. It allows you to: + EXAMPLE2 = """File-roller is an archive manager for the GNOME \ +environment. It allows you to: * Create and modify archives. * View the content of an archive. * View a file contained in an archive. @@ -1283,7 +1257,8 @@ * Lha archives (.lzh) * Single files compressed with gzip (.gz), bzip (.bz), bzip2 (.bz2), compress (.Z), lzip (.lz), lzop (.lzo), lzma (.lzma) and xz (.xz) -File-roller doesn't perform archive operations by itself, but relies on standard tools for this.""" +File-roller doesn't perform archive operations by itself, but relies on \ +standard tools for this.""" EXAMPLE3 = """This package includes the following CTAN packages: Asana-Math -- A font to typeset maths in Xe(La)TeX. @@ -1422,9 +1397,11 @@ yfonts -- Support for old German fonts. zefonts -- Virtual fonts to provide T1 encoding from existing fonts.""" - - EXAMPLE4 = """Arista is a simple multimedia transcoder, it focuses on being easy to use by making complex task of encoding for various devices simple. -Users should pick an input and a target device, choose a file to save to and go. Features: + EXAMPLE4 = """Arista is a simple multimedia transcoder, it focuses on \ +being easy to use by making complex task of encoding for various devices \ +simple. +Users should pick an input and a target device, choose a file to save to and \ +go. Features: * Presets for iPod, computer, DVD player, PSP, Playstation 3, and more. * Live preview to see encoded quality. * Automatically discover available DVD media and Video 4 Linux (v4l) devices. @@ -1438,13 +1415,12 @@ if widget.position >= len(descs): widget.position = 0 desc_widget.set_description(*descs[widget.position]) - return - descs = ((EXAMPLE0,''), - (EXAMPLE1,''), - (EXAMPLE2,''), - (EXAMPLE3,'texlive-fonts-extra'), - (EXAMPLE4,'')) + descs = ((EXAMPLE0, ''), + (EXAMPLE1, ''), + (EXAMPLE2, ''), + (EXAMPLE3, 'texlive-fonts-extra'), + (EXAMPLE4, '')) win = Gtk.Window() win.set_default_size(300, 400) @@ -1463,11 +1439,10 @@ win.show_all() b.connect("clicked", on_clicked, d, descs) - win.connect('destroy',lambda x: Gtk.main_quit()) + win.connect('destroy', lambda x: Gtk.main_quit()) return win if __name__ == '__main__': win = get_test_description_window() win.show_all() Gtk.main() - diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/exhibits.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/exhibits.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/exhibits.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/exhibits.py 2012-03-20 08:40:44.000000000 +0000 @@ -32,12 +32,11 @@ from softwarecenter.utils import SimpleFileDownloader from softwarecenter.ui.gtk3.em import StockEms -from softwarecenter.ui.gtk3.shapes import Circle from softwarecenter.ui.gtk3.drawing import rounded_rect from softwarecenter.ui.gtk3.utils import point_in import softwarecenter.paths -LOG=logging.getLogger(__name__) +LOG = logging.getLogger(__name__) _asset_cache = {} _HAND = Gdk.Cursor.new(Gdk.CursorType.HAND2) @@ -75,34 +74,40 @@ """ + class FeaturedExhibit(object): def __init__(self): self.id = 0 - self.package_names = "armagetronad,calibre,cheese,homebank,stellarium,gimp,inkscape,blender,audacity,gufw,frozen-bubble,fretsonfire,moovida,liferea,arista,gtg,freeciv-client-gtk,openshot,supertuxkart,tumiki-fighters,tuxpaint,webservice-office-zoho" + self.package_names = ("armagetronad,calibre,cheese,homebank," + "stellarium,gimp,inkscape,blender,audacity,gufw,frozen-bubble," + "fretsonfire,moovida,liferea,arista,gtg,freeciv-client-gtk," + "openshot,supertuxkart,tumiki-fighters,tuxpaint," + "webservice-office-zoho") self.title_translated = _("Our star apps") self.published = True - self.banner_url = "file:%s" % (os.path.abspath(os.path.join(softwarecenter.paths.datadir, "default_banner/fallback.png"))) - self.html = EXHIBIT_HTML % { - 'banner_url' : self.banner_url, - 'title' : _("Our star apps"), - 'subtitle' : _("Come and explore our favourites"), - } + self.banner_url = "file:%s" % (os.path.abspath(os.path.join( + softwarecenter.paths.datadir, "default_banner/fallback.png"))) + self.html = EXHIBIT_HTML % { + 'banner_url': self.banner_url, + 'title': _("Our star apps"), + 'subtitle': _("Come and explore our favourites"), + } # we should extract this automatically from the html #self.atk_name = _("Default Banner") - #self.atk_description = _("You see this banner because you have no cached banners") + #self.atk_description = _("You see this banner because you have no " + # "cached banners") class _HtmlRenderer(Gtk.OffscreenWindow): __gsignals__ = { - "render-finished" : (GObject.SignalFlags.RUN_LAST, - None, + "render-finished": (GObject.SignalFlags.RUN_LAST, + None, (), ) } - def __init__(self): Gtk.OffscreenWindow.__init__(self) self.view = WebKit.WebView() @@ -120,11 +125,10 @@ self.on_download_error) self.exhibit = None self.view.connect("notify::load-status", self._on_load_status) - return def _on_load_status(self, view, prop): - if view.get_property("load-status") == WebKit.LoadStatus.FINISHED: - # this needs to run with a timeout because otherwise the + if view.get_property("load-status") == WebKit.LoadStatus.FINISHED: + # this needs to run with a timeout because otherwise the # status is emited before the offscreen image is finihsed GObject.timeout_add(100, lambda: self.emit("render-finished")) @@ -137,26 +141,25 @@ if hasattr(self.exhibit, "html") and self.exhibit.html: html = self.exhibit.html else: - html = EXHIBIT_HTML % { 'banner_url' : self.exhibit.banner_url, - 'title' : self.exhibit.title_translated, - 'subtitle' : "", - } - # replace the server side path with the local image name, this + html = EXHIBIT_HTML % { + 'banner_url': self.exhibit.banner_url, + 'title': self.exhibit.title_translated, + 'subtitle': "", + } + # replace the server side path with the local image name, this # assumes that the image always comes from the same server as # the html scheme, netloc, server_path, para, query, frag = urlparse( self.exhibit.banner_url) html = html.replace(server_path, image_name) - self.view.load_string(html, "text/html", "UTF-8", + self.view.load_string(html, "text/html", "UTF-8", "file:%s/" % cache_dir) - return def set_exhibit(self, exhibit): self.exhibit = exhibit - self.loader.download_file(exhibit.banner_url, + self.loader.download_file(exhibit.banner_url, use_cache=True, simple_quoting_for_webkit=True) - return class ExhibitButton(Gtk.Button): @@ -183,7 +186,6 @@ self._dropshadow = self.DROPSHADOW.scale_simple( a.width, a.width, GdkPixbuf.InterpType.BILINEAR) self._margin = int(float(a.width) / self.DROPSHADOW.get_width() * 15) - return def do_draw(self, cr): a = self.get_allocation() @@ -194,9 +196,20 @@ y = (a.height - ds_h) / 2 Gdk.cairo_set_source_pixbuf(cr, self._dropshadow, 0, y) cr.paint() - Circle.layout(cr, self._margin, (a.height-ds_h)/2 + self._margin, - a.width-2*self._margin, - a.width-2*self._margin) + + # layout circle + x = self._margin + y = (a.height - ds_h) / 2 + self._margin + w = a.width - 2 * self._margin + h = a.width - 2 * self._margin + cr.new_path() + r = min(w, h) * 0.5 + x += int((w - 2 * r) / 2) + y += int((h - 2 * r) / 2) + from math import pi + cr.arc(r + x, r + y, r, 0, 2 * pi) + cr.close_path() + if self.is_active: color = context.get_background_color(Gtk.StateFlags.SELECTED) else: @@ -205,9 +218,8 @@ Gdk.cairo_set_source_rgba(cr, color) cr.fill() - for child in self: + for child in self: self.propagate_draw(child, cr) - return class ExhibitArrowButton(ExhibitButton): @@ -215,24 +227,24 @@ def __init__(self, arrow_type, shadow_type=Gtk.ShadowType.IN): ExhibitButton.__init__(self) a = Gtk.Alignment() - a.set_padding(1,1,1,1) - a.add(Gtk.Arrow.new(arrow_type, shadow_type)) + a.set_padding(1, 1, 1, 1) + a.add(Gtk.Arrow.new(arrow_type, shadow_type)) self.add(a) - return class ExhibitBanner(Gtk.EventBox): - # FIXME: sometimes despite set_exhibit being called the new exhibit isn't actually displayed + # FIXME: sometimes despite set_exhibit being called the new exhibit isn't + # actually displayed __gsignals__ = { - "show-exhibits-clicked" : (GObject.SignalFlags.RUN_LAST, - None, - (GObject.TYPE_PYOBJECT,), - ) + "show-exhibits-clicked": (GObject.SignalFlags.RUN_LAST, + None, + (GObject.TYPE_PYOBJECT,), + ) } DROPSHADOW_HEIGHT = 11 - MAX_HEIGHT = 200 # pixels + MAX_HEIGHT = 200 # pixels TIMEOUT_SECONDS = 10 def __init__(self): @@ -274,8 +286,8 @@ self.pressed = False self.alpha = 1.0 - self.image = None - self.old_image = None + self.image = None + self.old_image = None self.renderer = _HtmlRenderer() self.renderer.connect("render-finished", self.on_banner_rendered) @@ -295,9 +307,9 @@ def _init_event_handling(self): self.set_can_focus(True) - self.set_events(Gdk.EventMask.BUTTON_RELEASE_MASK| - Gdk.EventMask.BUTTON_PRESS_MASK| - Gdk.EventMask.ENTER_NOTIFY_MASK| + self.set_events(Gdk.EventMask.BUTTON_RELEASE_MASK | + Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK) self.connect("enter-notify-event", self.on_enter_notify) self.connect("leave-notify-event", self.on_leave_notify) @@ -316,12 +328,10 @@ def on_enter_notify(self, *args): self._init_mouse_pointer() - return def on_leave_notify(self, *args): window = self.get_window() window.set_cursor(None) - return def on_button_release(self, widget, event): if not point_in(self.get_allocation(), @@ -333,14 +343,11 @@ if exhibit.package_names and self.pressed: self.emit("show-exhibits-clicked", exhibit) self.pressed = False - return def on_button_press(self, widget, event): - if not point_in(self.get_allocation(), - int(event.x), int(event.y)): - return - self.pressed = True - return + if point_in(self.get_allocation(), + int(event.x), int(event.y)): + self.pressed = True def on_key_press(self, widget, event): # activate @@ -366,18 +373,15 @@ def on_next_clicked(self, *args): self.next_exhibit() self.queue_next() - return def on_previous_clicked(self, *args): self.previous() self.queue_next() - return def cleanup_timeout(self): if self._timeout > 0: GObject.source_remove(self._timeout) self._timeout = 0 - return def _render_exhibit_at_cursor(self): # init the mouse pointer @@ -402,7 +406,7 @@ # next() is a special function in py3 so we call this next_exhibit def next_exhibit(self): - if len(self.exhibits)-1 == self.cursor: + if len(self.exhibits) - 1 == self.cursor: self.cursor = 0 else: self.cursor += 1 @@ -411,7 +415,7 @@ def previous(self): if self.cursor == 0: - self.cursor = len(self.exhibits)-1 + self.cursor = len(self.exhibits) - 1 else: self.cursor -= 1 self._render_exhibit_at_cursor() @@ -433,7 +437,8 @@ return from gi.repository import Atk - self.get_accessible().set_name(self.exhibits[self.cursor].title_translated) + self.get_accessible().set_name( + self.exhibits[self.cursor].title_translated) self.get_accessible().set_role(Atk.Role.PUSH_BUTTON) self._fade_in() self.queue_next() @@ -455,12 +460,11 @@ return retval GObject.timeout_add(50, fade_step) - return def _cache_art_assets(self): global _asset_cache assets = _asset_cache - if assets: + if assets: return assets #~ surf = cairo.ImageSurface.create_from_png(self.NORTHERN_DROPSHADOW) @@ -490,7 +494,7 @@ a = self.get_allocation() - cr.set_source_rgb(1,1,1) + cr.set_source_rgb(1, 1, 1) cr.paint() # workaround a really odd bug in the offscreen window of the @@ -519,7 +523,7 @@ highlight = context.get_background_color(self.get_state_flags()) context.restore() - rounded_rect(cr, 1, 1, a.width-2, a.height-3, 5) + rounded_rect(cr, 1, 1, a.width - 2, a.height - 3, 5) Gdk.cairo_set_source_rgba(cr, highlight) cr.set_line_width(6) cr.stroke() @@ -531,26 +535,25 @@ #~ cr.paint() #~ cr.reset_clip() - cr.rectangle(0, a.height-self.DROPSHADOW_HEIGHT, + cr.rectangle(0, a.height - self.DROPSHADOW_HEIGHT, a.width, self.DROPSHADOW_HEIGHT) cr.clip() cr.save() - cr.translate(0, a.height-self.DROPSHADOW_HEIGHT) + cr.translate(0, a.height - self.DROPSHADOW_HEIGHT) cr.set_source(_asset_cache["s"]) cr.paint() cr.restore() cr.set_line_width(1) - cr.move_to(-0.5, a.height-0.5) - cr.rel_line_to(a.width+1, 0) - cr.set_source_rgba(1,1,1,0.75) + cr.move_to(-0.5, a.height - 0.5) + cr.rel_line_to(a.width + 1, 0) + cr.set_source_rgba(1, 1, 1, 0.75) cr.stroke() cr.restore() - for child in self: + for child in self: self.propagate_draw(child, cr) - return def _init_pause_handling_if_needed(self): # nothing todo if we have the toplevel already @@ -564,7 +567,7 @@ if not isinstance(w, Gtk.Window): return # connect to property changes for the toplevel focus - w.connect("notify::has-toplevel-focus", + w.connect("notify::has-toplevel-focus", self._on_main_window_is_active_changed) self._toplevel_window = w @@ -579,8 +582,7 @@ self.cleanup_timeout() def set_exhibits(self, exhibits_list): - - if not exhibits_list: + if not exhibits_list: return self.exhibits = exhibits_list @@ -600,13 +602,13 @@ self._dotsigs.append( dot.connect("clicked", self.on_paging_dot_clicked, - len(self.exhibits) - 1 - i) # index + len(self.exhibits) - 1 - i) # index ) self.index_hbox.pack_end(dot, False, False, 0) self.index_hbox.show_all() self._render_exhibit_at_cursor() - return + def get_test_exhibits_window(): from mock import Mock @@ -618,19 +620,22 @@ exhibits_list = [FeaturedExhibit()] for (i, (title, url)) in enumerate([ - ("1 some title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=orangeubuntulogo.png"), - ("2 another title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=blackeubuntulogo.png"), - ("3 yet another title", "https://wiki.ubuntu.com/Brand?action=AttachFile&do=get&target=xubuntu.png"), - ]): - exhibit = Mock() - exhibit.id = i - exhibit.package_names = "apt,2vcard" - exhibit.published = True - exhibit.style = "some uri to html" - exhibit.title_translated = title - exhibit.banner_url = url - exhibit.html = None - exhibits_list.append(exhibit) + ("1 some title", "https://wiki.ubuntu.com/Brand?" + "action=AttachFile&do=get&target=orangeubuntulogo.png"), + ("2 another title", "https://wiki.ubuntu.com/Brand?" + "action=AttachFile&do=get&target=blackeubuntulogo.png"), + ("3 yet another title", "https://wiki.ubuntu.com/Brand?" + "action=AttachFile&do=get&target=xubuntu.png"), + ]): + exhibit = Mock() + exhibit.id = i + exhibit.package_names = "apt,2vcard" + exhibit.published = True + exhibit.style = "some uri to html" + exhibit.title_translated = title + exhibit.banner_url = url + exhibit.html = None + exhibits_list.append(exhibit) exhibit_banner.set_exhibits(exhibits_list) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/imagedialog.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/imagedialog.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/imagedialog.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/imagedialog.py 2012-03-19 19:43:39.000000000 +0000 @@ -22,12 +22,15 @@ ICON_EXCEPTIONS = ["gnome"] + class Url404Error(IOError): pass + class Url403Error(IOError): pass + class SimpleShowImageDialog(Gtk.Dialog): """A dialog that shows a image """ @@ -61,7 +64,7 @@ self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) self.set_default_size(SimpleShowImageDialog.DEFAULT_WIDTH, SimpleShowImageDialog.DEFAULT_HEIGHT) - + def run(self): # show all and run the real thing self.show_all() @@ -71,5 +74,7 @@ if __name__ == "__main__": # pixbuf - d = SimpleShowImageDialog("Synaptic Screenshot", GdkPixbuf.Pixbuf.new_from_file("/usr/share/software-center/images/arrows.png")) + d = SimpleShowImageDialog("Synaptic Screenshot", + GdkPixbuf.Pixbuf.new_from_file( + "/usr/share/software-center/default_banner/fallback.png")) d.run() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/labels.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/labels.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/labels.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/labels.py 2012-03-16 08:30:18.000000000 +0000 @@ -19,28 +19,29 @@ from gi.repository import Gtk from gettext import gettext as _ -from softwarecenter.hw import TAG_DESCRIPTION +from softwarecenter.hw import get_hw_short_description + class HardwareRequirementsLabel(Gtk.HBox): - """ contains a single HW requirement string and a image that shows if - the requirements are meet + """ contains a single HW requirement string and a image that shows if + the requirements are meet """ SUPPORTED_SYM = { # TRANSLATORS: symbol for "hardware-supported" - 'yes' : _(u'\u2713'), + 'yes': _(u'\u2713'), # TRANSLATORS: symbol for hardware "not-supported" - 'no' : u'%s' % _(u'\u2718'), + 'no': u'%s' % _(u'\u2718'), } - # TRANSLATORS: this is a substring that used to build the + # TRANSLATORS: this is a substring that used to build the # "hardware-supported" string, where sym is # either a unicode checkmark or a cross # and hardware is the short hardware description # Note that this is the last substr, no trailing "," LABEL_LAST_ITEM = _("%(sym)s%(hardware)s") - # TRANSLATORS: this is a substring that used to build the + # TRANSLATORS: this is a substring that used to build the # "hardware-supported" string, where sym is # either a unicode checkmark or a cross # and hardware is the short hardware description @@ -53,27 +54,31 @@ self.result = None self.last_item = last_item self._build_ui() + def _build_ui(self): self._label = Gtk.Label() self._label.show() self.pack_start(self._label, True, True, 0) + def get_label(self): # get the right symbol sym = self.SUPPORTED_SYM[self.result] - # we add a trailing + # we add a trailing if self.last_item: s = self.LABEL_LAST_ITEM else: - s= self.LABEL + s = self.LABEL return _(s) % { - "sym" : sym, - "hardware" : _(TAG_DESCRIPTION[self.tag]), + "sym": sym, + "hardware": _(get_hw_short_description(self.tag)) } + def set_hardware_requirement(self, tag, result): self.tag = tag self.result = result self._label.set_markup(self.get_label()) + class HardwareRequirementsBox(Gtk.HBox): """ A collection of HW requirement labels """ @@ -86,17 +91,15 @@ def set_hardware_requirements(self, hw_requirements_result): self.clear() - for tag, supported in hw_requirements_result.iteritems(): + for i, (tag, sup) in enumerate(hw_requirements_result.iteritems()): # ignore unknown for now - if not supported in ("yes", "no"): + if not sup in ("yes", "no"): continue - label = HardwareRequirementsLabel(last_item=False) - label.set_hardware_requirement(tag, supported) + is_last_item = (i == len(hw_requirements_result) - 1) + label = HardwareRequirementsLabel(last_item=is_last_item) + label.set_hardware_requirement(tag, sup) label.show() self.pack_start(label, True, True, 6) - # tell the last item that its last - if self.get_children(): - self.get_children()[-1].last_item = True @property def hw_labels(self): @@ -107,9 +110,10 @@ win = Gtk.Window() win.set_size_request(300, 200) - HW_TEST_RESULT = { 'hardware::gps' : 'yes', - 'hardware::video:opengl' : 'no', - } + HW_TEST_RESULT = { + 'hardware::gps': 'yes', + 'hardware::video:opengl': 'no', + } # add it hwbox = HardwareRequirementsBox() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/menubutton.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/menubutton.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/menubutton.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/menubutton.py 2012-03-14 16:03:50.000000000 +0000 @@ -18,11 +18,12 @@ from gi.repository import Gtk + class MenuButton(Gtk.Button): def __init__(self, menu, icon=None, label=None): super(MenuButton, self).__init__() - + box = Gtk.Box() self.add(box) @@ -30,12 +31,12 @@ box.pack_start(icon, False, True, 1) if label: box.pack_start(Gtk.Label(label), True, True, 0) - + arrow = Gtk.Arrow.new(Gtk.ArrowType.DOWN, Gtk.ShadowType.OUT) box.pack_start(arrow, False, False, 1) self.menu = menu - + self.connect("button-press-event", self.on_button_pressed, menu) self.connect("clicked", self.on_keyboard_clicked, menu) @@ -44,18 +45,21 @@ return self.menu def on_button_pressed(self, button, event, menu): - menu.popup(None, None, self.menu_positionner, (button, event.x), event.button, event.time) + menu.popup(None, None, self.menu_positionner, (button, event.x), + event.button, event.time) def on_keyboard_clicked(self, button, menu): - menu.popup(None, None, self.menu_positionner, (button, None), 1, Gtk.get_current_event_time()) + menu.popup(None, None, self.menu_positionner, (button, None), 1, + Gtk.get_current_event_time()) def menu_positionner(self, menu, (button, x_cursor_pos)): (button_id, x, y) = button.get_window().get_origin() # compute button position x_position = x + button.get_allocation().x - y_position = y + button.get_allocation().y + button.get_allocated_height() - + y_position = (y + button.get_allocation().y + + button.get_allocated_height()) + # if pressed by the mouse, center the X position to it if x_cursor_pos: x_position += x_cursor_pos @@ -63,19 +67,24 @@ # computer current monitor height current_screen = button.get_screen() - num_monitor = current_screen.get_monitor_at_point(x_position, y_position) + num_monitor = current_screen.get_monitor_at_point(x_position, + y_position) monitor_geo = current_screen.get_monitor_geometry(num_monitor) - + # if the menu width is of the current monitor, shift is a little if x_position < monitor_geo.x: x_position = monitor_geo.x - if x_position + menu.get_allocated_width() > monitor_geo.x + monitor_geo.width: - x_position = monitor_geo.x + monitor_geo.width - menu.get_allocated_width() - - # if the menu height is too long for the monitor, put it above the widget + if (x_position + menu.get_allocated_width() > monitor_geo.x + + monitor_geo.width): + x_position = (monitor_geo.x + monitor_geo.width - + menu.get_allocated_width()) + + # if the menu height is too long for the monitor, put it above the + # widget if monitor_geo.height < y_position + menu.get_allocated_height(): - y_position = y_position - button.get_allocated_height() - menu.get_allocated_height() - + y_position = (y_position - button.get_allocated_height() - + menu.get_allocated_height()) + return (x_position, y_position, True) @@ -93,23 +102,24 @@ menuitem2.show() box1 = Gtk.Box() - box1.pack_start(Gtk.Label("something before to show we don't cheat"), True, True, 0) + box1.pack_start(Gtk.Label("something before to show we don't cheat"), + True, True, 0) win.add(box1) box2 = Gtk.Box() - box2.set_orientation(Gtk.Orientation.VERTICAL) + box2.set_orientation(Gtk.Orientation.VERTICAL) box1.pack_start(box2, True, True, 0) box2.pack_start(Gtk.Label("first label with multiple line"), True, True, 0) - + image = Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, Gtk.IconSize.BUTTON) label = "fooo" button_with_menu = MenuButton(menu, image, label) - box2.pack_start(button_with_menu, False, False, 1) + box2.pack_start(button_with_menu, False, False, 1) win.connect("destroy", lambda x: Gtk.main_quit()) win.show_all() settings = Gtk.Settings.get_default() settings.set_property("gtk-button-images", True) - + Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/oneconfviews.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/oneconfviews.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/oneconfviews.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/oneconfviews.py 2012-03-14 16:03:50.000000000 +0000 @@ -25,26 +25,28 @@ class OneConfViews(Gtk.TreeView): __gsignals__ = { - "computer-changed" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT), - ), - "current-inventory-refreshed" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (),), + "computer-changed": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT), + ), + "current-inventory-refreshed": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), + ), } - + (COL_ICON, COL_HOSTID, COL_HOSTNAME) = range(3) def __init__(self, icons): super(OneConfViews, self).__init__() - model = Gtk.ListStore(GdkPixbuf.Pixbuf, GObject.TYPE_STRING, GObject.TYPE_STRING) + model = Gtk.ListStore(GdkPixbuf.Pixbuf, GObject.TYPE_STRING, + GObject.TYPE_STRING) model.set_sort_column_id(self.COL_HOSTNAME, Gtk.SortType.ASCENDING) model.set_sort_func(self.COL_HOSTNAME, self._sort_hosts) self.set_model(model) self.set_headers_visible(False) self.col = Gtk.TreeViewColumn('hostname') - + hosticon_renderer = Gtk.CellRendererPixbuf() hostname_renderer = Gtk.CellRendererText() self.col.pack_start(hosticon_renderer, False) @@ -57,9 +59,9 @@ # TODO: load the dynamic one (if present), later self.default_computer_icon = icons.load_icon("computer", 22, 0) - + self.connect("cursor-changed", self.on_cursor_changed) - + def register_computer(self, hostid, hostname): '''Add a new computer to the model''' model = self.get_model() @@ -73,17 +75,20 @@ model.append([self.default_computer_icon, hostid, hostname]) def store_packagelist_changed(self, hostid): - '''Emit a signal for refreshing the installedpane if current view is concerned''' + '''Emit a signal for refreshing the installedpane if current view is + concerned + ''' if hostid == self.current_hostid: self.emit("current-inventory-refreshed") - + def remove_computer(self, hostid): '''Remove a computer from the model''' model = self.get_model() if not model: return if hostid not in self.hostids: - LOG.warning("ask to remove a computer that isn't registered: %s" % hostid) + LOG.warning("ask to remove a computer that isn't registered: %s" % + hostid) return iter_id = model.get_iter_first() while iter_id: @@ -92,8 +97,7 @@ self.hostids.remove(hostid) break iter_id = model.iter_next(iter_id) - - + def on_cursor_changed(self, widget): (path, column) = self.get_cursor() @@ -101,28 +105,30 @@ return model = self.get_model() if not model: - return + return hostid = model[path][self.COL_HOSTID] hostname = model[path][self.COL_HOSTNAME] if hostid != self.current_hostid: self.current_hostid = hostid self.emit("computer-changed", hostid, hostname) - + def select_first(self): '''Select first item''' self.set_cursor(Gtk.TreePath.new_first(), None, False) - + def _sort_hosts(self, model, iter1, iter2, user_data): '''Sort hosts, with "this computer" (NONE HOSTID) as first''' if not self.get_model().get_value(iter1, self.COL_HOSTID): return -1 if not self.get_model().get_value(iter2, self.COL_HOSTID): - return 1 - if self.get_model().get_value(iter1, self.COL_HOSTNAME) > self.get_model().get_value(iter2, self.COL_HOSTNAME): + return 1 + if (self.get_model().get_value(iter1, self.COL_HOSTNAME) > + self.get_model().get_value(iter2, self.COL_HOSTNAME)): return 1 else: return -1 - + + def get_test_window(): w = OneConfViews(Gtk.IconTheme.get_default()) @@ -141,14 +147,14 @@ w.register_computer("CCCCC", "NameC") w.register_computer("", "This computer should be first") w.select_first() - + GObject.timeout_add_seconds(5, w.register_computer, "EEEEE", "NameE") - + def print_selected_hostid(widget, hostid, hostname): print "%s selected for %s" % (hostid, hostname) - + w.connect("computer-changed", print_selected_hostid) - + w.remove_computer("DDDDD") win.show_all() return win diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/recommendations.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/recommendations.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/recommendations.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/recommendations.py 2012-03-20 10:52:42.000000000 +0000 @@ -28,20 +28,21 @@ from softwarecenter.db.categories import (RecommendedForYouCategory, AppRecommendationsCategory) from softwarecenter.backend.recagent import RecommenderAgent - +from softwarecenter.utils import utf8 LOG = logging.getLogger(__name__) + class RecommendationsPanel(FramedHeaderBox): """ Base class for widgets that display recommendations """ __gsignals__ = { - "application-activated" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT,), - ), + "application-activated": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT,), + ), } def __init__(self, catview): @@ -51,52 +52,118 @@ # extract this to a "leaner" widget self.catview = catview self.catview.connect( - "application-activated", self._on_application_activated) + "application-activated", self._on_application_activated) self.recommender_agent = RecommenderAgent() + def _on_application_activated(self, catview, app): self.emit("application-activated", app) -class RecommendationsPanelLobby(RecommendationsPanel): + def _on_recommender_agent_error(self, agent, msg): + LOG.warn("Error while accessing the recommender agent: %s" % msg) + # TODO: temporary, instead we will display cached recommendations here + self._hide_recommended_for_you_panel() + + def _hide_recommended_for_you_panel(self): + # and hide the pane + self.hide() + + +class RecommendationsPanelCategory(RecommendationsPanel): + """ + Panel for use in the category view that displays recommended apps for + the given category + """ + def __init__(self, catview, subcategory): + RecommendationsPanel.__init__(self, catview) + self.subcategory = subcategory + if self.subcategory: + self.set_header_label(GObject.markup_escape_text(utf8( + _("Recommended For You in %s")) % utf8(self.subcategory.name))) + self.recommended_for_you_content = None + if self.recommender_agent.is_opted_in(): + self._update_recommended_for_you_content() + else: + self._hide_recommended_for_you_panel() + + def _update_recommended_for_you_content(self): + # destroy the old content to ensure we don't see it twice + if self.recommended_for_you_content: + self.recommended_for_you_content.destroy() + # add the new stuff + self.recommended_for_you_content = FlowableGrid() + self.add(self.recommended_for_you_content) + self.spinner_notebook.show_spinner(_(u"Receiving recommendations…")) + # get the recommendations from the recommender agent + self.recommended_for_you_cat = RecommendedForYouCategory( + subcategory=self.subcategory) + self.recommended_for_you_cat.connect( + 'needs-refresh', + self._on_recommended_for_you_agent_refresh) + self.recommended_for_you_cat.connect('recommender-agent-error', + self._on_recommender_agent_error) + + def _on_recommended_for_you_agent_refresh(self, cat): + self.header_implements_more_button() + docs = cat.get_documents(self.catview.db) + # display the recommendedations + if len(docs) > 0: + self.catview._add_tiles_to_flowgrid(docs, + self.recommended_for_you_content, 12) + self.recommended_for_you_content.show_all() + self.spinner_notebook.hide_spinner() + self.more.connect('clicked', + self.catview.on_category_clicked, + cat) + self.show_all() + else: + # hide the panel if we have no recommendations to show + self._hide_recommended_for_you_panel() + + +class RecommendationsPanelLobby(RecommendationsPanelCategory): """ - Panel for use in the lobby view that manages the recommendations experience, - includes the initial opt-in screen and display of recommendations once they - have been received from the recommender agent + Panel for use in the lobby view that manages the recommendations + experience, includes the initial opt-in screen and display of + recommendations once they have been received from the recommender agent """ - __gsignals__ = { - "recommendations-opt-in" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_STRING,), + "recommendations-opt-in": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_STRING,), + ), + "recommendations-opt-out": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (), ), - "recommendations-opt-out" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (), - ), } - + + TURN_ON_RECOMMENDATIONS_TEXT = _(u"Turn On Recommendations") + RECOMMENDATIONS_OPT_IN_TEXT = _(u"To make recommendations, " + "Ubuntu Software Center " + "will occasionally send to Canonical an anonymous list " + "of software currently installed.") + def __init__(self, catview): RecommendationsPanel.__init__(self, catview) - self.set_header_label(_(u"Recommended for You")) - - # if we already have a recommender UUID, then the user is already - # opted-in to the recommender service + self.subcategory = None + self.set_header_label(_(u"Recommended For You")) self.recommended_for_you_content = None - if self.recommender_agent.recommender_uuid: + if self.recommender_agent.is_opted_in(): self._update_recommended_for_you_content() else: self._show_opt_in_view() - + def _show_opt_in_view(self): # opt in box vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, StockEms.MEDIUM) - vbox.set_border_width(StockEms.LARGE) + vbox.set_border_width(StockEms.MEDIUM) self.opt_in_vbox = vbox # for tests self.recommended_for_you_content = vbox # hook it up to the rest - + self.add(self.recommended_for_you_content) # opt in button - button = Gtk.Button(_("Turn On Recommendations")) + button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT)) button.connect("clicked", self._on_opt_in_button_clicked) hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL) hbox.pack_start(button, False, False, 0) @@ -104,34 +171,57 @@ self.opt_in_button = button # for tests # opt in text - text = _("To make recommendations, Ubuntu Software Center " - "will occasionally send to Canonical an anonymous list " - "of software currently installed.") - label = Gtk.Label(text) + text = _(self.RECOMMENDATIONS_OPT_IN_TEXT) + label = Gtk.Label() + label.set_use_markup(True) + markup = '%s' + label.set_name("subtle-label") + label.set_markup(markup % text) label.set_alignment(0, 0.5) label.set_line_wrap(True) vbox.pack_start(label, False, False, 0) - + def _on_opt_in_button_clicked(self, button): + self.opt_in_to_recommendations_service() + + def opt_in_to_recommendations_service(self): # we upload the user profile here, and only after this is finished # do we fire the request for recommendations and finally display # them here -- a spinner is shown for this process (the spec # wants a progress bar, but we don't have access to real-time # progress info) self._upload_user_profile_and_get_recommendations() - + + def opt_out_of_recommendations_service(self): + # tell the backend that the user has opted out + self.recommender_agent.opt_out() + # update the UI + if self.recommended_for_you_content: + self.recommended_for_you_content.destroy() + self._show_opt_in_view() + self.remove_more_button() + self.show_all() + self.emit("recommendations-opt-out") + try: + self.recommender_agent.disconnect_by_func( + self._on_profile_submitted) + self.recommender_agent.disconnect_by_func( + self._on_profile_submitted_error) + except TypeError: + pass + def _upload_user_profile_and_get_recommendations(self): # initiate upload of the user profile here self._upload_user_profile() - + def _upload_user_profile(self): - self.spinner_notebook.show_spinner(_("Submitting inventory…")) + self.spinner_notebook.show_spinner(_(u"Submitting inventory…")) self.recommender_agent.connect("submit-profile-finished", self._on_profile_submitted) self.recommender_agent.connect("error", self._on_profile_submitted_error) self.recommender_agent.post_submit_profile(self.catview.db) - + def _on_profile_submitted(self, agent, profile, recommender_uuid): # after the user profile data has been uploaded, make the request # and load the the recommended_for_you content @@ -139,59 +229,14 @@ "submitted to the recommender agent") self.emit("recommendations-opt-in", recommender_uuid) self._update_recommended_for_you_content() - + def _on_profile_submitted_error(self, agent, msg): LOG.warn("Error while submitting the recommendations profile to the " "recommender agent: %s" % msg) # TODO: handle this! display an error message in the panel self._hide_recommended_for_you_panel() - - def _update_recommended_for_you_content(self): - # destroy the old content to ensure we don't see it twice - # (also removes the opt-in panel if it was there) - if self.recommended_for_you_content: - self.recommended_for_you_content.destroy() - # add the new stuff - self.header_implements_more_button() - self.recommended_for_you_content = FlowableGrid() - self.add(self.recommended_for_you_content) - self.spinner_notebook.show_spinner(_("Receiving recommendations…")) - # get the recommendations from the recommender agent - self.recommended_for_you_cat = RecommendedForYouCategory() - self.recommended_for_you_cat.connect( - 'needs-refresh', - self._on_recommended_for_you_agent_refresh) - self.recommended_for_you_cat.connect('recommender-agent-error', - self._on_recommender_agent_error) - - def _on_recommended_for_you_agent_refresh(self, cat): - docs = cat.get_documents(self.catview.db) - # display the recommendedations - if len(docs) > 0: - self.catview._add_tiles_to_flowgrid(docs, - self.recommended_for_you_content, 8) - self.recommended_for_you_content.show_all() - self.spinner_notebook.hide_spinner() - self.more.connect('clicked', - self.catview.on_category_clicked, - cat) - else: - # TODO: this test for zero docs is temporary and will not be - # needed once the recommendation agent is up and running - self._hide_recommended_for_you_panel() - return - - def _on_recommender_agent_error(self, agent, msg): - LOG.warn("Error while accessing the recommender agent for the " - "lobby recommendations: %s" % msg) - # TODO: temporary, instead we will display cached recommendations here - self._hide_recommended_for_you_panel() - def _hide_recommended_for_you_panel(self): - # and hide the pane - self.hide() - - + class RecommendationsPanelDetails(RecommendationsPanel): """ Panel for use in the details view to display recommendations for a given @@ -202,14 +247,15 @@ self.set_header_label(_(u"People Also Installed")) self.app_recommendations_content = FlowableGrid() self.add(self.app_recommendations_content) - + def set_pkgname(self, pkgname): self.pkgname = pkgname self._update_app_recommendations_content() def _update_app_recommendations_content(self): - self.app_recommendations_content.remove_all() - self.spinner_notebook.show_spinner(_("Receiving recommendations…")) + if self.app_recommendations_content: + self.app_recommendations_content.remove_all() + self.spinner_notebook.show_spinner(_(u"Receiving recommendations…")) # get the recommendations from the recommender agent self.app_recommendations_cat = AppRecommendationsCategory(self.pkgname) self.app_recommendations_cat.connect( @@ -217,19 +263,18 @@ self._on_app_recommendations_agent_refresh) self.app_recommendations_cat.connect('recommender-agent-error', self._on_recommender_agent_error) - + def _on_app_recommendations_agent_refresh(self, cat): docs = cat.get_documents(self.catview.db) # display the recommendations if len(docs) > 0: self.catview._add_tiles_to_flowgrid(docs, - self.app_recommendations_content, 8) + self.app_recommendations_content, 3) self.show_all() self.spinner_notebook.hide_spinner() else: self._hide_app_recommendations_panel() - return - + def _on_recommender_agent_error(self, agent, msg): LOG.warn("Error while accessing the recommender agent for the " "details view recommendations: %s" % msg) @@ -239,31 +284,61 @@ def _hide_app_recommendations_panel(self): # and hide the pane self.hide() - + + +class RecommendationsOptInDialog(Gtk.MessageDialog): + """ + Dialog to display the recommendations opt-in message when opt-in is + initiated from the menu. + """ + def __init__(self, icons): + Gtk.MessageDialog.__init__(self, flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.INFO) + self.set_title("") + icon_name = "softwarecenter" + if icons.has_icon(icon_name): + icon = Gtk.Image.new_from_icon_name(icon_name, + Gtk.IconSize.DIALOG) + self.set_image(icon) + icon.show() + self.format_secondary_text( + _(RecommendationsPanelLobby.RECOMMENDATIONS_OPT_IN_TEXT)) + self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) + self.add_button( + _(RecommendationsPanelLobby.TURN_ON_RECOMMENDATIONS_TEXT), + Gtk.ResponseType.YES) + self.set_default_response(Gtk.ResponseType.YES) + # test helpers -def get_test_window(): +def get_test_window(panel_type="lobby"): import softwarecenter.log softwarecenter.log.root.setLevel(level=logging.DEBUG) fmt = logging.Formatter("%(name)s - %(message)s", None) softwarecenter.log.handler.setFormatter(fmt) - - # this is *way* to complicated we should *not* need a CatView + # this is *way* too complicated we should *not* need a CatView # here! see FIXME in RecommendationsPanel.__init__() from softwarecenter.ui.gtk3.views.catview_gtk import CategoriesViewGtk from softwarecenter.testutils import ( - get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache) + get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache, + get_test_categories) cache = get_test_pkg_info() db = get_test_db() icons = get_test_gtk3_icon_cache() catview = CategoriesViewGtk(softwarecenter.paths.datadir, softwarecenter.paths.APP_INSTALL_PATH, - cache, + cache, db, icons) - view = RecommendationsPanelLobby(catview) + if panel_type is "lobby": + view = RecommendationsPanelLobby(catview) + elif panel_type is "category": + cats = get_test_categories(db) + view = RecommendationsPanelCategory(catview, cats[0]) + else: # panel_type is "details": + view = RecommendationsPanelDetails(catview) win = Gtk.Window() win.connect("destroy", lambda x: Gtk.main_quit()) @@ -271,9 +346,9 @@ win.set_data("rec_panel", view) win.set_size_request(600, 200) win.show_all() - + return win - + if __name__ == "__main__": win = get_test_window() diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/reviews.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/reviews.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/reviews.py 2012-03-09 07:45:56.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/reviews.py 2012-03-14 16:03:50.000000000 +0000 @@ -131,7 +131,7 @@ self.sort_combo = Gtk.ComboBoxText() self._current_sort = 0 for sort_method in ReviewSortMethods.REVIEW_SORT_LIST_ENTRIES: - self.sort_combo.append_text(sort_method) + self.sort_combo.append_text(_(sort_method)) self.sort_combo.set_active(self._current_sort) self.sort_combo.connect('changed', self._on_sort_method_changed) self.header.pack_end(self.sort_combo, False, False, 3) diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/sections.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/sections.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/sections.py 2012-03-09 07:45:56.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/sections.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,54 +0,0 @@ -#from gi.repository import Gtk - -import cairo -import os - -from softwarecenter.enums import ViewPages -from softwarecenter.paths import datadir -from mkit import floats_from_string - - -class SectionPainter(object): - - # specify background overlay image and color mappings for available and - # installed view ids - BACKGROUND_IMAGES = { - ViewPages.AVAILABLE: cairo.ImageSurface.create_from_png( - os.path.join(datadir, 'images/clouds.png')), - ViewPages.INSTALLED: cairo.ImageSurface.create_from_png( - os.path.join(datadir, 'images/arrows.png')), - } - BACKGROUND_COLORS = {ViewPages.AVAILABLE: floats_from_string('#0769BC'), - ViewPages.INSTALLED: floats_from_string('#aea79f'), - } - - def __init__(self): - self._view_id = None - - def set_view_id(self, id): - self._view_id = id - - def draw(self, widget, cr): - # sky - #r,g,b = self.get_background_color() - #lin = cairo.LinearGradient(0,a.y,0,a.y+150) - #lin.add_color_stop_rgba(0, r,g,b, 0.3) - #lin.add_color_stop_rgba(1, r,g,b,0) - #cr.set_source(lin) - #cr.rectangle(0,0,a.width, 150) - #cr.fill() - - #s = self.get_background_image() - #if widget.get_direction() != Gtk.TextDirection.RTL: - # cr.set_source_surface(s, a.x+a.width-s.get_width(), 0) - #else: - # cr.set_source_surface(s, a.x, 0) - - #cr.paint() - pass - - def get_background_color(self): - return self.BACKGROUND_COLORS[self._view_id] - - def get_background_image(self): - return self.BACKGROUND_IMAGES[self._view_id] diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/stars.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/stars.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/stars.py 2012-03-08 19:37:37.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/stars.py 2012-03-20 08:40:44.000000000 +0000 @@ -24,7 +24,6 @@ from gi.repository import Gtk, Gdk, GObject -from softwarecenter.ui.gtk3.shapes import ShapeStar from softwarecenter.ui.gtk3.em import StockEms, em, small_em, big_em @@ -50,6 +49,38 @@ REACTIVE = -1 +class ShapeStar(): + def __init__(self, points, indent=0.61): + self.coords = self._calc_coords(points, 1 - indent) + + def _calc_coords(self, points, indent): + coords = [] + + from math import cos, pi, sin + step = pi / points + + for i in range(2 * points): + if i % 2: + x = (sin(step * i) + 1) * 0.5 + y = (cos(step * i) + 1) * 0.5 + else: + x = (sin(step * i) * indent + 1) * 0.5 + y = (cos(step * i) * indent + 1) * 0.5 + + coords.append((x, y)) + return coords + + def layout(self, cr, x, y, w, h): + points = [(sx_sy[0] * w + x, sx_sy[1] * h + y) + for sx_sy in self.coords] + cr.move_to(*points[0]) + + for p in points[1:]: + cr.line_to(*p) + + cr.close_path() + + class StarRenderer(ShapeStar): def __init__(self): diff -Nru software-center-5.1.12/softwarecenter/ui/gtk3/widgets/unused__pathbar.py software-center-5.1.13/softwarecenter/ui/gtk3/widgets/unused__pathbar.py --- software-center-5.1.12/softwarecenter/ui/gtk3/widgets/unused__pathbar.py 2012-03-08 19:37:37.000000000 +0000 +++ software-center-5.1.13/softwarecenter/ui/gtk3/widgets/unused__pathbar.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1235 +0,0 @@ -from gi.repository import Atk -from gi.repository import Gtk, Gdk -from gi.repository import GObject -from gi.repository import Pango - -from softwarecenter.ui.gtk3.em import em - -from gettext import gettext as _ - -import logging -LOG = logging.getLogger("softwarecenter.view.widgets.NavigationBar") - -# pi constants -from math import pi - -PI = pi -PI_OVER_180 = pi / 180 - - -class Shape: - - """ Base class for a Shape implementation. - - Currently implements a single method which is called - to layout the shape using cairo paths. It can also store the - 'direction' of the shape which should be on of the Gtk.TEXT_DIR - constants. Default 'direction' is Gtk.TextDirection.LTR. - - When implementing a Shape, there are two options available. - - If the Shape is direction dependent, the Shape MUST - implement <_layout_ltr> and <_layout_rtl> methods. - - If the Shape is not direction dependent, then it simply can - override the method. - - methods must take the following as arguments: - - cr : a CairoContext - x : x coordinate - y : y coordinate - w : width value - h : height value - - methods can then be passed Shape specific - keyword arguments which can be used as paint-time modifiers. - """ - - def __init__(self, direction): - self.direction = direction - self.name = 'Shapeless' - self.hadjustment = 0 - self._color = 1, 0, 0 - - def __eq__(self, other): - return self.name == other.name - - def layout(self, cr, x, y, w, h, r, aw): - if self.direction != Gtk.TextDirection.RTL: - self._layout_ltr(cr, x, y, w, h, r, aw) - else: - self._layout_rtl(cr, x, y, w, h, r, aw) - - -class ShapeRoundedRect(Shape): - - """ - RoundedRect lays out a rectangle with all four corners - rounded as specified at the layout call by the keyword argument: - - radius : an integer or float specifying the corner radius. - The radius must be > 0. - - RoundedRectangle is not direction sensitive. - """ - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - self.name = 'RoundedRect' - - def layout(self, cr, x, y, w, h, r, aw): - cr.new_sub_path() - cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180) - cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0) - cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180) - cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI) - cr.close_path() - - -class ShapeStartArrow(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - self.name = 'StartArrow' - - def _layout_ltr(self, cr, x, y, w, h, r, aw): - haw = aw / 2 - - cr.new_sub_path() - cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180) - - # arrow head - cr.line_to(x + w - haw, y) - cr.line_to(x + w + haw, y + (h / 2)) - cr.line_to(x + w - haw, y + h) - - cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI) - cr.close_path() - - def _layout_rtl(self, cr, x, y, w, h, r, aw): - haw = aw / 2 - - cr.new_sub_path() - cr.move_to(x - haw, (y + h) / 2) - cr.line_to(x + aw - haw, y) - cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0) - cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180) - cr.line_to(x + aw - haw, y + h) - cr.close_path() - - -class ShapeMidArrow(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - #~ self.draw_xoffset = -2 - self._color = 0, 1, 0 - self.name = 'MidArrow' - - def _layout_ltr(self, cr, x, y, w, h, r, aw): - self.hadjustment = haw = aw / 2 - cr.move_to(x - haw - 1, y) - # arrow head - cr.line_to(x + w - haw, y) - cr.line_to(x + w + haw, y + (h / 2)) - cr.line_to(x + w - haw, y + h) - cr.line_to(x - haw - 1, y + h) - - cr.line_to(x + haw - 1, y + (h / 2)) - - cr.close_path() - - def _layout_rtl(self, cr, x, y, w, h, r, aw): - self.hadjustment = haw = -aw / 2 - - cr.move_to(x + haw, (h + y) / 2) - cr.line_to(x + aw + haw, y) - cr.line_to(x + w - haw + 1, y) - cr.line_to(x + w - aw - haw + 1, (y + h) / 2) - cr.line_to(x + w - haw + 1, y + h) - cr.line_to(x + aw + haw, y + h) - cr.close_path() - - -class ShapeEndCap(Shape): - - def __init__(self, direction=Gtk.TextDirection.LTR): - Shape.__init__(self, direction) - #~ self.draw_xoffset = -2 - self._color = 0, 0, 1 - self.name = 'EndCap' - - def _layout_ltr(self, cr, x, y, w, h, r, aw): - self.hadjustment = haw = aw / 2 - - cr.move_to(x - haw - 1, y) - # rounded end - cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0) - cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180) - # arrow - cr.line_to(x - haw - 1, y + h) - cr.line_to(x + haw - 1, y + (h / 2)) - cr.close_path() - - def _layout_rtl(self, cr, x, y, w, h, r, aw): - self.hadjustment = haw = -aw / 2 - - cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180) - cr.line_to(x + w - haw + 1, y) - cr.line_to(x + w - haw - aw + 1, (y + h) / 2) - cr.line_to(x + w - haw + 1, y + h) - cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI) - cr.close_path() - - -class AnimationClock(GObject.GObject): - _1SECOND = 1000 - __gsignals__ = { - "animation-frame": (GObject.SignalFlags.RUN_LAST, - None, - (float,),), - - "animation-finished": (GObject.SignalFlags.RUN_FIRST, - None, - (bool,),), - } - - def __init__(self, fps, duration): - GObject.GObject.__init__(self) - - self.fps = fps - self.in_progress = False - self.set_duration(duration) - - self._clock = None - self._progress = 0 # progress as an msec offset - - def _get_timstep(self): - d = self.duration - return max(10, int(d / ((d / AnimationClock._1SECOND) * self.fps))) - - def _schedule_animation_frame(self): - if self._progress > self.duration: - self._clock = None - self.in_progress = False - self.emit('animation-finished', False) - return False - - self._progress += self._timestep - self.emit('animation-frame', self.progress) - return True - - @property - def progress(self): - return min(1.0, self._progress / self.duration) - - def set_duration(self, duration): - self.duration = float(duration) - self._timestep = self._get_timstep() - - def stop(self, who_called='?'): - - if self._clock: - #~ print who_called+'.Stop' - GObject.source_remove(self._clock) - self.emit('animation-finished', True) - - self._clock = None - self._progress = 0 - self.in_progress = False - - def start(self): - self.stop(who_called='start') - if not self.sequence: - return - - self._clock = GObject.timeout_add(self._timestep, - self._schedule_animation_frame, - priority=100) - self.in_progress = True - - -class PathBarAnimator(AnimationClock): - # animation display constants - FPS = 50 - DURATION = 150 # spec says 150ms - - # animation modes - NONE = 'animation-none' - OUT = 'animation-out' - IN = 'animation-in' - WIDTH_CHANGE = 'animation-width-change' - - def __init__(self, pathbar): - AnimationClock.__init__(self, self.FPS, self.DURATION) - - self.pathbar = pathbar - self.sequence = [] - - self.connect('animation-frame', self._on_animation_frame) - self.connect('animation-finished', self._on_animation_finished) - - def _animate_out(self, part, progress, kwargs): - real_alloc = part.get_allocation() - xo = real_alloc.width - int(real_alloc.width * progress) - - if self.pathbar.get_direction() == Gtk.TextDirection.RTL: - xo *= -1 - - anim_alloc = Gdk.Rectangle() - anim_alloc.x = real_alloc.x - xo - anim_alloc.y = real_alloc.y - anim_alloc.width = real_alloc.width - anim_alloc.height = real_alloc.height - - part.new_frame(anim_alloc) - - def _animate_in(self, part, progress, kwargs): - real_alloc = part.get_allocation() - xo = int(real_alloc.width * progress) - - if self.pathbar.get_direction() == Gtk.TextDirection.RTL: - xo *= -1 - - anim_alloc = Gdk.Rectangle() - anim_alloc.x = real_alloc.x - xo - anim_alloc.y = real_alloc.y - anim_alloc.width = real_alloc.width - anim_alloc.height = real_alloc.height - - part.new_frame(anim_alloc) - - def _animate_width_change(self, part, progress, kwargs): - start_w = kwargs['start_width'] - end_w = kwargs['end_width'] - - width = int(round(start_w + (end_w - start_w) * progress)) - part.set_size_request(width, part.get_height_request()) - - def _on_animation_frame(self, clock, progress): - if not self.sequence: - return - - for actor, animation, kwargs in self.sequence: - if animation == PathBarAnimator.NONE: - continue - - if animation == PathBarAnimator.OUT: - self._animate_out(actor, progress, kwargs) - - elif animation == PathBarAnimator.IN: - self._animate_in(actor, progress, kwargs) - - elif animation == PathBarAnimator.WIDTH_CHANGE: - self._animate_width_change(actor, progress, kwargs) - - def _on_animation_finished(self, clock, interrupted): - for actor, animation, kwargs in self.sequence: - actor.animation_finished() - - self.sequence = [] - self.pathbar.psuedo_parts = [] - self.pathbar.queue_draw() - - def append_animation(self, actor, animation, **kwargs): - self.sequence.append((actor, animation, kwargs)) - - def reset(self, who_called='?'): - AnimationClock.stop(self, who_called=who_called + '.reset') - self.sequence = [] - - -class PathBar(Gtk.HBox): - - MIN_PART_WIDTH = 25 # pixels - - def __init__(self): - GObject.GObject.__init__(self) - self.set_redraw_on_allocate(False) - self.set_size_request(-1, em(1.75)) - self._allocation = None - - # Accessibility info - atk_desc = self.get_accessible() - atk_desc.set_name(_("You are here:")) - atk_desc.set_role(Atk.Role.PANEL) - - self.use_animations = True - self.animator = PathBarAnimator(self) - - self.out_of_width = False - self.psuedo_parts = [] - - # used for certain button press logic - self._press_origin = None - # tracks the id of the revealer timeout - self._revealer = None - - # values derived from the gtk settings - s = Gtk.Settings.get_default() - # time to wait before revealing a part on enter event in ms - self._timeout_reveal = s.get_property("gtk-tooltip-timeout") - # time to wait until emitting click event in ms - self._timeout_initial = s.get_property("gtk-timeout-initial") - - # les signales! - self.connect('size-allocate', self._on_allocate) - self.connect('draw', self._on_draw) - - # sugar - def __len__(self): - return len(self.get_children()) - - def __getitem__(self, index): - return self.get_children()[index] - - # signal handlers - def _on_allocate(self, widget, _): - allocation = self.get_allocation() - - if self._allocation == allocation: - return True - - # prevent vertical bobby when the searchentry is shown/hidden - if allocation.height > self.get_property('height-request'): - self.set_property('height-request', allocation.height) - - if not self._allocation: - self._allocation = allocation - self.queue_draw() - return True - - pthbr_width = allocation.width - parts_width = self.get_parts_width() - - #~ print parts_width, pthbr_width - - #~ self.animator.reset('on_allocate') - self.set_use_animations(True) - - if pthbr_width > parts_width and self.out_of_width: - dw = pthbr_width - parts_width - self._grow_parts(dw) - - elif pthbr_width < parts_width: - overhang = parts_width - pthbr_width - if overhang > 0: - self.set_use_animations(False) - self._shrink_parts(overhang) - - self._allocation = allocation - if self.use_animations and self.animator.sequence and not \ - self.animator.in_progress: - self.animator.start() - else: - self.queue_draw() - - def _on_draw(self, widget, cr): - # always paint psuedo parts first - a = self.get_allocation() - context = self.get_style_context() - context.save() - context.add_class("button") - - self._paint_psuedo_parts(cr, context, a.x, a.y) - - # paint a frame around the entire pathbar - width = self.get_parts_width() - Gtk.render_background(context, cr, 1, 1, width - 2, a.height - 2) - - self._paint_widget_parts(cr, context, a.x, a.y) - - Gtk.render_frame(context, cr, 0, 0, width, a.height) - context.restore() - return True - - # private methods - def _paint_widget_parts(self, cr, context, xo, yo): - parts = self.get_children() - # paint in reverse order, so we get correct overlapping during - # animation - parts.reverse() - for part in parts: - part.paint(cr, - part.animation_allocation or part.get_allocation(), - context, - xo, yo) - - def _paint_psuedo_parts(self, cr, context, xo, yo): - # a special case: paint psuedo parts paint first, - # i.e those parts animating 'in' on their removal - for part in self.psuedo_parts: - part.paint(cr, - part.animation_allocation or part.get_allocation(), - context, - xo, yo) - - def _shrink_parts(self, overhang): - self.out_of_width = True - - for part in self: - old_width = part.get_width_request() - new_width = max(self.MIN_PART_WIDTH, old_width - overhang) - - if False: # self.use_animations: - self.animator.append_animation(part, - PathBarAnimator.WIDTH_CHANGE, - start_width=old_width, - end_width=new_width) - else: - part.set_size_request(new_width, - part.get_height_request()) - - overhang -= old_width - new_width - if overhang <= 0: - break - - def _grow_parts(self, claim): - children = self.get_children() - children.reverse() - - for part in children: - - if part.get_allocation().width == part.get_natural_width(): - continue - - growth = min(claim, (part.get_natural_width() - part.width)) - if growth <= 0: - break - - claim -= growth - - if self.use_animations: - self.animator.append_animation(part, - PathBarAnimator.WIDTH_CHANGE, - start_width=part.width, - end_width=part.width + growth) - else: - part.set_size_request(part.width + growth, - part.get_height_request()) - - def _make_space(self, part): - children = self.get_children() - if not children: - return - - cur_width = self.get_parts_width() - incomming_width = cur_width + part.get_width_request() - overhang = incomming_width - self.get_allocation().width - - if overhang > 0: - print 'shrink parts by:', overhang - self._shrink_parts(overhang) - - def _reclaim_space(self, part): - if not self.out_of_width: - return - - claim = part.get_width_request() - self._grow_parts(claim) - - def _append_compose_parts(self, new_part): - d = self.get_direction() - children = self.get_children() - n_parts = len(children) - - if n_parts > 0: - new_part.set_shape(ShapeEndCap(d)) - first_part = children[0] - first_part.set_shape(ShapeStartArrow(d)) - else: - new_part.set_shape(ShapeRoundedRect(d)) - - if not n_parts > 1: - return - - new_mid = children[-1] - new_mid.set_shape(ShapeMidArrow(d)) - - def _remove_compose_parts(self): - d = self.get_direction() - children = self.get_children() - n_parts = len(children) - - if n_parts == 0: - return - - elif n_parts == 1: - children[0].set_shape(ShapeRoundedRect(d)) - return - - last = children[-1] - last.set_shape(ShapeEndCap(d)) - self.queue_draw() - - def _cleanup_revealer(self): - if not self._revealer: - return - GObject.source_remove(self._revealer) - self._revealer = None - - def _theme(self, part): - #~ part.set_padding(self.theme['xpad'], self.theme['ypad']) - part.set_padding(12, 4) - - # public methods - @property - def first_part(self): - children = self.get_children() - if children: - return children[0] - - @property - def last_part(self): - children = self.get_children() - if children: - return children[-1] - - def reveal_part(self, part, animate=True): - # do not do here: - #~ self.animator.reset(who_called='reveal_animation') - self.set_use_animations(animate) - - part_old_width = part.get_width_request() - part_new_width = part.get_natural_width() - - if part_new_width == part_old_width: - return - - change_amount = part_new_width - part_old_width - - for p in self.get_children(): - - if p == part: - old_width = part_old_width - new_width = part_new_width - else: - if change_amount <= 0: - continue - - old_width = p.get_width_request() - new_width = max(self.MIN_PART_WIDTH, old_width - change_amount) - change_amount -= old_width - new_width - - if self.use_animations: - self.animator.append_animation(p, - PathBarAnimator.WIDTH_CHANGE, - start_width=old_width, - end_width=new_width) - else: - p.set_size_request(new_width, - p.get_height_request()) - - self.animator.start() - - def queue_reveal_part(self, part): - - def reveal_part_cb(part): - self.reveal_part(part) - return - - self._cleanup_revealer() - self._revealer = GObject.timeout_add(self._timeout_reveal, - reveal_part_cb, - part) - - def get_parts_width(self): - last = self.last_part - if not last: - return 0 - - if self.get_direction() != Gtk.TextDirection.RTL: - return last.x + last.width - self.first_part.x - - first = self.first_part - return first.x + first.width - last.x - - def get_visual_width(self): - last = self.last_part - first = self.first_part - if not last: - return 0 - - la = last.animation_allocation or last.get_allocation() - fa = first.animation_allocation or first.get_allocation() - - if self.get_direction() != Gtk.TextDirection.RTL: - return la.x + la.width - fa.x - - return fa.x + fa.width - la.x - - def set_use_animations(self, use_animations): - self.use_animations = use_animations - if not use_animations and self.animator.in_progress: - self.animator.reset() - - def append(self, part): - print 'append', part - print - part.set_nopaint(True) - self.animator.reset('append') - - self._theme(part) - self._append_compose_parts(part) - self._make_space(part) - - self.pack_start(part, False, False, 0) - part.show() - - if self.use_animations: - # XXX: please note that animations also get queued up - # within _shrink_parts() - self.animator.append_animation(part, PathBarAnimator.OUT) - #~ print self.animator.sequence - self.animator.start() - else: - part.set_nopaint(False) - part.queue_draw() - - def pop(self): - children = self.get_children() - if not children: - return - - self.animator.reset('pop') - - last = children[-1] - if self.use_animations: - # because we remove the real part immediately we need to - # replicate just enough attributes to preform the slide in - # animation - part = PsuedoPathPart(self, last) - self.psuedo_parts.append(part) - - self.remove(last) - - self._remove_compose_parts() - self._reclaim_space(last) - - last.destroy() - - if not self.use_animations: - return - - self.animator.append_animation(part, PathBarAnimator.IN) - self.animator.start() - - def navigate_up(self): - """ just another name for pop() """ - self.pop() - - -class PathPartCommon: - - def __init__(self): - self.animation_in_progress = False - self.animation_allocation = None - - @property - def x(self): - return self.get_allocation().x - - @property - def y(self): - return self.get_allocation().y - - @property - def width(self): - return self.get_allocation().width - - @property - def height(self): - return self.get_allocation().height - - def new_frame(self, allocation): - if self.is_nopaint: - self.is_nopaint = False - if not self.animation_in_progress: - self.animation_in_progress = True - - self.animation_allocation = allocation - self.queue_draw() - - def animation_finished(self): - self.animation_in_progress = False - self.animation_allocation = None - if self.get_parent(): - self.get_parent().queue_draw() - - def paint(self, cr, a, context, xo, yo): - if self.is_nopaint: - return - - cr.save() - - x, y = 0, 0 - w, h = a.width, a.height - arrow_width = 12 # theme['arrow-width'] - - if isinstance(self, PathPart): - _a = self.get_allocation() - self.shape.layout(cr, - _a.x - xo + 1, _a.y - yo, - w, h, 3, arrow_width) - cr.clip() - else: - Gtk.render_background(context, cr, - a.x - xo - 10, a.y - yo, - a.width + 10, a.height) - - cr.translate(a.x - xo, a.y - yo) - - if self.shape.name.find('Arrow') != -1: - # draw arrow head - cr.move_to(w - arrow_width / 2, 2) - cr.line_to(w + 5, h / 2) - cr.line_to(w - arrow_width / 2, h - 2) - # fetch the line color and stroke - rgba = context.get_border_color(Gtk.StateFlags.NORMAL) - cr.set_source_rgb(rgba.red, rgba.green, rgba.blue) - cr.set_line_width(1) - cr.stroke() - - # render the layout - e = self.layout.get_pixel_extents()[1] - lw, lh = e.width, e.height - pw, ph = a.width, a.height - - x = min(self.xpadding, (pw - lw) / 2) - y = (ph - lh) / 2 - - # layout area - Gtk.render_layout(context, - cr, - int(x), - int(y), - self.layout) - - # paint the focus frame if need be - if isinstance(self, PathPart) and self.has_focus(): - # layout area - x, w, h = x - 2, lw + 4, lh + 1 - Gtk.render_focus(context, cr, x, y, w, h) - - cr.restore() - - -class PsuedoPathPart(PathPartCommon): - - def __init__(self, pathbar, real_part): - PathPartCommon.__init__(self) - self.parent = pathbar - self.style = pathbar.get_style() - self.state = real_part.get_state() - self.allocation = real_part.get_allocation() - self.size_request = real_part.get_size_request() - self.xpadding = real_part.xpadding - self.ypadding = real_part.ypadding - - # PsuedoPathParts are only used during the remove animation - # sequence, so the shape is always a ShapeEndCap - self.shape = ShapeEndCap(pathbar.get_direction()) - - self.label = real_part.label - self.layout = real_part.create_pango_layout(self.label) - - self.is_nopaint = False - - def get_allocation(self): - return self.allocation - - def get_state(self): - return self.state - - def get_width_request(self): - return self.size_request[0] - - def get_height_request(self): - return self.size_request[1] - - def animation_finished(self): - pass - - def queue_draw(self): - a = self.allocation - aw = 12 - self.parent.queue_draw_area(a.x - aw / 2, a.y, - a.width + aw, a.height) - - -class PathPart(Gtk.EventBox, PathPartCommon): - - __gsignals__ = { - "clicked": (GObject.SignalFlags.RUN_LAST, - None, - (),), - } - - def __init__(self, label): - Gtk.EventBox.__init__(self) - PathPartCommon.__init__(self) - self.set_visible_window(False) - - self.atk = self.get_accessible() - self.atk.set_role(Atk.Role.PUSH_BUTTON) - - self.layout = self.create_pango_layout(label) - self.layout.set_ellipsize(Pango.EllipsizeMode.END) - - self.xpadding = 6 - self.ypadding = 3 - - self.shape = ShapeRoundedRect(self.get_direction()) - self.is_nopaint = False - - self.set_label(label) - self._init_event_handling() - - def __repr__(self): - return "PathPart: '%s'" % self.label - - def __str__(self): - return "PathPart: '%s'" % self.label - - # signal handlers - def _on_enter_notify(self, part, event): - self.pathbar.queue_reveal_part(self) - if self.pathbar._press_origin == part: - part.set_state(Gtk.StateFlags.ACTIVE) - else: - part.set_state(Gtk.StateFlags.PRELIGHT) - self.queue_draw() - - def _on_leave_notify(self, part, event): - self.pathbar.queue_reveal_part(self.pathbar.last_part) - part.set_state(Gtk.StateFlags.NORMAL) - self.queue_draw() - - def _on_button_press(self, part, event): - if event.button != 1: - return - self.pathbar._press_origin = part - part.set_state(Gtk.StateFlags.ACTIVE) - self.queue_draw() - - def _on_button_release(self, part, event): - if event.button != 1: - return - - if self.pathbar._press_origin != part: - self.pathbar._press_origin = None - return - - self.pathbar._press_origin = None - - state = part.get_state() - if state == Gtk.StateFlags.ACTIVE: - part.set_state(Gtk.StateFlags.PRELIGHT) - GObject.timeout_add(self.pathbar._timeout_initial, - self.emit, 'clicked') - - self.queue_draw() - - def _on_key_press(self, part, event): - if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter): - part.set_state(Gtk.StateFlags.ACTIVE) - self.queue_draw() - - def _on_key_release(self, part, event): - if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter): - part.set_state(Gtk.StateFlags.NORMAL) - GObject.timeout_add(self.pathbar._timeout_initial, - self.emit, 'clicked') - self.queue_draw() - - def _on_focus_in(self, part, event): - self.pathbar.reveal_part(self) - - def _on_focus_out(self, part, event): - self.queue_draw() - - # private methods - def _init_event_handling(self): - self.set_property("can-focus", True) - self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK) - - self.connect("enter-notify-event", self._on_enter_notify) - self.connect("leave-notify-event", self._on_leave_notify) - self.connect("button-press-event", self._on_button_press) - self.connect("button-release-event", self._on_button_release) - self.connect("key-press-event", self._on_key_press) - self.connect("key-release-event", self._on_key_release) - self.connect("focus-in-event", self._on_focus_in) - self.connect("focus-out-event", self._on_focus_out) - - def _calc_natural_size(self, who_called='?'): - ne = self.natural_extents - nw, nh = ne.width, ne.height - - nw += self.shape.hadjustment + 2 * self.xpadding - nh += 2 * self.ypadding - - self.natural_size = nw, nh - self.set_size_request(nw, nh) - - # public methods - @property - def pathbar(self): - return self.get_parent() - - def set_padding(self, xpadding, ypadding): - self.xpadding = xpadding - self.ypadding = ypadding - self._calc_natural_size() - - def set_size_request(self, width, height): - width = max(2 * self.xpadding + 1, width) - height = max(2 * self.ypadding + 1, height) - self.layout.set_width(Pango.SCALE * (width - 2 * self.xpadding)) - Gtk.Widget.set_size_request(self, width, height) - - def set_nopaint(self, is_nopaint): - self.is_nopaint = is_nopaint - self.queue_draw() - - def set_shape(self, shape): - if shape == self.shape: - return - self.shape = shape - self._calc_natural_size() - self.queue_draw() - - def set_label(self, label): - self.label = label - - self.atk.set_name(label) - self.atk.set_description(_('Navigates to the %s page.') % label) - - self.layout.set_markup(label, -1) - self.layout.set_width(-1) - self.natural_extents = self.layout.get_pixel_extents()[1] - - self._calc_natural_size() - self.queue_draw() - - def get_natural_size(self): - return self.natural_size - - def get_natural_width(self): - return self.natural_size[0] - - def get_natural_height(self): - return self.natural_size[1] - - def get_width_request(self): - return self.get_property("width-request") - - def get_height_request(self): - return self.get_property("height-request") - - def queue_draw(self): - a = self.get_allocation() - parent = self.get_parent() - if parent: - aw = 12 - else: - aw = 0 - self.queue_draw_area(a.x - aw / 2, a.y, - a.width + aw, a.height) - - -class NavigationBar(PathBar): - - def __init__(self, group=None): - PathBar.__init__(self) - self.id_to_part = {} - self._callback_id = None - - def _on_part_clicked(self, part): - part.callback(self, part) - - def add_with_id(self, label, callback, id, do_callback=True, animate=True): - """ - Add a new button with the given label/callback - - If there is the same id already, replace the existing one - with the new one - """ - LOG.debug("add_with_id label='%s' callback='%s' id='%s' " - "do_callback=%s animate=%s" % (label, callback, id, - do_callback, animate)) - - label = GObject.markup_escape_text(label) - - if not self.id_to_part: - self.set_use_animations(False) - else: - self.set_use_animations(animate) - - # check if we have the button of that id or need a new one - if id in self.id_to_part: - part = self.id_to_part[id] - if part.label == label: - return - - part.set_label(label) - else: - part = PathPart(label) - part.connect('clicked', self._on_part_clicked) - - part.set_name(id) - self.id_to_part[id] = part - - part.callback = callback - if do_callback: - # cleanup any superceeded idle callback - if self._callback_id: - GObject.source_remove(self._callback_id) - self._callback_id = None - - # if i do not have call the callback in an idle, - # all hell breaks loose - self._callback_id = GObject.idle_add(callback, - self, # pathbar - part) - - self.append(part) - - def remove_ids(self, *ids, **kwargs): - parts = self.get_parts() - - print 'remove ids', ids - - # it would seem parts can become stale within the id_to_part dict, - # so we clean these up ... - cleanup_ids = [] - # the index of the first part to be clipped - index = len(parts) - - for id, part in self.id_to_part.iteritems(): - if id not in ids: - continue - if part not in parts: - cleanup_ids.append(id) - part.destroy() - else: - index = min(index, parts.index(part)) - - if index == len(parts): - return - - # cleanup any stale id:part pairs in the id_to_part dict - for id in cleanup_ids: - del self.id_to_part[id] - - # remove id:part pairs from the id_to_part dict, for whom removal - # has been requested - for id in ids: - if id in self.id_to_part: - del self.id_to_part[id] - - #~ print index, self.id_to_part.keys() - - # the index is used to remove all parts after the index but we - # keep one part around to animate its removal - for part in parts[index + 1:]: - part.destroy() - - animate = True - if 'animate' in kwargs: - animate = kwargs['animate'] - - # animate the removal of the final part, or not - self.set_use_animations(animate) - self.pop() - - # check if we should call the new tail parts callback - if 'do_callback' in kwargs and kwargs['do_callback']: - part = self[-1] - part.callback(self, part) - - def remove_all(self, **kwargs): - if len(self) <= 1: - return - ids = filter(lambda k: k != 'category', - self.id_to_part.keys()) - self.remove_ids(*ids, **kwargs) - - def has_id(self, id): - return id in self.id_to_part - - def get_parts(self): - return self.get_children() - - def get_active(self): - parts = self.get_parts() - if parts: - return parts[-1] - - def get_button_from_id(self, id): - """ - return the button for the given id (or None) - """ - return self.id_to_part.get(id) - - def set_active_no_callback(self, part): - pass - - -class TestIt: - - def __init__(self): - - def append(button, entry, pathbar): - t = entry.get_text() or 'no label %s' % len(pathbar) - part = PathPart(t) - pathbar.append(part) - - def remove(button, entry, pathbar): - pathbar.pop() - - win = Gtk.Window() - win.set_border_width(30) - win.set_size_request(600, 300) - - vb = Gtk.VBox(spacing=6) - win.add(vb) - - pb = PathBar() - pb.set_size_request(-1, 30) - - vb.pack_start(pb, False, False, 0) - part = PathPart('Get Software') - pb.append(part) - - entry = Gtk.Entry() - vb.pack_start(entry, False, False, 0) - - b = Gtk.Button('Append') - vb.pack_start(b, True, True, 0) - b.connect('clicked', append, entry, pb) - - b = Gtk.Button('Remove') - vb.pack_start(b, True, True, 0) - b.connect('clicked', remove, entry, pb) - - win.show_all() - - win.connect('destroy', Gtk.main_quit) - self.win = win - self.win.pb = pb - - -def get_test_pathbar_window(): - t = TestIt() - return t.win - -if __name__ == '__main__': - win = get_test_pathbar_window() - Gtk.main() diff -Nru software-center-5.1.12/softwarecenter/utils.py software-center-5.1.13/softwarecenter/utils.py --- software-center-5.1.12/softwarecenter/utils.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/softwarecenter/utils.py 2012-03-20 08:00:09.000000000 +0000 @@ -16,6 +16,7 @@ # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +import dbus import gettext from gi.repository import GObject from gi.repository import Gio @@ -31,7 +32,7 @@ # py3 compat try: from urllib.parse import urlsplit - urlsplit # pyflakes + urlsplit # pyflakes except ImportError: from urlparse import urlsplit @@ -44,14 +45,16 @@ # define additional entities for the unescape method, needed # because only '&', '<', and '>' are included by default -ESCAPE_ENTITIES = {"'":"'", - '"':'"'} - +ESCAPE_ENTITIES = {"'": "'", + '"': '"'} + LOG = logging.getLogger(__name__) + class UnimplementedError(Exception): pass + class ExecutionTime(object): """ Helper that can be used in with statements to have a simple @@ -62,14 +65,17 @@ def __init__(self, info="", with_traceback=False): self.info = info self.with_traceback = with_traceback + def __enter__(self): self.now = time.time() + def __exit__(self, type, value, stack): logger = logging.getLogger("softwarecenter.performance") logger.debug("%s: %s" % (self.info, time.time() - self.now)) if self.with_traceback: log_traceback("populate model from query: '%s' (threaded: %s)") + def utf8(s): """ Takes a string or unicode object and returns a utf-8 encoded @@ -89,7 +95,7 @@ """ logger = logging.getLogger("softwarecenter.traceback") logger.debug("%s: %s" % (info, "".join(traceback.format_stack()))) - + def wait_for_apt_cache_ready(f): """ decorator that ensures that self.cache is ready using a @@ -97,10 +103,10 @@ """ def wrapper(*args, **kwargs): self = args[0] - # check if the cache is ready and + # check if the cache is ready and window = None if hasattr(self, "app_view"): - window = self.app_view.get_window() + window = self.app_view.get_window() if not self.cache.ready: if window: window.set_cursor(self.busy_cursor) @@ -113,6 +119,7 @@ return False return wrapper + def normalize_package_description(desc): """ this takes a package description and normalizes it so that all uneeded \n are stripped away and all @@ -147,7 +154,7 @@ # check if in a enumeration if part[:2] in BULLETS: in_blist = True - norm_description += "\n" + indent*' ' + "* " + part[2:] + norm_description += "\n" + indent * ' ' + "* " + part[2:] elif in_blist and indent > 0: norm_description += " " + part elif part.endswith('.') or part.endswith(':'): @@ -162,11 +169,12 @@ norm_description += part return norm_description.strip() + def get_title_from_html(html): """ takes a html string and returns the document title, if the document has no title it uses the first h1 (but only if that has no further html tags) - + returns "" if it can't find anything or can't parse the html """ import xml.etree.ElementTree @@ -182,17 +190,19 @@ all_h1 = root.findall(".//h1") if all_h1: h1 = all_h1[0] - # we don't support any sub html in the h1 when + # we don't support any sub html in the h1 when if len(h1) == 0: return h1.text return "" + def htmlize_package_description(desc): html = "" inside_li = False for part in normalize_package_description(desc).split("\n"): stripped_part = part.strip() - if not stripped_part: continue + if not stripped_part: + continue if stripped_part.startswith("* "): if not inside_li: html += "
    " @@ -207,15 +217,6 @@ html += "
" return html -def get_parent_xid(widget): - while widget.get_parent(): - widget = widget.get_parent() - window = widget.get_window() - #print dir(window) - if hasattr(window, 'xid'): - return window.xid - return 0 # cannot figure out how to get the xid of gdkwindow under pygi - def get_http_proxy_string_from_libproxy(url): """Helper that uses libproxy to get the http proxy for the given url """ @@ -229,51 +230,61 @@ else: return proxy + def get_http_proxy_string_from_gsettings(): """Helper that gets the http proxy from gsettings - Returns: string with http://auth:pw@proxy:port/ or None + May raise a exception if there is no schema for the proxy + (e.g. on non-gnome systems) + + Returns: string with http://auth:pw@proxy:port/, None """ - try: - # check if this is actually available and usable. if not - # well ... it segfaults (thanks pygi) - key = "org.gnome.system.proxy.http" - if not key in Gio.Settings.list_schemas(): - return None - settings = Gio.Settings.new(key) - if settings.get_boolean("enabled"): - authentication = "" - if settings.get_boolean("use-authentication"): - user = settings.get_string("authentication-user") - password = settings.get_string("authentication-password") - authentication = "%s:%s@" % (user, password) - host = settings.get_string("host") - port = settings.get_int("port") - http_proxy = "http://%s%s:%s/" % (authentication, host, port) - if host: - return http_proxy - except Exception: - logging.exception("failed to get proxy from gconf") + # check if this is actually available and usable. if not + # well ... it segfaults (thanks pygi) + key = "org.gnome.system.proxy.http" + if not key in Gio.Settings.list_schemas(): + raise ValueError("no key '%s'" % key) + settings = Gio.Settings.new(key) + if settings.get_string("host"): + authentication = "" + if settings.get_boolean("use-authentication"): + user = settings.get_string("authentication-user") + password = settings.get_string("authentication-password") + authentication = "%s:%s@" % (user, password) + host = settings.get_string("host") + port = settings.get_int("port") + # strip leading http (if there is one) + if host.startswith("http://"): + host = host[len("http://"):] + http_proxy = "http://%s%s:%s/" % (authentication, host, port) + if host: + return http_proxy + # no proxy + return None + def encode_for_xml(unicode_data, encoding="ascii"): """ encode a given string for xml """ return unicode_data.encode(encoding, 'xmlcharrefreplace') + def decode_xml_char_reference(s): - """ takes a string like - 'Search…' + """ takes a string like + 'Search…' and converts it to 'Search...' """ p = re.compile("\&\#x(\d\d\d\d);") return p.sub(r"\u\1", s).decode("unicode-escape") - + + def unescape(text): """ unescapes the given text """ return xml.sax.saxutils.unescape(text, ESCAPE_ENTITIES) + def uri_to_filename(uri): try: import apt_pkg @@ -283,6 +294,7 @@ uri = re.sub(p1, "", uri) return uri.replace("/", "_") + def human_readable_name_from_ppa_uri(ppa_uri): """ takes a PPA uri and returns a human readable name for it """ name = urlsplit(ppa_uri).path @@ -290,14 +302,16 @@ return name[0:-len("/ubuntu")] return name + def sources_filename_from_ppa_entry(entry): - """ + """ takes a PPA SourceEntry and returns a filename suitable for sources.list.d """ import apt_pkg name = "%s.list" % apt_pkg.URItoFileName(entry.uri) return name - + + def obfuscate_private_ppa_details(text): """ hides any private PPA details that may be found in the given text @@ -313,6 +327,7 @@ result = result.replace(url_parts.password, "hidden") return result + def release_filename_in_lists_from_deb_line(debline): """ takes a debline and returns the filename of the Release file @@ -322,12 +337,12 @@ entry = aptsources.sourceslist.SourceEntry(debline) name = "%s_dists_%s_Release" % (uri_to_filename(entry.uri), entry.dist) return name - + + def is_unity_running(): """ return True if Unity is currently running """ - import dbus unity_running = False try: bus = dbus.SessionBus() @@ -335,22 +350,27 @@ except: LOG.exception("could not check for Unity dbus service") return unity_running - -def get_icon_from_theme(icons, iconname=None, iconsize=Icons.APP_ICON_SIZE, missingicon=Icons.MISSING_APP): + + +def get_icon_from_theme(icons, iconname=None, iconsize=Icons.APP_ICON_SIZE, + missingicon=Icons.MISSING_APP): """ return the icon in the theme that corresponds to the given iconname - """ + """ if not iconname: iconname = missingicon try: icon = icons.load_icon(iconname, iconsize, 0) except Exception as e: - LOG.warning(utf8("could not load icon '%s', displaying missing icon instead: %s " - ) % (utf8(iconname), utf8(e.message))) + LOG.warning(utf8("could not load icon '%s', displaying missing "\ + "icon instead: %s ") % ( + utf8(iconname), utf8(e.message))) icon = icons.load_icon(missingicon, iconsize, 0) return icon - -def get_file_path_from_iconname(icons, iconname=None, iconsize=Icons.APP_ICON_SIZE): + + +def get_file_path_from_iconname(icons, iconname=None, + iconsize=Icons.APP_ICON_SIZE): """ return the file path of the icon in the theme that corresponds to the given iconname, or None if it cannot be determined @@ -366,41 +386,56 @@ icon_file_path = icon_info.get_filename() icon_info.free() return icon_file_path - -def convert_desktop_file_to_installed_location(app_install_data_file_path, pkgname): + + +def convert_desktop_file_to_installed_location(app_install_data_file_path, + pkgname): """ returns the installed desktop file path that corresponds to the given app-install-data file path, and will also check directly for the desktop file that corresponds to a given pkgname. """ if app_install_data_file_path and pkgname: # "normal" case - installed_desktop_file_path = app_install_data_file_path.replace("app-install/desktop/" - + pkgname + ":", - "applications/") + installed_desktop_file_path = app_install_data_file_path.replace( + "app-install/desktop/" + pkgname + ":", "applications/") if os.path.exists(installed_desktop_file_path): - return installed_desktop_file_path + return installed_desktop_file_path # next, try case where a subdirectory is encoded in the app-install # desktop filename, e.g. kde4_soundkonverter.desktop - installed_desktop_file_path = installed_desktop_file_path.replace(APP_INSTALL_PATH_DELIMITER, "/") + installed_desktop_file_path = installed_desktop_file_path.replace( + APP_INSTALL_PATH_DELIMITER, "/") if os.path.exists(installed_desktop_file_path): return installed_desktop_file_path - # lastly, just try checking directly for the desktop file based on the pkgname itself + # lastly, just try checking directly for the desktop file based on the + # pkgname itself if pkgname: - installed_desktop_file_path = "/usr/share/applications/%s.desktop" % pkgname + installed_desktop_file_path = "/usr/share/applications/%s.desktop" %\ + pkgname if os.path.exists(installed_desktop_file_path): return installed_desktop_file_path - LOG.warn("Could not determine the installed desktop file path for app-install desktop file: '%s'" % app_install_data_file_path) + LOG.warn("Could not determine the installed desktop file path for " + "app-install desktop file: '%s'" % app_install_data_file_path) return "" + def clear_token_from_ubuntu_sso(appname): - """ send a dbus signal to the com.ubuntu.sso service to clear + """ send a dbus signal to the com.ubuntu.sso service to clear the credentials for the given appname, e.g. _("Ubuntu Software Center") """ - import dbus + from ubuntu_sso import ( + DBUS_BUS_NAME, + DBUS_CREDENTIALS_IFACE, + DBUS_CREDENTIALS_PATH, + ) bus = dbus.SessionBus() - proxy = bus.get_object('com.ubuntu.sso', '/com/ubuntu/sso/credentials') + obj = bus.get_object(bus_name=DBUS_BUS_NAME, + object_path=DBUS_CREDENTIALS_PATH, + follow_name_owner_changes=True) + proxy = dbus.Interface(object=obj, + dbus_interface=DBUS_CREDENTIALS_IFACE) proxy.clear_credentials(appname, {}) + def get_nice_date_string(cur_t): """ return a "nice" human readable date, like "2 minutes ago" """ import datetime @@ -410,31 +445,28 @@ secs = dt.seconds if days < 1: - if secs < 120: # less than 2 minute ago s = _('a few minutes ago') # dont be fussy elif secs < 3600: # less than an hour ago s = gettext.ngettext("%(min)i minute ago", "%(min)i minutes ago", - (secs/60)) % { 'min' : (secs/60) } + (secs / 60)) % {'min': (secs / 60)} else: # less than a day ago s = gettext.ngettext("%(hours)i hour ago", "%(hours)i hours ago", - (secs/3600)) % { 'hours' : (secs/3600) } - - elif days <= 5: # less than a week ago + (secs / 3600)) % {'hours': (secs / 3600)} + elif days <= 5: # less than a week ago s = gettext.ngettext("%(days)i day ago", "%(days)i days ago", - days) % { 'days' : days } - + days) % {'days': days} else: # any timedelta greater than 5 days old # YYYY-MM-DD s = cur_t.isoformat().split('T')[0] - return s + def _get_from_desktop_file(desktop_file, key): import ConfigParser config = ConfigParser.ConfigParser() @@ -444,22 +476,26 @@ except ConfigParser.NoOptionError: return None + def get_exec_line_from_desktop(desktop_file): return _get_from_desktop_file(desktop_file, "Exec") + def is_no_display_desktop_file(desktop_file): - nd = _get_from_desktop_file(desktop_file, "NoDisplay") + nd = _get_from_desktop_file(desktop_file, "NoDisplay") # desktop spec says the booleans are always either "true" or "false if nd == "true": return True return False + def get_nice_size(n_bytes): - nice_size = lambda s:[(s%1024**i and "%.1f"%(s/1024.0**i) or \ - str(s/1024**i))+x.strip() for i,x in enumerate(' KMGTPEZY') \ - if s<1024**(i+1) or i==8][0] + nice_size = lambda s: [(s % 1024 ** i and "%.1f" % (s / 1024.0 ** i) or \ + str(s / 1024 ** i)) + x.strip() for i, x in enumerate(' KMGTPEZY') \ + if s < 1024 ** (i + 1) or i == 8][0] return nice_size(n_bytes) - + + def save_person_to_config(username): """ save the specified username value for Ubuntu SSO to the config file """ @@ -473,11 +509,12 @@ config.set("reviews", "username", username) config.write() # refresh usefulness cache in the background once we know - # the person + # the person from backend.reviews import UsefulnessCache UsefulnessCache(True) return - + + def get_person_from_config(): """ get the username value for Ubuntu SSO from the config file """ @@ -486,63 +523,69 @@ return cfg.get("reviews", "username") return None + def pnormaldist(qn): - '''Inverse normal distribution, based on the Ruby statistics2.pnormaldist''' + """ + Inverse normal distribution, based on the Ruby statistics2.pnormaldist + """ b = [1.570796288, 0.03706987906, -0.8364353589e-3, -0.2250947176e-3, 0.6841218299e-5, 0.5824238515e-5, -0.104527497e-5, 0.8360937017e-7, -0.3231081277e-8, 0.3657763036e-10, 0.6936233982e-12] - + if qn < 0 or qn > 1: raise ValueError("qn must be between 0.0 and 1.0") if qn == 0.5: return 0.0 - + w1 = qn if qn > 0.5: w1 = 1.0 - w1 w3 = -math.log(4.0 * w1 * (1.0 - w1)) w1 = b[0] - for i in range (1,11): + for i in range(1, 11): w1 = w1 + (b[i] * math.pow(w3, i)) - + if qn > 0.5: - return math.sqrt(w1*w3) + return math.sqrt(w1 * w3) else: - return -math.sqrt(w1*w3) + return -math.sqrt(w1 * w3) + def wilson_score(pos, n, power=0.2): if n == 0: return 0 - z = pnormaldist(1-power/2) + z = pnormaldist(1 - power / 2) phat = 1.0 * pos / n - return (phat + z*z/(2*n) - z * math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n) + return (phat + z * z / (2 * n) - z * math.sqrt( + (phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n) + def calc_dr(ratings, power=0.1): '''Calculate the dampened rating for an app given its collective ratings''' if not len(ratings) == 5: raise AttributeError('ratings argument must be a list of 5 integers') - + tot_ratings = 0 - for i in range (0,5): + for i in range(0, 5): tot_ratings = ratings[i] + tot_ratings - + sum_scores = 0.0 - for i in range (0,5): + for i in range(0, 5): ws = wilson_score(ratings[i], tot_ratings, power) - sum_scores = sum_scores + float((i+1)-3) * ws - + sum_scores = sum_scores + float((i + 1) - 3) * ws return sum_scores + 3 + # we need this because some iconnames have already split off the extension # (the desktop file standard suggests this) but still have a "." in the name. # From other sources we get icons with a full extension so a simple splitext() # is not good enough def split_icon_ext(iconname): - """ return the basename of a icon if it matches a known icon + """ return the basename of a icon if it matches a known icon extenstion like tiff, gif, jpg, svg, png, xpm, ico """ - SUPPORTED_EXTENSIONS = [".tiff", ".tif", ".gif", ".jpg", ".jpeg", ".svg", + SUPPORTED_EXTENSIONS = [".tiff", ".tif", ".gif", ".jpg", ".jpeg", ".svg", ".png", ".xpm", ".ico"] basename, ext = os.path.splitext(iconname) if ext.lower() in SUPPORTED_EXTENSIONS: @@ -555,26 +598,31 @@ # we are running in a local checkout, make life as easy as possible # for this if os.path.exists("./data/ui/gtk3/SoftwareCenter.ui"): - logging.getLogger("softwarecenter").info("Using data (UI, xapian) from current dir") + logging.getLogger("softwarecenter").info( + "Using data (UI, xapian) from current dir") # set pythonpath for the various helpers - if os.environ.get("PYTHONPATH",""): - os.environ["PYTHONPATH"]=os.path.abspath(".") + ":" + os.environ.get("PYTHONPATH","") + if os.environ.get("PYTHONPATH", ""): + os.environ["PYTHONPATH"] = os.path.abspath(".") + ":" +\ + os.environ.get("PYTHONPATH", "") else: - os.environ["PYTHONPATH"]=os.path.abspath(".") + os.environ["PYTHONPATH"] = os.path.abspath(".") datadir = "./data" xapian_base_path = datadir # set new global datadir softwarecenter.paths.datadir = datadir # also alter the app-install path - path = "%s/desktop/software-center.menu" % softwarecenter.paths.APP_INSTALL_PATH + path = "%s/desktop/software-center.menu" % \ + softwarecenter.paths.APP_INSTALL_PATH if not os.path.exists(path): softwarecenter.paths.APP_INSTALL_PATH = './build/share/app-install' - logging.warn("using local APP_INSTALL_PATH: %s" % softwarecenter.paths.APP_INSTALL_PATH) + logging.warn("using local APP_INSTALL_PATH: %s" %\ + softwarecenter.paths.APP_INSTALL_PATH) else: datadir = softwarecenter.paths.datadir xapian_base_path = softwarecenter.paths.XAPIAN_BASE_PATH return (datadir, xapian_base_path) - + + def get_uuid(): import uuid return str(uuid.uuid4()) @@ -585,18 +633,18 @@ LOG = logging.getLogger("softwarecenter.simplefiledownloader") __gsignals__ = { - "file-url-reachable" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (bool,),), - - "file-download-complete" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (str,),), - - "error" : (GObject.SIGNAL_RUN_LAST, - GObject.TYPE_NONE, - (GObject.TYPE_PYOBJECT, - GObject.TYPE_PYOBJECT,),), + "file-url-reachable": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (bool,),), + + "file-download-complete": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (str,),), + + "error": (GObject.SIGNAL_RUN_LAST, + GObject.TYPE_NONE, + (GObject.TYPE_PYOBJECT, + GObject.TYPE_PYOBJECT,),), } def __init__(self): @@ -606,7 +654,7 @@ def download_file(self, url, dest_file_path=None, use_cache=False, simple_quoting_for_webkit=False): - """ Download a url and emit the file-download-complete + """ Download a url and emit the file-download-complete once the file is there. Note that calling this twice will cancel the previous pending operation. If dest_file_path is given, download to that specific @@ -661,19 +709,20 @@ f = Gio.File.new_for_uri(url) # first check if the url is reachable - f.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_SIZE, 0, 0, + f.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_SIZE, 0, 0, self._cancellable, self._check_url_reachable_and_then_download_cb, None) - - def _check_url_reachable_and_then_download_cb(self, f, result, user_data=None): + + def _check_url_reachable_and_then_download_cb(self, f, result, + user_data=None): self.LOG.debug("_check_url_reachable_and_then_download_cb: %s" % f) try: info = f.query_info_finish(result) etag = info.get_etag() self.emit('file-url-reachable', True) self.LOG.debug("file reachable %s %s %s" % (self.url, - info, + info, etag)) # url is reachable, now download the file f.load_contents_async( @@ -686,7 +735,7 @@ def _file_download_complete_cb(self, f, result, path=None): self.LOG.debug("file download completed %s" % self.dest_file_path) - # The result from the download is actually a tuple with three + # The result from the download is actually a tuple with three # elements (content, size, etag?) # The first element is the actual content so let's grab that try: @@ -732,7 +781,8 @@ from softwarecenter.db.pkginfo import get_pkg_info # do not call here get_pkg_info, since package switch may not have been set # instead use an anonymous function delay -upstream_version_compare = lambda v1, v2: get_pkg_info().upstream_version_compare(v1, v2) +upstream_version_compare = lambda v1, v2: \ + get_pkg_info().upstream_version_compare(v1, v2) upstream_version = lambda v: get_pkg_info().upstream_version(v) version_compare = lambda v1, v2: get_pkg_info().version_compare(v1, v2) diff -Nru software-center-5.1.12/softwarecenter/version.py software-center-5.1.13/softwarecenter/version.py --- software-center-5.1.12/softwarecenter/version.py 2012-03-09 08:07:47.000000000 +0000 +++ software-center-5.1.13/softwarecenter/version.py 2012-03-20 11:24:47.000000000 +0000 @@ -1,5 +1,5 @@ -VERSION='5.1.12' +VERSION='5.1.13' CODENAME='precise' DISTRO='Ubuntu' RELEASE='12.04' diff -Nru software-center-5.1.12/test/coverage_summary software-center-5.1.13/test/coverage_summary --- software-center-5.1.12/test/coverage_summary 2012-03-09 08:07:38.000000000 +0000 +++ software-center-5.1.13/test/coverage_summary 2012-03-20 11:24:38.000000000 +0000 @@ -1,148 +1,148 @@ Name Stmts Miss Cover -------------------------------------------------------------------------------------------------------------------------------------------- -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/__init__ 2 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/channel 182 19 90% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/channel_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/channel_impl/aptchannels 144 46 68% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/fake_review_settings 77 33 57% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/installbackend 36 14 61% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/installbackend_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/installbackend_impl/aptd 478 296 38% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/launchpad 184 112 39% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/login 9 2 78% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/login_sso 96 32 67% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/oneconfhandler/__init__ 17 5 71% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/oneconfhandler/core 98 15 85% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/piston/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/piston/rnrclient 44 29 34% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/piston/rnrclient_fake 147 80 46% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/piston/rnrclient_pristine 80 26 68% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/recagent 115 34 70% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/reviews/__init__ 362 129 64% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/reviews/rnr 205 100 51% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/scagent 78 26 67% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/spawn_helper 87 14 84% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/transactionswatcher 50 19 62% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/ubuntusso 75 38 49% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/unitylauncher 26 11 58% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/weblive 168 100 40% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/backend/weblive_pristine 145 84 42% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/cmdfinder 31 3 90% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/config 28 9 68% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/__init__ 8 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/appfilter 67 11 84% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/application 518 122 76% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/categories 301 36 88% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/database 347 88 75% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/debfile 124 35 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/enquire 147 6 96% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/history 36 13 64% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/history_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/history_impl/apthistory 132 33 75% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/pkginfo 108 35 68% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/pkginfo_impl/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/pkginfo_impl/aptcache 520 147 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/update 653 63 90% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/db/utils 23 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/distro/Debian 88 66 25% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/distro/Ubuntu 126 51 60% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/distro/__init__ 90 43 52% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/enums 120 8 93% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/gwibber_helper 64 22 66% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/hw 11 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/i18n 46 4 91% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/log 62 14 77% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/netstatus 88 18 80% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/paths 53 13 75% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/plugin 62 4 94% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/region 79 5 94% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/testutils 114 4 96% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/toolkit 13 3 77% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/SimpleGtkbuilderApp 18 5 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/app 691 343 50% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/aptd_gtk3 44 36 18% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/__init__ 67 24 64% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog 78 66 15% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/dialogs/dependency_dialogs 79 19 76% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/drawing 68 34 50% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/em 33 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/gmenusearch 82 37 55% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/models/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/models/appstore2 280 42 85% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/models/pendingstore 111 65 41% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/availablepane 421 136 68% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/basepane 15 5 67% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/globalpane 60 4 93% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/historypane 254 23 91% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/installedpane 419 118 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/pendingpane 95 28 71% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/softwarepane 297 74 75% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/panes/viewswitcher 174 70 60% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/review_gui_helper 803 315 61% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/session/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/session/appmanager 89 12 87% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/session/navhistory 167 17 90% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/session/viewmanager 129 31 76% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/shapes 157 97 38% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/utils 53 7 87% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/appdetailsview 1306 200 85% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/appview 193 32 83% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/catview_gtk 437 61 86% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/pkgnamesview 64 8 88% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/views/purchaseview 235 104 56% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/actionbar 230 81 65% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/apptreeview 431 216 50% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/backforward 108 23 79% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/buttons 437 80 82% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/cellrenderers 330 49 85% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/containers 396 28 93% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/description 837 448 46% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/exhibits 381 67 82% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/imagedialog 35 3 91% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/labels 59 11 81% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/menubutton 64 45 30% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/oneconfviews 98 45 54% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/recommendations 133 12 91% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/reviews 578 99 83% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/searchaid 179 61 66% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/searchentry 89 25 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/separators 26 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/spinner 64 2 97% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/stars 340 44 87% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/symbolic_icons 149 10 93% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/thumbnail 340 59 83% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/videoplayer 110 56 49% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/viewport 24 15 38% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/gtk3/widgets/weblivedialog 68 58 15% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/qml/__init__ 0 0 100% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/qml/categoriesmodel 53 21 60% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/qml/pkglist 132 64 52% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/ui/qml/reviewslist 46 3 93% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/utils 433 120 72% -/home/egon/devel/software-center/build-area/software-center-5.1.12/softwarecenter/version 4 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/__init__ 2 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/channel 181 19 90% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/channel_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/channel_impl/aptchannels 144 49 66% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/fake_review_settings 76 33 57% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/installbackend 36 14 61% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/installbackend_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/installbackend_impl/aptd 490 307 37% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/launchpad 184 112 39% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/login 9 2 78% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/login_sso 96 32 67% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/oneconfhandler/__init__ 17 5 71% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/oneconfhandler/core 97 14 86% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/piston/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/piston/rnrclient 44 29 34% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/piston/rnrclient_fake 147 80 46% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/piston/rnrclient_pristine 80 26 68% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/recagent 121 41 66% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/reviews/__init__ 360 142 61% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/reviews/rnr 205 100 51% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/scagent 78 22 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/spawn_helper 88 14 84% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/transactionswatcher 50 19 62% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/ubuntusso 75 38 49% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/unitylauncher 26 11 58% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/weblive 168 100 40% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/backend/weblive_pristine 147 84 43% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/cmdfinder 31 3 90% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/config 28 9 68% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/__init__ 8 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/appfilter 67 11 84% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/application 518 124 76% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/categories 310 36 88% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/database 350 88 75% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/debfile 124 35 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/enquire 146 6 96% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/history 36 13 64% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/history_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/history_impl/apthistory 132 33 75% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/pkginfo 107 35 67% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/pkginfo_impl/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/pkginfo_impl/aptcache 520 147 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/update 657 62 91% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/db/utils 23 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/distro/Debian 87 65 25% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/distro/Ubuntu 125 51 59% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/distro/__init__ 90 43 52% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/enums 120 8 93% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/gwibber_helper 64 22 66% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/hw 40 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/i18n 46 4 91% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/log 62 14 77% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/netstatus 88 18 80% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/paths 43 8 81% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/plugin 62 4 94% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/region 79 5 94% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/testutils 115 4 97% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/SimpleGtkbuilderApp 18 5 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/app 717 357 50% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/aptd_gtk3 44 36 18% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/__init__ 80 28 65% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog 79 67 15% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dependency_dialogs 79 19 76% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/dialogs/dialog_tos 49 5 90% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/drawing 66 32 52% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/em 33 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/gmenusearch 80 35 56% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/models/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/models/appstore2 267 41 85% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/models/pendingstore 111 65 41% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/availablepane 419 133 68% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/basepane 15 5 67% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/globalpane 58 4 93% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/historypane 252 20 92% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/installedpane 418 119 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/pendingpane 95 28 71% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/softwarepane 290 72 75% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/panes/viewswitcher 168 65 61% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/review_gui_helper 794 311 61% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/session/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/session/appmanager 89 12 87% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/session/navhistory 164 17 90% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/session/viewmanager 125 28 78% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/utils 62 11 82% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/appdetailsview 1266 186 85% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/appview 185 31 83% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/catview_gtk 446 61 86% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/pkgnamesview 64 8 88% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/purchaseview 202 81 60% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/views/webkit 60 29 52% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/actionbar 223 79 65% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/apptreeview 432 222 49% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/backforward 101 20 80% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/buttons 410 66 84% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/cellrenderers 308 49 84% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/containers 378 24 94% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/description 791 424 46% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/exhibits 373 63 83% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/imagedialog 35 3 91% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/labels 58 11 81% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/menubutton 64 45 30% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/oneconfviews 98 45 54% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/recommendations 184 36 80% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/reviews 578 112 81% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/searchaid 179 61 66% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/searchentry 89 25 72% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/separators 26 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/spinner 64 2 97% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/stars 360 44 88% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/symbolic_icons 149 10 93% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/thumbnail 340 59 83% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/videoplayer 110 56 49% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/viewport 24 15 38% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/gtk3/widgets/weblivedialog 68 58 15% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/qml/__init__ 0 0 100% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/qml/categoriesmodel 53 21 60% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/qml/pkglist 132 64 52% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/ui/qml/reviewslist 46 3 93% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/utils 428 106 75% +/home/egon/devel/software-center/build-area/software-center-5.1.13/softwarecenter/version 4 0 100% __init__ 0 0 100% data/plugins/mock_plugin 4 0 100% gtk3/test_app_view 61 0 100% -gtk3/test_appdetailsview 323 5 98% +gtk3/test_appdetailsview 332 5 98% gtk3/test_appmanager 51 0 100% gtk3/test_appstore2 35 0 100% -gtk3/test_catview 136 0 100% +gtk3/test_catview 172 0 100% gtk3/test_custom_lists 38 0 100% gtk3/test_debfile_view 27 0 100% -gtk3/test_dialogs 27 0 100% +gtk3/test_dialogs 31 0 100% gtk3/test_globalpane 13 0 100% gtk3/test_install_progress 32 0 100% gtk3/test_installedpane 40 0 100% gtk3/test_navhistory 135 0 100% gtk3/test_panes 42 0 100% -gtk3/test_purchase 61 0 100% -gtk3/test_recommendations_widgets 14 0 100% +gtk3/test_purchase 77 0 100% +gtk3/test_recommendations_widgets 24 0 100% gtk3/test_reviews 129 2 98% gtk3/test_search 48 0 100% gtk3/test_unity_launcher_integration 84 0 100% @@ -152,7 +152,7 @@ test_addons 30 0 100% test_aptd 72 28 61% test_apthistory 71 1 99% -test_categories 58 0 100% +test_categories 64 0 100% test_channels 25 0 100% test_cmdfiner 20 0 100% test_database 398 1 99% @@ -163,6 +163,7 @@ test_enquire 28 2 93% test_gwibber 34 7 79% test_htmlize 21 0 100% +test_hw 22 0 100% test_i18n 36 0 100% test_launchpad 31 1 97% test_login_backend 19 0 100% @@ -170,17 +171,18 @@ test_netstatus 16 0 100% test_origin 22 0 100% test_package_info 59 0 100% -test_pep8 41 1 98% +test_pep8 39 1 97% test_pkginfo 36 1 97% test_plugin 18 0 100% test_ppa_iconfilename 41 1 98% test_purchase_backend 47 23 51% test_pyflakes 9 0 100% -test_recagent 102 20 80% +test_recagent 102 26 75% test_region 46 0 100% test_reinstall_purchased 140 0 100% test_rnr_api 14 0 100% test_scagent 40 2 95% +test_spawn_helper 17 0 100% test_startup 39 24 38% test_testutils 37 0 100% test_ubuntu_sso_api 19 0 100% @@ -189,4 +191,4 @@ test_xapian 71 1 99% test_xapian_query 53 1 98% -------------------------------------------------------------------------------------------------------------------------------------------- -TOTAL 23680 6021 75% +TOTAL 23640 5924 75% diff -Nru software-center-5.1.12/test/data/desktop/software-center.menu software-center-5.1.13/test/data/desktop/software-center.menu --- software-center-5.1.12/test/data/desktop/software-center.menu 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/data/desktop/software-center.menu 2012-03-16 08:30:18.000000000 +0000 @@ -23,5 +23,15 @@ + + + Dynamic + + + AudioVideo + + + + diff -Nru software-center-5.1.12/test/gtk3/test_appdetailsview.py software-center-5.1.13/test/gtk3/test_appdetailsview.py --- software-center-5.1.12/test/gtk3/test_appdetailsview.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_appdetailsview.py 2012-03-16 08:30:18.000000000 +0000 @@ -51,7 +51,7 @@ do_events() self.assertTrue(self.view.videoplayer.get_property("visible")) - def test_page_pkgstatusbar(self): + def test_page_pkgstates(self): # show app app = Application("", "abiword") self.view.show_app(app) @@ -68,7 +68,7 @@ mock_details._error_not_found = "error not found" mock_details.price = "1.00" mock_details.pkgname = "abiword" - mock_details.error = "" + mock_details.error = "error-text" self.view.app_details = mock_details # the states and what labels we expect in the pkgstatusbar @@ -79,18 +79,25 @@ PkgStates.NEEDS_PURCHASE : ('US$ 1.00', u'Buy\u2026'), PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED : ('Purchased on 2011-11-20', 'Install'), } + # this describes if a button is visible or invisible + button_invisible = [ PkgStates.ERROR, + PkgStates.NOT_FOUND, + PkgStates.INSTALLING_PURCHASED, + PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES, + PkgStates.UNKNOWN, + ] - # show a app through the various states + # show a app through the various states and test if the right ui + # elements are visible and have the right text for var in vars(PkgStates): - # FIXME: this just ensures we are not crashing, also - # add functional tests to ensure on error we show - # the right info etc state = getattr(PkgStates, var) mock_details.pkg_state = state # reset app to ensure its shown again self.view.app = None # show it self.view.show_app(mock_app) + #do_events() + # check button label if state in pkg_states_to_labels: label, button_label = pkg_states_to_labels[state] self.assertEqual( @@ -99,6 +106,19 @@ self.assertEqual( self.view.pkg_statusbar.get_button_label().decode("utf-8"), button_label) + # check if button should be there or not + if state in button_invisible: + self.assertFalse( + self.view.pkg_statusbar.button.get_property("visible"), + "button visible error for state %s" % state) + else: + self.assertTrue( + self.view.pkg_statusbar.button.get_property("visible"), + "button visible error for state %s" % state) + # regression test for #955005 + if state == PkgStates.NOT_FOUND: + self.assertFalse(self.view.review_stats.get_property("visible")) + self.assertFalse(self.view.reviews.get_property("visible")) def test_app_icon_loading(self): # get icon @@ -565,6 +585,6 @@ if __name__ == "__main__": - #import logging - #logging.basicConfig(level=logging.DEBUG) + import logging + logging.basicConfig(level=logging.DEBUG) unittest.main() diff -Nru software-center-5.1.12/test/gtk3/test_catview.py software-center-5.1.13/test/gtk3/test_catview.py --- software-center-5.1.12/test/gtk3/test_catview.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_catview.py 2012-03-16 08:30:18.000000000 +0000 @@ -1,4 +1,4 @@ -from gi.repository import Gtk, GObject +from gi.repository import Gtk import time import unittest from mock import patch, Mock @@ -97,14 +97,14 @@ self.assertFalse(view.whats_new_frame.get_property("visible")) self._p() win.destroy() - + def test_subcatview_recommended_for_you_opt_in_display(self): # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_uuid_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent._get_recommender_uuid') - self.addCleanup(get_recommender_uuid_patcher.stop) - mock_get_recommender_uuid = get_recommender_uuid_patcher.start() - mock_get_recommender_uuid.return_value = "" + get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + self.addCleanup(get_recommender_opted_in_patcher.stop) + mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + mock_get_recommender_opted_in.return_value = False from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview # get the widgets we need @@ -123,10 +123,10 @@ def test_subcatview_recommended_for_you_spinner_display(self, mock_query): # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_uuid_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent._get_recommender_uuid') - self.addCleanup(get_recommender_uuid_patcher.stop) - mock_get_recommender_uuid = get_recommender_uuid_patcher.start() - mock_get_recommender_uuid.return_value = "" + get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + self.addCleanup(get_recommender_opted_in_patcher.stop) + mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + mock_get_recommender_opted_in.return_value = False from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview # get the widgets we need @@ -137,20 +137,10 @@ # click the opt-in button to initiate the process, this will show the spinner rec_panel.opt_in_button.emit('clicked') self._p() - from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox - self.assertTrue(rec_panel.spinner_notebook.get_current_page() == FramedHeaderBox.SPINNER) + from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook + self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.SPINNER_PAGE) self.assertTrue(rec_panel.opt_in_vbox.get_property("visible")) - # now pretent that we got data and ensure its displayed - rec_panel._update_recommended_for_you_content() - rec_panel.recommended_for_you_cat._recommend_me_result( - None, make_recommender_agent_recommend_me_dict()) - self._p() - self.assertTrue(rec_panel.recommended_for_you_content.get_property("visible")) - self.assertFalse(rec_panel.opt_in_vbox.get_property("visible")) - # exit after brief timeout - TIMEOUT=100 - GObject.timeout_add(TIMEOUT, lambda: win.destroy()) - Gtk.main() + win.destroy() # patch out the agent query method to avoid making the actual server call @patch('softwarecenter.backend.recagent.RecommenderAgent' @@ -158,10 +148,10 @@ def test_subcatview_recommended_for_you_display_recommendations(self, mock_query): # patch the recommender UUID value to insure that we are not opted-in for this test - get_recommender_uuid_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent._get_recommender_uuid') - self.addCleanup(get_recommender_uuid_patcher.stop) - mock_get_recommender_uuid = get_recommender_uuid_patcher.start() - mock_get_recommender_uuid.return_value = "" + get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + self.addCleanup(get_recommender_opted_in_patcher.stop) + mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + mock_get_recommender_opted_in.return_value = False from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview # get the widgets we need @@ -180,15 +170,86 @@ make_recommender_agent_recommend_me_dict()) self.assertNotEqual( lobby.recommended_for_you_panel.recommended_for_you_cat.get_documents(self.db), []) - from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox - self.assertTrue(rec_panel.spinner_notebook.get_current_page() == FramedHeaderBox.CONTENT) + from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook + self.assertTrue(rec_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE) self._p() # test clicking recommended_for_you More button lobby.connect("category-selected", self._on_category_selected) lobby.recommended_for_you_panel.more.clicked() self._p() self.assertNotEqual(self._cat, None) - self.assertEqual(self._cat.name, "Recommended for You") + self.assertEqual(self._cat.name, "Recommended For You") + win.destroy() + + # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent' + '.query_recommend_me') + def test_subcatview_recommended_for_you_display_recommendations_not_opted_in(self, mock_query): + + # patch the recommender UUID value to insure that we are not opted-in for this test + get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + self.addCleanup(get_recommender_opted_in_patcher.stop) + mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + mock_get_recommender_opted_in.return_value = False + + from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview + # get the widgets we need + win = get_test_window_catview() + # we want to work in the "subcat" view + notebook = win.get_child() + notebook.next_page() + + subcat_view = win.get_data("subcat") + self._p() + self.assertFalse(subcat_view.recommended_for_you_in_cat.get_property("visible")) + win.destroy() + + # patch out the agent query method to avoid making the actual server call + @patch('softwarecenter.backend.recagent.RecommenderAgent' + '.query_recommend_me') + def test_subcatview_recommended_for_you_display_recommendations_opted_in(self, mock_query): + + # patch the recommender UUID value to insure that we are not opted-in for this test + get_recommender_opted_in_patcher = patch('softwarecenter.backend.recagent.RecommenderAgent.is_opted_in') + self.addCleanup(get_recommender_opted_in_patcher.stop) + mock_get_recommender_opted_in = get_recommender_opted_in_patcher.start() + mock_get_recommender_opted_in.return_value = True + + from softwarecenter.ui.gtk3.views.catview_gtk import get_test_window_catview + # get the widgets we need + win = get_test_window_catview() + # we want to work in the "subcat" view + notebook = win.get_child() + notebook.next_page() + + subcat_view = win.get_data("subcat") + rec_cat_panel = subcat_view.recommended_for_you_in_cat + self._p() + rec_cat_panel._update_recommended_for_you_content() + self._p() + # we fake the callback from the agent here + rec_cat_panel.recommended_for_you_cat._recommend_me_result( + None, + make_recommender_agent_recommend_me_dict()) + result_docs = rec_cat_panel.recommended_for_you_cat.get_documents(self.db) + self.assertNotEqual(result_docs, []) + # check that we are getting the correct number of results, corresponding + # to the following Internet items: + # Mangler, Midori, Midori Private Browsing, Psi + self.assertTrue(len(result_docs) == 4) + from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook + self.assertTrue(rec_cat_panel.spinner_notebook.get_current_page() == SpinnerNotebook.CONTENT_PAGE) + # check that the tiles themselves are visible + self._p() + self.assertTrue(rec_cat_panel.recommended_for_you_content.get_property("visible")) + self.assertTrue(rec_cat_panel.recommended_for_you_content.get_children()[0].title.get_property("visible")) + self._p() + # test clicking recommended_for_you More button + subcat_view.connect("category-selected", self._on_category_selected) + rec_cat_panel.more.clicked() + self._p() + self.assertNotEqual(self._cat, None) + self.assertEqual(self._cat.name, "Recommended For You in Internet") win.destroy() def _p(self): diff -Nru software-center-5.1.12/test/gtk3/test_dialogs.py software-center-5.1.13/test/gtk3/test_dialogs.py --- software-center-5.1.12/test/gtk3/test_dialogs.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_dialogs.py 2012-03-20 08:00:09.000000000 +0000 @@ -33,6 +33,12 @@ res = softwarecenter.ui.gtk3.dialogs.error( parent=None, primary="primary", secondary="secondary") self.assertEqual(res, False) + + def test_accept_tos_dialog(self): + GObject.timeout_add(TIMEOUT, self._close_dialog) + res = softwarecenter.ui.gtk3.dialogs.show_accept_tos_dialog( + parent=None) + self.assertEqual(res, False) # helper def _close_dialog(self): diff -Nru software-center-5.1.12/test/gtk3/test_purchase.py software-center-5.1.13/test/gtk3/test_purchase.py --- software-center-5.1.12/test/gtk3/test_purchase.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_purchase.py 2012-03-20 08:00:09.000000000 +0000 @@ -3,11 +3,12 @@ import time import unittest -from mock import Mock +from mock import Mock,patch -from testutils import setup_test_env, do_events +from testutils import setup_test_env setup_test_env() +from softwarecenter.testutils import do_events from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane import softwarecenter.paths @@ -38,6 +39,23 @@ # run another one win.destroy() + def test_purchase_view_tos(self): + from softwarecenter.ui.gtk3.views.purchaseview import get_test_window_purchaseview + win = get_test_window_purchaseview() + view = win.get_data("view") + # install the mock + mock_config = Mock() + mock_config.has_option.return_value = False + mock_config.getboolean.return_value = False + view.config = mock_config + func = "softwarecenter.ui.gtk3.views.purchaseview.show_accept_tos_dialog" + with patch(func) as mock_func: + mock_func.return_value = False + res = view.initiate_purchase(None, None) + self.assertFalse(res) + self.assertTrue(mock_func.called) + win.destroy() + def test_spinner_emits_signals(self): from softwarecenter.ui.gtk3.views.purchaseview import get_test_window_purchaseview win = get_test_window_purchaseview() diff -Nru software-center-5.1.12/test/gtk3/test_recommendations_widgets.py software-center-5.1.13/test/gtk3/test_recommendations_widgets.py --- software-center-5.1.12/test/gtk3/test_recommendations_widgets.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_recommendations_widgets.py 2012-03-19 17:03:56.000000000 +0000 @@ -16,8 +16,20 @@ class TestRecommendationsWidgets(unittest.TestCase): - def test_recommendations_widgets(self): - win = get_test_window() + def test_recommendations_lobby(self): + win = get_test_window(panel_type="lobby") + win.show_all() + GObject.timeout_add(TIMEOUT, lambda: win.destroy()) + Gtk.main() + + def test_recommendations_category(self): + win = get_test_window(panel_type="category") + win.show_all() + GObject.timeout_add(TIMEOUT, lambda: win.destroy()) + Gtk.main() + + def test_recommendations_details(self): + win = get_test_window(panel_type="details") win.show_all() GObject.timeout_add(TIMEOUT, lambda: win.destroy()) Gtk.main() diff -Nru software-center-5.1.12/test/gtk3/testutils.py software-center-5.1.13/test/gtk3/testutils.py --- software-center-5.1.12/test/gtk3/testutils.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/gtk3/testutils.py 2012-03-19 16:41:16.000000000 +0000 @@ -24,23 +24,25 @@ m_dbus = m_polkit = m_aptd = None + + def start_dummy_backend(): global m_dbus, m_polkit, m_aptd # start private dbus - m_dbus = subprocess.Popen(["dbus-daemon", - "--session", + m_dbus = subprocess.Popen(["dbus-daemon", + "--session", "--nofork", - "--print-address"], + "--print-address"], stdout=subprocess.PIPE) # get and store address bus_address = m_dbus.stdout.readline().strip() os.environ["SOFTWARE_CENTER_APTD_FAKE"] = bus_address # start fake polkit from python-aptdaemon.test - env = { "DBUS_SESSION_BUS_ADDRESS" : bus_address, - "DBUS_SYSTEM_BUS_ADDRESS" : bus_address, + env = {"DBUS_SESSION_BUS_ADDRESS": bus_address, + "DBUS_SYSTEM_BUS_ADDRESS": bus_address, } m_polkit = subprocess.Popen( - ["/usr/share/aptdaemon/tests/fake-polkitd.py", + ["/usr/share/aptdaemon/tests/fake-polkitd.py", "--allowed-actions=all"], env=env) # start aptd in dummy mode @@ -51,6 +53,7 @@ # to ensure that the fake daemon and fake polkit is ready time.sleep(0.5) + def stop_dummy_backend(): global m_dbus, m_polkit, m_aptd m_aptd.terminate() @@ -60,6 +63,7 @@ m_dbus.terminate() m_dbus.wait() + def get_test_gtk3_viewmanager(): from gi.repository import Gtk from softwarecenter.ui.gtk3.session.viewmanager import ( @@ -68,9 +72,10 @@ if not vm: notebook = Gtk.Notebook() vm = ViewManager(notebook) - vm.view_to_pane = {None : None} + vm.view_to_pane = {None: None} return vm + def get_test_db(): from softwarecenter.db.database import StoreDatabase from softwarecenter.db.pkginfo import get_pkg_info @@ -81,34 +86,41 @@ db.open() return db + def get_test_install_backend(): from softwarecenter.backend.installbackend import get_install_backend backend = get_install_backend() return backend + def get_test_gtk3_icon_cache(): from softwarecenter.ui.gtk3.utils import get_sc_icon_theme import softwarecenter.paths icons = get_sc_icon_theme(softwarecenter.paths.datadir) return icons + def get_test_pkg_info(): from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() cache.open() return cache + def get_test_datadir(): import softwarecenter.paths return softwarecenter.paths.datadir + def get_test_categories(db): import softwarecenter.paths from softwarecenter.db.categories import CategoriesParser parser = CategoriesParser(db) - cats = parser.parse_applications_menu(softwarecenter.paths.APP_INSTALL_PATH) + cats = parser.parse_applications_menu( + softwarecenter.paths.APP_INSTALL_PATH) return cats + def get_test_enquirer_matches(db, query=None, limit=20, sortmode=0): from softwarecenter.db.enquire import AppEnquire import xapian @@ -121,12 +133,14 @@ nonblocking_load=False) return enquirer.matches + def do_events(): from gi.repository import GObject main_loop = GObject.main_context_default() while main_loop.pending(): main_loop.iteration() + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified @@ -138,12 +152,14 @@ details = app.get_details(db) details_mock = Mock(details) for a in dir(details): - if a.startswith("_"): continue + if a.startswith("_"): + continue setattr(details_mock, a, getattr(details, a)) app.details = details_mock app.get_details = lambda db: app.details return app + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -163,7 +179,7 @@ # factory stuff for the agent def make_software_center_agent_app_dict(): app_dict = { - u'archive_root' : 'http://private-ppa.launchpad.net/', + u'archive_root': 'http://private-ppa.launchpad.net/', u'archive_id': u'commercial-ppa-uploaders/photobomb', u'description': u"Easy and Social Image Editor\nPhotobomb " u"give you easy access to images in your " @@ -171,18 +187,21 @@ u'name': u'Photobomb', u'package_name': u'photobomb', u'signing_key_id': u'1024R/75254D99', - u'screenshot_url': 'http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot.png', + u'screenshot_url': 'http://software-center.ubuntu.com/site_media/'\ + 'screenshots/2011/08/Screenshot.png', u'license': 'Proprietary', u'support_url': 'mailto:support@example.com', - u'series': { 'oneiric' : ['i386', 'amd64'], - 'natty' : ['i386', 'amd64'], + u'series': {'oneiric': ['i386', 'amd64'], + 'natty': ['i386', 'amd64'], }, - u'channel' : 'For Purchase', - u'icon_url' : 'http://software-center.ubuntu.com/site_media/icons/2011/08/64_Chainz.png', + u'channel': 'For Purchase', + u'icon_url': 'http://software-center.ubuntu.com/site_media/icons/'\ + '2011/08/64_Chainz.png', u'categories': 'Game;LogicGame', } return app_dict + def make_software_center_agent_subscription_dict(app_dict): subscription_dict = { u'application': app_dict, @@ -198,78 +217,90 @@ } return subscription_dict + def make_recommender_agent_recommend_me_dict(): # best to have a list of likely not-installed items app_dict = { u'data': [ { u'package_name': u'clementine' - }, + }, { u'package_name': u'hedgewars' }, { - u'package_name': u'gelemental' - }, + u'package_name': u'mangler' + }, { u'package_name': u'nexuiz' }, { u'package_name': u'fgo' - }, + }, { u'package_name': u'musique' }, { u'package_name': u'pybik' - }, + }, { u'package_name': u'radiotray' }, { u'package_name': u'cherrytree' - }, + }, { u'package_name': u'phlipple' + }, + { + u'package_name': u'psi' + }, + { + u'package_name': u'midori' } - ] -} + ] + } return app_dict - -def make_recommender_profile_upload_data(): + + +def make_recommender_profile_upload_data(): from softwarecenter.utils import get_uuid recommender_uuid = get_uuid() profile_upload_data = [ { - 'uuid': recommender_uuid, + 'uuid': recommender_uuid, 'package_list': [ u'clementine', u'hedgewars', - u'gelemental', + u'mangler', u'nexuiz', u'fgo', u'musique', u'pybik', u'radiotray', u'cherrytree', - u'phlipple' + u'phlipple', + u'psi', + u'midori' ] } ] return profile_upload_data - + + def make_recommend_app_data(): - recommend_app_data = {u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', - u'data': [{u'rating': 4.0, u'package_name': u'kftpgrabber'}, - {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, - {u'rating': 3.0, u'package_name': u'wakeup'}, - {u'rating': 3.0, u'package_name': u'xvidcap'}, - {u'rating': 2.0, u'package_name': u'airstrike'}, - {u'rating': 2.0, u'package_name': u'pixbros'}, - {u'rating': 2.0, u'package_name': u'bomber'}, - {u'rating': 2.0, u'package_name': u'ktron'}, - {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, - {u'rating': 1.5, u'package_name': u'tucan'}], - u'app': u'pitivi'} + recommend_app_data = { + u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', + u'data': [ + {u'rating': 4.0, u'package_name': u'kftpgrabber'}, + {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, + {u'rating': 3.0, u'package_name': u'wakeup'}, + {u'rating': 3.0, u'package_name': u'xvidcap'}, + {u'rating': 2.0, u'package_name': u'airstrike'}, + {u'rating': 2.0, u'package_name': u'pixbros'}, + {u'rating': 2.0, u'package_name': u'bomber'}, + {u'rating': 2.0, u'package_name': u'ktron'}, + {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, + {u'rating': 1.5, u'package_name': u'tucan'}], + u'app': u'pitivi'} return recommend_app_data - diff -Nru software-center-5.1.12/test/gtk3/test_widgets.py software-center-5.1.13/test/gtk3/test_widgets.py --- software-center-5.1.12/test/gtk3/test_widgets.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/test/gtk3/test_widgets.py 2012-03-19 19:43:39.000000000 +0000 @@ -72,10 +72,10 @@ def test_show_image_dialog(self): from softwarecenter.ui.gtk3.widgets.imagedialog import SimpleShowImageDialog - if os.path.exists("../../data/images/arrows.png"): - f = "../../data/images/arrows.png" + if os.path.exists("../../data/default_banner/fallback.png"): + f = "../../data/default_banner/fallback.png" else: - f = "../data/images/arrows.png" + f = "../data/default_banner/fallback.png" pix = GdkPixbuf.Pixbuf.new_from_file(f) d = SimpleShowImageDialog("test caption", pix) GObject.timeout_add(TIMEOUT, lambda: d.destroy()) diff -Nru software-center-5.1.12/test/test_categories.py software-center-5.1.13/test/test_categories.py --- software-center-5.1.12/test/test_categories.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/test_categories.py 2012-03-16 08:30:18.000000000 +0000 @@ -8,7 +8,8 @@ setup_test_env() from softwarecenter.db.categories import ( - CategoriesParser, RecommendedForYouCategory, + CategoriesParser, + RecommendedForYouCategory, get_category_by_name, get_query_for_category) from softwarecenter.testutils import (get_test_db, make_recommender_agent_recommend_me_dict) @@ -31,7 +32,23 @@ None, make_recommender_agent_recommend_me_dict()) self.assertNotEqual(recommends_cat.get_documents(self.db), []) - + + @patch('softwarecenter.db.categories.RecommenderAgent') + def test_recommends_in_category_category(self, AgentMockCls): + # ensure we use the same instance in test and code + parser = CategoriesParser(self.db) + cats = parser.parse_applications_menu("./data") + # "2" is a multimedia query + # see ./test/data/desktop/software-center.menu + recommends_cat = RecommendedForYouCategory(cats[2]) + # ensure we get a query when the callback is called + recommends_cat._recommend_me_result( + None, + make_recommender_agent_recommend_me_dict()) + recommendations_in_cat = recommends_cat.get_documents(self.db) + print recommendations_in_cat + self.assertNotEqual(recommendations_in_cat, []) + def test_get_query(self): query = get_query_for_category(self.db, "Education") self.assertNotEqual(query, None) @@ -89,6 +106,6 @@ if __name__ == "__main__": - import logging - logging.basicConfig(level=logging.DEBUG) + #import logging + #logging.basicConfig(level=logging.DEBUG) unittest.main() diff -Nru software-center-5.1.12/test/test_hw.py software-center-5.1.13/test/test_hw.py --- software-center-5.1.12/test/test_hw.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.1.13/test/test_hw.py 2012-03-19 15:01:13.000000000 +0000 @@ -0,0 +1,47 @@ +#!/usr/bin/python + +import unittest + +from mock import patch + +from testutils import setup_test_env +setup_test_env() +from softwarecenter.hw import ( + get_hardware_support_for_tags, + get_hw_missing_long_description, + OPENGL_DRIVER_BLACKLIST_TAG) +from softwarecenter.utils import utf8 + +class TestHW(unittest.TestCase): + """ tests the hardware support detection """ + + def test_get_hardware_support_for_tags(self): + tags = [OPENGL_DRIVER_BLACKLIST_TAG + "intel", + "hardware::input:mouse", + ] + with patch("debtagshw.opengl.get_driver") as mock_get_driver: + # test with the intel driver + mock_get_driver.return_value = "intel" + supported = get_hardware_support_for_tags(tags) + self.assertEqual(supported[tags[0]], "no") + self.assertEqual(len(supported), 2) + # now with fake amd driver + mock_get_driver.return_value = "amd" + supported = get_hardware_support_for_tags(tags) + self.assertEqual(supported[tags[0]], "yes") + + def test_get_hw_missing_long_description(self): + s = get_hw_missing_long_description( + { "hardware::input:keyboard": "yes", + OPENGL_DRIVER_BLACKLIST_TAG + "intel": "no", + }) + self.assertEqual(s, + utf8(u'This software does not work with the ' + u'\u201cintel\u201D graphics driver this ' + u'computer is using.')) + + +if __name__ == "__main__": + #import logging + #logging.basicConfig(level=logging.DEBUG) + unittest.main() diff -Nru software-center-5.1.12/test/test_pep8.py software-center-5.1.13/test/test_pep8.py --- software-center-5.1.12/test/test_pep8.py 2012-03-09 07:45:56.000000000 +0000 +++ software-center-5.1.13/test/test_pep8.py 2012-03-19 16:41:16.000000000 +0000 @@ -7,19 +7,12 @@ setup_test_env() # Only test these two packages for now: -import softwarecenter.db.pkginfo_impl -import softwarecenter.ui.gtk3.widgets -import softwarecenter.ui.qml +import softwarecenter class PackagePep8TestCase(unittest.TestCase): maxDiff = None - packages = [softwarecenter.ui.qml, - softwarecenter.ui.gtk3.widgets, - softwarecenter.db.pkginfo_impl] - exclude = ['recommendations.py', 'oneconfviews.py', 'menubutton.py', - 'labels.py', 'imagedialog.py', 'exhibits.py', 'description.py', - 'containers.py', 'cellrenderers.py', 'buttons.py', 'backforward.py', - 'apptreeview.py', 'animatedimage.py', 'actionbar.py'] + packages = [softwarecenter] + exclude = [] def message(self, text): self.errors.append(text) diff -Nru software-center-5.1.12/test/test_recagent.py software-center-5.1.13/test/test_recagent.py --- software-center-5.1.12/test/test_recagent.py 2012-03-08 20:28:40.000000000 +0000 +++ software-center-5.1.13/test/test_recagent.py 2012-03-20 09:20:55.000000000 +0000 @@ -24,8 +24,8 @@ self.error = False self.orig_host = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") if not "SOFTWARE_CENTER_RECOMMENDER_HOST" in os.environ: - #server = "https://rec.staging.ubuntu.com" - server = "https://rec.ubuntu.com" + server = "https://rec.staging.ubuntu.com" + #server = "https://rec.ubuntu.com" os.environ["SOFTWARE_CENTER_RECOMMENDER_HOST"] = server def tearDown(self): @@ -124,8 +124,9 @@ recommender_agent.query_recommend_app("pitivi") self.loop.run() self.assertFalse(self.error) - - def test_recagent_query_recommend_all_apps(self): + + # disabled for now (2012-03-20) as the server is returning 504 + def disabled_test_recagent_query_recommend_all_apps(self): # NOTE: This requires a working recommender host that is reachable recommender_agent = RecommenderAgent() recommender_agent.connect("recommend-all-apps", self.on_query_done) diff -Nru software-center-5.1.12/test/test_reinstall_purchased.py software-center-5.1.13/test/test_reinstall_purchased.py --- software-center-5.1.12/test/test_reinstall_purchased.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/test/test_reinstall_purchased.py 2012-03-19 08:35:49.000000000 +0000 @@ -145,7 +145,7 @@ self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE), "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" - "/photobomb/ubuntu natty main") + "/photobomb/ubuntu precise main") class SCAApplicationParserTestCase(unittest.TestCase): @@ -250,14 +250,14 @@ parser = self._make_application_parser() expected_results = { - "Deb-Line": "deb https://username:random3atoken@" + "X-AppInstall-Deb-Line": "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu quintessential main", - "Deb-Line-Orig": + "X-AppInstall-Deb-Line-Orig": "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu natty main", - "Purchased-Date": "2011-09-16 06:37:52", + "X-AppInstall-Purchased-Date": "2011-09-16 06:37:52", } for key in expected_results: result = parser.get_desktop(key) diff -Nru software-center-5.1.12/test/test_spawn_helper.py software-center-5.1.13/test/test_spawn_helper.py --- software-center-5.1.12/test/test_spawn_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ software-center-5.1.13/test/test_spawn_helper.py 2012-03-20 08:00:09.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/python + +import unittest +from mock import patch + +from testutils import setup_test_env +setup_test_env() +from softwarecenter.backend.spawn_helper import SpawnHelper + +class TestSpawnHelper(unittest.TestCase): + + def test_spawn_helper_lp957599(self): + days_delta = 6 + spawn_helper = SpawnHelper() + with patch.object(spawn_helper, "run") as mock_run: + spawn_helper.run_generic_piston_helper( + "RatingsAndReviewsAPI", "review_stats", days=days_delta) + cmd = mock_run.call_args[0][0] + #print mock_run.call_args_list + #print cmd + self.assertEqual(cmd[3], 'RatingsAndReviewsAPI') + self.assertEqual(cmd[4], 'review_stats') + self.assertEqual(cmd[5], '{"days": 6}') + + + +if __name__ == "__main__": + #import logging + #logging.basicConfig(level=logging.DEBUG) + unittest.main() diff -Nru software-center-5.1.12/test/testutils.py software-center-5.1.13/test/testutils.py --- software-center-5.1.12/test/testutils.py 2012-03-09 07:45:49.000000000 +0000 +++ software-center-5.1.13/test/testutils.py 2012-03-19 16:41:16.000000000 +0000 @@ -24,23 +24,25 @@ m_dbus = m_polkit = m_aptd = None + + def start_dummy_backend(): global m_dbus, m_polkit, m_aptd # start private dbus - m_dbus = subprocess.Popen(["dbus-daemon", - "--session", + m_dbus = subprocess.Popen(["dbus-daemon", + "--session", "--nofork", - "--print-address"], + "--print-address"], stdout=subprocess.PIPE) # get and store address bus_address = m_dbus.stdout.readline().strip() os.environ["SOFTWARE_CENTER_APTD_FAKE"] = bus_address # start fake polkit from python-aptdaemon.test - env = { "DBUS_SESSION_BUS_ADDRESS" : bus_address, - "DBUS_SYSTEM_BUS_ADDRESS" : bus_address, + env = {"DBUS_SESSION_BUS_ADDRESS": bus_address, + "DBUS_SYSTEM_BUS_ADDRESS": bus_address, } m_polkit = subprocess.Popen( - ["/usr/share/aptdaemon/tests/fake-polkitd.py", + ["/usr/share/aptdaemon/tests/fake-polkitd.py", "--allowed-actions=all"], env=env) # start aptd in dummy mode @@ -51,6 +53,7 @@ # to ensure that the fake daemon and fake polkit is ready time.sleep(0.5) + def stop_dummy_backend(): global m_dbus, m_polkit, m_aptd m_aptd.terminate() @@ -60,6 +63,7 @@ m_dbus.terminate() m_dbus.wait() + def get_test_gtk3_viewmanager(): from gi.repository import Gtk from softwarecenter.ui.gtk3.session.viewmanager import ( @@ -68,9 +72,10 @@ if not vm: notebook = Gtk.Notebook() vm = ViewManager(notebook) - vm.view_to_pane = {None : None} + vm.view_to_pane = {None: None} return vm + def get_test_db(): from softwarecenter.db.database import StoreDatabase from softwarecenter.db.pkginfo import get_pkg_info @@ -81,34 +86,41 @@ db.open() return db + def get_test_install_backend(): from softwarecenter.backend.installbackend import get_install_backend backend = get_install_backend() return backend + def get_test_gtk3_icon_cache(): from softwarecenter.ui.gtk3.utils import get_sc_icon_theme import softwarecenter.paths icons = get_sc_icon_theme(softwarecenter.paths.datadir) return icons + def get_test_pkg_info(): from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() cache.open() return cache + def get_test_datadir(): import softwarecenter.paths return softwarecenter.paths.datadir + def get_test_categories(db): import softwarecenter.paths from softwarecenter.db.categories import CategoriesParser parser = CategoriesParser(db) - cats = parser.parse_applications_menu(softwarecenter.paths.APP_INSTALL_PATH) + cats = parser.parse_applications_menu( + softwarecenter.paths.APP_INSTALL_PATH) return cats + def get_test_enquirer_matches(db, query=None, limit=20, sortmode=0): from softwarecenter.db.enquire import AppEnquire import xapian @@ -121,12 +133,14 @@ nonblocking_load=False) return enquirer.matches + def do_events(): from gi.repository import GObject main_loop = GObject.main_context_default() while main_loop.pending(): main_loop.iteration() + def get_mock_app_from_real_app(real_app): """ take a application and return a app where the details are a mock of the real details so they can easily be modified @@ -138,12 +152,14 @@ details = app.get_details(db) details_mock = Mock(details) for a in dir(details): - if a.startswith("_"): continue + if a.startswith("_"): + continue setattr(details_mock, a, getattr(details, a)) app.details = details_mock app.get_details = lambda db: app.details return app + def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. @@ -163,7 +179,7 @@ # factory stuff for the agent def make_software_center_agent_app_dict(): app_dict = { - u'archive_root' : 'http://private-ppa.launchpad.net/', + u'archive_root': 'http://private-ppa.launchpad.net/', u'archive_id': u'commercial-ppa-uploaders/photobomb', u'description': u"Easy and Social Image Editor\nPhotobomb " u"give you easy access to images in your " @@ -171,18 +187,21 @@ u'name': u'Photobomb', u'package_name': u'photobomb', u'signing_key_id': u'1024R/75254D99', - u'screenshot_url': 'http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot.png', + u'screenshot_url': 'http://software-center.ubuntu.com/site_media/'\ + 'screenshots/2011/08/Screenshot.png', u'license': 'Proprietary', u'support_url': 'mailto:support@example.com', - u'series': { 'oneiric' : ['i386', 'amd64'], - 'natty' : ['i386', 'amd64'], + u'series': {'oneiric': ['i386', 'amd64'], + 'natty': ['i386', 'amd64'], }, - u'channel' : 'For Purchase', - u'icon_url' : 'http://software-center.ubuntu.com/site_media/icons/2011/08/64_Chainz.png', + u'channel': 'For Purchase', + u'icon_url': 'http://software-center.ubuntu.com/site_media/icons/'\ + '2011/08/64_Chainz.png', u'categories': 'Game;LogicGame', } return app_dict + def make_software_center_agent_subscription_dict(app_dict): subscription_dict = { u'application': app_dict, @@ -198,78 +217,90 @@ } return subscription_dict + def make_recommender_agent_recommend_me_dict(): # best to have a list of likely not-installed items app_dict = { u'data': [ { u'package_name': u'clementine' - }, + }, { u'package_name': u'hedgewars' }, { - u'package_name': u'gelemental' - }, + u'package_name': u'mangler' + }, { u'package_name': u'nexuiz' }, { u'package_name': u'fgo' - }, + }, { u'package_name': u'musique' }, { u'package_name': u'pybik' - }, + }, { u'package_name': u'radiotray' }, { u'package_name': u'cherrytree' - }, + }, { u'package_name': u'phlipple' + }, + { + u'package_name': u'psi' + }, + { + u'package_name': u'midori' } - ] -} + ] + } return app_dict - -def make_recommender_profile_upload_data(): + + +def make_recommender_profile_upload_data(): from softwarecenter.utils import get_uuid recommender_uuid = get_uuid() profile_upload_data = [ { - 'uuid': recommender_uuid, + 'uuid': recommender_uuid, 'package_list': [ u'clementine', u'hedgewars', - u'gelemental', + u'mangler', u'nexuiz', u'fgo', u'musique', u'pybik', u'radiotray', u'cherrytree', - u'phlipple' + u'phlipple', + u'psi', + u'midori' ] } ] return profile_upload_data - + + def make_recommend_app_data(): - recommend_app_data = {u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', - u'data': [{u'rating': 4.0, u'package_name': u'kftpgrabber'}, - {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, - {u'rating': 3.0, u'package_name': u'wakeup'}, - {u'rating': 3.0, u'package_name': u'xvidcap'}, - {u'rating': 2.0, u'package_name': u'airstrike'}, - {u'rating': 2.0, u'package_name': u'pixbros'}, - {u'rating': 2.0, u'package_name': u'bomber'}, - {u'rating': 2.0, u'package_name': u'ktron'}, - {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, - {u'rating': 1.5, u'package_name': u'tucan'}], - u'app': u'pitivi'} + recommend_app_data = { + u'rid': u'265c0bb1dece93a96c5a528e7ea5dd75', + u'data': [ + {u'rating': 4.0, u'package_name': u'kftpgrabber'}, + {u'rating': 4.0, u'package_name': u'sugar-emulator-0.90'}, + {u'rating': 3.0, u'package_name': u'wakeup'}, + {u'rating': 3.0, u'package_name': u'xvidcap'}, + {u'rating': 2.0, u'package_name': u'airstrike'}, + {u'rating': 2.0, u'package_name': u'pixbros'}, + {u'rating': 2.0, u'package_name': u'bomber'}, + {u'rating': 2.0, u'package_name': u'ktron'}, + {u'rating': 2.0, u'package_name': u'gnome-mousetrap'}, + {u'rating': 1.5, u'package_name': u'tucan'}], + u'app': u'pitivi'} return recommend_app_data - diff -Nru software-center-5.1.12/utils/delete_review_gtk3.py software-center-5.1.13/utils/delete_review_gtk3.py --- software-center-5.1.12/utils/delete_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/delete_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/utils/expunge-cache.py software-center-5.1.13/utils/expunge-cache.py --- software-center-5.1.12/utils/expunge-cache.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/expunge-cache.py 2012-03-16 08:30:18.000000000 +0000 @@ -10,11 +10,13 @@ import time import sys + class ExpungeCache(object): + def __init__(self, dirs, args): self.dirs = dirs # days to keep data in the cache (0 == disabled) - self.keep_time = 60*60*24* args.by_days + self.keep_time = 60 * 60 * 24 * args.by_days self.keep_only_http200 = args.by_unsuccessful_http_states self.dry_run = args.dry_run diff -Nru software-center-5.1.12/utils/modify_review_gtk3.py software-center-5.1.13/utils/modify_review_gtk3.py --- software-center-5.1.12/utils/modify_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/modify_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/utils/piston-helpers/piston_generic_helper.py software-center-5.1.13/utils/piston-helpers/piston_generic_helper.py --- software-center-5.1.12/utils/piston-helpers/piston_generic_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/piston-helpers/piston_generic_helper.py 2012-03-16 08:30:18.000000000 +0000 @@ -56,14 +56,17 @@ from softwarecenter.backend.piston.ubuntusso_pristine import UbuntuSsoAPI from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.backend.piston.scaclient import SoftwareCenterAgentAPI -from softwarecenter.backend.piston.sreclient_pristine import SoftwareCenterRecommenderAPI +from softwarecenter.backend.piston.sreclient_pristine import ( + SoftwareCenterRecommenderAPI) # patch default_service_root to the one we use -from softwarecenter.enums import SSO_LOGIN_HOST -UbuntuSsoAPI.default_service_root = SSO_LOGIN_HOST+"/api/1.0" +from softwarecenter.enums import UBUNTU_SSO_SERVICE +# *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE +UbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE from softwarecenter.enums import RECOMMENDER_HOST -SoftwareCenterRecommenderAPI.default_service_root = RECOMMENDER_HOST+"/api/1.0" +SoftwareCenterRecommenderAPI.default_service_root = \ + RECOMMENDER_HOST + "/api/1.0" RatingsAndReviewsAPI # pyflakes @@ -73,6 +76,7 @@ from gettext import gettext as _ + # helper that is only used to verify that the token is ok # and trigger cleanup if not class SSOLoginHelper(object): @@ -90,10 +94,9 @@ def verify_token_sync(self, token): LOG.debug("verify_token") - auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], - token["token_secret"], - token["consumer_key"], - token["consumer_secret"]) + auth = piston_mini_client.auth.OAuthAuthorizer( + token["token"], token["token_secret"], + token["consumer_key"], token["consumer_secret"]) api = UbuntuSsoAPI(auth=auth) try: res = api.whoami() @@ -128,7 +131,6 @@ return token - LOG = logging.getLogger(__name__) if __name__ == "__main__": @@ -179,10 +181,9 @@ sys.stderr.write("ERROR: can not obtain a oauth token\n") sys.exit(1) - auth = piston_mini_client.auth.OAuthAuthorizer(token["token"], - token["token_secret"], - token["consumer_key"], - token["consumer_secret"]) + auth = piston_mini_client.auth.OAuthAuthorizer( + token["token"], token["token_secret"], + token["consumer_key"], token["consumer_secret"]) api = klass(cachedir=cachedir, auth=auth) else: api = klass(cachedir=cachedir) diff -Nru software-center-5.1.12/utils/piston-helpers/x2go_helper.py software-center-5.1.13/utils/piston-helpers/x2go_helper.py --- software-center-5.1.12/utils/piston-helpers/x2go_helper.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/piston-helpers/x2go_helper.py 2012-03-16 08:30:18.000000000 +0000 @@ -1,5 +1,11 @@ #!/usr/bin/python -u -import x2go, gevent, sys, fcntl, os, shlex +import fcntl +import gevent +import os +import shlex +import sys +import x2go + def connect(server, port, login, password, session): print "PROGRESS: creating" diff -Nru software-center-5.1.12/utils/report_review_gtk3.py software-center-5.1.13/utils/report_review_gtk3.py --- software-center-5.1.12/utils/report_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/report_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/utils/submit_review_gtk3.py software-center-5.1.13/utils/submit_review_gtk3.py --- software-center-5.1.12/utils/submit_review_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/submit_review_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/utils/submit_usefulness_gtk3.py software-center-5.1.13/utils/submit_usefulness_gtk3.py --- software-center-5.1.12/utils/submit_usefulness_gtk3.py 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/submit_usefulness_gtk3.py 2012-03-16 08:30:18.000000000 +0000 @@ -69,10 +69,10 @@ parser = OptionParser() parser.add_option("", "--datadir", default=default_datadir) - logfile_path = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") - logfile_handler = logging.handlers.RotatingFileHandler(logfile_path, - maxBytes=100*1000, - backupCount=5) + logfile_path = os.path.join( + SOFTWARE_CENTER_CACHE_DIR, "reviews-helper.log") + logfile_handler = logging.handlers.RotatingFileHandler( + logfile_path, maxBytes = 100 * 1000, backupCount = 5) logfile_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) @@ -103,9 +103,9 @@ if not (options.pkgname and options.version): parser.error(_("Missing arguments")) - + if options.debug: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") @@ -113,14 +113,13 @@ # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, - app=theapp, + app=theapp, parent_xid=options.parent_xid, iconname=options.iconname, origin=options.origin, version=options.version) review_app.run() - # run "report" personality if "report_review" in sys.argv[0]: # check options @@ -134,7 +133,7 @@ if not (options.review_id): parser.error(_("Missing review-id arguments")) - + if options.debug: logging.basicConfig(level=logging.DEBUG) diff -Nru software-center-5.1.12/utils/update-software-center-agent software-center-5.1.13/utils/update-software-center-agent --- software-center-5.1.12/utils/update-software-center-agent 2012-03-07 17:25:50.000000000 +0000 +++ software-center-5.1.13/utils/update-software-center-agent 2012-03-19 08:35:49.000000000 +0000 @@ -75,7 +75,11 @@ # setup path pathname = XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT+".tmp" if not os.path.exists(pathname): - os.makedirs(pathname) + try: + os.makedirs(pathname) + except OSError as e: + logging.warn("Could not create agent dir '%s' (%s)'" % ( + pathname, e)) # check that we can write if not os.access(pathname, os.W_OK):