software-center-13.10/0000755000202700020270000000000012224614355015054 5ustar dobeydobey00000000000000software-center-13.10/MANIFEST0000644000202700020270000003344212224614354016212 0ustar dobeydobey00000000000000# file GENERATED by distutils, do NOT edit COPYING COPYING.LGPL MANIFEST README README.debug-server README.tests-dep8 TODO run-tests.sh run_against_testing_env.sh run_local.sh run_with_fake_review_api.sh setup.cfg setup.py apt_xapian_index_plugin/__init__.py apt_xapian_index_plugin/display_name.py apt_xapian_index_plugin/origin.py apt_xapian_index_plugin/software_center.py bin/software-center bin/software-center-dbus bin/software-center-qml contrib/USC-start-stop-times.py contrib/simulate-slow-network.sh contrib/appstream-xml/appdata.xml data/delete_review_gtk3.py data/expunge-cache.py data/extra-unity-categories.menu.in data/featured.menu.in data/modify_review_gtk3.py data/piston_generic_helper.py data/piston_get_reviews_helper.py data/report_review_gtk3.py data/software-center.menu.in data/submit_review_gtk3.py data/submit_usefulness_gtk3.py data/top-rated.menu.in data/ubuntu-software-center.desktop.in data/update-software-center-agent data/update-software-center-channels data/whats_new.menu.in data/x2go_helper.py data/channels/Ubuntu/ubuntu-extras.eula data/channels/Ubuntu/ubuntu-extras.list.in data/dbus/com.ubuntu.SoftwareCenter.conf data/dbus/com.ubuntu.SoftwareCenterDataProvider.service data/default_banner/fallback.png data/emblems/software-center-installed.png data/icons/128x128/apps/softwarecenter.svg data/icons/16x16/apps/softwarecenter.svg data/icons/24x24/apps/ppa.svg data/icons/24x24/apps/softwarecenter.svg data/icons/24x24/apps/unknown-channel.svg data/icons/48x48/apps/softwarecenter.svg data/icons/scalable/apps/category-show-all.svg data/icons/scalable/apps/partner.svg data/icons/scalable/apps/softwarecenter.svg data/images/spinner.gif data/ui/gtk3/SoftwareCenter.ui data/ui/gtk3/dialogs.ui data/ui/gtk3/report_abuse.ui data/ui/gtk3/submit_review.ui data/ui/gtk3/submit_usefulness.ui data/ui/gtk3/art/circle-dropshadow.png data/ui/gtk3/art/exhibit-dropshadow-n.png data/ui/gtk3/art/exhibit-dropshadow-s.png data/ui/gtk3/art/frame-border-image-2px-border-radius.png data/ui/gtk3/art/frame-border-image.png data/ui/gtk3/art/itemview-background.png data/ui/gtk3/art/stipple.png data/ui/gtk3/art/icons/available-dropshadow.png data/ui/gtk3/art/icons/available.png data/ui/gtk3/art/icons/history-dropshadow.png data/ui/gtk3/art/icons/history.png data/ui/gtk3/art/icons/installed-dropshadow.png data/ui/gtk3/art/icons/installed.png data/ui/gtk3/art/icons/pending-dropshadow.png data/ui/gtk3/art/icons/pending.png data/ui/gtk3/css/softwarecenter.css data/ui/gtk3/css/softwarecenter.highcontrast.css data/ui/gtk3/css/softwarecenter.highcontrastinverse.css data/ui/sso/sso.ui doc/example_plugin.py help/C/index.docbook help/C/legal.xml help/C/figures/placeholder.png man/software-center.1 man/update-software-center.8 po/POTFILES.in po/POTFILES.skip po/ar.po po/ca.po po/cs.po po/de.po po/en_AU.po po/en_CA.po po/en_GB.po po/eo.po po/es.po po/et.po po/fi.po po/fr.po po/gl.po po/hu.po po/it.po po/lt.po po/nl.po po/pl.po po/pt.po po/ro.po po/ru.po po/sco.po po/sq.po po/sr.po po/sv.po po/th.po po/zh_CN.po softwarecenter/__init__.py softwarecenter/cmdfinder.py softwarecenter/config.py softwarecenter/enums.py softwarecenter/expunge.py softwarecenter/gwibber_helper.py softwarecenter/hw.py softwarecenter/i18n.py softwarecenter/log.py softwarecenter/netstatus.py softwarecenter/paths.py softwarecenter/plugin.py softwarecenter/region.py softwarecenter/utils.py softwarecenter/backend/__init__.py softwarecenter/backend/channel.py softwarecenter/backend/fake_review_settings.py softwarecenter/backend/installbackend.py softwarecenter/backend/login.py softwarecenter/backend/recagent.py softwarecenter/backend/scagent.py softwarecenter/backend/spawn_helper.py softwarecenter/backend/transactionswatcher.py softwarecenter/backend/ubuntusso.py softwarecenter/backend/unitylauncher.py softwarecenter/backend/weblive.py softwarecenter/backend/weblive_pristine.py softwarecenter/backend/zeitgeist_logger.py softwarecenter/backend/channel_impl/__init__.py softwarecenter/backend/channel_impl/aptchannels.py softwarecenter/backend/installbackend_impl/__init__.py softwarecenter/backend/installbackend_impl/aptd.py softwarecenter/backend/installbackend_impl/packagekitd.py softwarecenter/backend/login_impl/__init__.py softwarecenter/backend/login_impl/login_fake.py softwarecenter/backend/login_impl/login_sso.py softwarecenter/backend/oneconfhandler/__init__.py softwarecenter/backend/oneconfhandler/core.py softwarecenter/backend/piston/__init__.py softwarecenter/backend/piston/rnrclient.py softwarecenter/backend/piston/rnrclient_fake.py softwarecenter/backend/piston/rnrclient_pristine.py softwarecenter/backend/piston/scaclient.py softwarecenter/backend/piston/scaclient_pristine.py softwarecenter/backend/piston/sreclient_pristine.py softwarecenter/backend/piston/ubuntusso_pristine.py softwarecenter/backend/reviews/__init__.py softwarecenter/backend/reviews/rnr.py softwarecenter/db/__init__.py softwarecenter/db/appfilter.py softwarecenter/db/application.py softwarecenter/db/categories.py softwarecenter/db/database.py softwarecenter/db/dataprovider.py softwarecenter/db/debfile.py softwarecenter/db/enquire.py softwarecenter/db/history.py softwarecenter/db/pkginfo.py softwarecenter/db/update.py softwarecenter/db/utils.py softwarecenter/db/history_impl/__init__.py softwarecenter/db/history_impl/apthistory.py softwarecenter/db/history_impl/packagekit.py softwarecenter/db/pkginfo_impl/__init__.py softwarecenter/db/pkginfo_impl/aptcache.py softwarecenter/db/pkginfo_impl/packagekit.py softwarecenter/distro/__init__.py softwarecenter/distro/debian.py softwarecenter/distro/fedora.py softwarecenter/distro/suselinux.py softwarecenter/distro/ubuntu.py softwarecenter/plugins/__init__.py softwarecenter/plugins/webapps_activation.py softwarecenter/ui/__init__.py softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py softwarecenter/ui/gtk3/__init__.py softwarecenter/ui/gtk3/app.py softwarecenter/ui/gtk3/aptd_gtk3.py softwarecenter/ui/gtk3/drawing.py softwarecenter/ui/gtk3/em.py softwarecenter/ui/gtk3/gmenusearch.py softwarecenter/ui/gtk3/review_gui_helper.py softwarecenter/ui/gtk3/utils.py softwarecenter/ui/gtk3/dialogs/__init__.py softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py softwarecenter/ui/gtk3/dialogs/dialog_tos.py softwarecenter/ui/gtk3/models/__init__.py softwarecenter/ui/gtk3/models/appstore2.py softwarecenter/ui/gtk3/models/pendingstore.py softwarecenter/ui/gtk3/panes/__init__.py softwarecenter/ui/gtk3/panes/availablepane.py softwarecenter/ui/gtk3/panes/basepane.py softwarecenter/ui/gtk3/panes/globalpane.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/viewswitcher.py softwarecenter/ui/gtk3/session/__init__.py softwarecenter/ui/gtk3/session/appmanager.py softwarecenter/ui/gtk3/session/displaystate.py softwarecenter/ui/gtk3/session/navhistory.py softwarecenter/ui/gtk3/session/viewmanager.py softwarecenter/ui/gtk3/views/__init__.py softwarecenter/ui/gtk3/views/appdetailsview.py softwarecenter/ui/gtk3/views/appview.py softwarecenter/ui/gtk3/views/catview.py softwarecenter/ui/gtk3/views/lobbyview.py softwarecenter/ui/gtk3/views/pkgnamesview.py softwarecenter/ui/gtk3/views/purchaseview.py softwarecenter/ui/gtk3/widgets/__init__.py softwarecenter/ui/gtk3/widgets/actionbar.py softwarecenter/ui/gtk3/widgets/apptreeview.py softwarecenter/ui/gtk3/widgets/backforward.py softwarecenter/ui/gtk3/widgets/buttons.py softwarecenter/ui/gtk3/widgets/cellrenderers.py softwarecenter/ui/gtk3/widgets/containers.py softwarecenter/ui/gtk3/widgets/description.py softwarecenter/ui/gtk3/widgets/exhibits.py softwarecenter/ui/gtk3/widgets/imagedialog.py softwarecenter/ui/gtk3/widgets/labels.py softwarecenter/ui/gtk3/widgets/menubutton.py softwarecenter/ui/gtk3/widgets/navlog.py softwarecenter/ui/gtk3/widgets/oneconfviews.py softwarecenter/ui/gtk3/widgets/recommendations.py softwarecenter/ui/gtk3/widgets/reviews.py softwarecenter/ui/gtk3/widgets/searchaid.py softwarecenter/ui/gtk3/widgets/searchentry.py softwarecenter/ui/gtk3/widgets/separators.py softwarecenter/ui/gtk3/widgets/spinner.py softwarecenter/ui/gtk3/widgets/stars.py softwarecenter/ui/gtk3/widgets/symbolic_icons.py softwarecenter/ui/gtk3/widgets/thumbnail.py softwarecenter/ui/gtk3/widgets/videoplayer.py softwarecenter/ui/gtk3/widgets/viewport.py softwarecenter/ui/gtk3/widgets/webkit.py softwarecenter/ui/gtk3/widgets/weblivedialog.py softwarecenter/ui/qml/AppListView.qml softwarecenter/ui/qml/BreadCrumbs.qml softwarecenter/ui/qml/Button.qml softwarecenter/ui/qml/CategoriesView.qml softwarecenter/ui/qml/CloudsHeader.qml softwarecenter/ui/qml/DetailsView.qml softwarecenter/ui/qml/Frame.qml softwarecenter/ui/qml/FrameSwitcher.qml softwarecenter/ui/qml/NavigationBar.qml softwarecenter/ui/qml/ProgressBar.qml softwarecenter/ui/qml/ScrollBar.qml softwarecenter/ui/qml/SearchBox.qml softwarecenter/ui/qml/Stars.qml softwarecenter/ui/qml/__init__.py softwarecenter/ui/qml/app.py softwarecenter/ui/qml/categoriesmodel.py softwarecenter/ui/qml/pkglist.py softwarecenter/ui/qml/reviewslist.py softwarecenter/ui/qml/sc.qml softwarecenter/ui/qml/sc.qmlproject softwarecenter/ui/qml/star-empty.svg softwarecenter/ui/qml/star-full.svg softwarecenter/ui/qml/star-large-empty.png softwarecenter/ui/qml/star-large-full.png softwarecenter/ui/qml/star-large-half.png softwarecenter/ui/qml/star-small-empty.png softwarecenter/ui/qml/star-small-full.png softwarecenter/ui/qml/star-small-half.png softwarecenter/ui/qml/dummydata/categoriesmodel.qml softwarecenter/ui/qml/dummydata/pkglistmodel.qml softwarecenter/ui/qml/dummydata/reviewslistmodel.qml tests/Makefile tests/__init__.py tests/channel_query.py tests/create_transactions.py tests/disabled_test_config.py tests/disabled_test_dataprovider.py tests/disabled_test_description_norm.py tests/disabled_test_gnomekeyring.py tests/disabled_test_gui_ldtp.py tests/disabled_test_pep8.py tests/disabled_test_where_is_it.py tests/test_addons.py tests/test_application.py tests/test_aptd.py tests/test_apthistory.py tests/test_categories.py tests/test_channels.py tests/test_cmdfiner.py tests/test_database.py tests/test_debfileapplication.py tests/test_distro.py tests/test_enquire.py tests/test_gwibber.py tests/test_htmlize.py tests/test_hw.py tests/test_i18n.py tests/test_logging.py tests/test_login_backend.py tests/test_mime.py tests/test_netstatus.py tests/test_origin.py tests/test_package_info.py tests/test_pkginfo.py tests/test_plugin.py tests/test_ppa_iconfilename.py tests/test_purchase_backend.py tests/test_pyflakes.py tests/test_recagent.py tests/test_region.py tests/test_reinstall_purchased.py tests/test_rnr_api.py tests/test_scagent.py tests/test_spawn_helper.py tests/test_startup.py tests/test_testutils.py tests/test_ubuntu_sso_api.py tests/test_unity_launcher.py tests/test_utils.py tests/test_webapps_activation_plugin.py tests/test_xapian.py tests/test_xapian_query.py tests/test_zeitgeist_logger.py tests/utils.py tests/data/fake-applications.menu tests/data/notadeb.txt tests/data/ubuntu_dist_channel tests/data/app-info/appdata.xml tests/data/app-info/archive.ubuntu.com_ubuntu_dists_maverick_main_amd64_AppInfo tests/data/app-info-json/apps.json tests/data/app-install/desktop/deja-dup:deja-dup.desktop tests/data/app-install/desktop/soundkonverter:kde4__soundkonverter.desktop tests/data/appdetails/var/lib/dpkg/status tests/data/applications/deja-dup.desktop tests/data/applications/kde4/soundkonverter.desktop tests/data/apt-history/history.log tests/data/apt-history/history.log.1.gz tests/data/aptroot/etc/apt/sources.list tests/data/aptroot/etc/apt/trusted.gpg tests/data/desktop/expensive-gem.desktop tests/data/desktop/music-banshee.scope tests/data/desktop/pay-app.desktop tests/data/desktop/scintillant-orange.desktop tests/data/desktop/software-center.menu tests/data/desktop/ubuntu-software-center.desktop tests/data/desktop/zynjacku.desktop tests/data/plugins/mock_plugin.py tests/data/test_debs/corrupt.deb tests/data/test_debs/gdebi-test1.deb tests/data/test_debs/gdebi-test2.deb tests/data/test_debs/gdebi-test3.deb tests/data/test_debs/gdebi-test9.deb tests/data/test_images/fallback.png tests/graph/gen-startup-data.sh tests/graph/gen-test-coverage-data.sh tests/graph/gnuplot.plot tests/graph/plot-reviews-spread.py tests/graph/plot-startup-data.py tests/graph/plot-test-coverage.py tests/gtk3/__init__.py tests/gtk3/disabled_test_catview.py tests/gtk3/disabled_test_memleak.py tests/gtk3/test_app.py tests/gtk3/test_app_view.py tests/gtk3/test_appdetailsview.py tests/gtk3/test_appmanager.py tests/gtk3/test_appstore2.py tests/gtk3/test_availablepane.py tests/gtk3/test_buttons.py tests/gtk3/test_custom_lists.py tests/gtk3/test_debfile_view.py tests/gtk3/test_dialogs.py tests/gtk3/test_exhibits.py tests/gtk3/test_globalpane.py tests/gtk3/test_install_progress.py tests/gtk3/test_installedpane.py tests/gtk3/test_lp1048912.py tests/gtk3/test_navhistory.py tests/gtk3/test_panes.py tests/gtk3/test_purchase.py tests/gtk3/test_recommendations_widgets.py tests/gtk3/test_reviews.py tests/gtk3/test_search.py tests/gtk3/test_spinner.py tests/gtk3/test_unity_launcher_integration_gui.py tests/gtk3/test_views.py tests/gtk3/test_webkit.py tests/gtk3/test_widgets.py tests/gtk3/test_zeitgeist_logger_gui.py tests/gtk3/windows.py tests/mago/mago_simple.py tests/qml/test_ui_qml_helpers.py utils/bench.py utils/delete_review_gtk3.py utils/expunge-cache.py utils/gen-coverage-report.sh utils/installedapps.py utils/modify_review_gtk3.py utils/query.py utils/report_review_gtk3.py utils/search_query.py utils/show_top_rated_for_various_powers.py utils/stats.py utils/submit_review_gtk3.py utils/submit_usefulness_gtk3.py utils/topapps.py utils/update-software-center utils/update-software-center-agent utils/update-software-center-channels utils/wildcard_query_parser.py utils/piston-helpers/piston_generic_helper.py utils/piston-helpers/piston_get_reviews_helper.py utils/piston-helpers/x2go_helper.py software-center-13.10/apt_xapian_index_plugin/0000755000202700020270000000000012224614354021744 5ustar dobeydobey00000000000000software-center-13.10/apt_xapian_index_plugin/software_center.py0000664000202700020270000001364012151440100025477 0ustar dobeydobey00000000000000# add software-center custom metadata to the index import apt import os import sys import xapian sys.path.insert(0, "/usr/share/software-center") from softwarecenter.enums import ( CustomKeys, XapianValues, ) from softwarecenter.db.update import ( WEIGHT_DESKTOP_NAME, get_pkgname_terms, ) from softwarecenter.distro import get_distro class SoftwareCenterMetadataPlugin: def info(self): """ Return general information about the plugin. The information returned is a dict with various keywords: timestamp (required) the last modified timestamp of this data source. This will be used to see if we need to update the database or not. A timestamp of 0 means that this data source is either missing or always up to date. values (optional) an array of dicts { name: name, desc: description }, one for every numeric value indexed by this data source. Note that this method can be called before init. The idea is that, if the timestamp shows that this plugin is currently not needed, then the long initialisation can just be skipped. """ file = apt.apt_pkg.config.find_file("Dir::Cache::pkgcache") if not os.path.exists(file): return dict(timestamp=0) return dict(timestamp=os.path.getmtime(file)) def init(self, info, progress): """ If needed, perform long initialisation tasks here. info is a dictionary with useful information. Currently it contains the following values: "values": a dict mapping index mnemonics to index numbers The progress indicator can be used to report progress. """ self.indexer = xapian.TermGenerator() def doc(self): """ Return documentation information for this data source. The documentation information is a dictionary with these keys: name: the name for this data source shortDesc: a short description fullDoc: the full description as a chapter in ReST format """ return dict( name="SoftwareCenterMetadata", shortDesc="SoftwareCenter meta information", fullDoc=""" Software-center metadata It uses the prefixes: AA for the Application name AP for the Package name AC for the categories AT to "application" for applications It sets the following xapian values from the software-center enums: XapianValues.ICON XapianValues.ICON_NEEDS_DOWNLOAD XapianValues.ICON_URL XapianValues.SCREENSHOT_URLS XapianValues.THUMBNAIL_URL """) def index(self, document, pkg): """ Update the document with the information from this data source. document is the document to update pkg is the python-apt Package object for this package """ ver = pkg.candidate # if there is no version or the AppName custom key is not # found we can skip the pkg if ver is None or not CustomKeys.APPNAME in ver.record: return # we want to index the following custom fields: # XB-AppName, # XB-Icon, # XB-Screenshot-Url, # XB-Thumbnail-Url, # XB-Category if CustomKeys.APPNAME in ver.record: name = ver.record[CustomKeys.APPNAME] self.indexer.set_document(document) # add s-c values/terms for the name document.add_term("AA"+name) document.add_value(XapianValues.APPNAME, name) for t in get_pkgname_terms(pkg.name): document.add_term(t) self.indexer.index_text_without_positions( name, WEIGHT_DESKTOP_NAME) # we pretend to be an application document.add_term("AT" + "application") # and we inject a custom component value to indicate "independent" document.add_value(XapianValues.ARCHIVE_SECTION, "independent") if CustomKeys.ICON in ver.record: icon = ver.record[CustomKeys.ICON] document.add_value(XapianValues.ICON, icon) # calculate the url and add it (but only if there actually is # a url) try: distro = get_distro() if distro: base_uri = ver.uri # new python-apt returns None instead of StopIteration if base_uri: url = distro.get_downloadable_icon_url(base_uri, icon) document.add_value(XapianValues.ICON_URL, url) except StopIteration: pass if CustomKeys.SCREENSHOT_URLS in ver.record: screenshot_url = ver.record[CustomKeys.SCREENSHOT_URLS] document.add_value(XapianValues.SCREENSHOT_URLS, screenshot_url) if CustomKeys.THUMBNAIL_URL in ver.record: url = ver.record[CustomKeys.THUMBNAIL_URL] document.add_value(XapianValues.THUMBNAIL_URL, url) if CustomKeys.CATEGORY in ver.record: categories_str = ver.record[CustomKeys.CATEGORY] for cat in categories_str.split(";"): if cat: document.add_term("AC" + cat.lower()) def indexDeb822(self, document, pkg): """ Update the document with the information from this data source. This is alternative to index, and it is used when indexing with package data taken from a custom Packages file. document is the document to update pkg is the Deb822 object for this package """ # NOTHING here, does not make sense for non-downloadable data return def init(): """ Create and return the plugin object. """ return SoftwareCenterMetadataPlugin() software-center-13.10/apt_xapian_index_plugin/__init__.py0000664000202700020270000000000012151440100024026 0ustar dobeydobey00000000000000software-center-13.10/apt_xapian_index_plugin/origin.py0000664000202700020270000000707012151440100023574 0ustar dobeydobey00000000000000# Add origin tags to the index import apt import os class OriginPlugin: def info(self): """ Return general information about the plugin. The information returned is a dict with various keywords: timestamp (required) the last modified timestamp of this data source. This will be used to see if we need to update the database or not. A timestamp of 0 means that this data source is either missing or always up to date. values (optional) an array of dicts { name: name, desc: description }, one for every numeric value indexed by this data source. Note that this method can be called before init. The idea is that, if the timestamp shows that this plugin is currently not needed, then the long initialisation can just be skipped. """ file = apt.apt_pkg.config.find_file("Dir::Cache::pkgcache") if not os.path.exists(file): return dict(timestamp=0) return dict(timestamp=os.path.getmtime(file)) def init(self, info, progress): """ If needed, perform long initialisation tasks here. info is a dictionary with useful information. Currently it contains the following values: "values": a dict mapping index mnemonics to index numbers The progress indicator can be used to report progress. """ pass def doc(self): """ Return documentation information for this data source. The documentation information is a dictionary with these keys: name: the name for this data source shortDesc: a short description fullDoc: the full description as a chapter in ReST format """ return dict( name="Origin", shortDesc="Origin information", fullDoc=""" The Origin data source indexes origin information It uses the prefix XO """) def index(self, document, pkg): """ Update the document with the information from this data source. document is the document to update pkg is the python-apt Package object for this package """ ver = pkg.candidate if ver is None: return if not ver.downloadable: document.add_term("XOL" + "notdownloadable") for origin in ver.origins: document.add_term("XOA" + origin.archive) document.add_term("XOC" + origin.component) document.add_term("XOL" + origin.label) document.add_term("XOO" + origin.origin) document.add_term("XOS" + origin.site) # FIXME: this doesn't really belong in this file, but we can put it in # here until we get a display_name/display_summary plugin which # is being prepared in the experimental-fastlist branch. if '-' in pkg.name: # we need this to work around xapian oddness document.add_term(pkg.name.replace('-', '_')) def indexDeb822(self, document, pkg): """ Update the document with the information from this data source. This is alternative to index, and it is used when indexing with package data taken from a custom Packages file. document is the document to update pkg is the Deb822 object for this package """ # NOTHING here, does not make sense for non-downloadable data return def init(): """ Create and return the plugin object. """ return OriginPlugin() software-center-13.10/apt_xapian_index_plugin/display_name.py0000664000202700020270000000661712151440100024760 0ustar dobeydobey00000000000000import apt_pkg import os class DisplayNames: def info(self): """ Return general information about the plugin. The information returned is a dict with various keywords: timestamp (required) the last modified timestamp of this data source. This will be used to see if we need to update the database or not. A timestamp of 0 means that this data source is either missing or always up to date. values (optional) an array of dicts { name: name, desc: description }, one for every numeric value indexed by this data source. Note that this method can be called before init. The idea is that, if the timestamp shows that this plugin is currently not needed, then the long initialisation can just be skipped. """ file = apt_pkg.config.find_file("Dir::Cache::pkgcache") if not os.path.exists(file): return dict(timestamp=0) return dict( timestamp=os.path.getmtime(file), values=[ dict(name="display_name", desc="display name"), dict(name="pkgname", desc="Pkgname as value"), ]) def doc(self): """ Return documentation information for this data source. The documentation information is a dictionary with these keys: name: the name for this data source shortDesc: a short description fullDoc: the full description as a chapter in ReST format """ return dict( name="DisplayNames", shortDesc="pkgname and package display names indexed as values", fullDoc=""" The DisplayNames data source indexes the display name as the ``display_name`` Xapian value. ``pkgname`` Xapian value. """) def init(self, info, progress): """ If needed, perform long initialisation tasks here. info is a dictionary with useful information. Currently it contains the following values: "values": a dict mapping index mnemonics to index numbers The progress indicator can be used to report progress. """ # Read the value indexes we will use values = info['values'] self.val_display_name = values.get("display_name", -1) self.val_pkgname = values.get("pkgname", -1) def index(self, document, pkg): """ Update the document with the information from this data source. document is the document to update pkg is the python-apt Package object for this package """ ver = pkg.candidate if ver is None: return if self.val_display_name != -1: name = ver.summary document.add_value(self.val_display_name, name) if self.val_pkgname != -1: document.add_value(self.val_pkgname, pkg.name) def indexDeb822(self, document, pkg): """ Update the document with the information from this data source. This is alternative to index, and it is used when indexing with package data taken from a custom Packages file. document is the document to update pkg is the Deb822 object for this package """ return def init(): """ Create and return the plugin object. """ return DisplayNames() software-center-13.10/README.debug-server0000664000202700020270000000236212151440100020312 0ustar dobeydobey00000000000000When debugging server issues or inspecting the expected results its often useful to run "utils/piston-helpers/piston_generic_helper.py" in debug mode. Some examples: $ PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py \ --output=text --debug --needs-auth \ SoftwareCenterAgentAPI subscriptions_for_me $ PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py \ --output=text --debug --needs-auth \ SoftwareCenterAgentAPI available_apps_qa \ '{ "lang" : "en", "series" : "oneiric", "arch" : "i386" }' $ PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py \ --output=text --debug \ SoftwareCenterAgentAPI available_apps \ '{ "lang" : "en", "series" : "oneiric", "arch" : "i386" }' $ PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py \ --output=text --debug \ SoftwareCenterRecommenderAPI recommend_top $ PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py \ --output=json --needs-auth \ RatingsAndReviewsAPI submit_usefulness \ '{ "review_id": 4468, "useful": "True" }' Any piston-mini-client API should work, first name is the class second the method to run, optional is a json encoded kwargs string. To get http debug output you can use the environment variable SOFTWARE_CENTER_DEBUG_HTTP=1. software-center-13.10/doc/0000755000202700020270000000000012224614354015620 5ustar dobeydobey00000000000000software-center-13.10/doc/example_plugin.py0000664000202700020270000000110012151440100021156 0ustar dobeydobey00000000000000 from gi.repository import GLib import sys import softwarecenter.plugin class ExamplePlugin(softwarecenter.plugin.Plugin): """ example plugin that will hide the exhibits banner """ def _try_to_hide_banner(self): if not self.app.available_pane.view_initialized: # wait for the pane to fully initialize return True self.app.available_pane.cat_view.vbox.get_children()[0].hide() return False def init_plugin(self): sys.stderr.write("init_plugin\n") GLib.timeout_add(100, self._try_to_hide_banner) software-center-13.10/softwarecenter/0000755000202700020270000000000012224614354020106 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/paths.py0000664000202700020270000001107112151440100021562 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Andrew Higginson # # 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 os # ensure we don't create directories in /home/$user if os.getuid() == 0 and "SUDO_USER" in os.environ and "HOME" in os.environ: del os.environ["HOME"] # the check above must be *before* xdg is imported # py3 possible compat mode (there is no python3-xdg yet) # try: # from xdg import BaseDirectory as xdg # except ImportError: # import collections # klass = collections.namedtuple('xdg', 'xdg_config_home, xdg_cache_home') # xdg = klass(xdg_config_home=os.path.expanduser("~/.config"), # xdg_cache_home=os.path.expanduser("~/.cache")) from xdg import BaseDirectory as xdg # system paths 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/" APPSTREAM_BASE_PATH = "/usr/share/app-info" APPSTREAM_XML_PATH = APPSTREAM_BASE_PATH + "/xmls/" SOFTWARE_CENTER_BASE = "/usr/share/software-center" SOFTWARE_CENTER_PLUGIN_DIRS = [ os.environ.get("SOFTWARE_CENTER_PLUGINS_DIR", ""), os.path.join(SOFTWARE_CENTER_BASE, "plugins"), os.path.join(xdg.xdg_data_home, "software-center", "plugins"), os.path.join(os.path.dirname(__file__), "plugins"), ] # FIXME: use relative paths here INSTALLED_ICON = \ "/usr/share/software-center/icons/software-center-installed.png" # xapian paths XAPIAN_BASE_PATH = "/var/cache/software-center" XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT = os.path.join( xdg.xdg_cache_home, "software-center", "software-center-agent.db") XAPIAN_PATH = os.path.join(XAPIAN_BASE_PATH, "xapian") # AXI APT_XAPIAN_INDEX_BASE_PATH = "/var/lib/apt-xapian-index" APT_XAPIAN_INDEX_DB_PATH = APT_XAPIAN_INDEX_BASE_PATH + "/index" APT_XAPIAN_INDEX_UPDATE_STAMP_PATH = (APT_XAPIAN_INDEX_BASE_PATH + "/update-timestamp") # global datadirs, this may be overridden at startup datadir = "/usr/share/software-center/" desktopdir = APP_INSTALL_PATH xapiandir = XAPIAN_BASE_PATH # OEM OEM_CHANNEL_DESCRIPTOR = "/var/lib/ubuntu_dist_channel" # apport APPORT_RECOVERABLE_ERROR = "/usr/share/apport/recoverable_problem" # ratings&review # relative to datadir class RNRApps: 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 def try_to_fixup_root_owned_dir_via_remove(directory): if os.path.exists(directory) and not os.access(directory, os.W_OK): try: logging.warn("trying to fix not writable cache directory") os.rmdir(directory) except: 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") 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") # 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-13.10/softwarecenter/plugins/0000755000202700020270000000000012224614354021567 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/plugins/__init__.py0000664000202700020270000000000012151440100023651 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/plugins/webapps_activation.py0000664000202700020270000000400212151440100026002 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2012 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 import logging import softwarecenter.plugin from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.backend.installbackend import get_install_backend LOG = logging.getLogger(__name__) class UnityWebappsActivationPlugin(softwarecenter.plugin.Plugin): """Webapps activation plugin """ def init_plugin(self): self.pkg_info = get_pkg_info() self.install_backend = get_install_backend() self.install_backend.connect( "transaction-finished", self._on_transaction_finished) def _on_transaction_finished(self, backend, result): if not result.success or not result.pkgname: return if not result.pkgname in self.pkg_info: return pkg = self.pkg_info[result.pkgname] if not pkg.candidate: return webdomain = pkg.candidate.record.get("Ubuntu-Webapps-Domain", None) if webdomain: self.activate_unity_webapp_for_domain(webdomain) def activate_unity_webapp_for_domain(self, domain): try: from gi.repository import UnityWebapps except ImportError: LOG.warn("failed to import UnityWebapps GIR") return LOG.debug("activating webapp for domain '%s'", domain) UnityWebapps.permissions_allow_domain(domain) software-center-13.10/softwarecenter/__init__.py0000664000202700020270000000130312151440100022177 0ustar dobeydobey00000000000000# Copyright (C) 2009-2010 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 software-center-13.10/softwarecenter/backend/0000755000202700020270000000000012224614354021475 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/spawn_helper.py0000664000202700020270000001320512151440100024522 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011-2013 Canonical Ltd. # # 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 # py3 compat try: import cPickle as pickle pickle # pyflakes except ImportError: import pickle import logging import os import json import softwarecenter.paths from softwarecenter.paths import PistonHelpers from gi import version_info as gi_version from gi.repository import GObject, GLib 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,), ), } def __init__(self, format="pickle"): super(SpawnHelper, self).__init__() self._expect_format = format self._stdout = None self._stderr = None self._io_watch = None self._child_watch = None self._cmd = None self.needs_auth = False self.no_relogin = False self.ignore_cache = False self.parent_xid = None def run_generic_piston_helper(self, klass, func, **kwargs): binary = os.path.join( softwarecenter.paths.datadir, PistonHelpers.GENERIC_HELPER) cmd = [binary] cmd += ["--datadir", softwarecenter.paths.datadir] if self.needs_auth: cmd.append("--needs-auth") if self.ignore_cache: cmd.append("--ignore-cache") if self.no_relogin: cmd.append("--no-relogin") if self.parent_xid: cmd.append("--parent-xid") cmd.append(str(self.parent_xid)) cmd += [klass, func] if kwargs: cmd.append(json.dumps(kwargs)) LOG.debug("run_generic_piston_helper()") self.run(cmd) def run(self, cmd): # only useful for debugging if "SOFTWARE_CENTER_DISABLE_SPAWN_HELPER" in os.environ: return self._cmd = cmd (pid, stdin, stdout, stderr) = GLib.spawn_async( cmd, flags=GObject.SPAWN_DO_NOT_REAP_CHILD, standard_output=True, standard_error=True) LOG.debug("running: '%s' as pid: '%s'" % (cmd, pid)) # python-gobject >= 3.7.3 has changed some API in incompatible # ways, so we need to check the version for which one to use. if gi_version < (3, 7, 3): self._child_watch = GLib.child_watch_add( pid, self._helper_finished, (stdout, stderr)) self._io_watch = GLib.io_add_watch( stdout, GObject.IO_IN, self._helper_io_ready, (stdout, )) else: self._child_watch = GLib.child_watch_add( GLib.PRIORITY_DEFAULT, pid, self._helper_finished, data=(stdout, stderr)) self._io_watch = GLib.io_add_watch( stdout, GLib.PRIORITY_DEFAULT, GObject.IO_IN, self._helper_io_ready, (stdout, )) def _helper_finished(self, pid, status, (stdout, stderr)): LOG.debug("helper_finished: '%s' '%s'" % (pid, status)) # get status code res = os.WEXITSTATUS(status) if res == 0: self.emit("exited", res) else: LOG.warn("exit code %s from helper for '%s'" % (res, self._cmd)) # check stderr err = os.read(stderr, 4 * 1024) self._stderr = err if err: LOG.warn("got error from helper: '%s'" % err) self.emit("error", err) os.close(stderr) if self._io_watch: # remove with a delay timeout delay to ensure that any # pending data is still flushed GLib.timeout_add(100, GLib.source_remove, self._io_watch) if self._child_watch: GLib.source_remove(self._child_watch) def _helper_io_ready(self, source, condition, (stdout,)): # read the raw data data = "" while True: s = os.read(stdout, 1024) if not s: break data += s os.close(stdout) self._stdout = data if self._expect_format == "pickle": # unpickle it, we should *always* get valid data here, so if # we don't this should raise a error try: data = pickle.loads(data) except: LOG.exception("can not load pickle data: '%s'" % data) elif self._expect_format == "json": try: data = json.loads(data) except: LOG.exception("can not load json: '%s'" % data) elif self._expect_format == "none": pass else: LOG.error("unknown format: '%s'", self._expect_format) LOG.debug("got data for cmd: '%s'='%s'" % (self._cmd, data)) self.emit("data-available", data) return False software-center-13.10/softwarecenter/backend/channel_impl/0000755000202700020270000000000012224614354024126 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/channel_impl/__init__.py0000664000202700020270000000000012151440100026210 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/channel_impl/aptchannels.py0000664000202700020270000002766712151440100027005 0ustar dobeydobey00000000000000# Copyright (C) 2010-2013 Canonical Ltd. # # 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 """Software Center apt channels handling.""" import os import logging import xapian import softwarecenter.paths from gi import version_info as gi_version from gi.repository import GLib from aptsources.sourceslist import SourceEntry, SourcesList from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.backend.channel import (ChannelsManager, SoftwareChannel) from softwarecenter.distro import get_distro from softwarecenter.utils import human_readable_name_from_ppa_uri from softwarecenter.enums import (ViewPages, AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, ) 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._remove_no_longer_needed_extra_channels) # kick off a background check for changes that may have been made # in the channels list GLib.timeout_add_seconds(60, self._check_for_channel_updates_timer) # extra channels from e.g. external sources self.extra_channels = [] self._logger = LOG # external API @property def channels(self): """ return a list of SoftwareChannel objects in display order according to: Distribution, Partners, PPAs alphabetically, Other channels alphabetically, Unknown channel last """ return self._get_channels() @property def channels_installed_only(self): """ return a list of SoftwareChannel objects displaying installed packages only in display order according to: Distribution, Partners, PPAs alphabetically, Other channels alphabetically, Unknown channel last """ return self._get_channels(installed_only=True) def feed_in_private_sources_list_entries(self, entries): added = False for entry in entries: added |= self._feed_in_private_sources_list_entry(entry) if added: self.backend.emit("channels-changed", True) def add_channel(self, name, icon, query): """ create a channel with the name, icon and query specified and append it to the set of channels return the new channel object """ # print name, icon, query channel = SoftwareChannel(name, None, None, channel_icon=icon, channel_query=query) self.extra_channels.append(channel) self.backend.emit("channels-changed", True) if channel.installed_only: channel._channel_view_id = ViewPages.INSTALLED else: channel._channel_view_id = ViewPages.AVAILABLE return channel @staticmethod def channel_available(channelname): import apt_pkg p = os.path.join(apt_pkg.config.find_dir("Dir::Etc::sourceparts"), "%s.list" % channelname) return os.path.exists(p) # internal def _feed_in_private_sources_list_entry(self, source_entry): """ 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 """ # FIXME: strip out password and use apt/auth.conf potential_new_entry = SourceEntry(source_entry) # look if we have it sources = SourcesList() for source in sources.list: if source == potential_new_entry: return False # need to add it as a not yet enabled channel name = human_readable_name_from_ppa_uri(potential_new_entry.uri) # FIXME: use something better than uri as name private_channel = SoftwareChannel(name, None, None, source_entry=source_entry) private_channel.needs_adding = True if private_channel in self.extra_channels: return False # add it self.extra_channels.append(private_channel) return True def _remove_no_longer_needed_extra_channels(self, backend, res): """ go over the extra channels and remove no longer needed ones""" removed = False for channel in self.extra_channels: if not channel._source_entry: continue sources = SourcesList() for source in sources.list: if source == SourceEntry(channel._source_entry): self.extra_channels.remove(channel) removed = True if removed: self.backend.emit("channels-changed", True) def _check_for_channel_updates_timer(self): """ 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) = GLib.spawn_async( [channel_update], flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD) # python-gobject >= 3.7.3 has changed some API in incompatible # ways, so we need to check the version for which one to use. if gi_version < (3, 7, 3): GLib.child_watch_add(pid, self._on_check_for_channel_updates_finished) else: GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, self._on_check_for_channel_updates_finished) def _on_check_for_channel_updates_finished(self, pid, condition): # 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 """ distro_channel_name = self.distro.get_distro_channel_name() # gather the set of software channels and order them other_channel_list = [] cached_origins = [] for channel_iter in self.db.xapiandb.allterms("XOL"): if len(channel_iter.term) == 3: 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): channel_origin = term_iter.term[3:] break self._logger.debug("channel_name: %s" % channel_name) self._logger.debug("channel_origin: %s" % channel_origin) 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 new_apps_channel = None ppa_channels = [] other_channels = [] unknown_channel = [] local_channel = None 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)) elif channel_name == distro_channel_name: dist_channel = (SoftwareChannel(distro_channel_name, channel_origin, None, installed_only=installed_only)) elif channel_name == "Partner archive": partner_channel = SoftwareChannel(channel_name, 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) elif (channel_origin and 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) else: ppa_channels.append(SoftwareChannel(channel_name, 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)) # 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", 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: channels.append(dist_channel) if partner_channel is not None: channels.append(partner_channel) if get_distro().PURCHASE_APP_URL: channels.append(for_purchase_channel) if new_apps_channel is not None: channels.append(new_apps_channel) channels.extend(ppa_channels) channels.extend(other_channels) channels.extend(unknown_channel) channels.extend(self.extra_channels) if local_channel is not None: channels.append(local_channel) for channel in channels: if installed_only: channel._channel_view_id = ViewPages.INSTALLED else: channel._channel_view_id = ViewPages.AVAILABLE return channels software-center-13.10/softwarecenter/backend/__init__.py0000664000202700020270000000127612151440100023577 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 software-center-13.10/softwarecenter/backend/login_impl/0000755000202700020270000000000012224614354023626 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/login_impl/__init__.py0000664000202700020270000000000012151440100025710 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/login_impl/login_fake.py0000664000202700020270000000535712151440100026273 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2012 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 import dbus import logging import random import string from softwarecenter.backend.fake_review_settings import ( FakeReviewSettings, network_delay, ) from softwarecenter.backend.login import LoginBackend LOG = logging.getLogger(__name__) class LoginBackendDbusSSOFake(LoginBackend): def __init__(self, window_id, appname, help_text): super(LoginBackendDbusSSOFake, self).__init__() self.appname = appname self.help_text = help_text self._window_id = window_id self._fake_settings = FakeReviewSettings() @network_delay def login(self): 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): retval = retval + random.choice(string.letters + string.digits) return retval.decode('utf-8') def _return_credentials(self): 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)) }, signature=dbus.Signature('ss') ) return c software-center-13.10/softwarecenter/backend/login_impl/login_sso.py0000664000202700020270000001013312216063163026171 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010 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 import dbus import logging from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) from ubuntu_sso import ( DBUS_BUS_NAME, DBUS_CREDENTIALS_IFACE, DBUS_CREDENTIALS_PATH, ) from softwarecenter.backend.login import LoginBackend from softwarecenter.utils import utf8 LOG = logging.getLogger(__name__) class LoginBackendDbusSSO(LoginBackend): def __init__(self, window_id, appname, help_text): super(LoginBackendDbusSSO, self).__init__() self.appname = 'Ubuntu One' self.help_text = help_text self.bus = dbus.SessionBus() obj = self.bus.get_object(bus_name=DBUS_BUS_NAME, object_path=DBUS_CREDENTIALS_PATH, follow_name_owner_changes=True) self.proxy = dbus.Interface(object=obj, dbus_interface=DBUS_CREDENTIALS_IFACE) self.proxy.connect_to_signal("CredentialsFound", self._on_credentials_found) self.proxy.connect_to_signal("CredentialsNotFound", self._on_credentials_not_found) self.proxy.connect_to_signal("CredentialsError", self._on_credentials_error) self.proxy.connect_to_signal("AuthorizationDenied", self._on_authorization_denied) self._window_id = window_id self._credentials = None def _get_params(self): p = {} if self.help_text: p['help_text'] = utf8(self.help_text) if self._window_id: p['window_id'] = self._window_id return p def find_credentials(self): LOG.debug("find_credentials()") self._credentials = None self.proxy.find_credentials(self.appname, self._get_params()) def login(self): 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 self.proxy.register(self.appname, self._get_params()) def _on_credentials_not_found(self, app_name): LOG.debug("_on_credentials_not_found for '%s'" % app_name) if app_name != self.appname: return self.emit("login-failed") def _on_credentials_found(self, app_name, credentials): LOG.debug("_on_credentials_found for '%s'" % app_name) if app_name != self.appname: return # only emit signal here once, otherwise it may happen that a # different process that triggers the on the dbus triggers # another signal emission here! if self._credentials != credentials: self.emit("login-successful", credentials) self._credentials = credentials def _on_credentials_error(self, app_name, error, detailed_error=""): LOG.error("_on_credentials_error for %s: %s (%s)" % ( app_name, error, detailed_error)) if app_name != self.appname: return # FIXME: do something useful with the error self.emit("login-failed") def _on_authorization_denied(self, app_name): LOG.info("_on_authorization_denied: %s" % app_name) if app_name != self.appname: return self.cancel_login() self.emit("login-canceled") software-center-13.10/softwarecenter/backend/login.py0000664000202700020270000000543412151440100023150 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010 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 import logging import os LOG = logging.getLogger(__name__) class LoginBackend(GObject.GObject): NEW_ACCOUNT_URL = None FORGOT_PASSWORD_URL = None __gsignals__ = { "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, (), ), } def login(self): raise NotImplemented def login_or_register(self): raise NotImplemented def find_credentials(self): raise NotImplemented def cancel_login(self): self.emit("login-canceled") def get_login_backend(window_id, appname, help_text): """ factory that returns an SSO loader singleton """ if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: from softwarecenter.backend.login_impl.login_fake import ( LoginBackendDbusSSOFake) sso_class = LoginBackendDbusSSOFake(window_id, appname, help_text) LOG.warn('Using fake login SSO functionality. Only meant for ' 'testing purposes') else: from softwarecenter.backend.login_impl.login_sso import ( LoginBackendDbusSSO) sso_class = LoginBackendDbusSSO(window_id, appname, help_text) return sso_class if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) from softwarecenter.enums import SOFTWARE_CENTER_NAME_KEYRING login = get_login_backend(0, SOFTWARE_CENTER_NAME_KEYRING, "login-text") login.login() from gi.repository import Gtk Gtk.main() software-center-13.10/softwarecenter/backend/scagent.py0000664000202700020270000001433412151440100023463 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2011 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 import logging import softwarecenter.paths from spawn_helper import SpawnHelper from softwarecenter.i18n import get_language from softwarecenter.distro import get_distro, get_current_arch 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,), ), } def __init__(self, ignore_cache=False, xid=None): GObject.GObject.__init__(self) self.distro = get_distro() self.ignore_cache = ignore_cache self.xid = xid def query_available(self, series_name=None, arch_tag=None): self._query_available(series_name, arch_tag, for_qa=False) def query_available_qa(self, series_name=None, arch_tag=None): self._query_available(series_name, arch_tag, for_qa=True) def _query_available(self, series_name, arch_tag, for_qa): if not series_name: series_name = self.distro.get_codename() if not arch_tag: arch_tag = get_current_arch() # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.ignore_cache = self.ignore_cache spawner.connect("data-available", self._on_query_available_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) if for_qa: spawner.needs_auth = True spawner.run_generic_piston_helper( "SoftwareCenterAgentAPI", "available_apps_qa", lang=get_language(), series=series_name, arch=arch_tag) else: spawner.run_generic_piston_helper( "SoftwareCenterAgentAPI", "available_apps", lang=get_language(), series=series_name, arch=arch_tag) def _on_query_available_data(self, spawner, piston_available): self.emit("available", piston_available) def query_available_for_me(self, no_relogin=False): spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.ignore_cache = self.ignore_cache spawner.connect("data-available", self._on_query_available_for_me_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.needs_auth = True spawner.no_relogin = no_relogin spawner.run_generic_piston_helper( "SoftwareCenterAgentAPI", "subscriptions_for_me", complete_only=True) 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): spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.ignore_cache = self.ignore_cache 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", lang=get_language(), series=self.distro.get_codename()) def _on_exhibits_data_available(self, spawner, exhibits): for exhibit in exhibits: # special case, if there is no title provided by the server # just extract the title from the first "h1" html 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) else: exhibit.title_translated = "" # allow having urls to click on in a banner if not hasattr(exhibit, "click_url"): exhibit.click_url = "" # ensure to fix #1004417 and #1043152 if exhibit.package_names: exhibit.package_names = ",".join( [s.strip() for s in exhibit.package_names.split(",")]) self.emit("exhibits", exhibits) if __name__ == "__main__": def _available(agent, available): print ("_available: %s" % available) def _available_for_me(agent, available_for_me): print ("_available_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() # test specific stuff logging.basicConfig() softwarecenter.paths.datadir = "./data" scagent = SoftwareCenterAgent() scagent.connect("available-for-me", _available_for_me) scagent.connect("available", _available) scagent.connect("exhibits", _exhibits) scagent.connect("error", _error) #scagent.query_available("natty", "i386") #scagent.query_available_for_me() scagent.query_exhibits() from gi.repository import Gtk Gtk.main() software-center-13.10/softwarecenter/backend/channel.py0000664000202700020270000003073712151440100023454 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # Authors: # Gary Lasker # 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 xapian from gettext import gettext as _ import softwarecenter.distro from softwarecenter.enums import ( SortMethods, Icons, ViewPages, ) LOG = logging.getLogger(__name__) class ChannelsManager(object): def __init__(self, db, **kwargs): self.distro = softwarecenter.distro.get_distro() self.db = db # public @property def channels(self): return self._get_channels_from_db() @property def channels_installed_only(self): return self._get_channels_from_db(True) @classmethod def channel_available(kls, channelname): pass # private def _get_channels_from_db(self, installed_only=False): """ (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 = [] for channel_iter in self.db.xapiandb.allterms("XOL"): if len(channel_iter.term) == 3: 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): channel_origin = term_iter.term[3:] break LOG.debug("channel_name: %s" % channel_name) LOG.debug("channel_origin: %s" % channel_origin) 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 = [] local_channel = None 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)) elif channel_origin == distro_channel_origin: 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) else: other_channels.append(SoftwareChannel( channel_name, channel_origin, None, installed_only=installed_only)) # set them in order channels = [] if dist_channel is not None: channels.append(dist_channel) channels.extend(other_channels) channels.extend(unknown_channel) if local_channel is not None: channels.append(local_channel) for channel in channels: if installed_only: channel._channel_view_id = ViewPages.INSTALLED else: 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, channel_sort_mode=SortMethods.BY_ALPHABET): """ configure the software channel object based on channel name, origin, and component (the latter for detecting the partner channel) """ self._channel_name = channel_name self._channel_origin = channel_origin self._channel_component = channel_component self._channel_color = None self._channel_view_id = None self.installed_only = installed_only self._channel_sort_mode = channel_sort_mode # distro specific stuff self.distro = softwarecenter.distro.get_distro() # configure the channel 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) 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) else: self._channel_query = channel_query # a sources.list entry attached to the channel (this is currently # only used for not-yet-enabled channels) 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 def origin(self): """ return the channel origin as represented in the xapian database """ return self._channel_origin @property def component(self): """ return the channel component as represented in the xapian database """ return self._channel_component @property def display_name(self): """ return the display name for the corresponding channel for use in the UI """ return self._channel_display_name @property def icon(self): """ return the icon that corresponds to each channel based on the channel name, its origin string or its component """ return self._channel_icon @property def query(self): """ return the xapian query to be used with this software channel """ return self._channel_query @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): if channel_component == "partner": channel_display_name = _("Canonical Partners") elif not channel_origin: channel_display_name = _("Unknown") elif channel_origin == self.distro.get_distro_channel_name(): channel_display_name = self.distro.get_distro_channel_description() elif channel_name == "For Purchase": channel_display_name = _("For Purchase") elif channel_name == "Previous Purchases": channel_display_name = _("Previous Purchases") elif channel_name == "Application Review Board PPA": channel_display_name = _("Independent") elif channel_name == "notdownloadable": channel_display_name = _("Other") else: return channel_name return channel_display_name def _get_icon_for_channel(self, channel_name, channel_origin, channel_component): if channel_component == "partner": channel_icon = "partner" elif not channel_name: channel_icon = "unknown-channel" elif channel_origin == self.distro.get_distro_channel_name(): channel_icon = "distributor-logo" elif channel_name == "Application Review Board PPA": channel_icon = "system-users" elif channel_name == "For Purchase": channel_icon = "emblem-money" elif channel_origin and channel_origin.startswith("LP-PPA"): channel_icon = "ppa" elif channel_name == "notdownloadable": channel_icon = "application-default-icon" # TODO: add check for generic repository source (e.g., Google, Inc.) # channel_icon = "generic-repository" else: channel_icon = "unknown-channel" return channel_icon 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, xapian.Query("XOL" + channel_name), xapian.Query("ATapplication")) elif channel_origin: channel_query = xapian.Query("XOO" + channel_origin) else: channel_query = xapian.Query("XOL" + channel_name) return channel_query def __str__(self): details = [] details.append("* SoftwareChannel") details.append(" name: %s" % self.name) details.append(" origin: %s" % self.origin) details.append(" component: %s" % self.component) details.append(" display_name: %s" % self.display_name) details.append(" iconname: %s" % self.icon) details.append(" query: %s" % self.query) details.append(" sort_mode: %s" % self.sort_mode) details.append(" installed_only: %s" % self.installed_only) return unicode('\n'.join(details), 'utf8').encode('utf8') class AllChannel(SoftwareChannel): def __init__(self, channel_name, installed_only): SoftwareChannel.__init__( self, channel_name, "all", None, installed_only=installed_only, channel_icon=Icons.FALLBACK) # overrides def _get_display_name_for_channel(self, channel_name, channel_origin, channel_component): return channel_name def _get_channel_query_for_channel(self, *args): pass class AllAvailableChannel(AllChannel): def __init__(self): AllChannel.__init__(self, _("All Software"), False) class AllInstalledChannel(AllChannel): 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) 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) return AptChannelsManager.channel_available(channelname) if __name__ == "__main__": distro = softwarecenter.distro.get_distro() channel = SoftwareChannel(distro.get_distro_channel_name(), None, None) print(channel) channel = SoftwareChannel(distro.get_distro_channel_name(), None, "partner") print(channel) software-center-13.10/softwarecenter/backend/weblive.py0000664000202700020270000003245512151440100023500 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) Canonical # # Author: 2011 Stephane Graber # 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; either version 2 # of the License, or (at your option) any later version. # # 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. # # taken from lp:~weblive-dev/weblive/trunk/client/weblive.py # and put into weblive_pristine.py import re import os import random import subprocess import string import imp from gi.repository import GObject, GLib from threading import Thread, Event from weblive_pristine import WebLive import softwarecenter.paths class WebLiveBackend(object): """ Backend for interacting with the WebLive service """ client = None URL = os.environ.get('SOFTWARE_CENTER_WEBLIVE_HOST', 'https://weblive.stgraber.org/weblive/json') def __init__(self): self.weblive = WebLive(self.URL, True) self.available_servers = [] for client in (WebLiveClientX2GO, WebLiveClientQTNX): if client.is_supported(): self.client = client() break self._ready = Event() @property def ready(self): """ Return true if data from the remote server was loaded """ return self.client and self._ready.is_set() def query_available(self): """ Get all the available data from WebLive """ self._ready.clear() servers = self.weblive.list_everything() self._ready.set() return servers def query_available_async(self): """ Call query_available in a thread and set self.ready """ def _query_available_helper(): self.available_servers = self.query_available() p = Thread(target=_query_available_helper) p.start() def is_pkgname_available_on_server(self, pkgname, serverid=None): """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: for pkg in server.packages: if pkg.pkgname == pkgname: return True return False def get_servers_for_pkgname(self, pkgname): """ Return a list of servers having a given package """ servers = [] for server in self.available_servers: # No point in returning a server that's full if server.current_users >= server.userlimit: continue for pkg in server.packages: if pkg.pkgname == pkgname: servers.append(server) return servers def create_automatic_user_and_run_session(self, serverid, session="desktop", wait=False): """ Create a user on 'serverid' and start the session """ # Use the boot_id to get a temporary unique identifier # (until next reboot) if os.path.exists('/proc/sys/kernel/random/boot_id'): 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)) # 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' # Send the user's locale so it's automatically selected when connecting 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) # 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) else: raise IOError("No remote desktop client available.") class WebLiveClient(GObject.GObject): """ Generic WebLive client """ __gsignals__ = { "progress": ( GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_INT,) ), "connected": ( GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,) ), "disconnected": ( GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, () ), "exception": ( GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,) ), "warning": ( GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,) ) } state = "disconnected" class WebLiveClientQTNX(WebLiveClient): """ qtnx client """ # NXML template NXML_TEMPLATE = """ """ BINARY_PATH = "/usr/bin/qtnx" @classmethod def is_supported(cls): """ Return if the current system will work (has the required dependencies) """ if os.path.exists(cls.BINARY_PATH): return True return False def start_session(self, host, port, session, username, password, wait): """ Start a session using qtnx """ self.state = "connecting" if not os.path.exists(os.path.expanduser('~/.qtnx')): 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) nxml.write(config) nxml.close() # Prepare qtnx call cmd = [self.BINARY_PATH, '%s-%s-%s' % (str(host), str(port), session.replace("/", "_")), username, password] def qtnx_countdown(): """ Send progress events every two seconds """ if self.helper_progress == 10: self.state = "connected" self.emit("connected", False) return False else: self.emit("progress", self.helper_progress * 10) self.helper_progress += 1 return True def qtnx_start_timer(): """ As we don't have a way of knowing the connection status, we countdown from 20s """ self.helper_progress = 0 qtnx_countdown() GLib.timeout_add_seconds(2, qtnx_countdown) qtnx_start_timer() if wait is False: # Start in the background and attach a watch for when it exits (self.helper_pid, stdin, stdout, stderr) = GLib.spawn_async( cmd, standard_input=True, standard_output=True, standard_error=True, flags=GObject.SPAWN_DO_NOT_REAP_CHILD) GLib.child_watch_add( GLib.PRIORITY_DEFAULT, self.helper_pid, self._on_qtnx_exit, filename) else: # Start it and wait until it finishes p = subprocess.Popen(cmd) p.wait() def _on_qtnx_exit(self, pid, status, filename): """ Called when the qtnx process exits (when in the background) """ # Remove configuration file self.state = "disconnected" self.emit("disconnected") if os.path.exists(filename): os.remove(filename) class WebLiveClientX2GO(WebLiveClient): """ x2go client """ @classmethod def is_supported(cls): """ Return if the current system will work (has the required dependencies) """ try: imp.find_module("x2go") return True except: return False def start_session(self, host, port, session, username, password, wait): """ 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)] (self.helper_pid, stdin, stdout, stderr) = GLib.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) # Add a watch for when the process exits GLib.child_watch_add( GLib.PRIORITY_DEFAULT, self.helper_pid, self._on_x2go_exit) # Add a watch on stdout channel = GLib.IOChannel.unix_new(self.helper_stdout) GLib.io_add_watch(channel, GLib.PRIORITY_DEFAULT, 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.flush() def disconnect_session(self): """ Disconnect the current session """ if self.state == "connected": self.state = "disconnecting" self.helper_stdin.write("DISCONNECT\n") self.helper_stdin.flush() def _on_x2go_exit(self, pid, status): # We get everything by just watching stdout pass def _on_x2go_activity(self, stdout, condition): """ Called when something appears on stdout """ line = stdout.readline().strip() if line.startswith("PROGRESS: "): if line.endswith("creating"): self.emit("progress", 10) elif line.endswith("connecting"): self.emit("progress", 30) elif line.endswith("starting"): self.emit("progress", 60) elif line == "CONNECTED": self.emit("connected", True) self.state = "connected" elif line == "DISCONNECTED": self.emit("disconnected") self.state = "disconnected" elif line.startswith("EXCEPTION: "): self.emit("exception", line.split(": ")[1]) self.state = "disconnected" elif line.startswith("WARNING: "): self.emit("warning", line.split(": ")[1]) else: pass return True # singleton _weblive_backend = None def get_weblive_backend(): global _weblive_backend if _weblive_backend is None: _weblive_backend = WebLiveBackend() # initial query if _weblive_backend.client: _weblive_backend.query_available_async() return _weblive_backend if __name__ == "__main__": # Contact the weblive daemon to get all servers weblive = get_weblive_backend() weblive.query_available_async() weblive._ready.wait() # Show the currently available servers 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) software-center-13.10/softwarecenter/backend/recagent.py0000664000202700020270000002762312151440100023634 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2012 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 import logging import hashlib import softwarecenter.paths from .spawn_helper import SpawnHelper from softwarecenter.config import get_config from softwarecenter.db.utils import get_installed_apps_list from softwarecenter.utils import get_recommender_uuid 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,), ), "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,), ), "submit-implicit-feedback-finished": (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 self.config = get_config() def query_server_status(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_server_status_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "server_status") def _calc_profile_id(self, profile): """ Return a profile id (md5 hash of a profile) for the given profile """ return hashlib.md5(str(profile)).hexdigest() @property def opt_in_requested(self): return self.config.recommender_opt_in_requested @property def recommender_uuid(self): return self.config.recommender_uuid @property def recommender_profile_id(self): return self.config.recommender_profile_id def _set_recommender_profile_id(self, profile_id): self.config.recommender_profile_id = profile_id def _set_recommender_uuid(self, uuid): self.config.recommender_uuid = uuid def recommender_opt_in_requested(self, opt_in_requested): self.config.recommender_opt_in_requested = opt_in_requested def post_submit_profile(self, db): """ This will post the user's profile to the recommender server and also generate the UUID for the user if that is not there yet """ recommender_uuid = self.recommender_uuid if not recommender_uuid: # generate a new uuid, but do not save it yet, this will # be done later in _on_submit_profile_data recommender_uuid = get_recommender_uuid() installed_pkglist = [app.pkgname for app in get_installed_apps_list(db)] profile = self._generate_submit_profile_data(recommender_uuid, installed_pkglist) # compare profiles to see if there has been a change, and if there # has, do the profile update current_recommender_profile_id = self._calc_profile_id(profile) if current_recommender_profile_id != self.recommender_profile_id: LOG.info("Submitting recommendations profile to the server") self._set_recommender_profile_id(current_recommender_profile_id) # build the command and upload the profile spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_profile_data, recommender_uuid) spawner.connect( "error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "submit_profile", data=profile) def post_submit_anon_profile(self, uuid, installed_packages, extra): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_anon_profile_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "submit_anon_profile", uuid=uuid, installed_packages=installed_packages, extra=extra) def query_profile(self, pkgnames): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_profile_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "profile", pkgnames=pkgnames) def query_recommend_me(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_recommend_me_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_me", uuid=self.recommender_uuid) def query_recommend_app(self, pkgname): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_app_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_app", pkgname=pkgname) def query_recommend_all_apps(self): # build the command spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_all_apps_data) 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() spawner.parent_xid = self.xid spawner.connect("data-available", self._on_recommend_top_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "recommend_top") def post_implicit_feedback(self, pkgname, action): # build the command LOG.debug("called post_implicit_feedback with %s, '%s'" % (pkgname, action)) spawner = SpawnHelper() spawner.parent_xid = self.xid spawner.needs_auth = True spawner.connect("data-available", self._on_submit_implicit_feedback_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.run_generic_piston_helper( "SoftwareCenterRecommenderAPI", "implicit_feedback", pkgname=pkgname, action=action) def is_opted_in(self): """ Return True is the user is currently opted-in to the recommender service """ if self.recommender_uuid or self.opt_in_requested: return True else: return False def opt_out(self): self.config.recommender_uuid = "" self.config.recommender_profile_id = "" self.config.recommender_opt_in_requested = False 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, recommender_uuid): self._set_recommender_uuid(recommender_uuid) self.emit("submit-profile-finished", piston_submit_profile) def _on_submit_anon_profile_data(self, spawner, piston_submit_anon_profile): self.emit("submit-anon-profile-finished", 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 _on_submit_implicit_feedback_data(self, spawner, piston_submit_implicit_feedback): self.emit("submit-implicit-feedback-finished", piston_submit_implicit_feedback) def _generate_submit_profile_data(self, recommender_uuid, 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() # test specific stuff logging.basicConfig() softwarecenter.paths.datadir = "./data" agent = RecommenderAgent() agent.connect("recommend-top", _recommend_top) agent.connect("recommend-me", _recommend_me) agent.connect("error", _error) agent.query_recommend_top() agent.query_recommend_me() Gtk.main() software-center-13.10/softwarecenter/backend/reviews/0000755000202700020270000000000012224614354023161 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/reviews/__init__.py0000664000202700020270000007334512151440100025271 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # 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 import datetime import logging import operator import os import random import struct import shutil import subprocess import time import threading from bsddb import db as bdb from gi.repository import GObject, GLib # py3 compat try: import cPickle as pickle pickle # pyflakes except ImportError: import pickle from softwarecenter.db.categories import get_query_for_category from softwarecenter.db.database import Application, StoreDatabase import softwarecenter.distro from softwarecenter.i18n import get_languages from softwarecenter.utils import (upstream_version_compare, uri_to_filename, get_person_from_config, wilson_score, ) from softwarecenter.paths import (SOFTWARE_CENTER_CACHE_DIR, XAPIAN_BASE_PATH, ) from softwarecenter.enums import ReviewSortMethods from softwarecenter.backend.spawn_helper import SpawnHelper 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.dampened_rating = 3.00 self.histogram = None def __repr__(self): 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) self._retrieve_votes_from_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)) except: LOG.exception("usefulness cache load fallback failure") 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") return False # run the command and add watcher spawn_helper = SpawnHelper() spawn_helper.connect("data-available", self._on_usefulness_data) spawn_helper.run_generic_piston_helper( "RatingsAndReviewsAPI", "get_usefulness", username=user) def _on_usefulness_data(self, spawn_helper, results): '''called if usefulness retrieved from server''' LOG.debug("_usefulness_loaded started") self.USEFULNESS_CACHE.clear() 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") def save_usefulness_cache_file(self): """write the dict out to cache file""" cachedir = SOFTWARE_CENTER_CACHE_DIR try: if not os.path.exists(cachedir): os.makedirs(cachedir) pickle.dump(self.USEFULNESS_CACHE, open(self.USEFULNESS_CACHE_FILE, "w")) 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 """ 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 """ return self.USEFULNESS_CACHE.get(str(review_id)) class Review(object): """A individual review object """ def __init__(self, app): # a softwarecenter.db.database.Application object self.app = app self.app_name = app.appname self.package_name = app.pkgname # the review items that the object fills in self.id = None self.language = None self.summary = "" self.review_text = "" self.package_version = None self.date_created = None self.rating = None self.reviewer_username = None self.reviewer_displayname = None self.version = "" self.usefulness_total = 0 self.usefulness_favorable = 0 # this will be set if trying 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, self.usefulness_total), wilson_score(other.usefulness_favorable, other.usefulness_total)) if uc != 0: 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') return cmp(t1, t2) @classmethod def from_piston_mini_client(cls, other): """ converts the rnrclient reviews we get into "our" Review object (we need this as we have more attributes then the rnrclient review object) """ app = Application("", other.package_name) review = cls(app) for (attr, value) in other.__dict__.items(): if not attr.startswith("_"): setattr(review, attr, value) return review @classmethod def from_json(cls, other): """ convert json reviews into "out" review objects """ app = Application("", other["package_name"]) review = cls(app) for k, v in other.items(): 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, # review-stats list (GObject.TYPE_PYOBJECT,), ), "get-reviews-finished": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # application, reviewslist (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT), ), "remove-review": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # app, the review (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT), ), "replace-review": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # app, the review (GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT), ), "update-usefulness-votes": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # useful_votes (GObject.TYPE_PYOBJECT, ), ), } # cache the ReviewStats REVIEW_STATS_CACHE = {} _cache_version_old = False _review_sort_methods = ReviewSortMethods.REVIEW_SORT_METHODS def __init__(self, cache, db, distro=None): GObject.GObject.__init__(self) self.cache = cache self.db = db self.distro = distro if not self.distro: self.distro = softwarecenter.distro.get_distro() fname = "%s_%s" % (uri_to_filename(self.distro.REVIEWS_SERVER), "review-stats-pkgnames.p") 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, bdb.DB_VERSION_MINOR) self.language = get_languages()[0] if os.path.exists(self.REVIEW_STATS_CACHE_FILE): try: 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") 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''' for app in self.REVIEW_STATS_CACHE.values(): result = getattr(app, 'rating_spread', False) if not result: return True return False def get_reviews(self, application, page=1, language=None, sort=0, relaxed=False): """run callback f(app, review_list) with list of review objects for the given db.database.Application object """ return [] def update_review_stats(self, translated_application, stats): application = Application("", translated_application.pkgname) self.REVIEW_STATS_CACHE[application] = stats def get_review_stats(self, translated_application): """return a ReviewStats (number of reviews, rating) for a given application. this *must* be super-fast as it is called a lot during tree view display """ # check cache try: application = Application("", translated_application.pkgname) if application in self.REVIEW_STATS_CACHE: return self.REVIEW_STATS_CACHE[application] except ValueError: pass def refresh_review_stats(self): """ get the review statistics and call callback when its there """ pass def save_review_stats_cache_file(self, nonblocking=True): """ save review stats cache file in xdg cache dir """ cachedir = SOFTWARE_CENTER_CACHE_DIR if not os.path.exists(cachedir): os.makedirs(cachedir) # write out the stats if nonblocking: t = threading.Thread(target=self._save_review_stats_cache_blocking) t.run() else: self._save_review_stats_cache_blocking() def _save_review_stats_cache_blocking(self): # dump out for software-center in simple pickle self._dump_pickle_for_sc() # dump out in c-friendly dbm format for unity try: outfile = self.REVIEW_STATS_BSDDB_FILE outdir = self.REVIEW_STATS_BSDDB_FILE + ".dbenv/" self._dump_bsddbm_for_unity(outfile, outdir) except (bdb.DBError, MemoryError) as e: # see bug #858437, db corruption seems to be rather common # on ecryptfs # see also bug #1054070 for the MemoryError LOG.warn("error creating bsddb: '%s' (corrupted?)" % e) try: shutil.rmtree(outdir) self._dump_bsddbm_for_unity(outfile, outdir) except: LOG.exception("trying to repair DB failed") def _dump_pickle_for_sc(self): """ 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 """ 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) 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', stats.ratings_average or 0, stats.ratings_total, stats.dampened_rating) 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'): 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): 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""" filtered_cache = {} for key in cache.keys(): 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, origin, parent_xid, datadir, callback): """Spawn the UI for writing a new review and adds it automatically to the reviews DB. """ pass def spawn_report_abuse_ui(self, review_id, parent_xid, datadir, callback): """ this spawns the UI for reporting a review as inappropriate and adds the review-id to the internal hide list. once the operation is complete it will call callback with the updated review list """ pass def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir, callback): """Spawn a helper to submit a usefulness vote.""" pass def spawn_delete_review_ui(self, review_id, parent_xid, datadir, callback): """Spawn a helper to delete a review.""" pass 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"] SUMMARIES = ["Cool", "Medium", "Bad", "Too difficult"] IPSUM = "no ipsum\n\nstill no ipsum" def __init__(self, cache, db): 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, page=1, language=None, sort=0, relaxed=False): if not application in self._review_stats_cache: self.get_review_stats(application) stats = self._review_stats_cache[application] if not application in self._reviews_cache: reviews = [] for i in range(0, stats.ratings_total): review = Review(application) review.id = random.randint(1, 50000) # FIXME: instead of random, try to match the avg_rating 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.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] self.emit("get-reviews-finished", 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) self._review_stats_cache[application] = stat return self._review_stats_cache[application] def refresh_review_stats(self): review_stats = [] self.emit("refresh-review-stats-finished", 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] 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 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 like LUA to provide a execution environment based on JIT technology. The software in this packages has a wonderful GUI, its based on OpenGL but can alternative use DirectX (on platforms were it is available). Dynamic shading utilizes all GPU cores and out-of-order thread scheduling is used to visualize the data optimally on multi core systems. The database support in this application is bleeding edge. Not only classical SQL techniques are supported but also object-relational models and advanced ORM technology that will do auto-lookups based on dynamic join/select optimizations to leverage sharded or multihosted databases to their peak performance. The Enterprise computer system is controlled by three primary main processing cores cross linked with a redundant melacortz ramistat and fourteen kiloquad interface modules. The core elements are based on FTL nanoprocessor units arranged into twenty-five bilateral kelilactirals with twenty of those units being slaved to the central heisenfram terminal. . . . Now this is the isopalavial interface which controls the main firomactal drive unit. . . . The ramistat kiloquad capacity is a function of the square root of the intermix ratio times the sum of the plasma injector quotient. The iApp is using the new touch UI that feels more natural then traditional window based offerings. It supports a Job button that will yell at you when pressed and a iAmCool mode where the logo of your new device blinks so that you attract maximum attention. This app is a lifestyle choice. It sets you apart from those who are content with bland UI designed around 1990's paradigms. This app represents you as a dynamic trend setter with taste. The carefully controlled user interface is perfectly tailored to the needs of a new age individual, and extreme care has been taken to ensure that all buttons are large enough for even the most robust digits. Designed with the web 2.0 and touch screen portable technologies in mind this app is the ultimate in media experience. With this lifestyle application you extend your social media and search reach. Exciting innovations in display and video reinvigorates the user experience, offering beautifully rendered advertisements straight to 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 """ #This text is under public domain #Lorem ipsum #Cicero 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 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 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 duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi lorem ipsum dolor sit amet consectetuer adipiscing elit sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat ut wisi enim ad minim veniam quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum lorem ipsum dolor sit amet consectetuer adipiscing elit sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat ut wisi enim ad minim veniam quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat vel illum dolore eu feugiat nulla facilisis 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 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 ipsum dolor sit amet consetetur sadipscing elitr at accusam aliquyam diam diam dolore dolores duo eirmod eos erat et nonumy sed tempor et et invidunt justo labore stet clita ea et gubergren kasd magna no rebum sanctus sea sed takimata ut vero voluptua est lorem ipsum dolor sit amet lorem ipsum dolor sit amet consetetur sadipscing elitr sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat 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 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 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""" class ReviewLoaderNull(ReviewLoader): """A dummy review loader which just returns empty results.""" def __init__(self, cache, db): ReviewLoader.__init__(self, cache, db) self._review_stats_cache = {} self._reviews_cache = {} def get_reviews(self, application, page=1, language=None, sort=0, relaxed=False): self.emit("get-reviews-finished", application, []) def get_review_stats(self, application): pass def refresh_review_stats(self): review_stats = [] self.emit("refresh-review-stats-finished", review_stats) review_loader = None def get_review_loader(cache, db=None): """ factory that returns a reviews loader singelton """ global review_loader if not review_loader: if "SOFTWARE_CENTER_IPSUM_REVIEWS" in os.environ: review_loader = ReviewLoaderIpsum(cache, db) elif "SOFTWARE_CENTER_FORTUNE_REVIEWS" in os.environ: review_loader = ReviewLoaderFortune(cache, db) elif "SOFTWARE_CENTER_TECHSPEAK_REVIEWS" in os.environ: review_loader = ReviewLoaderTechspeak(cache, db) else: try: from softwarecenter.backend.reviews.rnr import ( ReviewLoaderSpawningRNRClient) # no service_root will raise ValueError review_loader = ReviewLoaderSpawningRNRClient(cache, db) except (ImportError, ValueError): review_loader = ReviewLoaderNull(cache, db) return review_loader if __name__ == "__main__": def callback(loader, app, reviews): print "app callback:" print app, reviews def stats_callback(loader, stats): print "stats callback:" print stats # cache from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() cache.open() 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 ) loader = ReviewLoaderSpawningRNRClient(cache, db) loader.connect("refresh-review-stats-finished", stats_callback) loader.connect("get-reviews-finished", callback) loader.refresh_review_stats() print loader.get_reviews(app) print "\n\n" print "default loader, press ctrl-c for next loader" context = GLib.main_context_default() main = GLib.MainLoop(context) main.run() # default loader app = Application("", "2vcard") loader = get_review_loader(cache, db) loader.refresh_review_stats(stats_callback) loader.get_reviews(app) main.run() software-center-13.10/softwarecenter/backend/reviews/rnr.py0000664000202700020270000004156512151440100024332 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # 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 import logging import json import os import time from softwarecenter.backend.spawn_helper import SpawnHelper 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 import softwarecenter.distro from softwarecenter.netstatus import network_state_is_connected from softwarecenter.paths import ( SOFTWARE_CENTER_CACHE_DIR, PistonHelpers, RNRApps, ) from softwarecenter.utils import ( calc_dr, utf8, save_person_to_config, ) LOG = logging.getLogger(__name__) # this code had several incarnations: # - python threads, slow and full of latency (GIL) # - python multiprocessing, crashed when accessibility was turned on, # does not work in the quest session (#743020) # - GLib.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 """ def __init__(self, cache, db, distro=None): super(ReviewLoaderSpawningRNRClient, self).__init__(cache, db, distro) cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") self.rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) self._reviews = {} def _update_rnrclient_offline_state(self): # this needs the lp:~mvo/piston-mini-client/offline-mode branch self.rnrclient._offline_mode = not network_state_is_connected() # reviews def get_reviews(self, translated_app, page=1, language=None, sort=0, relaxed=False): """ public API, triggers fetching a review and emits get-reviews-finished signal when its ready """ # its fine to use the translated appname here, we only submit the # pkgname to the server app = translated_app self._update_rnrclient_offline_state() sort_method = self._review_sort_methods[sort] if language is None: language = self.language # gather args for the helper if relaxed: origin = 'any' distroseries = 'any' else: try: origin = self.cache.get_origin(app.pkgname) except: # this can happen if e.g. the app has multiple origins, this # will be handled later origin = None # special case for not-enabled PPAs if not origin and self.db: details = app.get_details(self.db) ppa = details.ppaname if ppa: origin = "lp-ppa-%s" % ppa.replace("/", "-") # if there is no origin, there is nothing to do if not origin: self.emit("get-reviews-finished", app, []) 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 "--page", str(page), "--sort", sort_method, ] spawn_helper = SpawnHelper() spawn_helper.connect( "data-available", self._on_reviews_helper_data, app) spawn_helper.run(cmd) def _on_reviews_helper_data(self, spawn_helper, piston_reviews, app): # convert into our review objects reviews = [] for r in piston_reviews: reviews.append(Review.from_piston_mini_client(r)) # add to our dicts and emit signal self._reviews[app] = reviews self.emit("get-reviews-finished", app, self._reviews[app]) return False # stats def refresh_review_stats(self): """ 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 += 1 except OSError: days_delta = 0 LOG.debug("refresh with days_delta: %s" % days_delta) # FIXME: the server currently has bug (#757695) so we # can not turn this on just yet and need to use # the old "catch-all" review-stats for now #origin = "any" #distroseries = self.distro.get_codename() spawn_helper = SpawnHelper() spawn_helper.connect("data-available", self._on_review_stats_data) 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): """ process stdout from the helper """ review_stats = self.REVIEW_STATS_CACHE 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() return # convert to the format that s-c uses for r in piston_review_stats: s = ReviewStats(Application("", r.package_name)) s.ratings_average = float(r.ratings_average) s.ratings_total = float(r.ratings_total) if r.histogram: s.rating_spread = json.loads(r.histogram) else: 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 self.emit("refresh-review-stats-finished", review_stats) self.save_review_stats_cache_file() def _server_has_histogram(self, piston_review_stats): '''check response from server to see if histogram is supported''' supported = getattr(piston_review_stats[0], "histogram", False) if not supported: return False return True # writing new reviews spawns external helper def spawn_write_new_review_ui(self, translated_app, version, iconname, origin, parent_xid, datadir): """ 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), "--pkgname", app.pkgname, "--iconname", iconname, "--parent-xid", "%s" % parent_xid, "--version", version, "--origin", origin, "--datadir", datadir, ] if app.appname: # needs to be (utf8 encoded) str, otherwise call fails cmd += ["--appname", utf8(app.appname)] spawn_helper = SpawnHelper(format="json") spawn_helper.connect( "data-available", self._on_submit_review_data, app) spawn_helper.connect("exited", self._on_exited_callback, app) spawn_helper.connect("error", self._on_error_callback, app) spawn_helper.run(cmd) def _on_exited_callback(self, spawn_helper, return_code, app): # FIXME: send a proper error here instead! self.emit("get-reviews-finished", app, []) def _on_error_callback(self, spawn_helper, error_str, app): # FIXME: send a proper error here instead! self.emit("get-reviews-finished", app, []) def _on_submit_review_data(self, spawn_helper, review_json, app): """ called when submit_review finished, when the review was send successfully the callback is triggered with the new reviews """ LOG.debug("_on_submit_review_data") # read stdout from submit_review review = ReviewDetails.from_dict(review_json) # FIXME: ideally this would be stored in ubuntu-sso-client # but it doesn't so we store it here save_person_to_config(review.reviewer_username) if not app in self._reviews: self._reviews[app] = [] self._reviews[app].insert(0, Review.from_piston_mini_client(review)) self.emit("get-reviews-finished", app, self._reviews[app]) def spawn_report_abuse_ui(self, review_id, parent_xid, datadir): """ this spawns the UI for reporting a review as inappropriate and adds the review-id to the internal hide list. once the operation is complete it will emit remove-review with the updated review list """ 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, review_id) spawn_helper.run(cmd) def _on_report_abuse_finished(self, spawn_helper, exitcode, review_id): """ called when report_abuse finished """ LOG.debug("hide id %s " % review_id) if exitcode == 0: for (app, reviews) in self._reviews.items(): for review in reviews: if str(review.id) == str(review_id): # remove the one we don't want to see anymore self._reviews[app].remove(review) self.emit("remove-review", app, review) break def spawn_submit_usefulness_ui(self, review_id, is_useful, parent_xid, datadir): 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, review_id, is_useful) spawn_helper.connect("error", self._on_submit_usefulness_error, review_id) spawn_helper.run(cmd) def _on_submit_usefulness_finished(self, spawn_helper, res, review_id, is_useful): """ called when report_usefulness finished """ # "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) return LOG.debug("usefulness id %s " % review_id) useful_votes = UsefulnessCache() useful_votes.add_usefulness_vote(review_id, is_useful) for (app, reviews) in self._reviews.items(): for review in reviews: 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 if is_useful: review.usefulness_favorable = getattr( review, "usefulness_favorable", 0) + 1 self.emit("update-usefulness-votes", useful_votes) self.emit("replace-review", app, review) break def _on_submit_usefulness_error(self, spawn_helper, error_str, review_id): 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 self.emit("replace-review", app, review) break def spawn_delete_review_ui(self, review_id, parent_xid, datadir): 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, review_id) spawn_helper.connect("error", self._on_delete_review_error, review_id) spawn_helper.run(cmd) def _on_delete_review_finished(self, spawn_helper, res, review_id): """ called when delete_review finished""" LOG.debug("delete id %s " % review_id) for (app, reviews) in self._reviews.items(): for review in reviews: if str(review.id) == str(review_id): # remove the one we don't want to see anymore self._reviews[app].remove(review) self.emit("remove-review", app, review) break def _on_delete_review_error(self, spawn_helper, error_str, review_id): """called if delete review errors""" 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 self.emit("remove-review", app, review) break def spawn_modify_review_ui(self, parent_xid, iconname, datadir, review_id): """ 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), "--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, review_id) spawn_helper.connect("error", self._on_modify_review_error, review_id) spawn_helper.run(cmd) def _on_modify_review_finished(self, spawn_helper, review_json, review_id): """called when modify_review finished""" LOG.debug("_on_modify_review_finished") #review_json = spawn_helper._stdout mod_review = ReviewDetails.from_dict(review_json) for (app, reviews) in self._reviews.items(): for review in reviews: if str(review.id) == str(review_id): # remove the one we don't want to see anymore self._reviews[app].remove(review) new_review = Review.from_piston_mini_client(mod_review) self._reviews[app].insert(0, new_review) self.emit("replace-review", app, new_review) break def _on_modify_review_error(self, spawn_helper, error_str, review_id): """called if modify review errors""" 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 self.emit("replace-review", app, review) break software-center-13.10/softwarecenter/backend/ubuntusso.py0000664000202700020270000002037312151440100024106 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010 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, GLib from gettext import gettext as _ import logging import os import piston_mini_client.auth import piston_mini_client.failhandlers import softwarecenter.paths # mostly for testing from fake_review_settings import FakeReviewSettings, network_delay from spawn_helper import SpawnHelper from softwarecenter.config import get_config from softwarecenter.backend.login import get_login_backend from softwarecenter.backend.piston.ubuntusso_pristine import ( UbuntuSsoAPI as PristineUbuntuSsoAPI, ) # patch default_service_root to the one we use from softwarecenter.enums import UBUNTU_SSO_SERVICE # *Don't* append /api/1.0, as it's already included in UBUNTU_SSO_SERVICE PristineUbuntuSsoAPI.default_service_root = UBUNTU_SSO_SERVICE from softwarecenter.enums import ( SOFTWARE_CENTER_NAME_KEYRING, SOFTWARE_CENTER_SSO_DESCRIPTION, ) from softwarecenter.utils import clear_token_from_ubuntu_sso_sync LOG = logging.getLogger(__name__) class UbuntuSSO(GObject.GObject): """ Ubuntu SSO interface using the oauth token from the keyring The methods that work synchronously are suffixed with _sync() """ __gsignals__ = { "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, xid=0): GObject.GObject.__init__(self) self.oauth = None self.xid = xid self.loop = GLib.MainLoop(GLib.main_context_default()) def _on_whoami_data(self, spawner, piston_whoami): # once we have data, make sure to save it config = get_config() config.email = piston_whoami["preferred_email"] config.reviews_username = piston_whoami["username"] # emit self.emit("whoami", piston_whoami) def whoami(self): """ trigger request for getting the account information, this will also verify if the current token is valid and if not, trigger a cleanup/re-authenticate """ LOG.debug("whoami called") spawner = SpawnHelper() spawner.connect("data-available", self._on_whoami_data) spawner.connect("error", lambda spawner, err: self.emit("error", err)) spawner.needs_auth = True spawner.run_generic_piston_helper("UbuntuSsoAPI", "whoami") def _login_successful(self, sso_backend, oauth_result): LOG.debug("_login_successful") self.oauth = oauth_result self.loop.quit() # sync calls def verify_token_sync(self, token): """ Verify that the token is valid Note that this may raise httplib2 exceptions if the server is not reachable """ LOG.debug("verify_token") auth = piston_mini_client.auth.OAuthAuthorizer( token["token"], token["token_secret"], token["consumer_key"], token["consumer_secret"]) api = PristineUbuntuSsoAPI(auth=auth) try: res = api.whoami() except piston_mini_client.failhandlers.APIError as e: LOG.exception("api.whoami failed with APIError: '%s'" % e) return False return len(res) > 0 def clear_token(self): clear_token_from_ubuntu_sso_sync(SOFTWARE_CENTER_NAME_KEYRING) def _get_login_backend_and_connect(self): sso = get_login_backend( self.xid, SOFTWARE_CENTER_NAME_KEYRING, _(SOFTWARE_CENTER_SSO_DESCRIPTION)) sso.connect("login-successful", self._login_successful) sso.connect("login-failed", lambda s: self.loop.quit()) sso.connect("login-canceled", lambda s: self.loop.quit()) return sso def find_oauth_token_sync(self): self.oauth = None sso = self. _get_login_backend_and_connect() sso.find_credentials() self.loop.run() return self.oauth def get_oauth_token_sync(self): self.oauth = None sso = self. _get_login_backend_and_connect() sso.login_or_register() self.loop.run() return self.oauth def get_oauth_token_and_verify_sync(self, no_relogin=False): token = self.get_oauth_token_sync() # check if the token is valid and reset it if not if token: # verify token will return false if there is a API error, # but there may be httplib2 errors if there is no network, # so ignore them try: if not self.verify_token_sync(token): attempt_relogin = not no_relogin if attempt_relogin: self.clear_token() # re-trigger login once token = self.get_oauth_token_sync() else: return None except Exception as e: LOG.warn( "token could not be verified (network problem?): %s" % e) return token class UbuntuSSOAPIFake(UbuntuSSO): def __init__(self): UbuntuSSO.__init__(self) self._fake_settings = FakeReviewSettings() @network_delay 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": self.emit("error", self._make_error()) def _create_whoami_response(self): 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'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 singleton """ if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: ubuntu_sso_class = UbuntuSSOAPIFake() LOG.warn('Using fake Ubuntu SSO API. Only meant for testing purposes') else: ubuntu_sso_class = UbuntuSSO() return ubuntu_sso_class # 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: ") sys.stdout.flush() user = sys.stdin.readline().strip() sys.stdout.write("pass: ") sys.stdout.flush() password = sys.stdin.readline().strip() sso.login(user, password) # interactive test code if __name__ == "__main__": 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 = UbuntuSSO() sso.connect("whoami", _whoami) sso.connect("error", _error) sso.whoami() from gi.repository import Gtk logging.basicConfig(level=logging.DEBUG) softwarecenter.paths.datadir = "./data" backend = get_login_backend("", "appname", "help_text") backend.connect("login-successful", _dbus_maybe_login_successful) backend.login_or_register() Gtk.main() software-center-13.10/softwarecenter/backend/installbackend_impl/0000755000202700020270000000000012224614354025474 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/installbackend_impl/packagekitd.py0000664000202700020270000004031412151440100030302 0ustar dobeydobey00000000000000# Copyright (C) 2009-2010 Canonical # # Authors: # Alex Eftimie # # 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 dbus import dbus.mainloop.glib from gi.repository import GObject from gi.repository import PackageKitGlib as packagekit from softwarecenter.enums import TransactionTypes from softwarecenter.backend.transactionswatcher import ( BaseTransactionsWatcher, BaseTransaction, TransactionFinishedResult, TransactionProgress ) from softwarecenter.backend.installbackend import InstallBackend # 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 """ BaseTransaction.__init__(self) self._trans = trans self._setup_signals() def _setup_signals(self): """ Connect signals to the PkProgress from libpackagekitlib, 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') # 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) 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)) @property def tid(self): return self._trans.get_property('transaction-id') @property def status_details(self): 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.role_enum_to_localised_present(role)) def get_status_description(self, status=None): if status is None: status = self._trans.get_property('status') return packagekit.info_enum_to_localised_present(status) def is_waiting(self): """ return true if a time consuming task is taking place """ #LOG.debug('is_waiting ' + str(self._trans.get_property('status'))) status = self._trans.get_property('status') return status == packagekit.StatusEnum.WAIT or \ status == packagekit.StatusEnum.LOADING_CACHE or \ status == packagekit.StatusEnum.SETUP def is_downloading(self): #LOG.debug('is_downloading ' + str(self._trans.get_property('status'))) status = self._trans.get_property('status') return status == packagekit.StatusEnum.DOWNLOAD or \ (status >= packagekit.StatusEnum.DOWNLOAD_REPOSITORY and status <= packagekit.StatusEnum.DOWNLOAD_UPDATEINFO) def cancel(self): proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid) trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction') trans.Cancel() def _remove(self): """ delete transaction from _tlist """ # also notify pk install backend, so that this transaction gets removed # from pending_transactions self.emit('deleted') if self.tid in PackagekitTransactionsWatcher._tlist.keys(): del PackagekitTransactionsWatcher._tlist[self.tid] LOG.debug("Delete transaction %s" % self.tid) class PackagekitTransactionsWatcher(BaseTransactionsWatcher): _tlist = {} def __init__(self): super(PackagekitTransactionsWatcher, self).__init__() self.client = packagekit.Client() bus = dbus.SystemBus() proxy = bus.get_object('org.freedesktop.PackageKit', '/org/freedesktop/PackageKit') daemon = dbus.Interface(proxy, 'org.freedesktop.PackageKit') daemon.connect_to_signal("TransactionListChanged", self._on_transactions_changed) queued = daemon.GetTransactionList() self._on_transactions_changed(queued) def _on_transactions_changed(self, queued): if len(queued) > 0: current = queued[0] queued = queued[1:] if len(queued) > 1 else [] else: current = None self.emit("lowlevel-transactions-changed", current, queued) def add_transaction(self, tid, trans): """ return a tuple, (transaction, is_new) """ if tid not in PackagekitTransactionsWatcher._tlist.keys(): LOG.debug("Trying to setup %s" % tid) if not trans: trans = self.client.get_progress(tid, None) trans = PackagekitTransaction(trans) LOG.debug("Add return new transaction %s %s" % (tid, trans)) PackagekitTransactionsWatcher._tlist[tid] = trans return (trans, True) return (PackagekitTransactionsWatcher._tlist[tid], False) def get_transaction(self, tid): if tid not in PackagekitTransactionsWatcher._tlist.keys(): trans, new = self.add_transaction(tid, None) return trans return PackagekitTransactionsWatcher._tlist[tid] class PackagekitBackend(GObject.GObject, InstallBackend): __gsignals__ = {'transaction-started': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (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, 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,)), # the number/names of the available channels changed # FIXME: not emitted. 'channels-changed': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (bool,)), } def __init__(self): GObject.GObject.__init__(self) InstallBackend.__init__(self) # transaction details for setting as meta self.new_pkgname, self.new_appname, self.new_iconname = '', '', '' # this is public exposed self.pending_transactions = {} self.client = packagekit.Client() self.pkginfo = get_pkg_info() self.pkginfo.open() self._transactions_watcher = PackagekitTransactionsWatcher() self._transactions_watcher.connect('lowlevel-transactions-changed', self._on_lowlevel_transactions_changed) def upgrade(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None): pass # FIXME implement it def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): self.remove_multiple( (app,), (iconname,), addons_install, addons_remove, metadata) def remove_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): pkgnames = [app.pkgname for app in apps] appnames = [app.appname for app in apps] # keep track of pkg, app and icon for setting them as meta 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 self._on_progress_changed, None, # progress data self._on_remove_ready, # callback ready None # callback data ) 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 else: self.install_multiple( (app,), (iconname,), addons_install, addons_remove, metadata) def install_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): pkgnames = [app.pkgname for app in apps] appnames = [app.appname for app in apps] # keep track of pkg, app and icon for setting them as meta self.new_pkgname = pkgnames[0] self.new_appname = appnames[0] self.new_iconname = iconnames[0] # temporary hack pkgnames = self._fix_pkgnames(pkgnames) LOG.debug("Installing multiple packages: " + str(pkgnames)) # FIXME we set the only_trusted flag, which will prevent # 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 pkgnames, None, # cancellable self._on_progress_changed, None, # progress data self._on_install_ready, # GAsyncReadyCallback None # ready data ) 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 def _on_transaction_deleted(self, trans): name = trans.meta_data.get('sc_pkgname', '') if name in self.pending_transactions: del self.pending_transactions[name] LOG.debug("Deleted transaction " + name) else: LOG.error("Could not delete: " + name + str(trans)) # this is needed too self.emit('transactions-changed', self.pending_transactions) # also hack PackagekitInfo cache so that it emits a cache-ready signal if hasattr(self.pkginfo, '_reset_cache'): self.pkginfo._reset_cache(name) def _on_progress_changed(self, progress, ptype, data=None): """ de facto callback on transaction's progress change """ tid = progress.get_property('transaction-id') status = progress.get_property('status') if not tid: LOG.debug("Progress without transaction") return trans, new = self._transactions_watcher.add_transaction(tid, progress) if new: trans.connect('deleted', self._on_transaction_deleted) LOG.debug("new transaction" + str(trans)) # should add it to pending_transactions, but # i cannot get the pkgname here trans.meta_data['sc_appname'] = self.new_appname trans.meta_data['sc_pkgname'] = self.new_pkgname trans.meta_data['sc_iconname'] = self.new_iconname 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'))) if status == packagekit.StatusEnum.FINISHED: LOG.debug("Transaction finished %s" % tid) 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)) if ptype == packagekit.ProgressType.PACKAGE: # this should be done better # mvo: why getting package here at all? #package = progress.get_property('package') # fool sc UI about the name change trans.emit('role-changed', packagekit.RoleEnum.LAST) if ptype == packagekit.ProgressType.PERCENTAGE: pkgname = trans.meta_data.get('sc_pkgname', '') prog = progress.get_property('percentage') if prog >= 0: self.emit("transaction-progress-changed", pkgname, prog) else: self.emit("transaction-progress-changed", pkgname, 0) def _on_lowlevel_transactions_changed(self, watcher, current, pending): # update self.pending_transactions self.pending_transactions.clear() for tid in [current] + pending: if not tid: continue trans = self._transactions_watcher.get_transaction(tid) trans_progress = TransactionProgress(trans) try: self.pending_transactions[ trans_progress.pkgname] = trans_progress except: self.pending_transactions[trans.tid] = trans_progress self.emit('transactions-changed', self.pending_transactions) def _on_install_ready(self, source, result, data=None): LOG.debug("install done %s %s", source, result) def _on_remove_ready(self, source, result, data=None): LOG.debug("remove done %s %s", source, result) def _fix_pkgnames(self, pkgnames): is_pk_id = lambda a: ';' in a res = [] for p in pkgnames: if not is_pk_id(p): candidate = self.pkginfo[p].candidate p = candidate.package.get_id() res.append(p) return res if __name__ == "__main__": package = 'firefox' loop = dbus.mainloop.glib.DBusGMainLoop() dbus.set_default_main_loop(loop) backend = PackagekitBackend() pkginfo = get_pkg_info() if pkginfo[package].is_installed: backend.remove(package, package, '') backend.install(package, package, '') else: backend.install(package, package, '') backend.remove(package, package, '') from gi.repository import Gtk Gtk.main() #print backend._fix_pkgnames(('cheese',)) software-center-13.10/softwarecenter/backend/installbackend_impl/__init__.py0000664000202700020270000000000012151440100027556 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/installbackend_impl/aptd.py0000664000202700020270000013402412216063163027001 0ustar dobeydobey00000000000000# Copyright (C) 2009-2010 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 import apt_pkg import dbus import logging import os import re import traceback from subprocess import ( Popen, PIPE, ) from gi.repository import GObject, GLib from softwarecenter.utils import ( sources_filename_from_ppa_entry, release_filename_in_lists_from_deb_line, obfuscate_private_ppa_details, utf8, ) from softwarecenter.enums import ( TransactionTypes, PURCHASE_TRANSACTION_ID, ) from softwarecenter.paths import APPORT_RECOVERABLE_ERROR from aptdaemon import client from aptdaemon import enums from aptdaemon import errors from aptsources.sourceslist import SourceEntry from aptdaemon import policykit1 from defer import inline_callbacks, return_value from softwarecenter.db.application import Application from softwarecenter.backend.transactionswatcher import ( BaseTransactionsWatcher, BaseTransaction, TransactionFinishedResult, TransactionProgress, ) from softwarecenter.backend.installbackend import InstallBackend from gettext import gettext as _ # 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 dbus_address = os.environ["SOFTWARE_CENTER_APTD_FAKE"] if dbus_address in buses: return buses[dbus_address] bus = buses[dbus_address] = dbus.bus.BusConnection(dbus_address) else: bus = dbus.SystemBus() return bus class FakePurchaseTransaction(object): def __init__(self, app, iconname): self.pkgname = app.pkgname self.appname = app.appname self.iconname = iconname self.progress = 0 self.tid = PURCHASE_TRANSACTION_ID class AptdaemonTransaction(BaseTransaction): def __init__(self, trans): self._trans = trans @property def tid(self): return self._trans.tid @property def status_details(self): return self._trans.status_details @property def meta_data(self): return self._trans.meta_data @property def cancellable(self): return self._trans.cancellable @property def progress(self): return self._trans.progress def get_role_description(self, role=None): role = self._trans.role if role is None else role return enums.get_role_localised_present_from_enum(role) def get_status_description(self, status=None): status = self._trans.status if status is None else status return enums.get_status_string_from_enum(status) def is_waiting(self): return self._trans.status == enums.STATUS_WAITING_LOCK def is_downloading(self): return self._trans.status == enums.STATUS_DOWNLOADING def cancel(self): return self._trans.cancel() def connect(self, signal, handler, *args): """ append the real handler to the arguments """ args = args + (handler, ) return self._trans.connect(signal, self._handler, *args) def _handler(self, trans, *args): """ translate trans to BaseTransaction type. call the real handler after that """ real_handler = args[-1] args = tuple(args[:-1]) if isinstance(trans, client.AptTransaction): trans = AptdaemonTransaction(trans) return real_handler(trans, *args) class AptdaemonTransactionsWatcher(BaseTransactionsWatcher): """ 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 """ def __init__(self): super(AptdaemonTransactionsWatcher, self).__init__() # watch the daemon exit and (re)register the signal bus = get_dbus_bus() self._owner_watcher = bus.watch_name_owner( "org.debian.apt", self._register_active_transactions_watch) def _register_active_transactions_watch(self, connection): #print "_register_active_transactions_watch", connection bus = get_dbus_bus() apt_daemon = client.get_aptdaemon(bus=bus) apt_daemon.connect_to_signal("ActiveTransactionsChanged", self._on_transactions_changed) current, queued = apt_daemon.GetActiveTransactions() self._on_transactions_changed(current, queued) def _on_transactions_changed(self, current, queued): self.emit("lowlevel-transactions-changed", current, queued) def get_transaction(self, tid): """ synchronously return a transaction """ try: trans = client.get_transaction(tid) return AptdaemonTransaction(trans) except dbus.DBusException: pass class AptdaemonBackend(GObject.GObject, InstallBackend): """ software center specific code that interacts with aptdaemon """ __gsignals__ = {'transaction-started': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (str, str, str, str)), # emits a TransactionFinished object 'transaction-finished': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT, )), # emits a TransactionFinished object 'transaction-stopped': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,)), # emits a TransactionFinished object 'transaction-cancelled': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,)), # emits with a pending_transactions list object 'transactions-changed': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT, )), # emits pkgname, percent '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,)), # cache reload emits this specific signal as well 'reload-finished': (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT, bool,)), } LICENSE_KEY_SERVER = "ubuntu-production" def __init__(self): GObject.GObject.__init__(self) InstallBackend.__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) # 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) # public methods def update_xapian_index(self): self._logger.debug("update_xapian_index") system_bus = get_dbus_bus() # axi is optional, so just do nothing if its not installed try: axi = dbus.Interface( 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) return axi.connect_to_signal("UpdateFinished", self._axi_finished) # we don't really care for updates at this point #axi.connect_to_signal("UpdateProgress", progress) try: # first arg is force, second update_only axi.update_async(True, False) except: self._logger.warning("could not update axi") @inline_callbacks def fix_broken_depends(self): trans = None try: trans = yield self.aptd_client.fix_broken_depends(defer=True) 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, trans) @inline_callbacks def fix_incomplete_install(self): trans = None try: trans = yield self.aptd_client.fix_incomplete_install(defer=True) 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, trans) # FIXME: upgrade add-ons here @inline_callbacks def upgrade(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): """ upgrade a single package """ pkgname = app.pkgname appname = app.appname trans = None 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) except Exception as error: self._on_trans_error(error, trans, pkgname) # broken # @inline_callbacks # def _simulate_remove_multiple(self, pkgnames): # try: # trans = yield self.aptd_client.remove_packages(pkgnames, # defer=True) # trans.connect("dependencies-changed", # self._on_dependencies_changed) # except Exception: # logging.exception("simulate_remove") # return_value(trans) # # def _on_dependencies_changed(self, *args): # print "_on_dependencies_changed", args # self.have_dependencies = True # # @inline_callbacks # def simulate_remove_multiple(self, pkgnames): # self.have_dependencies = False # trans = yield self._simulate_remove_multiple(pkgnames) # print trans # while not self.have_dependencies: # while gtk.events_pending(): # gtk.main_iteration() # time.sleep(0.01) @inline_callbacks def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): """ remove a single package """ pkgname = app.pkgname appname = app.appname trans = None 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) except Exception as error: self._on_trans_error(error, trans, pkgname) @inline_callbacks def remove_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): """ queue a list of packages for removal """ if metadatas is None: metadatas = [] for item in apps: metadatas.append(None) for app, iconname, metadata in zip(apps, iconnames, metadatas): yield self.remove(app, iconname, metadata) @inline_callbacks 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. """ trans = None pkgname = app.pkgname appname = app.appname # this will force aptdaemon to use the right archive suite on install if app.archive_suite: pkgname = "%s/%s" % (pkgname, app.archive_suite) try: if filename: # 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) yield trans.set_meta_data(sc_filename=filename, defer=True) else: install = [pkgname] + addons_install remove = addons_remove reinstall = remove = purge = upgrade = downgrade = [] trans = yield self.aptd_client.commit_packages( install, reinstall, remove, purge, upgrade, downgrade, defer=True) 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, trans, pkgname) @inline_callbacks def install_multiple(self, apps, iconnames, addons_install=[], addons_remove=[], metadatas=None): """ queue a list of packages for install """ if metadatas is None: metadatas = [] for item in apps: 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): """ install and remove add-ons """ pkgname = app.pkgname appname = app.appname trans = None try: install = addons_install remove = addons_remove reinstall = remove = purge = upgrade = downgrade = [] trans = yield self.aptd_client.commit_packages( install, reinstall, remove, purge, upgrade, downgrade, defer=True) 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, trans) @inline_callbacks def reload(self, sources_list=None, metadata=None): """ reload package list """ # 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 # sources.list.d file (LP: #666956) d = apt_pkg.config.find_dir("Dir::Etc::sourceparts") trans = None if (not sources_list or not os.path.exists(os.path.join(d, sources_list))): sources_list = "" try: trans = yield self.aptd_client.update_cache( sources_list=sources_list, defer=True) yield self._run_transaction(trans, None, None, None, metadata) except Exception as error: self._on_trans_error(error, trans) # note that the cache re-open will happen via the connected # "transaction-finished" signal @inline_callbacks def enable_component(self, component): self._logger.debug("enable_component: %s" % component) trans = None try: trans = yield self.aptd_client.enable_distro_component(component) # don't use self._run_transaction() here, to avoid sending unneeded # signals yield trans.run(defer=True) except Exception as error: self._on_trans_error(error, trans, component) return_value(None) # now update the cache yield self.reload() @inline_callbacks def enable_channel(self, channelfile): trans = None # read channel file and add all relevant lines for line in open(channelfile): line = line.strip() if not line: continue entry = SourceEntry(line) if entry.invalid: continue sourcepart = os.path.basename(channelfile) yield self.add_sources_list_entry(entry, sourcepart) keyfile = channelfile.replace(".list", ".key") if os.path.exists(keyfile): trans = yield self.aptd_client.add_vendor_key_from_file( keyfile, wait=True) # don't use self._run_transaction() here, to avoid sending # unneeded 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): # strip the keysize if "/" in keyid: keyid = keyid.split("/")[1] if not keyid.startswith("0x"): keyid = "0x%s" % keyid trans = None try: trans = yield self.aptd_client.add_vendor_key_from_keyserver( keyid, keyserver, defer=True) yield self._run_transaction(trans, None, None, None, metadata) except Exception as error: self._on_trans_error(error, trans) @inline_callbacks def add_sources_list_entry(self, source_entry, sourcepart=None): if isinstance(source_entry, basestring): entry = SourceEntry(source_entry) elif isinstance(source_entry, SourceEntry): entry = source_entry else: raise ValueError("Unsupported entry type %s" % type(source_entry)) if not sourcepart: sourcepart = sources_filename_from_ppa_entry(entry) args = (entry.type, entry.uri, entry.dist, entry.comps, "Added by software-center", sourcepart) trans = None try: trans = yield self.aptd_client.add_repository(*args, defer=True) yield self._run_transaction(trans, None, None, None) except errors.NotAuthorizedError, err: self._logger.error("add_repository: '%s'" % err) return_value(None) return_value(sourcepart) @inline_callbacks def authenticate_for_purchase(self): """ helper that authenticates with aptdaemon for a purchase operation """ bus = get_dbus_bus() name = bus.get_unique_name() action = policykit1.PK_ACTION_INSTALL_PURCHASED_PACKAGES flags = policykit1.CHECK_AUTH_ALLOW_USER_INTERACTION 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): """ add a license key for a purchase. """ self._logger.debug( "adding license_key for pkg '%s' of len: %i" % ( pkgname, len(license_key))) trans = None # HOME based license keys if license_key_path and license_key_path.startswith("~"): # check if its inside HOME and if so, just create it dest = os.path.expanduser(os.path.normpath(license_key_path)) dirname = os.path.dirname(dest) if not os.path.exists(dirname): os.makedirs(dirname) if not os.path.exists(dest): f = open(dest, "w") f.write(license_key) f.close() os.chmod(dest, 0600) else: self._logger.warn("license file '%s' already exists" % dest) else: # system-wide keys try: self._logger.info("adding license key for '%s'" % pkgname) trans = yield self.aptd_client.add_license_key( pkgname, license_key_oauth, self.LICENSE_KEY_SERVER) yield self._run_transaction(trans, None, None, None) except Exception as e: self._logger.error("add_license_key: '%s'" % e) @inline_callbacks def add_repo_add_key_and_install_app(self, deb_line, signing_key_id, app, iconname, license_key, 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._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.pkgname)) if purchase: # pre-authenticate try: yield self.authenticate_for_purchase() except: self._logger.exception("authenticate_for_purchase failed") self._clean_pending_purchases(app.pkgname) result = TransactionFinishedResult(None, False) result.pkgname = app.pkgname self.emit("transaction-stopped", result) return # done fake_trans = FakePurchaseTransaction(app, iconname) self.emit("transaction-started", app.pkgname, app.appname, fake_trans.tid, TransactionTypes.INSTALL) self.pending_purchases[app.pkgname] = fake_trans else: # FIXME: add authenticate_for_added_repo here pass # 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 "", } self._logger.info("add_sources_list_entry()") sourcepart = yield self.add_sources_list_entry(deb_line) trans_metadata['sc_add_repo_and_install_sources_list'] = sourcepart # metadata so that we know those the add-key and reload transactions # are part of a group self._logger.info("add_vendor_key_from_keyserver()") yield self.add_vendor_key_from_keyserver(signing_key_id, metadata=trans_metadata) self._logger.info("reload_for_commercial_repo()") yield self._reload_for_commercial_repo(app, trans_metadata, sourcepart) @inline_callbacks 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 """ trans_metadata["sc_add_repo_and_install_ignore_errors"] = "1" # and then queue the install only when the reload finished # 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, trans_metadata, app) # reload to ensure we have the new package data yield self.reload(sources_list=sources_list, metadata=trans_metadata) def _reload_for_commercial_repo(self, app, trans_metadata, sources_list): """ this reloads a commercial repo in a glib timeout See _reload_for_commercial_repo_inline() for the actual work that is done """ self._logger.info("_reload_for_commercial_repo() %s" % app) # trigger inline_callbacked function self._reload_for_commercial_repo_defer( app, trans_metadata, sources_list) # return False to stop the timeout (one shot only) return False @inline_callbacks 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)) # 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): 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"] 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" 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 if not os.path.exists(release_signature): self._logger.warn("no %s found, re-trying" % release_signature) result = False # disconnect again, this is only a one-time operation self.disconnect_by_func( self._on_reload_for_add_repo_and_install_app_finished) # FIXME: this logic will *fail* if the sources.list of the user # was broken before # 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 # self.install(app, "", metadata=metadata) # 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( [app.pkgname], defer=True) self._logger.info("run_transaction()") # notify about the install so that the unity-launcher # integration works self.emit("transaction-started", app.pkgname, app.appname, trans.tid, TransactionTypes.INSTALL) yield self._run_transaction(trans, app.pkgname, app.appname, "", metadata) except Exception as error: self._on_trans_error(error, trans, app.pkgname) # add license_key # FIXME: aptd fails if there is a license_key_path already # 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, app.pkgname) else: # download failure # ok, here is the fun! we can not reload() immediately, because # there is a delay of up to 5(!) minutes between s-c-agent telling # us that we can download software and actually being able to # download it retry = int(trans.meta_data['sc_add_repo_and_install_try']) if retry > 10: self._logger.error("failed to add repo after 10 tries") self._clean_pending_purchases( trans.meta_data['sc_add_repo_and_install_pkgname']) self._show_transaction_failed_dialog(trans, result) return_value(False) # this just sets the meta_data locally, but that is ok, the # 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"] GLib.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) if self._progress_signal: GLib.source_remove(self._progress_signal) self._progress_signal = None # attach progress-changed signal for current transaction if current: try: trans = client.get_transaction(current) self._progress_signal = trans.connect("progress-changed", self._on_progress_changed) except dbus.DBusException: pass # now update pending transactions self.pending_transactions.clear() for tid in [current] + pending: if not tid: continue try: 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 except KeyError: # if its not a transaction from us (sc_pkgname) still # add it with the tid as key to get accurate results # (the key of pending_transactions is never directly # exposed in the UI) self.pending_transactions[trans.tid] = trans_progress # emit signal 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 of FakePurchaseTransaction into the transactions dict for pkgname in self.pending_purchases: 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 (only showing pkg progress currently) """ try: pkgname = trans.meta_data["sc_pkgname"] self.pending_transactions[pkgname].progress = progress self.emit("transaction-progress-changed", pkgname, progress) except KeyError: pass def _show_transaction_failed_dialog(self, trans, enum, alternative_action=None): # daemon died are messages that result from broken # cancel handling in aptdaemon (LP: #440941) # FIXME: this is not a proper fix, just a workaround if trans.error_code == enums.ERROR_DAEMON_DIED: self._logger.warn("daemon dies, ignoring: %s %s" % (trans, enum)) 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) msg = utf8("%s: %s\n%s\n\n%s") % ( utf8(_("Error")), utf8(enums.get_error_string_from_enum(trans.error_code)), utf8(enums.get_error_description_from_enum(trans.error_code)), utf8(cleaned_error_details)) 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 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)) 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" % ( trans, enum, trans.meta_data)) # first check if there has been a cancellation of # the install and fire a transaction-cancelled signal # (see LP: #1027209) if enum == enums.EXIT_CANCELLED: result = TransactionFinishedResult(trans, False) self.emit("transaction-cancelled", result) return # 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 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 functionality elif (trans.error and 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 # Finish a cancelled installation before resuming. If the # user e.g. rebooted during a debconf question apt # will hang and the user is required to call # dpkg --configure -a, see LP#659438 elif (trans.error and trans.error.code == enums.ERROR_INCOMPLETE_INSTALL): action = _("Repair") res = self._show_transaction_failed_dialog(trans, enum, action) if res == "yes": self.fix_incomplete_install() 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 # dbus mangles a None to a str("None") pkgname = "" try: pkgname = trans.meta_data["sc_pkgname"] del self.pending_transactions[pkgname] self.emit("transaction-progress-changed", pkgname, 100) except KeyError: pass # if it was a cache-reload, trigger a-x-i update if trans.role == enums.ROLE_UPDATE_CACHE: if enum == enums.EXIT_SUCCESS: self.update_xapian_index() 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)) @inline_callbacks def _config_file_conflict(self, transaction, old, new): reply = self.ui.ask_config_file_conflict(old, new) if reply == "replace": yield transaction.resolve_config_file_conflict(old, "replace", defer=True) elif reply == "keep": yield transaction.resolve_config_file_conflict(old, "keep", defer=True) else: raise Exception( "unknown reply: '%s' in _ask_config_file_conflict " % reply) @inline_callbacks def _medium_required(self, transaction, medium, drive): res = self.ui.ask_medium_required(medium, drive) if res: yield transaction.provide_medium(medium, defer=True) else: yield transaction.cancel(defer=True) @inline_callbacks def _run_transaction(self, trans, pkgname, appname, iconname, metadata=None): # connect signals trans.connect("config-file-conflict", self._config_file_conflict) trans.connect("medium-required", self._medium_required) trans.connect("finished", self._on_trans_finished) try: # set appname/iconname/pkgname only if we actually have one if appname: yield trans.set_meta_data(sc_appname=appname, defer=True) if iconname: yield trans.set_meta_data(sc_iconname=iconname, defer=True) # we do not always have a pkgname, e.g. "cache_update" does not if pkgname: # ensure the metadata is just the pkgname sc_pkgname = pkgname.split("/")[0].split("=")[0] yield trans.set_meta_data(sc_pkgname=sc_pkgname, defer=True) # 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.pending_transactions[pkgname] = TransactionProgress(trans) # generic metadata if metadata: yield trans.set_meta_data(defer=True, **metadata) yield trans.run(defer=True) except Exception as error: self._on_trans_error(error, trans, pkgname) # on error we need to clean the pending purchases self._clean_pending_purchases(pkgname) # on success the pending purchase is cleaned when the package # that was purchased finished installing if trans.role == enums.ROLE_INSTALL_PACKAGES: self._clean_pending_purchases(pkgname) def _clean_pending_purchases(self, pkgname): if pkgname and pkgname in self.pending_purchases: del self.pending_purchases[pkgname] def _on_trans_error(self, error, trans, pkgname=None): self._logger.warn("_on_trans_error: '%r' '%s' '%s'" % ( error, trans, pkgname)) # re-enable the action button again if anything went wrong result = TransactionFinishedResult(None, False) result.pkgname = pkgname # clean up pending transactions if pkgname and pkgname in self.pending_transactions: del self.pending_transactions[pkgname] # calculate a dupes_signature here to have different buckets # on errors.ubuntu.com for the different crash types if trans: error_code = trans.error_code else: error_code = "no-transaction" dupes_signature = "software-center:trans-failed:%s" % error_code self.emit("transaction-stopped", result) if isinstance(error, dbus.DBusException): # ignore errors that the user knows about already (like # that he entered a wrong password or that he does not # care about (like no-reply) name = error.get_dbus_name() if name in ["org.freedesktop.PolicyKit.Error.NotAuthorized", "org.freedesktop.DBus.Error.NoReply"]: return # we want to give some advice here to the user but also # report this via apport if name in ["org.freedesktop.PolicyKit.Error.Failed"]: summary = _("Authentication Error") text = _("Software can't be installed or removed because " "the authentication service is not available. " "(%s") % error # send to apport for reporting self._call_apport_recoverable_error( text, error, dupes_signature) # ... and display as a dialog self.ui.error(None, summary, text) return # lintian errors are ignored and not send to apport_recoverable_error # and dpkg errors as well as they will already be recorded separately # by apt itself if error_code in (enums.ERROR_INVALID_PACKAGE_FILE, enums.ERROR_PACKAGE_MANAGER_FAILED): return # show a apport recoverable error dialog to the user as we want # to know about these issues self._call_apport_recoverable_error( _("There was an error submitting the transaction"), error, dupes_signature) def _call_apport_recoverable_error(self, text, error, dupes_signature): """Call apport's recoverable_problem dialog """ # ensure we have a proper exception string in the report if isinstance(error, Exception): error = traceback.format_exc(error) # mvo: I don't think we need to send "Package\0software-center", # apport should figure this out itself data = ("DialogBody\0%(text)s\0" "Traceback\0%(error)s\0" "DuplicateSignature\0%(dupes_signature)s" % { 'text': text, 'error': error, 'dupes_signature': dupes_signature, }) # This will be quick as it just writes the report file. Then # the report gets picked up asynchronously by a inotify watch # and displayed to the user in a separate process. p = Popen( [APPORT_RECOVERABLE_ERROR], stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout, stderr) = p.communicate(input=data) if p.returncode != 0: logging.warn("%s returned '%s' ('%s', '%s')" % ( APPORT_RECOVERABLE_ERROR, p.returncode, stdout, stderr)) if __name__ == "__main__": #c = client.AptClient() #c.remove_packages(["4g8"], remove_unused_dependencies=True) backend = AptdaemonBackend() #backend.reload() backend.enable_component("multiverse") from gi.repository import Gtk Gtk.main() software-center-13.10/softwarecenter/backend/oneconfhandler/0000755000202700020270000000000012224614354024462 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/oneconfhandler/__init__.py0000664000202700020270000000247612151440100026567 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Canonical # # Authors: # Didier Roche # # 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 # singleton oneconf_handler = None def get_oneconf_handler(oneconfviewpickler=None): global oneconf_handler try: from softwarecenter.backend.oneconfhandler.core import OneConfHandler except ImportError: return None if oneconf_handler is None and oneconfviewpickler: oneconf_handler = OneConfHandler(oneconfviewpickler) return oneconf_handler def is_oneconf_available(): try: from softwarecenter.backend.oneconfhandler.core import OneConfHandler OneConfHandler # pyflakes return True except ImportError: pass return False software-center-13.10/softwarecenter/backend/oneconfhandler/core.py0000664000202700020270000001712212151440100025752 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Canonical # # Authors: # Didier Roche # # 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 oneconf.dbusconnect import DbusConnect from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY from softwarecenter.backend.login import get_login_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, GLib import logging from gettext import gettext as _ 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,), ), } def __init__(self, oneconfviewpickler): '''Controller of the installed pane''' LOG.debug("OneConf Handler init") super(OneConfHandler, self).__init__() # OneConf stuff self.oneconf = DbusConnect() self.oneconf.hosts_dbus_object.connect_to_signal('hostlist_changed', self.refresh_hosts) self.oneconf.hosts_dbus_object.connect_to_signal('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.already_registered_hostids = [] self.is_current_registered = False self.oneconfviewpickler = oneconfviewpickler # refresh host list self._refreshing_hosts = False GLib.timeout_add_seconds(MIN_TIME_WITHOUT_ACTIVITY, self.get_latest_oneconf_sync) GLib.idle_add(self.refresh_hosts) def refresh_hosts(self): """refresh hosts list in the panel view""" LOG.debug('oneconf: refresh hosts') # this function can be called in different threads if self._refreshing_hosts: return self._refreshing_hosts = True #view_switcher = self.app.view_switcher #model = view_switcher.get_model() #previous_iter = model.installed_iter all_hosts = self.oneconf.get_all_hosts() for hostid in all_hosts: current, hostname, share_inventory = all_hosts[hostid] if not hostid in self.already_registered_hostids and not current: self.oneconfviewpickler.register_computer(hostid, hostname) 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') the_daybefore = today - datetime.timedelta(days=1) if last_sync > today: msg = _("Last sync %s") % last_sync.strftime('%H:%M') 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') except (TypeError, ValueError): 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 ''' if share_inventory == self.is_current_registered: return self.is_current_registered = 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 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 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\nNo-one else will be able to see " "what you have installed.") self.sso = get_login_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 """ 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 # login (once) if its expired self.ssoapi.whoami() def _whoami_done(self, ssologin, result): logging.debug("_whoami_done") self._share_inventory(True) def _whoami_error(self, ssologin, e): logging.error("whoami error '%s'" % e) self._share_inventory(False) software-center-13.10/softwarecenter/backend/installbackend.py0000664000202700020270000000614012151440100025011 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 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): pass def remove(self, app, iconname, addons_install=[], addons_remove=[], metadata=None): pass 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): pass 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): 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 "replace" to replace the old with the new one """ raise UnimplementedError("need custom ask_config_file_conflict method") def ask_medium_required(self, medium, drive): """ ask the user to provide a medium in drive 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): """ 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) install_backend = AptdaemonBackend() else: from softwarecenter.backend.installbackend_impl.packagekitd \ import PackagekitBackend install_backend = PackagekitBackend() return install_backend software-center-13.10/softwarecenter/backend/fake_review_settings.py0000664000202700020270000001730112151440100026243 0ustar dobeydobey00000000000000 import time import os import pickle from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR # decorator to add a fake network delay if set # in FakeReviewSettings.fake_network_delay def network_delay(fn): def slp(self, *args, **kwargs): fake_settings = FakeReviewSettings() delay = fake_settings.get_setting('fake_network_delay') if delay: 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 running the unit test. ''' _FAKE_SETTINGS = {} #general settings #***************************** #delay (in seconds) before returning from any of the fake rnr methods #useful for emulating real network timings (use None for no delays) _FAKE_SETTINGS['fake_network_delay'] = 2 #server status #***************************** #raises APIError if True _FAKE_SETTINGS['server_response_error'] = False #review stats #***************************** #raises APIError if True _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) _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 _FAKE_SETTINGS['review_pages'] = 1 #the following has no effect if get_reviews_error = True #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 #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 _FAKE_SETTINGS['flag_review_error'] = False #fake username(str) to give back as 'flagger' _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 #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] #THE FOLLOWING SETTINGS RELATE TO LOGIN SSO FUNCTIONALITY # LoginBackendDbusSSO # login() #*********************** # 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 # choices (strings): "whoami", "error" _FAKE_SETTINGS['whoami_response'] = "whoami" #this only has effect if whoami_response = 'whoami' #determines the username to return in a successful whoami #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''' 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() 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). 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) 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''' for key, value in settings.items(): if not key in self._FAKE_SETTINGS: 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''' if not key_name in self._FAKE_SETTINGS: 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") return def _save_settings(self): """write the dict out to cache file""" try: if not os.path.exists(SOFTWARE_CENTER_CACHE_DIR): os.makedirs(SOFTWARE_CENTER_CACHE_DIR) pickle.dump(self._FAKE_SETTINGS, open(self.LOCATION, "w")) return True except: return False software-center-13.10/softwarecenter/backend/weblive_pristine.py0000664000202700020270000002165612151440100025416 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2010-2011 Stephane Graber # # 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; either version 2 # of the License, or (at your option) any later version. # # 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. # # taken from # lp:~weblive-dev/weblive/trunk/client/weblive.py # and put into weblive_pristine.py until a ubuntu package is in main import json import urllib import urllib2 class WebLiveJsonError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class WebLiveError(Exception): def __init__(self, value): self.value = value 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): self.name = name self.title = title self.description = description self.timelimit = timelimit self.userlimit = userlimit self.current_users = users 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) class WebLiveEverythingServer(WebLiveServer): 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) 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)) 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) 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()) except ValueError: raise WebLiveJsonError("Returned json object is invalid.") if reply['status'] != 'ok': if reply['message'] == -1: raise WebLiveJsonError("Missing 'action' field in query.") elif reply['message'] == -2: raise WebLiveJsonError("Missing parameter") elif reply['message'] == -3: raise WebLiveJsonError("Function '%s' isn't exported " "over JSON." % query['action']) else: 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) if not isinstance(reply['message'], list): if reply['message'] == 1: raise WebLiveError("Reached user limit, return false.") elif reply['message'] == 2: raise WebLiveError("Different user with same username " "already exists.") elif reply['message'] == 3: raise WebLiveError("Invalid fullname, must only contain " "alphanumeric characters and spaces.") elif reply['message'] == 4: raise WebLiveError("Invalid login, must only contain " "lowercase letters.") elif reply['message'] == 5: raise WebLiveError("Invalid password, must contain only " "alphanumeric characters.") elif reply['message'] == 7: raise WebLiveError("Invalid server: %s" % serverid) else: raise WebLiveError("Unknown error code: %s" % reply['message']) return reply['message'] def list_everything(self): query = {} query['action'] = 'list_everything' reply = self.do_query(query) if not isinstance(reply['message'], dict): raise WebLiveError("Invalid value, expected '%s' and got '%s'." % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: servers = [] for server in reply['message']: attr = reply['message'][server] servers.append(WebLiveEverythingServer( server, attr['title'], attr['description'], attr['timelimit'], attr['userlimit'], attr['users'], attr['autoinstall'], attr['locales'], attr['packages'])) return servers def list_locales(self, serverid): query = {} query['action'] = 'list_locales' query['serverid'] = serverid reply = self.do_query(query) if not isinstance(reply['message'], list): raise WebLiveError("Invalid value, expected '%s' and got '%s'." % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: 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) if not isinstance(reply['message'], list): raise WebLiveError("Invalid value, expected '%s' and got '%s'." % (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) if not isinstance(reply['message'], list): raise WebLiveError("Invalid value, expected '%s' and got '%s'." % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: 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) if not isinstance(reply['message'], dict): raise WebLiveError("Invalid value, expected '%s' and got '%s'." % (type({}), type(reply['message']))) if not self.as_object: return reply['message'] else: servers = [] for server in reply['message']: attr = reply['message'][server] servers.append(WebLiveServer( server, attr['title'], attr['description'], attr['timelimit'], attr['userlimit'], attr['users'], attr['autoinstall'])) return servers software-center-13.10/softwarecenter/backend/zeitgeist_logger.py0000644000202700020270000000737712216063163025430 0ustar dobeydobey00000000000000# Copyright (C) 2013 Canonical # # Authors: # Marco Trevisan # # 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 from softwarecenter.utils import get_desktop_id LOG = logging.getLogger(__name__) APPLICATION_URI_PREFIX = "application://" HAVE_MODULE = False try: from zeitgeist.client import ZeitgeistClient from zeitgeist import datamodel as ZeitgeistDataModel from zeitgeist.datamodel import (Event as ZeitgeistEvent, Subject as ZeitgeistSubject) HAVE_MODULE = True except ImportError: LOG.warn("Support for Zeitgeist disabled") class ZeitgeistLogger(object): def __init__(self, distro): self.distro = distro def __create_user_event(self): event = ZeitgeistEvent() event.actor = APPLICATION_URI_PREFIX + self.distro.get_app_id() + ".desktop" event.manifestation = ZeitgeistDataModel.Manifestation.EVENT_MANIFESTATION.USER_ACTIVITY return event def __create_app_subject(self, desktop_file): subject = ZeitgeistSubject() subject.interpretation = ZeitgeistDataModel.Interpretation.SOFTWARE subject.manifestation = ZeitgeistDataModel.Manifestation.SOFTWARE_ITEM subject.uri = APPLICATION_URI_PREFIX + get_desktop_id(desktop_file); subject.current_uri = subject.uri subject.mimetype = "application/x-desktop" return subject def log_install_event(self, desktop_file): """Logs an install event on Zeitgeist""" if not HAVE_MODULE: LOG.warn("No zeitgeist support, impossible to log event") return False if not desktop_file or not len(desktop_file): LOG.warn("Invalid desktop file provided, impossible to log event") return False subject = self.__create_app_subject(desktop_file) subject.text = "Installed with " + self.distro.get_app_name() event = self.__create_user_event() event.interpretation = ZeitgeistDataModel.Interpretation.EVENT_INTERPRETATION.CREATE_EVENT event.append_subject(subject) ZeitgeistClient().insert_event(event) subject.text = "Accessed by " + self.distro.get_app_name() event = self.__create_user_event() event.interpretation = ZeitgeistDataModel.Interpretation.EVENT_INTERPRETATION.ACCESS_EVENT event.append_subject(subject) ZeitgeistClient().insert_event(event) return True def log_uninstall_event(self, desktop_file): """Logs an uninstall event on Zeitgeist""" if not HAVE_MODULE: LOG.warn("No zeitgeist support, impossible to log event") return False if not desktop_file or not len(desktop_file): LOG.warn("Invalid desktop file provided, impossible to log event") return False subject = self.__create_app_subject(desktop_file) subject.text = "Uninstalled with " + self.distro.get_app_name() event = self.__create_user_event() event.interpretation = ZeitgeistDataModel.Interpretation.EVENT_INTERPRETATION.DELETE_EVENT event.append_subject(subject) ZeitgeistClient().insert_event(event) return True software-center-13.10/softwarecenter/backend/transactionswatcher.py0000664000202700020270000001032412151440100026120 0ustar dobeydobey00000000000000# 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 class BaseTransaction(GObject.GObject): """ wrapper class for install backend dbus Transaction objects """ __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, 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, []), } @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 def get_role_description(self, role=None): pass def get_status_description(self, status=None): pass def is_waiting(self): return False def is_downloading(self): return False def cancel(self): pass class BaseTransactionsWatcher(GObject.GObject): """ 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)), } def get_transaction(self, tid): """ should return a _Transaction object """ pass class TransactionFinishedResult(object): """ represents the result of a transaction """ def __init__(self, trans, success): self.success = success if trans: self.pkgname = trans.meta_data.get("sc_pkgname") self.meta_data = trans.meta_data else: self.pkgname = None self.meta_data = None class TransactionProgress(object): """ represents the progress of the transaction """ def __init__(self, trans): self.pkgname = trans.meta_data.get("sc_pkgname") self.meta_data = trans.meta_data self.progress = trans.progress # 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) _tw = AptdaemonTransactionsWatcher() else: from softwarecenter.backend.installbackend_impl.packagekitd \ import PackagekitTransactionsWatcher _tw = PackagekitTransactionsWatcher() return _tw software-center-13.10/softwarecenter/backend/piston/0000755000202700020270000000000012224614354023011 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/piston/sreclient_pristine.py0000664000202700020270000000636212151440100027262 0ustar dobeydobey00000000000000from piston_mini_client import (PistonAPI, returns_json) from piston_mini_client.validators import ( validate, validate_integer, validate_pattern, oauth_protected, ) # These are factored out as constants for if you need to work against a # server that doesn't support both schemes (like http-only dev servers) PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' class SoftwareCenterRecommenderAPI(PistonAPI): default_service_root = 'http://localhost:8000/api/1.0' @returns_json def server_status(self): return self._get('server-status/', scheme=PUBLIC_API_SCHEME) @oauth_protected @returns_json def profile(self, pkgnames): """Return True if a profile has already been uploaded.""" return self._get('profile/', scheme=AUTHENTICATED_API_SCHEME) @oauth_protected @returns_json def submit_profile(self, data): return self._post('profile/', data=data, scheme=AUTHENTICATED_API_SCHEME) @returns_json def submit_anon_profile(self, uuid, installed_packages, extra): data = { 'installed_packages': installed_packages, 'extra': extra, } return self._post('profile/%s/' % uuid, data=data, scheme=PUBLIC_API_SCHEME) @oauth_protected @validate_pattern('uuid', r'[\dabcdef]{32}') @returns_json def recommend_me(self, uuid): """Fetch personalized (authenticated) recommendations. We still need to submit an UUID to link to the anon profile if it was submitted. """ return self._get('recommend_me/%s/' % uuid, scheme=AUTHENTICATED_API_SCHEME) @validate_pattern('uuid', r'[\dabcdef]{32}') def anon_recommend_me(self, uuid): return self._get('recommend_me/%s/' % uuid, scheme=AUTHENTICATED_API_SCHEME) @validate_pattern('pkgname', '[^/]+') @returns_json def recommend_app(self, pkgname): return self._get('recommend_app/%s/' % pkgname, scheme=PUBLIC_API_SCHEME) @returns_json def recommend_all_apps(self): return self._get('recommend_all_apps/', scheme=PUBLIC_API_SCHEME) @returns_json def recommend_top(self): return self._get('recommend_top/', scheme=PUBLIC_API_SCHEME) @oauth_protected @validate_pattern('rid', '\w+') @validate_integer('feedback') @returns_json def feedback(self, rid, feedback): data = { 'feedback': feedback, 'rid': rid, } return self._post('feedback/', data=data, scheme=AUTHENTICATED_API_SCHEME) @oauth_protected @validate_pattern('pkgname', '[^/]+') @validate_pattern('action', '\w{3,20}') @returns_json def implicit_feedback(self, pkgname, action): data = { 'package_name': pkgname, 'action': action, } return self._post('implicit_feedback/', data=data, scheme=AUTHENTICATED_API_SCHEME) @oauth_protected @returns_json @validate('remove', bool) def remove_app(self, appname, remove): data = { 'app': appname, 'remove': remove, } return self._post('remove_app/', data=data, scheme=AUTHENTICATED_API_SCHEME) software-center-13.10/softwarecenter/backend/piston/rnrclient.py0000664000202700020270000000654412151440100025357 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # 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 # # # taken from lp:~rnr-developers/rnr-server/rnrclient and put into # rnrclient_pristine.py import logging import os import sys LOG = logging.getLogger(__name__) # useful for debugging if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ: import httplib2 httplib2.debuglevel = 1 # get the server to use from softwarecenter.distro import get_distro distro = get_distro() SERVER_ROOT = distro.REVIEWS_SERVER try: if "SOFTWARE_CENTER_FAKE_REVIEW_API" in os.environ: 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") else: # patch default_service_root from rnrclient_pristine import RatingsAndReviewsAPI RatingsAndReviewsAPI.default_service_root = SERVER_ROOT import rnrclient_pristine if "SOFTWARE_CENTER_FORCE_NON_SSL" in os.environ: LOG.warn("forcing transmission over NON ENCRYPTED CHANNEL") rnrclient_pristine.AUTHENTICATED_API_SCHEME = "http" except ImportError: LOG.error("need python-piston-mini client\n" "available in natty or from:\n" " ppa:software-store-developers/daily-build ") raise if __name__ == "__main__": import urllib # force stdout to be utf-8 import codecs sys.stdout = codecs.getwriter('utf8')(sys.stdout) # dump all reviews rnr = RatingsAndReviewsAPI() print(rnr.server_status()) # 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)) 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(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") print(reviews) 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")) software-center-13.10/softwarecenter/backend/piston/ubuntusso_pristine.py0000664000202700020270000000145412151440100027336 0ustar dobeydobey00000000000000from piston_mini_client import PistonAPI, returns_json from piston_mini_client.validators import oauth_protected # These are factored out as constants for if you need to work against a # server that doesn't support both schemes (like http-only dev servers) PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' # this is only here because: # a) ubuntu-sso-client does not support verifying if the credentials # are still valid # b) the restful client interface is not really needed because we just # need this one single call class UbuntuSsoAPI(PistonAPI): default_service_root = 'http://localhost:8000/api/2.0' @oauth_protected @returns_json def whoami(self, id=None): return self._get('accounts?ws.op=me', scheme=AUTHENTICATED_API_SCHEME) software-center-13.10/softwarecenter/backend/piston/__init__.py0000664000202700020270000000000012151440100025073 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/backend/piston/rnrclient_pristine.py0000664000202700020270000001543712151440100027275 0ustar dobeydobey00000000000000"""This module provides the RatingsAndReviewsAPI class for talking to the ratings and reviews API, plus a few helper classes. """ from urllib import quote_plus from piston_mini_client import ( PistonAPI, PistonResponseObject, PistonSerializable, returns, returns_json, returns_list_of, ) from piston_mini_client.validators import validate_pattern, validate # These are factored out as constants for if you need to work against a # server that doesn't support both schemes (like http-only dev servers) PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' class ReviewRequest(PistonSerializable): """A review request. Instantiate one of these objects to describe a new review you wish to submit, then pass it in as an argument to submit_review(). """ _atts = ('app_name', 'package_name', 'summary', 'version', 'review_text', 'rating', 'language', 'origin', 'distroseries', 'arch_tag') app_name = '' class ReviewsStats(PistonResponseObject): """A ratings summary for a package/app. This class will be automatically populated with JSON retrieved from the server. Each ReviewStats object will have the following fields: * package_name * app_name * ratings_total * ratings_average """ pass class ReviewDetails(PistonResponseObject): """A detailed review description. This class will be automatically populated with JSON retrieved from the server. Each ReviewDetails object will have the following fields: * id * package_name * app_name * language * date_created * rating * reviewer_username * summary * review_text * hide * version """ pass class RatingsAndReviewsAPI(PistonAPI): """A client for talking to the reviews and ratings API. If you pass no arguments into the constructor it will try to connect to localhost:8000 so you probably want to at least pass in the ``service_root`` constructor argument. """ default_service_root = 'http://localhost:8000/reviews/api/1.0' default_content_type = 'application/x-www-form-urlencoded' @returns_json def server_status(self): """Check the state of the server, to see if everything's ok.""" return self._get('server-status/', scheme=PUBLIC_API_SCHEME) @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) def review_stats(self, origin='any', distroseries='any', days=None, valid_days=(1, 3, 7)): """Fetch ratings for a particular distroseries""" url = 'review-stats/{0}/{1}/'.format(origin, distroseries) if days is not None: # the server only knows valid_days (1,3,7) currently for valid_day in valid_days: # pick the day from valid_days that is the next bigger than # days if days <= valid_day: url += 'updates-last-{0}-days/'.format(valid_day) break return self._get(url, scheme=PUBLIC_API_SCHEME) @validate_pattern('language', r'\w+', required=False) @validate_pattern('origin', r'[0-9a-z+-.:/]+', required=False) @validate_pattern('distroseries', r'\w+', required=False) @validate_pattern('version', r'[-\w+.:~]+', required=False) @validate_pattern('packagename', r'[a-z0-9.+-]+') @validate('appname', str, required=False) @validate('page', int, required=False) @validate('sort', str, required=False) @returns_list_of(ReviewDetails) def get_reviews(self, packagename, language='any', origin='any', distroseries='any', version='any', appname='', page=1, sort='helpful'): """Fetch ratings and reviews for a particular package name. If any of the optional arguments are provided, fetch reviews for that particular app, language, origin, distroseries or version. """ if appname: 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), scheme=PUBLIC_API_SCHEME) @validate('review_id', int) @returns(ReviewDetails) def get_review(self, review_id): """Fetch a particular review via its id.""" return self._get('reviews/%s/' % review_id) @validate('review', ReviewRequest) @returns(ReviewDetails) def submit_review(self, review): """Submit a rating/review.""" return self._post('reviews/', data=review, scheme=AUTHENTICATED_API_SCHEME, content_type='application/json') @validate('review_id', int) @validate_pattern('reason', r'[^\n]+') @validate_pattern('text', r'[^\n]+') @returns_json def flag_review(self, review_id, reason, text): """Flag a review as being inappropriate""" data = {'reason': reason, 'text': text, } return self._post('reviews/%s/flags/' % review_id, data=data, scheme=AUTHENTICATED_API_SCHEME) @validate('review_id', int) @validate_pattern('useful', 'True|False') @returns_json def submit_usefulness(self, review_id, useful): """Submit a usefulness vote.""" return self._post('/reviews/%s/recommendations/' % review_id, data={'useful': useful}, scheme=AUTHENTICATED_API_SCHEME) @validate('review_id', int, required=False) @validate_pattern('username', r'[^\n]+', required=False) @returns_json def get_usefulness(self, review_id=None, username=None): """Get a list of usefulness filtered by username/review_id""" if not username and not review_id: return None data = {} if username: data['username'] = username if review_id: data['review_id'] = str(review_id) return self._get('usefulness/', args=data, scheme=PUBLIC_API_SCHEME) @validate('review_id', int) @returns_json def delete_review(self, review_id): """Delete a review""" return self._post('/reviews/delete/%s/' % review_id, data={}, scheme=AUTHENTICATED_API_SCHEME) @validate('review_id', int) @validate('rating', int) @validate_pattern('summary', r'[^\n]+') @validate('review_text', str) @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 } return self._put('/reviews/modify/%s/' % review_id, data=data, scheme=AUTHENTICATED_API_SCHEME) software-center-13.10/softwarecenter/backend/piston/rnrclient_fake.py0000664000202700020270000003004112151440100026332 0ustar dobeydobey00000000000000"""This module provides the RatingsAndReviewsAPI class for talking to the ratings and reviews API, plus a few helper classes. """ from piston_mini_client import ( PistonAPI, returns, returns_json, returns_list_of, ) from piston_mini_client.validators import validate_pattern, validate from piston_mini_client.failhandlers import APIError # These are factored out as constants for if you need to work against a # server that doesn't support both schemes (like http-only dev servers) PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' from rnrclient_pristine import ReviewRequest, ReviewsStats, ReviewDetails 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. """ 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", "Bar Tender", "Baz Lightyear"] _SUMMARIES = ["Cool", "Medium", "Bad", "Too difficult"] _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): if self._fake_settings.get_setting('server_response_error'): 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)): 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)), 'histogram': None } stats.append(s) return json.dumps(stats) @validate_pattern('language', r'\w+', required=False) @validate_pattern('origin', r'[0-9a-z+-.:/]+', required=False) @validate_pattern('distroseries', r'\w+', required=False) @validate_pattern('version', r'[-\w+.:~]+', required=False) @validate_pattern('packagename', r'[a-z0-9.+-]+') @validate('appname', str, required=False) @validate('page', int, required=False) @validate('sort', str, required=False) @returns_list_of(ReviewDetails) @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 elif page == self._fake_settings.get_setting('review_pages') + 1: 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) @validate('review_id', int) @returns(ReviewDetails) @network_delay def get_review(self, review_id): if self._fake_settings.get_setting('get_review_error'): raise APIError(self._exception_msg) review = self._make_fake_reviews(single_id=review_id) return json.dumps(review) @validate('review', ReviewRequest) @returns(ReviewDetails) @network_delay 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) r = { "origin": review.origin, "rating": review.rating, "hide": False, "app_name": review.app_name, "language": review.language, "reviewer_username": user, "usefulness_total": 0, "usefulness_favorable": 0, "review_text": review.review_text, "date_deleted": None, "summary": review.summary, "version": review.version, "id": review_id, "date_created": time.strftime("%Y-%m-%d %H:%M:%S"), "reviewer_displayname": "Fake User", "package_name": review.package_name, "distroseries": review.distroseries } return json.dumps(r) @validate('review_id', int) @validate_pattern('reason', r'[^\n]+') @validate_pattern('text', r'[^\n]+') @returns_json @network_delay 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) f = { "user_id": random.randint(1, 500), "description": text, "review_moderation_id": mod_id, "_user_cache": self._make_user_cache(username), "summary": reason, "_review_moderation_cache": { "status": 0, "review_id": review_id, "_review_cache": self._make_fake_reviews(packagename=pkg, single_id=review_id), "moderation_text": text, "date_moderated": None, "moderator_id": None, "date_created": time.strftime("%Y-%m-%d %H:%M:%S"), "id": mod_id }, "date_created": time.strftime("%Y-%m-%d %H:%M:%S"), "id": mod_id } return json.dumps(f) @validate('review_id', int) @validate_pattern('useful', 'True|False') @returns_json @network_delay 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')) @validate('review_id', int, required=False) @validate_pattern('username', r'[^\n]+', required=False) @returns_json @network_delay 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 } 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') id_quantity = len(id_list) #figure out if we need to accommodate requested review ids if id_quantity == 0: 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) u = { 'username': username, 'useful': random.choice(['True', 'False']), 'review_id': id } votes.append(u) return json.dumps(votes) @validate('review_id', int) @returns_json def delete_review(self, review_id): """Delete a review""" return json.dumps(True) @validate('review_id', int) @validate('rating', int) @validate_pattern('summary', r'[^\n]+') @validate_pattern('review_text', r'[^\n]+') @returns(ReviewDetails) 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', 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 r = { "origin": "ubuntu", "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), "review_text": random.choice(self._TEXT), "date_deleted": None, "summary": random.choice(self._SUMMARIES), "version": "1:0.9.4", "id": id, "date_created": time.strftime("%Y-%m-%d %H:%M:%S"), "reviewer_displayname": "Fake Person", "package_name": packagename, "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, "first_name": "Fake", "last_name": "User", "is_active": True, "email": "fakeuser@email.com", "is_superuser": False, "is_staff": False, "last_login": time.strftime("%Y-%m-%d %H:%M:%S"), "password": "!", "id": random.randint(1, 500), "date_joined": time.strftime("%Y-%m-%d %H:%M:%S") } software-center-13.10/softwarecenter/backend/piston/scaclient.py0000664000202700020270000000320012151440100025306 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # 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 # # # taken from lp:~canonical-ca-hackers/software-center/scaclient # and put into scaclient_pristine.py import logging import os import sys # useful for debugging if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ: import httplib2 httplib2.debuglevel = 1 # patch default_service_root to the one we use from softwarecenter.enums import BUY_SOMETHING_HOST try: from .scaclient_pristine import SoftwareCenterAgentAPI 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) print(available_for_qa) for_me = sca.subscriptions_for_me() print(for_me) software-center-13.10/softwarecenter/backend/piston/scaclient_pristine.py0000664000202700020270000000443012151440100027231 0ustar dobeydobey00000000000000from piston_mini_client import (PistonAPI, PistonResponseObject, returns_list_of, returns_json) from piston_mini_client.validators import (validate_pattern, validate, oauth_protected) # These are factored out as constants for if you need to work against a # server that doesn't support both schemes (like http-only dev servers) PUBLIC_API_SCHEME = 'http' AUTHENTICATED_API_SCHEME = 'https' class SoftwareCenterAgentAPI(PistonAPI): default_service_root = 'http://localhost:8000/api/2.0' @validate_pattern('lang', r'[^/]{1,9}$') @validate_pattern('series', r'[^/]{1,20}$') @validate_pattern('arch', r'[^/]{1,10}$') @returns_list_of(PistonResponseObject) def available_apps(self, lang, series, arch): """Retrieve the list of currently available apps for purchase.""" return self._get( 'applications/%s/ubuntu/%s/%s/' % (lang, series, arch), scheme=PUBLIC_API_SCHEME) @validate_pattern('lang', r'[^/]{1,9}$') @validate_pattern('series', r'[^/]{1,20}$') @validate_pattern('arch', r'[^/]{1,10}$') @oauth_protected @returns_list_of(PistonResponseObject) def available_apps_qa(self, lang, series, arch): """Retrieve the list of currently available apps for purchase.""" return self._get( 'applications_qa/%s/ubuntu/%s/%s/' % (lang, series, arch), scheme=AUTHENTICATED_API_SCHEME) @oauth_protected @validate('complete_only', bool, required=False) @returns_list_of(PistonResponseObject) def subscriptions_for_me(self, complete_only=False): return self._get('subscriptions/', args={'complete_only': complete_only}, scheme=AUTHENTICATED_API_SCHEME) @oauth_protected @validate('id', int) @returns_json def subscription_by_id(self, id=None): return self._get('subscription/%d' % (id), scheme=AUTHENTICATED_API_SCHEME) @validate_pattern('lang', r'[^/]{1,9}$') @validate_pattern('series', r'[^/]{1,20}$', required=False) @returns_list_of(PistonResponseObject) def exhibits(self, lang, series=''): """Retrieve the list of currently published exhibits.""" url = 'exhibits/%s/' % lang if series != '': url += '%s/' % series return self._get(url) software-center-13.10/softwarecenter/backend/unitylauncher.py0000664000202700020270000001114212216063163024737 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Gary Lasker # 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 dbus import logging import os import tempfile LOG = logging.getLogger(__name__) class UnityLauncherInfo(object): """ Simple class to keep track of application details needed for Unity launcher integration """ def __init__(self, name, icon_name, icon_file_path, icon_x, icon_y, icon_size, installed_desktop_file_path, trans_id): self.name = name self.icon_name = icon_name self.icon_file_path = icon_file_path self.icon_x = icon_x self.icon_y = icon_y self.icon_size = icon_size self.installed_desktop_file_path = installed_desktop_file_path self.trans_id = trans_id class UnityLauncher(object): """ Implements the integration between Software Center and the Unity launcher """ def __init__(self): self._pkgname_to_temp_desktop_file = {} def _get_launcher_dbus_iface(self): bus = dbus.SessionBus() launcher_obj = bus.get_object('com.canonical.Unity.Launcher', '/com/canonical/Unity/Launcher') launcher_iface = dbus.Interface(launcher_obj, 'com.canonical.Unity.Launcher') return launcher_iface def cancel_application_to_launcher(self, pkgname): filename = self._pkgname_to_temp_desktop_file.pop(pkgname, None) if filename: os.unlink(filename) def _get_temp_desktop_file(self, pkgname, launcher_info): with tempfile.NamedTemporaryFile(prefix="software-center-agent:", suffix=":%s.desktop" % pkgname, delete=False) as fp: s = """ [Desktop Entry] Name=%(name)s Icon=%(icon_file_path)s Type=Application""" % {'name': launcher_info.name, 'icon_file_path': launcher_info.icon_file_path, } fp.write(s) fp.flush() LOG.debug("create temp desktop file '%s'" % fp.name) return fp.name 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 """ # stuff from the agent has no desktop file so we create a fake # one here just for the install if (launcher_info.installed_desktop_file_path == "software-center-agent"): temp_desktop = self._get_temp_desktop_file(pkgname, launcher_info) launcher_info.installed_desktop_file_path = temp_desktop self._pkgname_to_temp_desktop_file[pkgname] = temp_desktop LOG.debug("sending dbus signal to Unity launcher for application: %r", launcher_info.name) LOG.debug(" launcher_info: icon_file_path: %r ", launcher_info.icon_file_path) LOG.debug(" launcher_info.installed_desktop_file_path: %r", launcher_info.installed_desktop_file_path) LOG.debug(" launcher_info.trans_id: %r", launcher_info.trans_id) LOG.debug(" launcher_info.icon_x: %r icon_y: %r", launcher_info.icon_x, launcher_info.icon_y) try: launcher_iface = self._get_launcher_dbus_iface() launcher_iface.AddLauncherItemFromPosition( launcher_info.name, launcher_info.icon_file_path, launcher_info.icon_x, launcher_info.icon_y, launcher_info.icon_size, launcher_info.installed_desktop_file_path, launcher_info.trans_id) except Exception as e: LOG.warn("could not send dbus signal to the Unity launcher: (%s)", e) software-center-13.10/softwarecenter/plugin.py0000664000202700020270000001050512151440100021742 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # Authors: # Lars Wirzenius # 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 imp import inspect import os import logging LOG = logging.getLogger(__name__) 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 overwritten by the individual plugins and should return as fast as possible. if some longer init is required, start a glib timer or a thread """ 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): plugin_dirs = [plugin_dirs] self._plugin_dirs = plugin_dirs self._plugins = None 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) if x.endswith(".py")] 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): """Find and instantiate all plugins in a module.""" plugins = [] for dummy, member in inspect.getmembers(module): if inspect.isclass(member) and issubclass(member, Plugin): plugins.append(member) LOG.debug("Plugins in %s: %s" % (module, " ".join(str(x) for x in plugins))) return [plugin() for plugin in plugins] def _load_module(self, filename): """Load a module from a filename.""" LOG.debug("Loading module %s" % filename) module_name, dummy = os.path.splitext(os.path.basename(filename)) f = file(filename, "r") 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)" % (module_name, e)) return None f.close() return module @property def plugins(self): return self._plugins def load_plugins(self): """Return all plugins that have been found. """ if self._plugins is None: self._plugins = [] 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) continue with ExecutionTime("loading plugin: '%s'" % filename): module = self._load_module(filename) for plugin in self._find_plugins(module): plugin.app = self._app try: LOG.info("activating plugin '%s'" % module) plugin.init_plugin() self._plugins.append(plugin) except: LOG.exception("failed to init plugin: %s" % module) # get the matching plugins plugins = [p for p in self._plugins] LOG.debug("plugins are '%s'" % plugins) return plugins software-center-13.10/softwarecenter/log.py0000664000202700020270000001065412151440100021232 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2010 Canonical # # Authors: # Geliy Sokolov # 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 logging.handlers import os.path import softwarecenter.paths from softwarecenter.utils import ( ensure_file_writable_and_delete_if_not, safe_makedirs, ) """ 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) """ def filter(self, record): if not record.name.startswith("softwarecenter"): fixme_msg = logging.getLogger("softwarecenter.fixme").findCaller() 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] == ".")): 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." if that is not passed and will split "," to support multiple filters """ logging.debug("adding filter: '%s'" % long_filter_str) logfilter = OrFilter() for filter_str in long_filter_str.split(","): filter_str = filter_str.strip("") if filter_str == "": return if not (filter_str.startswith("sc") or filter_str.startswith("softwarecenter")): filter_str = "sc.%s" % filter_str if filter_str.startswith("sc"): filter_str = "softwarecenter" + filter_str[2:] logfilter.add(filter_str) # attach or filter handler.addFilter(logfilter) # setup global software-center logging root = logging.getLogger() fmt = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s", None) handler = logging.StreamHandler() handler.setFormatter(fmt) root.addHandler(handler) handler.addFilter(NullFilterThatWarnsAboutRootLoggerUsage()) # create log file safe_makedirs(softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR) # try to fix inaccessible s-c directory (#688682) if not os.access(softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR, os.W_OK): logging.warn("found not writable '%s' dir, trying to fix" % softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR) # if we have to do more renames, something else is wrong and it's # ok to crash later to learn about the problem for i in range(10): target = "%s.%s" % (softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR, i) if not os.path.exists(target): softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR = target break safe_makedirs(softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR) logfile_path = os.path.join(softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR, "software-center.log") # according to bug 688682 many people have a non-writeable logfile ensure_file_writable_and_delete_if_not(logfile_path) 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) software-center-13.10/softwarecenter/netstatus.py0000664000202700020270000001343312151440100022501 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Matthew McGowan # 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 dbus import logging from urlparse import urlparse from dbus.mainloop.glib import DBusGMainLoop from gi.repository import GObject, GLib 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 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] # The NetworkManager daemon is disconnecting. 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] class NetworkStatusWatcher(GObject.GObject): """ simple watcher which notifies subscribers to network events...""" __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 NETWORK_STATE = int(state) __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, } for envkey, state in env_map.iteritems(): if envkey in os.environ: NETWORK_STATE = state return dbus_loop = DBusGMainLoop() try: 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") return except Exception as e: logging.warn("failed to init network state watcher '%s'" % e) NETWORK_STATE = NetState.NM_STATE_UNKNOWN # test ping to check if there is internet connectivity despite # NetworkManager not being available import threading thread = threading.Thread(target=test_ping, name='test_ping') thread.start() return #helper def test_ping(host=None): global NETWORK_STATE import subprocess # ping the screenshots provider from softwarecenter.distro import get_distro distro = get_distro() host = urlparse(distro.SCREENSHOT_LARGE_URL)[1] msg = ("Attempting one time ping of %s to test if internet " "connectivity exists." % host) logging.info(msg) ping = subprocess.Popen(["ping", "-c", "1", host], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, error = ping.communicate() res = ping.wait() if res != 0: NETWORK_STATE = NetState.NM_STATE_DISCONNECTED msg = "Could not detect an internet connection\n%s" % error else: NETWORK_STATE = NetState.NM_STATE_CONNECTED_GLOBAL msg = "Internet connection available!\n%s" % out __WATCHER__.emit("changed", NETWORK_STATE) logging.info("ping output: '%s'" % msg) return NETWORK_STATE # 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 """ # unknown because in doubt, just assume we have network return get_network_state() in NetState.NM_STATE_UNKNOWN_LIST + \ NetState.NM_STATE_CONNECTED_LIST # init it once __init_network_state() if __name__ == '__main__': loop = GLib.MainLoop() loop.run() software-center-13.10/softwarecenter/enums.py0000664000202700020270000002353112151440100021576 0ustar dobeydobey00000000000000# 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 import os from gettext import gettext as _ # 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 SOFTWARE_CENTER_NAME_KEYRING = "Ubuntu Software Center" SOFTWARE_CENTER_SSO_DESCRIPTION = _( "To reinstall previous purchases, sign in to the " "Ubuntu Single Sign-On account you used to pay for them.") SOFTWARE_CENTER_DEBUG_TABS = os.environ.get( 'SOFTWARE_CENTER_DEBUG_TABS', False) SOFTWARE_CENTER_BUY_HOST = os.environ.get( "SOFTWARE_CENTER_BUY_HOST", "https://software-center.ubuntu.com") # buy-something base url #BUY_SOMETHING_HOST = "http://localhost:8000/" BUY_SOMETHING_HOST = os.environ.get( "SOFTWARE_CENTER_AGENT_HOST", SOFTWARE_CENTER_BUY_HOST) BUY_SOMETHING_HOST_ANONYMOUS = BUY_SOMETHING_HOST # recommender RECOMMENDER_HOST = os.environ.get( "SOFTWARE_CENTER_RECOMMENDER_HOST", "https://rec.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/api/1.0") # the terms-of-service links (the first is for display in a web browser # as it has the header and footer, the second is for display in a dialog # as it lacks them and so looks better) SOFTWARE_CENTER_TOS_LINK = "https://apps.ubuntu.com/cat/tos/" SOFTWARE_CENTER_TOS_LINK_NO_HEADER = "https://apps.ubuntu.com/cat/tos/plain/ " # version of the database, every time something gets added (like # terms for mime-type) increase this (but keep as a string!) DB_SCHEMA_VERSION = "7" # the default limit for a search DEFAULT_SEARCH_LIMIT = 10000 # 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" SEPARATOR_1 = "view-page-separator-1" PENDING = "view-page-pending" CHANNEL = "view-page-channel" # items considered "permanent", that is, if a item disappears # (e.g. progress) then switch back to the previous on in permanent # views (LP: #431907) PERMANENT_VIEWS = (AVAILABLE, INSTALLED, CHANNEL, 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" 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 FALLBACK = "applications-other" MISSING_APP = FALLBACK MISSING_PKG = "dialog-question" # XXX: Not used? GENERIC_MISSING = "gtk-missing-image" INSTALLED_OVERLAY = "software-center-installed" # sorting class SortMethods: (UNSORTED, BY_ALPHABET, BY_SEARCH_RANKING, BY_CATALOGED_TIME, 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 PKGNAME = 171 ICON = 172 GETTEXT_DOMAIN = 173 ARCHIVE_SECTION = 174 ARCHIVE_ARCH = 175 POPCON = 176 SUMMARY = 177 ARCHIVE_CHANNEL = 178 DESKTOP_FILE = 179 PRICE = 180 ARCHIVE_PPA = 181 ARCHIVE_DEB_LINE = 182 ARCHIVE_SIGNING_KEY_ID = 183 PURCHASED_DATE = 184 SCREENSHOT_URLS = 185 # multiple urls, comma separated ICON_NEEDS_DOWNLOAD = 186 # no longer used THUMBNAIL_URL = 187 # no longer used SC_DESCRIPTION = 188 APPNAME_UNTRANSLATED = 189 ICON_URL = 190 CATEGORIES = 191 LICENSE_KEY = 192 LICENSE_KEY_PATH = 193 # no longer used LICENSE = 194 VIDEO_URL = 195 DATE_PUBLISHED = 196 SUPPORT_SITE_URL = 197 VERSION_INFO = 198 SC_SUPPORTED_DISTROS = 199 WEBSITE = 200 CURRENCY = 201 # this is used to provide a cataloged time if there is no a-x-i in use # or if a-x-i is not available yet DB_CATALOGED_TIME = 202 # download size as the agent provides it DOWNLOAD_SIZE = 203 class AppInfoFields: ARCH = 'ARCH' CHANNEL = 'CHANNEL' DEB_LINE = 'DEB_LINE' DEB_LINE_ORIG = 'DEB_LINE_ORIG' DOWNLOAD_SIZE = 'DOWNLOAD_SIZE' CATEGORIES = 'CATEGORIES' DATE_PUBLISHED = 'DATE_PUBLISHED' DESCRIPTION = 'DESCRIPTION' GENERIC_NAME = 'GENERIC_NAME' GETTEXT_DOMAIN = 'GETTEXT_DOMAIN' ICON = 'ICON' ICON_URL = 'ICON_URL' IGNORE = 'IGNORE' KEYWORDS = 'KEYWORDS' LICENSE = 'LICENSE' LICENSE_KEY = 'LICENSE_KEY' LICENSE_KEY_PATH = 'LICENSE_KEY_PATH' MIMETYPE = 'MIMETYPE' NAME = 'NAME' NAME_UNTRANSLATED = 'NAME_UNTRANSLATED' PACKAGE = 'PACKAGE' POPCON = 'POPCON' PPA = 'PPA' PRICE = 'PRICE' PURCHASED_DATE = 'PURCHASED_DATE' SECTION = 'SECTION' SIGNING_KEY_ID = 'SIGNING_KEY_ID' SCREENSHOT_URLS = 'SCREENSHOT_URLS' SUMMARY = 'SUMMARY' SUPPORT_URL = 'SUPPORT_URL' SUPPORTED_DISTROS = 'SUPPORTED_DISTROS' TAGS = 'TAGS' THUMBNAIL_URL = 'THUMBNAIL_URL' TYPE = 'TYPE' VERSION = 'VERSION' VIDEO_URL = 'VIDEO_URL' WEBSITE = 'WEBSITE' CURRENCY = 'CURRENCY' # fake channels 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 class CustomKeys: APPNAME = "AppName" ICON = "Icon" SCREENSHOT_URLS = "Screenshot-Url" THUMBNAIL_URL = "Thumbnail-Url" CATEGORY = "Category" # pkg action state constants class PkgStates: # current INSTALLED = "installed" UNINSTALLED = "uninstalled" UPGRADABLE = "upgradable" REINSTALLABLE = "reinstallable" # progress INSTALLING = "installing" REMOVING = "removing" UPGRADING = "upgrading" ENABLING_SOURCE = "enabling_source" INSTALLING_PURCHASED = "installing_purchase" # special NEEDS_SOURCE = "needs_source" NEEDS_PURCHASE = "needs_purchase" PURCHASED_BUT_REPO_MUST_BE_ENABLED = "purchased_but_repo_must_be_enabled" ERROR = "error" FORCE_VERSION = "force_version" # the package is not found in the DB or cache NOT_FOUND = "not_found" # its purchased but not found for the current series PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES = \ "purchased_but_not_available_for_series" # something went wrong and we don't have a state for this PKG UNKNOWN = "unknown" # visibility of non-applications in the search results class NonAppVisibility: ALWAYS_VISIBLE = "non-apps-always-visible" MAYBE_VISIBLE = "non-apps-maybe-visible" NEVER_VISIBLE = "non-apps-never-visible" # application actions class AppActions: INSTALL = "install" REMOVE = "remove" UPGRADE = "upgrade" APPLY = "apply_changes" PURCHASE = "purchase" # transaction types class TransactionTypes: INSTALL = "install" REMOVE = "remove" UPGRADE = "upgrade" APPLY = "apply_changes" REPAIR = "repair_dependencies" # Search separators class SearchSeparators: REGULAR = " " PACKAGE = "," # action values for recommendations feedback class RecommenderFeedbackActions: VIEWED = "viewed" INSTALLED = "installed" # 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: # /apps/nautilus/preferences/mouse_forward_button # /apps/nautilus/preferences/mouse_back_button MOUSE_EVENT_FORWARD_BUTTON = 9 MOUSE_EVENT_BACK_BUTTON = 8 # delimiter for directory path separator in app-install APP_INSTALL_PATH_DELIMITER = "__" #carousel app limit to override limit in .menu file for category TOP_RATED_CAROUSEL_LIMIT = 12 WHATS_NEW_CAROUSEL_LIMIT = 9 LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT = 9 DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT = 4 # Transaction ID for the "fake" purchase transaction PURCHASE_TRANSACTION_ID = "FakePurchaseTransactionID" from .version import VERSION, DISTRO, RELEASE, CODENAME WEBKIT_USER_AGENT_SUFFIX = "SoftwareCenter/%s %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 # pyflakes USE_PACKAGEKIT_BACKEND = False except ImportError: try: from gi.repository import PackageKitGlib 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 software-center-13.10/softwarecenter/gwibber_helper.py0000664000202700020270000001227012151440100023425 0ustar dobeydobey00000000000000# Copyright (C) 2010 Matthew McGowan # # Authors: # Matthew McGowan # 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 dbus import logging from xdg import BaseDirectory as xdg import os.path import json import sys from random import random class GwibberHelper(object): """ A helper class for gwibber. ideally we would just use from gi.repository import Gwibber accounts = Gwibbers.Accounts() accounts.list() ... instead of the dbus iface, but the gi stuff fails to export "Accounts.list()" (and possible more) currently """ def accounts(self): """ returns accounts that are send_enabled """ accounts = [] try: bus = dbus.SessionBus() proxy_obj = bus.get_object("com.Gwibber.Accounts", "/com/gwibber/Accounts") accounts_iface = dbus.Interface(proxy_obj, "com.Gwibber.Accounts") for account in json.loads(accounts_iface.List()): if 'send_enabled' in account and account["send_enabled"]: accounts.append(account) return accounts except: logging.exception("GwibberHelper.accounts() failed") return accounts def send_message(self, message, account_id=None): """ send message to all accounts with send_enabled """ bus = dbus.SessionBus() proxy_obj = bus.get_object("com.Gwibber.Service", "/com/gwibber/Service") service_iface = dbus.Interface(proxy_obj, "com.Gwibber.Service") if account_id: json_str = json.dumps({'message': message, 'accounts': [account_id], }) service_iface.Send(json_str) else: service_iface.SendMessage(message) return True @staticmethod def has_accounts_in_sqlite(): """ return if there are accounts for gwibber in sqlite """ # don't use dbus, triggers a gwibber start each time we call this dbpath = "%s/gwibber/gwibber.sqlite" % xdg.xdg_config_home if not os.path.exists(dbpath): return False try: import sqlite3 with sqlite3.connect(dbpath) as db: results = db.execute("SELECT data FROM accounts") if len(results.fetchall()) > 0: return True return False except: 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'}] def accounts(self): import copy num = os.environ["SOFTWARE_CENTER_GWIBBER_MOCK_USERS"] if int(num) == 0: return [] elif int(num) == 1: return copy.copy(self.fake_gwibber_accounts_one) else: return copy.copy(self.fake_gwibber_accounts_multiple) 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 r = random() 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")) software-center-13.10/softwarecenter/expunge.py0000664000202700020270000000576112151440100022127 0ustar dobeydobey00000000000000# 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 import logging import os import time from softwarecenter.utils import get_lock, release_lock class ExpungeCache(object): """ Expunge a httplib2 cache dir based on either age of the cache file or status of the http data """ def __init__(self, dirs, by_days, by_unsuccessful_http_states, dry_run=False): self.dirs = dirs # days to keep data in the cache (0 == disabled) self.keep_time = 60 * 60 * 24 * by_days self.keep_only_http200 = by_unsuccessful_http_states self.dry_run = dry_run def _rm(self, f): if self.dry_run: print "Would delete: %s" % f else: logging.debug("Deleting: %s" % f) try: os.unlink(f) except OSError as e: logging.warn("When expunging the cache, could not unlink " "file '%s' (%s)'" % (f, e)) def _cleanup_dir(self, path): """ cleanup the given directory (and subdirectories) using the age or http state of the cache """ now = time.time() for root, dirs, files in os.walk(path): for f in files: fullpath = os.path.join(root, f) try: header = open(fullpath).readline().strip() if not header.startswith("status:"): logging.debug( "Skipping files with unknown header: '%s'" % f) continue if self.keep_only_http200 and header != "status: 200": self._rm(fullpath) if self.keep_time: mtime = os.path.getmtime(fullpath) logging.debug("mtime of '%s': '%s" % (f, mtime)) if (mtime + self.keep_time) < now: self._rm(fullpath) except IOError as e: logging.debug("ioerror in cleandir: %s" % e) def clean(self): # go over the directories for d in self.dirs: lock = get_lock(os.path.join(d, "expunge.lock")) if lock > 0: self._cleanup_dir(d) release_lock(lock) else: logging.info("dir '%s' locked by another process" % d) software-center-13.10/softwarecenter/cmdfinder.py0000664000202700020270000000432012151440100022375 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # 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 __future__ import absolute_import import os import logging 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", ] def __init__(self, cache): self._cache = cache return 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 os.access(f, os.X_OK)) def _get_exec_candidates(self, pkg): return filter(self._is_exec, pkg.installed_files) def _find_alternatives_for_cmds(self, cmds): alternatives = set() root = "/etc/alternatives" for p in os.listdir(root): if os.path.realpath(os.path.join(root, p)) in cmds: alternatives.add(p) return alternatives def find_cmds_from_pkgname(self, pkgname): """ find the executable binaries for a given package """ try: pkg = self._cache[pkgname] except KeyError: LOG.debug("can't find %s" % pkgname) return [] if not pkg.is_installed: return [] cmds = self._get_exec_candidates(pkg) cmds += self._find_alternatives_for_cmds(cmds) return sorted([os.path.basename(p) for p in cmds]) software-center-13.10/softwarecenter/utils.py0000664000202700020270000010361112216063163021621 0ustar dobeydobey00000000000000# 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 import dbus import gc import gettext from gi.repository import GObject from gi.repository import GLib from gi.repository import Gio import logging import math import os import re import tempfile import traceback import time import xml.sax.saxutils import errno # py3 compat try: from urllib.parse import urlsplit urlsplit # pyflakes except ImportError: from urlparse import urlsplit from enums import Icons, APP_INSTALL_PATH_DELIMITER from paths import ( SOFTWARE_CENTER_CACHE_DIR, OEM_CHANNEL_DESCRIPTOR, ) from config import get_config from gettext import gettext as _ # define additional entities for the unescape method, needed # because only '&', '<', and '>' are included by default 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 measure of the timing of a particular block of code, e.g. with ExecutinTime("db flush"): db.flush() """ def __init__(self, info="", with_traceback=False, suppress_less_than_n_seconds=0.1): self.info = info self.with_traceback = with_traceback self.suppress_less_than_n_seconds = suppress_less_than_n_seconds def __enter__(self): self.now = time.time() def __exit__(self, type, value, stack): time_spend = time.time() - self.now if time_spend < self.suppress_less_than_n_seconds: return logger = logging.getLogger("softwarecenter.performance") logger.debug("%s: %s" % (self.info, time_spend)) if self.with_traceback: log_traceback("populate model from query: '%s' (threaded: %s)") class TraceActiveObjectTypes(object): """ A context manager that tracks what and how many new object types are added in the context block. Note that due to the gc.collect() call this is slow and should not be used in production code, but its very useful inside e.g. tests """ def __init__(self, info): self.info = info self._known_obj_ids = set() def __enter__(self): gc.collect() for obj in gc.get_objects(): self._known_obj_ids.add(id(obj)) def __exit__(self, atype, value, stack): # ensure we start clean gc.collect() new_obj_types = {} for obj in gc.get_objects(): if id(obj) in self._known_obj_ids: continue self._known_obj_ids.add(id(obj)) if not type(obj) in new_obj_types: new_obj_types[type(obj)] = 0 new_obj_types[type(obj)] += 1 print "+++ new types after '%s':" % self.info #print new_obj_types for v in sorted(new_obj_types, key=new_obj_types.get): print v, new_obj_types[v] print "/+++\n" class TraceMemoryUsage(object): """Trace amount of memory used by the app using /proc/$$/statm""" def __init__(self, info): self.info = info def __enter__(self): self.resident = self._get_mem_from_proc()["resident"] self.data = self._get_mem_from_proc()["data"] def __exit__(self, atype, value, stack): now_resident = self._get_mem_from_proc()["resident"] - self.resident now_data = self._get_mem_from_proc()["data"] - self.data print "++++ MEMORY DELTA for '%s': res: %s data: %s (%s %s)\n" % ( self.info, # assume page size of 4k here get_nice_size(now_resident * 4 * 1024), get_nice_size(now_data * 4 * 1024), now_resident, now_data) def _get_mem_from_proc(self): with open("/proc/%i/statm" % os.getpid()) as f: size, resident, share, text, lib, data, dt = f.readline().split() return {'resident': int(resident), 'data': int(data), } def utf8(s): """ Takes a string or unicode object and returns a utf-8 encoded string, errors are ignored """ if s is None: return None if isinstance(s, unicode): return s.encode("utf-8", "ignore") return unicode(s, "utf8", "ignore").encode("utf8") def log_traceback(info): """ Helper that can be used as a debug helper to show what called the code at this place. Logs to softwarecenter.traceback """ 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 gtk idle_add - needs a cache as argument """ def wrapper(*args, **kwargs): self = args[0] # check if the cache is ready and window = None if hasattr(self, "app_view"): window = self.app_view.get_window() if not self.cache.ready: if window: window.set_cursor(self.busy_cursor) GLib.timeout_add(500, lambda: wrapper(*args, **kwargs)) return False # cache ready now if window: window.set_cursor(None) f(*args, **kwargs) return False return wrapper def capitalize_first_word(string): """ this takes a package synopsis and uppercases the first word's first letter """ if len(string) > 1 and string[0].isalpha() and not string[0].isupper(): return string[0].capitalize() + string[1:] return string def normalize_package_description(desc): """ this takes a package description and normalizes it so that all unneeded \n are stripped away and all enumerations are at the start of the line and start with a "*" E.g.: Some potentially very long paragraph that is in a single line. A new paragraph. A list: * item1 * item2 that may again be very very long """ def get_indent(part, whitespace=" "): i = 0 for i, char in enumerate(part): if char != whitespace: break return i def maybe_add_space(prefix): if not (prefix.endswith("\n") or prefix.endswith(" ")): return " " else: return "" BULLETS = ('- ', '* ', 'o ') norm_description = "" in_blist = False # process it for i, part in enumerate(desc.split("\n")): indent = get_indent(part) part = part.strip() # explicit newline if not part: norm_description += '\n' continue # check if in a enumeration if part[:2] in BULLETS: in_blist = True norm_description += "\n" + indent * ' ' + "* " + part[2:] elif in_blist and indent > 0: norm_description += " " + part elif part.endswith('.') or part.endswith(':'): if in_blist: in_blist = False norm_description += '\n' norm_description += maybe_add_space(norm_description) + part + '\n' else: in_blist = False norm_description += maybe_add_space(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 try: root = xml.etree.ElementTree.fromstring(html) except Exception as e: logging.warn("failed to parse: '%s' (%s)" % (html, e)) return "" title = root.findall(".//title") if title: text = title[0].text return text all_h1 = root.findall(".//h1") if all_h1: h1 = all_h1[0] # 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 stripped_part.startswith("* "): if not inside_li: html += "
    " inside_li = True html += '
  • %s
  • ' % stripped_part[2:] else: if inside_li: html += "
" html += '

%s

' % part inside_li = False if inside_li: html += "" return html def get_http_proxy_string_from_libproxy(url): """Helper that uses libproxy to get the http proxy for the given url """ import libproxy pf = libproxy.ProxyFactory() proxies = pf.getProxies(url) # FIXME: how to deal with multiple proxies? proxy = proxies[0] if proxy == "direct://": return "" else: return proxy def get_http_proxy_string_from_gsettings(): """Helper that gets the http proxy from gsettings 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 """ # check if this is actually available and usable. if not # well ... it segfaults (thanks pygi) schemas = ["org.gnome.system.proxy", "org.gnome.system.proxy.http"] for schema in schemas: if not schema in Gio.Settings.list_schemas(): raise ValueError("no schema '%s'" % schema) # Check to see if proxy mode is set to none before checking for host # (LP: #982567) psettings = Gio.Settings.new("org.gnome.system.proxy") if "none" in psettings.get_string("mode"): return None # If proxy mode isn't "none" check to see if host and port is set settings = Gio.Settings.new("org.gnome.system.proxy.http") 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…' 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 return apt_pkg.uri_to_filename(uri) except ImportError: p1 = re.compile("http://[^/]+/") 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 if name.endswith("/ubuntu"): 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.uri_to_filename(entry.uri) return name def obfuscate_private_ppa_details(text): """ hides any private PPA details that may be found in the given text """ result = text s = text.split() for item in s: if "private-ppa.launchpad.net" in item: url_parts = urlsplit(item) if url_parts.username: result = result.replace(url_parts.username, "hidden") if url_parts.password: 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 in /var/lib/apt/lists """ import aptsources.sourceslist entry = aptsources.sourceslist.SourceEntry(debline) name = "%s_dists_%s_Release" % (uri_to_filename(entry.uri), entry.dist) return name def is_dbus_service_running(service_name): running = False try: bus = dbus.SessionBus() running = bus.name_has_owner(service_name) except: LOG.exception("could not check for dbus service %s" % service_name) return running def is_gnome_shell_running(): return is_dbus_service_running("org.gnome.Shell") def is_unity_running(): """ return True if Unity is currently running """ return is_dbus_service_running("com.canonical.Unity") 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))) icon = icons.load_icon(missingicon, iconsize, 0) return icon 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 """ if not iconname or not icons.has_icon(iconname): iconname = Icons.MISSING_APP try: icon_info = icons.lookup_icon(iconname, iconsize, 0) except Exception: icon_info = icons.lookup_icon(Icons.MISSING_APP, iconsize, 0) if icon_info is not None: icon_file_path = icon_info.get_filename() return icon_file_path 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/") if os.path.exists(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, "/") 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 for the case of for-purchase items, etc. if pkgname: datadirs = GLib.get_system_data_dirs() for dir in datadirs: path = "%s/applications/%s.desktop" % (dir, pkgname) if os.path.exists(path): return path # files in the extras archive have their desktop filenames prepended # by "extras-", so we check for that also (LP: #1012877) for dir in datadirs: path = "%s/applications/extras-%s.desktop" % (dir, pkgname) if os.path.exists(path): return path LOG.warn("Could not determine the installed desktop file path for " "app-install desktop file: '%s'" % app_install_data_file_path) return "" def get_desktop_id(desktop_file): """ Gets a desktop-id from a .desktop file path""" datadirs = [(i if i[-1] == '/' else i+'/') + 'applications/' for i in \ GLib.get_system_data_dirs() + [GLib.get_user_data_dir()]] for dir in datadirs: if desktop_file.startswith(dir): return desktop_file[len(dir):].replace('/', '-') return desktop_file def clear_token_from_ubuntu_sso_sync(appname): """ send a dbus signal to the com.ubuntu.sso service to clear the credentials for the given appname, e.g. _("Ubuntu Software Center") and wait for it to finish (or 2s) """ from ubuntu_sso import ( DBUS_BUS_NAME, DBUS_CREDENTIALS_IFACE, DBUS_CREDENTIALS_PATH, ) # clean loop = GLib.MainLoop() bus = dbus.SessionBus() 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.connect_to_signal("CredentialsCleared", loop.quit) proxy.connect_to_signal("CredentialsNotFound", loop.quit) proxy.connect_to_signal("CredentialsError", loop.quit) proxy.clear_credentials(appname, {}) # ensure we don't hang forever here GLib.timeout_add_seconds(2, loop.quit) # run the mainloop until the credentials are clear loop.run() def get_nice_date_string(cur_t): """ return a "nice" human readable date, like "2 minutes ago" """ import datetime dt = datetime.datetime.utcnow() - cur_t days = dt.days secs = dt.seconds if days < 1: if secs < 120: # less than 2 minute ago s = _('a few minutes ago') # don't 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)} 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 s = gettext.ngettext("%(days)i day ago", "%(days)i days ago", 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() config.read(desktop_file) try: return config.get("Desktop Entry", key) 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") # 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] return nice_size(n_bytes) def save_person_to_config(username): """ save the specified username value for Ubuntu SSO to the config file """ # FIXME: ideally this would be stored in ubuntu-sso-client # but it doesn't, so we store it here curr_name = get_person_from_config() if curr_name != username: config = get_config() config.reviews_username = username # refresh usefulness cache in the background once we know # 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 """ cfg = get_config() return cfg.reviews_username def pnormaldist(qn): """ 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): w1 = w1 + (b[i] * math.pow(w3, i)) if qn > 0.5: return math.sqrt(w1 * w3) else: return -math.sqrt(w1 * w3) def wilson_score(pos, n, power=0.2): if n == 0: return 0 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) 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): tot_ratings = ratings[i] + tot_ratings sum_scores = 0.0 for i in range(0, 5): ws = wilson_score(ratings[i], tot_ratings, power) 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 extension like tiff, gif, jpg, svg, png, xpm, ico """ SUPPORTED_EXTENSIONS = [".tiff", ".tif", ".gif", ".jpg", ".jpeg", ".svg", ".png", ".xpm", ".ico"] basename, ext = os.path.splitext(iconname) if ext.lower() in SUPPORTED_EXTENSIONS: return basename return iconname def mangle_paths_if_running_in_local_checkout(): import softwarecenter.paths # 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") # set pythonpath for the various helpers if os.environ.get("PYTHONPATH", ""): os.environ["PYTHONPATH"] = os.path.abspath(".") + ":" +\ os.environ.get("PYTHONPATH", "") else: os.environ["PYTHONPATH"] = os.path.abspath(".") datadir = "./data" xapian_base_path = datadir # set new global datadir softwarecenter.paths.datadir = datadir softwarecenter.paths.XAPIAN_BASE_PATH = xapian_base_path # also alter the app-install path path = "./build/share/app-install/desktop/software-center.menu" if 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) 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()) def get_recommender_uuid(): """ the recommender service requires a UUID that does not contain dashes """ return get_uuid().replace("-", "") def get_lock(path): """ return a lock that can be released with release_lock on success and -1 on failure """ try: import apt_pkg return apt_pkg.get_lock(path, False) except ImportError: # implement me on non-apt system, I wish python had this in the stdlib pass def release_lock(lock): """ release a lock acquired with get_lock """ os.close(lock) def make_string_from_list(base_str, item_list): """ This function takes a list of items and builds a nice human readable string with it of the form. Note that the base string needs a "%s". Example return: The base string with the list items a,b and c in it. Note that base_str needs to be a ngettext string already, so the example usage is: l = ["foo", "bar"] base_str = ngettext("This list: %s.", "This list: %s", len(l)) s = make_string_from_list(base_string, l) """ list_str = item_list[0] if len(item_list) > 1: # TRANSLATORS: this is a generic list delimit char, e.g. "foo, bar" list_str = _(", ").join(item_list[:-1]) # TRANSLATORS: this is the last part of a list, e.g. "foo, bar and baz" list_str = _("%s and %s") % (list_str, item_list[-1]) s = base_str % list_str return s def ensure_file_writable_and_delete_if_not(file_path): """ This function checks for writeable access to a file and attempts to remove it if it is found to indeed be set as unwriteable """ if os.path.exists(file_path) and not os.access(file_path, os.W_OK): try: LOG.warn("encountered non-writeable file, attempting to fix " "by deleting: %s" % file_path) os.remove(file_path) except Exception as e: LOG.exception("failed to fix non-writeable file '%s': %s", file_path, e) def safe_makedirs(dir_path): """ This function can be used in place of a straight os.makedirs to handle the possibility of a race condition when more than one process may potentially be creating the same directory. It will not fail if two processes try to create the same dir at the same time """ # avoid throwing an OSError, see for example LP: #743003 if not os.path.exists(dir_path): try: os.makedirs(dir_path) except OSError as e: if e.errno == errno.EEXIST: # it seems that another process has already created this # directory in the meantime, that's ok pass else: # the error is due to something else, so we want to raise it raise def get_oem_channel_descriptor(path=OEM_CHANNEL_DESCRIPTOR): """Return the ubuntu distribution channel descriptor or a empty string """ if not os.path.exists(path): return "" with open(path) as f: for line in filter(lambda l: not l.startswith("#"), f): return line.strip() class SimpleFileDownloader(GObject.GObject): 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,),), } def __init__(self): GObject.GObject.__init__(self) self.tmpdir = None self._cancellable = None 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 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 local filename. If use_cache is given it will not use a tempdir, but instead a permanent cache dir - no etag or timestamp checks are performed. """ self.LOG.debug( "download_file: %s %s %s" % (url, dest_file_path, use_cache)) # cancel anything pending to avoid race conditions # like bug #839462 if self._cancellable: self._cancellable.cancel() self._cancellable.reset() else: self._cancellable = Gio.Cancellable() # no need to cache file urls and no need to really download # them, its enough to adjust the dest_file_path if url.startswith("file:"): dest_file_path = url[len("file:"):] use_cache = False # if the cache is used, we use that as the dest_file_path if use_cache: cache_path = os.path.join( SOFTWARE_CENTER_CACHE_DIR, "download-cache") if not os.path.exists(cache_path): os.makedirs(cache_path) dest_file_path = os.path.join(cache_path, uri_to_filename(url)) if simple_quoting_for_webkit: dest_file_path = dest_file_path.replace("%", "") dest_file_path = dest_file_path.replace("?", "") # no cache and no dest_file_path, use tempdir if dest_file_path is None: if self.tmpdir is None: self.tmpdir = tempfile.mkdtemp(prefix="software-center-") dest_file_path = os.path.join(self.tmpdir, uri_to_filename(url)) self.url = url self.dest_file_path = dest_file_path # FIXME: we actually need to do etag based checking here to see # if the source needs refreshing if os.path.exists(self.dest_file_path): self.emit('file-url-reachable', True) self.emit("file-download-complete", self.dest_file_path) return 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, self._cancellable, self._check_url_reachable_and_then_download_cb, url) def _ensure_correct_url(self, want_url): """This function will ensure that the url we requested to download earlier matches that is now downloaded. """ # this function is needed as there is a race condition when the # operation is finished but the signal is not delivered yet (it's # still in the gtk event loop). in this case there is nothing to # self._cancel but self.url/self.dest_file_path will still point to # the wrong file if self.url != want_url: self.LOG.warn( "url changed from '%s' to '%s'" % (want_url, self.url)) return False return True def _check_url_reachable_and_then_download_cb(self, f, result, want_url): self.LOG.debug("_check_url_reachable_and_then_download_cb: %s" % f) if not self._ensure_correct_url(want_url): return # normal operation 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, etag)) # url is reachable, now download the file f.load_contents_async( self._cancellable, self._file_download_complete_cb, want_url) except GObject.GError as e: self.LOG.debug("file *not* reachable %s" % self.url) self.emit('file-url-reachable', False) self.emit('error', GObject.GError, e) del f def _file_download_complete_cb(self, f, result, want_url): self.LOG.debug("file download completed %s" % self.dest_file_path) if not self._ensure_correct_url(want_url): return # 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: res, content, etag = f.load_contents_finish(result) except Exception as e: # I witnessed a strange error[1], so make the loader robust in this # situation # 1. content = f.load_contents_finish(result)[0] # Gio.Error: DBus error org.freedesktop.DBus.Error.NoReply self.LOG.debug(e) self.emit('error', Exception, e) return # write out the data outputfile = open(self.dest_file_path, "w") outputfile.write(content) outputfile.close() self.emit('file-download-complete', self.dest_file_path) # those helpers are packaging-system specific from softwarecenter.db.pkginfo import get_pkg_info # do not call get_pkg_info here, 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 = lambda v: get_pkg_info().upstream_version(v) version_compare = lambda v1, v2: get_pkg_info().version_compare(v1, v2) if __name__ == "__main__": s = decode_xml_char_reference('Search…') print(s) print(type(s)) print(unicode(s)) software-center-13.10/softwarecenter/config.py0000664000202700020270000001571212167624525021744 0ustar dobeydobey00000000000000# Copyright (C) 20011 Canonical # # Authors: # Andrew Higginson # # 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 # py3 compat try: from configparser import SafeConfigParser SafeConfigParser # pyflakes except ImportError: from ConfigParser import SafeConfigParser import os import logging from paths import SOFTWARE_CENTER_CONFIG_FILE LOG = logging.getLogger(__name__) class SoftwareCenterConfig(SafeConfigParser): SECTIONS = ("general", "reviews") def __init__(self, config): SafeConfigParser.__init__(self) # imported here to avoid cycle from utils import safe_makedirs safe_makedirs(os.path.dirname(config)) # we always want this sections, even on fresh installs for section in self.SECTIONS: self.add_section(section) # read the config self.configfile = config try: self.read(self.configfile) except Exception as e: # don't crash on a corrupted config file LOG.warn("Could not read the config file '%s': %s", self.configfile, e) pass def write(self): tmpname = self.configfile + ".new" # see LP: #996333, its ok to remove the old configfile as # its rewritten anyway from utils import ensure_file_writable_and_delete_if_not ensure_file_writable_and_delete_if_not(tmpname) ensure_file_writable_and_delete_if_not(self.configfile) try: f = open(tmpname, "w") SafeConfigParser.write(self, f) f.close() os.rename(tmpname, self.configfile) except Exception as e: # don't crash if there's an error when writing to the config file # (LP: #996333) LOG.warn("Could not write the config file '%s': %s", self.configfile, e) pass # generic property helpers def _generic_get(self, option, section="general", default=""): if not self.has_option(section, option): self.set(section, option, default) return self.get(section, option) def _generic_set(self, option, value, section="general"): self.set(section, option, value) def _generic_getbool(self, option, section="general", default=False): if not self.has_option(section, option): self.set(section, option, str(default)) return self.getboolean(section, option) def _generic_setbool(self, option, value, section="general"): if value: self.set(section, option, "True") else: self.set(section, option, "False") # our properties that will automatically sync with the configfile add_to_unity_launcher = property( lambda self: self._generic_getbool("add_to_launcher", default=True), lambda self, value: self._generic_setbool("add_to_launcher", value), None, "Defines if apps should get added to the unity launcher") app_window_maximized = property( lambda self: self._generic_getbool("maximized", default=False), lambda self, value: self._generic_setbool("maximized", value), None, "Defines if apps should be started maximized") recommender_uuid = property( # remove any dashes for the case where a user has opted in before # we required UUIDs without dashes lambda self: self._generic_get("recommender_uuid").replace("-", ""), lambda self, value: self._generic_set("recommender_uuid", value), None, "The UUID generated for the recommendations") recommender_profile_id = property( lambda self: self._generic_get("recommender_profile_id"), lambda self, value: self._generic_set("recommender_profile_id", value), None, "The recommendation profile id of the user") recommender_opt_in_requested = property( lambda self: self._generic_getbool( "recommender_opt_in_requested", default=False), lambda self, value: self._generic_setbool( "recommender_opt_in_requested", value), None, "The user has requested a opt-in and its pending") user_accepted_tos = property( lambda self: self._generic_getbool("accepted_tos", default=False), lambda self, value: self._generic_setbool("accepted_tos", value), None, "The user has accepted the terms-of-service") email = property( lambda self: self._generic_get("email", default=""), lambda self, value: self._generic_set("email", value), None, "The preferred email of the user, automatically set via ubuntu-sso") # the review section reviews_username = property( lambda self: self._generic_get( "username", section="reviews", default=""), lambda self, value: self._generic_set( "username", value, section="reviews"), None, "The sso username") reviews_post_via_gwibber = property( lambda self: self._generic_getbool( "gwibber_send", section="reviews", default=False), lambda self, value: self._generic_setbool( "gwibber_send", value, section="reviews"), None, "Also post reviews via gwibber") reviews_gwibber_account_id = property( lambda self: self._generic_get( "account_id", section="reviews", default=""), lambda self, value: self._generic_setbool( "account_id", value, section="reviews"), None, "The account id to use when sending via gwibber") # app_window_size is special as its a tuple def _app_window_size_get(self): size_as_string = self._generic_get("size", default="-1, -1") return [int(v) for v in size_as_string.split(",")] def _app_window_size_set(self, size_tuple): size_as_string = "%s, %s" % (size_tuple[0], size_tuple[1]) self._generic_set("size", size_as_string) app_window_size = property( _app_window_size_get, _app_window_size_set, None, "Defines the size of the application window as a tuple (x,y)") # one global instance of the config _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 software-center-13.10/softwarecenter/distro/0000755000202700020270000000000012224614354021412 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/distro/__init__.py0000664000202700020270000001603412216063163023526 0ustar dobeydobey00000000000000# 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 import logging import os from gettext import gettext as _ import platform from softwarecenter.utils import UnimplementedError, utf8 LOG = logging.getLogger(__name__) class Distro(object): """ abstract base class for a distribution """ # list of code names for the distro from newest to oldest, this is # used e.g. in the reviews loader if no reviews for the current codename # 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") # You need to set this var to enable purchases PURCHASE_APP_URL = "" # Point developers to a web page DEVELOPER_URL = "" def __init__(self): """Return a new generic Distro instance.""" def get_app_name(self): """ The name of the application (as displayed in the main window and the about window) """ return _("Software Center") def get_app_id(self): """ The application-id (as used on the .desktop file) """ 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.") 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" def get_codename(self): """ The codename of the distro, e.g. lucid """ # for tests and similar if "SOFTWARE_CENTER_DISTRO_CODENAME" in os.environ: distrocode = os.environ["SOFTWARE_CENTER_DISTRO_CODENAME"] LOG.warn("overriding distro codename to '%s'" % distrocode) return distrocode # normal behavior if not hasattr(self, "_distro_code_name"): self._distro_code_name = platform.dist()[2] return self._distro_code_name 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 the distribution """ raise UnimplementedError def get_supported_query(self): """ return a xapian query that gives all supported documents """ import xapian return xapian.Query() def get_supported_filter_name(self): 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)) button_text = _("Install Anyway") # alter it if a meta-package is affected for m in depends: 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) button_text = _("Install Anyway") depends = [] break # 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) button_text = _("Install Anyway") depends = None break return (primary, button_text) # generic version of deauthorize, can be customized by the distro 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 else: 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 else: primary = _('Deauthorizing this computer for purchases ' 'will remove the following purchased software:') button_text = _('Remove All') return (primary, button_text) # generic architecture detection code def get_architecture(self): pass def _get_distro(): distro_info = platform.linux_distribution() distro_id = distro_info[0] LOG.debug("get_distro: '%s'", distro_id) # normalize name for the import distro_module_name = distro_id.lower() distro_class_name = distro_id.capitalize() # start with a import, this gives us only a softwarecenter module module = __import__(distro_module_name, globals(), locals(), [], -1) # get the right class and instantiate it distro_class = getattr(module, distro_class_name) 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: arch = os.environ["SOFTWARE_CENTER_ARCHITECTURE"] LOG.warn("overriding architecture to '%s'" % arch) return arch return get_distro().get_architecture() def get_foreign_architectures(): return get_distro().get_foreign_architectures() # singleton distro_instance = _get_distro() if __name__ == "__main__": print(get_distro()) software-center-13.10/softwarecenter/distro/suselinux.py0000664000202700020270000000627612151440100024021 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Alex Eftimie # # 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 os 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_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/") 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" def get_distro_channel_description(self): """ The description of the main distro channel """ return _("Provided by openSUSE") def get_removal_warning_text(self, cache, pkg, appname, depends): primary = _("To remove %s, these items must be removed " "as well:") % appname button_text = _("Remove All") return (primary, button_text) def get_license_text(self, component): if component in ("main", "universe", "independent"): return _("Open source") elif component in ("restricted", "commercial"): return _("Proprietary") def is_supported(self, cache, doc, pkgname): # FIXME return False def get_supported_query(self): # FIXME import xapian 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): # FIXME pass def get_downloadable_icon_url(self, full_archive_url, icon_filename): # FIXME pass software-center-13.10/softwarecenter/distro/ubuntu.py0000664000202700020270000003072712216063163023316 0ustar dobeydobey00000000000000# # Copyright 2009-2013 Canonical Ltd. # # 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 datetime import locale import logging import os import re from apt.utils import (get_release_filename_for_pkg, get_release_date_from_release_file, get_maintenance_end_date) from gettext import gettext as _ from softwarecenter.distro.debian import Debian from softwarecenter.enums import BUY_SOMETHING_HOST from softwarecenter.utils import utf8 LOG = logging.getLogger(__name__) class Ubuntu(Debian): # see __init__.py description DISTROSERIES = [ "saucy", "raring", "quantal", "precise", "oneiric", "natty", ] # metapackages IMPORTANT_METAPACKAGES = ( "ubuntu-standard", "ubuntu-minimal", "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") # 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") # 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/") #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" # Starting point for Ubuntu app developers DEVELOPER_URL = "http://developer.ubuntu.com/" def get_app_name(self): return _("Ubuntu Software Center") def get_app_id(self): return "ubuntu-software-center" def get_app_description(self): 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" def get_distro_channel_description(self): """ The description of the main distro channel """ return _("Provided by Ubuntu") def get_removal_warning_text(self, cache, pkg, appname, depends): primary = _("To remove %s, these items must be removed " "as well:") % utf8(appname) button_text = _("Remove All") # alter it if an important meta-package is affected for m in self.IMPORTANT_METAPACKAGES: if m in depends: primary = _("%s is a core item in Ubuntu. " "Removing it may cause future upgrades " "to be incomplete. Are you sure you want to " "continue?") % appname button_text = _("Remove Anyway") depends = [] break # alter it if a meta-package is affected for m in depends: 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) button_text = _("Remove Anyway") depends = [] break return (primary, button_text) def get_license_text(self, component): if component in ("main", "universe", "independent"): return _("Open source") 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" return _("Unknown") def is_supported(self, cache, doc, pkgname): # the doc does not by definition contain correct data regarding the # 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 (origin.component == "main" or origin.component == "restricted")): return True return False def get_supported_query(self): import xapian 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): # 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 # 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", self.get_codename()) time_t = get_release_date_from_release_file(releasef) # check the release date and show support information # based on this if time_t: release_date = datetime.datetime.fromtimestamp(time_t) #print "release_date: ", release_date now = datetime.datetime.now() #release_age = (now - release_date).days #print "release age: ", release_age # init with the default time support_month = 18 # 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") if support_time: if support_time.endswith("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) # 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)) # check if the support has ended support_ended = (now.year >= support_end_year and now.month > support_end_month) if component == "main": if support_ended: return _("Canonical does no longer provide " "updates for %s in Ubuntu %s. " "Updates may be available in a newer version of " "Ubuntu.") % (appname, self.get_distro_release()) else: d = {'appname': appname, 'support_end_month_str': support_end_month_str, 'support_end_year': support_end_year} return _("Canonical provides critical updates for " "%(appname)s until %(support_end_month_str)s " "%(support_end_year)s.") % d elif component == "restricted": if support_ended: return _("Canonical does no longer provide " "updates for %s in Ubuntu %s. " "Updates may be available in a newer version of " "Ubuntu.") % (appname, self.get_distro_release()) else: d = {'appname': appname, 'support_end_month_str': support_end_month_str, 'support_end_year': support_end_year, } return _("Canonical provides critical updates supplied " "by the developers of %(appname)s until " "%(support_end_month_str)s " "%(support_end_year)s.") % d # if we couldn't determine a support date, use a generic maintenance # string without the date if (channelname or component in ("partner", "independent", "commercial")): return _("Provided by the vendor.") elif component == "main": return _("Canonical provides critical updates for %s.") % appname elif component == "restricted": return _("Canonical provides critical updates supplied by the " "developers of %s.") % appname elif component == "universe" or component == "multiverse": 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) def get_downloadable_icon_url(self, full_archive_url, icon_filename): """ generates the url for a downloadable icon based on the download uri and the icon filename itself """ split_at_pool = full_archive_url.split("pool")[0] # support ppas and extras.ubuntu.com if split_at_pool.endswith("/ppa/ubuntu/"): # it's a ppa, generate the icon_url for a ppa split_at_ppa = split_at_pool.split("/ppa/")[0] downloadable_icon_url = [] downloadable_icon_url.append(split_at_ppa) downloadable_icon_url.append("/meta/ppa/") downloadable_icon_url.append(icon_filename) return "".join(downloadable_icon_url) elif re.match("http://(.*)extras.ubuntu.com/", split_at_pool): # it's from extras.ubuntu.com, generate the icon_url for a ppa split_at_ubuntu = split_at_pool.split("/ubuntu/")[0] downloadable_icon_url = [] downloadable_icon_url.append(split_at_ubuntu) downloadable_icon_url.append("/meta/") 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) 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) software-center-13.10/softwarecenter/distro/fedora.py0000664000202700020270000000705412216063163023231 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Michael Vogt # Julian Andres Klode # # 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 os from softwarecenter.distro import Distro from gettext import gettext as _ class Fedora(Distro): DISTROSERIES = [ 'Beefy Miracle', 'Verne', 'Lovelock', 'Laughlin', 'Leonidas', 'Constantine', ] # disable paid software PURCHASE_APP_URL = None # 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_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/") 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 Overrides what's present in yum.conf for fedora, updates, updates-testing, their respective -source and -debuginfo """ return _("Provided by Fedora") def get_app_name(self): return _("Fedora Software Center") def get_app_id(self): return "fedora-software-center" def get_removal_warning_text(self, cache, pkg, appname, depends): primary = _("To remove %s, these items must be removed " "as well:") % appname 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) return _("Open source") def get_architecture(self): return os.uname()[4] def get_foreign_architectures(self): return [] def is_supported(self, cache, doc, pkgname): origin = cache.get_origin(pkgname) return origin == 'fedora' or origin == 'updates' def get_maintenance_status(self, cache, appname, pkgname, component, channelname): # FIXME pass def get_supported_query(self): import xapian query1 = xapian.Query("XOO" + "fedora") query2 = xapian.Query("XOO" + "updates") return xapian.Query(xapian.Query.OP_OR, query1, query2) software-center-13.10/softwarecenter/distro/debian.py0000664000202700020270000001503212151440100023172 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Michael Vogt # Julian Andres Klode # # 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 apt from softwarecenter.distro import Distro from gettext import gettext as _ class Debian(Distro): # metapackages IMPORTANT_METAPACKAGES = ("kde", "gnome", "gnome-desktop-environment") # screenshot handling SCREENSHOT_THUMB_URL = ("http://screenshots.debian.net/" "thumbnail/%(pkgname)s") SCREENSHOT_LARGE_URL = ("http://screenshots.debian.net/" "screenshot/%(pkgname)s") # the json description of the available screenshots SCREENSHOT_JSON_URL = "http://screenshots.debian.net/json/package/%s" REVIEWS_SERVER = "" DEVELOPER_URL = "http://www.debian.org/devel/" def get_distro_channel_name(self): """ The name in the Release file """ return "Debian" def get_distro_channel_description(self): """ The description of the main distro channel """ return _("Provided by Debian") def get_removal_warning_text(self, cache, pkg, appname, depends): primary = _("To remove %s, these items must be removed " "as well:") % appname button_text = _("Remove All") # alter it if a meta-package is affected for m in depends: 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) button_text = _("Remove Anyway") depends = [] break # alter it if an important meta-package is affected for m in self.IMPORTANT_METAPACKAGES: if m in depends: primary = _("%s is a core application in Debian. " "Uninstalling it may cause future upgrades " "to be incomplete. Are you sure you want to " "continue?") % appname button_text = _("Remove Anyway") depends = None break return (primary, button_text) def get_license_text(self, component): if component in ("main",): return _("Meets the Debian Free Software Guidelines") elif component == "contrib": return _("Meets the Debian Free Software Guidelines itself " "but requires additional non-free software to work") elif component == "non-free": return _("Non-free since it is either restricted " "in use, redistribution or modification.") def get_architecture(self): return apt.apt_pkg.config.find("Apt::Architecture") def get_foreign_architectures(self): import subprocess out = subprocess.Popen( ['dpkg', '--print-foreign-architectures'], stdout=subprocess.PIPE).communicate()[0].rstrip('\n') if out: return out.split(' ') return [] def is_supported(self, cache, doc, pkgname): # the doc does not by definition contain correct data regarding the # 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 == "Debian" and origin.trusted and origin.component == "main"): return True return False 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 try: origins = cache[pkgname].candidate.origins except (KeyError, AttributeError): return else: for origin in origins: if (origin.origin == "Debian" and origin.trusted): pkg_comp = origin.component pkg_archive = origin.archive break else: return if pkg_comp in ("contrib", "non-free"): if pkg_archive == "oldstable": return _("Debian does not provide critical updates.") else: return _("Debian does not provide critical updates. " "Some updates may be provided by the developers " "of %s and redistributed by Debian.") % appname elif pkg_comp == "main": if pkg_archive == "stable": return _("Debian provides critical updates for %s.") % appname elif pkg_archive == "oldstable": return _("Debian only provides updates for %s during " "a transition phase. " "Please consider upgrading to a later stable " "release of Debian.") % appname elif pkg_archive == "testing": return _("Debian provides critical updates for %s. But " "updates could be delayed or skipped.") % appname elif pkg_archive == "unstable": return _("Debian does not provide critical updates " "for %s") % appname def get_supported_query(self): import xapian 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)) software-center-13.10/softwarecenter/ui/0000755000202700020270000000000012224614354020523 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/__init__.py0000664000202700020270000000000012151440100022605 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/0000755000202700020270000000000012224614354021373 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/views/0000755000202700020270000000000012224614354022530 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/views/__init__.py0000664000202700020270000000000012151440100024612 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/views/pkgnamesview.py0000664000202700020270000000577112151440100025577 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 Gtk, GdkPixbuf from gi.repository import Pango import logging from softwarecenter.db.application import Application from softwarecenter.enums import Icons 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 uses we only show installed packages. Useful in dialogs. """ (COL_ICON, COL_TEXT) = range(2) def __init__(self, header, cache, pkgnames, icons, icon_size, db): super(PackageNamesView, self).__init__() model = Gtk.ListStore(GdkPixbuf.Pixbuf, str) self.set_model(model) tp = Gtk.CellRendererPixbuf() column = Gtk.TreeViewColumn("Icon", tp, pixbuf=self.COL_ICON) self.append_column(column) tr = Gtk.CellRendererText() tr.set_property("ellipsize", Pango.EllipsizeMode.END) column = Gtk.TreeViewColumn(header, tr, markup=self.COL_TEXT) self.append_column(column) for pkgname in sorted(pkgnames): if not pkgname in cache or not cache[pkgname].installed: 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): proposed_icon = Icons.MISSING_APP if icons.has_icon(proposed_icon): icon = icons.load_icon(proposed_icon, icon_size, 0) pb = icon.scale_simple( icon_size, icon_size, GdkPixbuf.InterpType.BILINEAR) else: 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, 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) software-center-13.10/softwarecenter/ui/gtk3/views/appdetailsview.py0000664000202700020270000024276412151440100026125 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2009-2011 Canonical # # Authors: # Matthew McGowan # 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 gi gi.require_version("Gtk", "3.0") from gi.repository import Atk, Gtk, Gdk, GLib, GObject, GdkPixbuf, Pango import datetime import gettext import logging import cairo import os from gettext import gettext as _ import softwarecenter.paths from softwarecenter.cmdfinder import CmdFinder from softwarecenter.netstatus import (NetState, get_network_watcher, network_state_is_connected) from softwarecenter.db.application import Application from softwarecenter.db import DebFileApplication from softwarecenter.backend.reviews import ReviewStats from softwarecenter.enums import (AppActions, PkgStates, Icons, SOFTWARE_CENTER_PKGNAME) from softwarecenter.utils import (is_unity_running, is_gnome_shell_running, upstream_version, get_exec_line_from_desktop, SimpleFileDownloader, utf8) from softwarecenter.distro import get_distro from softwarecenter.backend.weblive import get_weblive_backend from softwarecenter.ui.gtk3.dialogs import error from softwarecenter.ui.gtk3.em import StockEms, em from softwarecenter.ui.gtk3.drawing import color_to_hex from softwarecenter.ui.gtk3.session.appmanager import get_appmanager from softwarecenter.ui.gtk3.widgets.labels import HardwareRequirementsBox from softwarecenter.ui.gtk3.widgets.separators import HBar from softwarecenter.ui.gtk3.widgets.viewport import Viewport from softwarecenter.ui.gtk3.widgets.reviews import UIReviewsList from softwarecenter.ui.gtk3.widgets.containers import SmallBorderRadiusFrame from softwarecenter.ui.gtk3.widgets.stars import Star, StarRatingsWidget from softwarecenter.ui.gtk3.widgets.description import AppDescription from softwarecenter.ui.gtk3.widgets.thumbnail import ScreenshotGallery from softwarecenter.ui.gtk3.widgets.videoplayer import VideoPlayer from softwarecenter.ui.gtk3.widgets.weblivedialog import ( ShowWebLiveServerChooserDialog) from softwarecenter.ui.gtk3.widgets.recommendations import ( RecommendationsPanelDetails) from softwarecenter.ui.gtk3.gmenusearch import GMenuSearcher import softwarecenter.ui.gtk3.dialogs as dialogs from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.hw import get_hw_missing_long_description from softwarecenter.region import REGION_WARNING_STRING from softwarecenter.backend.reviews import get_review_loader from softwarecenter.backend.installbackend import get_install_backend LOG = logging.getLogger(__name__) class StatusBar(Gtk.Alignment): """ Subclass of Gtk.Alignment that draws a small dash border around the rectangle. """ def __init__(self, view): Gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) self.set_padding(StockEms.SMALL, StockEms.SMALL, 0, 0) self.hbox = Gtk.HBox() self.hbox.set_spacing(StockEms.SMALL) self.add(self.hbox) self.view = view # default bg self._bg = [1, 1, 1, 0.3] self.connect("style-updated", self.on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") def on_style_updated(self, widget): context = self.get_style_context() context.add_class("item-view-separator") context = widget.get_style_context() border = context.get_border(Gtk.StateFlags.NORMAL) self._border_width = max(1, max(border.top, border.bottom)) def do_draw(self, cr): cr.save() a = self.get_allocation() width = self._border_width # fill bg cr.rectangle(-width, 0, a.width + 2 * width, a.height) cr.set_source_rgba(*self._bg) cr.fill_preserve() # paint dashed top/bottom borders context = self.get_style_context() context.save() context.add_class("item-view-separator") bc = context.get_border_color(self.get_state_flags()) context.restore() Gdk.cairo_set_source_rgba(cr, bc) cr.set_dash((width, 2 * width), 1) cr.set_line_width(2 * width) cr.stroke() cr.restore() for child in self: self.propagate_draw(child, cr) class WarningStatusBar(StatusBar): def __init__(self, view): StatusBar.__init__(self, view) self.label = Gtk.Label() self.label.set_line_wrap(True) self.label.set_alignment(0.0, 0.5) self.warn = Gtk.Label() self.warn.set_markup( '%s' % u'\u26A0') self.hbox.pack_start(self.label, True, True, 0) self.hbox.pack_end(self.warn, False, False, 0) # 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. """ def __init__(self, view): StatusBar.__init__(self, view) 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) self.button = Gtk.Button() self.combo_multiple_versions = Gtk.ComboBoxText.new() model = Gtk.ListStore(str, str) self.combo_multiple_versions.set_model(model) self.combo_multiple_versions.connect( "changed", self._on_combo_multiple_versions_changed) self.progress = Gtk.ProgressBar() self.pkg_state = None self.hbox.pack_start(self.installed_icon, False, False, 0) self.hbox.pack_start(self.label, False, False, 0) self.hbox.pack_end(self.button, False, False, 0) self.hbox.pack_end(self.combo_multiple_versions, False, False, 0) self.hbox.pack_end(self.progress, False, False, 0) self.show_all() self.app_manager = get_appmanager() self.button.connect('clicked', self._on_button_clicked) GLib.timeout_add(500, self._pulse_helper) def _pulse_helper(self): if (self.pkg_state == PkgStates.INSTALLING_PURCHASED and self.progress.get_fraction() == 0.0): self.progress.pulse() return True def _on_combo_multiple_versions_changed(self, combo): # disconnect to ensure no updates are happening in here model = combo.get_model() it = combo.get_active_iter() if it is None: return archive_suite = model[it][1] # ignore if there is nothing set here if archive_suite is None: return combo.disconnect_by_func(self._on_combo_multiple_versions_changed) # reset "default" to "" as this is what it takes to reset the # thing if archive_suite != self.view.app.archive_suite: # force not-automatic version self.view.app_details.force_not_automatic_archive_suite( archive_suite) # update it self.view._update_all(self.view.app_details) # reconnect again combo.connect("changed", self._on_combo_multiple_versions_changed) def _on_button_clicked(self, button): button.set_sensitive(False) state = self.pkg_state app = self.view.app addons_to_install = self.view.addons_manager.addons_to_install addons_to_remove = self.view.addons_manager.addons_to_remove app_manager = self.app_manager if state == PkgStates.INSTALLED: app_manager.remove( app, addons_to_install, addons_to_remove) elif state == PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED: app_manager.reinstall_purchased(app) elif state == PkgStates.NEEDS_PURCHASE: app_manager.buy_app(app) elif state in (PkgStates.UNINSTALLED, PkgStates.FORCE_VERSION): app_manager.install( app, addons_to_install, addons_to_remove) elif state == PkgStates.REINSTALLABLE: app_manager.install( app, addons_to_install, addons_to_remove) elif state == PkgStates.UPGRADABLE: app_manager.upgrade( app, addons_to_install, addons_to_remove) elif state == PkgStates.NEEDS_SOURCE: app_manager.enable_software_source(app) def set_label(self, label): m = '%s' % label self.label.set_markup(m) def get_label(self): return self.label.get_text() def set_button_label(self, label): self.button.set_label(label) def get_button_label(self): return self.button.get_label() def _build_combo_multiple_versions(self): # the currently forced archive_suite for the given app app_version = self.app_details.version # all available not-automatic (version, archive_suits) not_auto_suites = self.app_details.get_not_automatic_archive_versions() # populate 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_auto_suites): # get the version, archive_suite ver, archive_suite = archive_suite # the string to display is something like: # "v1.0 (precise-backports)" displayed_archive_suite = archive_suite if i == 0: displayed_archive_suite = _("default") s = "v%s (%s)" % (upstream_version(ver), displayed_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 if self.combo_multiple_versions.get_active_iter() is None: self.combo_multiple_versions.set_active(0) self.combo_multiple_versions.show() combo.connect("changed", self._on_combo_multiple_versions_changed) else: self.combo_multiple_versions.hide() def configure(self, app_details, state): LOG.debug("configure %s state=%s pkgstate=%s" % ( app_details.pkgname, state, app_details.pkg_state)) self.pkg_state = state self.app_details = app_details # configure the not-automatic stuff self._build_combo_multiple_versions() if state in (PkgStates.INSTALLING, PkgStates.INSTALLING_PURCHASED, PkgStates.REMOVING, PkgStates.UPGRADING, PkgStates.ERROR, AppActions.APPLY): self.show() elif state == PkgStates.NOT_FOUND: self.hide() else: # mvo: why do we override state here again? state = app_details.pkg_state self.pkg_state = state self.button.set_sensitive(True) self.button.show() self.show() self.progress.hide() self.installed_icon.hide() # 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 the associated callback. if state == PkgStates.INSTALLING: self.set_label(_(u'Installing\u2026')) self.button.set_sensitive(False) elif state == PkgStates.INSTALLING_PURCHASED: self.set_label(_(u'Installing purchase\u2026')) self.button.hide() self.progress.show() elif state == PkgStates.REMOVING: self.set_label(_(u'Removing\u2026')) self.button.set_sensitive(False) elif state == PkgStates.UPGRADING: 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 self.installed_icon.show() 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 # 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 # 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 # 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 self.set_button_label(_('Reinstall')) elif state == PkgStates.INSTALLED: self.set_button_label(_('Remove')) elif state == PkgStates.NEEDS_PURCHASE: self.set_label("%s" % (app_details.price)) if (app_details.hardware_requirements_satisfied and app_details.region_requirements_satisfied): self.set_button_label(_(u'Buy\u2026')) else: self.set_button_label(_(u'Buy Anyway\u2026')) elif state == PkgStates.FORCE_VERSION: self.set_button_label(_('Change')) elif state in (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 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 representation) label = pdate.strftime(_('Purchased on %Y-%m-%d')) self.set_button_label(_('Install')) if state == PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES: label = pdate.strftime( _('Purchased on %Y-%m-%d but not available for your ' 'current Ubuntu version. Please contact the vendor ' 'for an update.')) self.button.hide() self.set_label(label) elif state == PkgStates.UNINSTALLED: #special label only if the app being viewed is software centre # itself 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 self.set_label(_("Free")) if (app_details.hardware_requirements_satisfied and app_details.region_requirements_satisfied): self.set_button_label(_('Install')) else: self.set_button_label(_('Install Anyway')) elif state == PkgStates.UPGRADABLE: self.set_label(_('Upgrade Available')) self.set_button_label(_('Upgrade')) elif state == AppActions.APPLY: self.set_label(_(u'Changing Add-ons\u2026')) self.button.set_sensitive(False) elif state == PkgStates.UNKNOWN: self.button.hide() self.set_label(_("Error")) elif state == PkgStates.ERROR: # this is used when the pkg can not be installed # we display the error in the description field self.installed_icon.hide() self.progress.hide() self.button.hide() 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 # rest self.button.hide() elif state == PkgStates.NEEDS_SOURCE: channelfile = self.app_details.channelfile # 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 elif self.app_details._unavailable_component(): self.set_button_label(_("Use This Source")) else: # FIXME: This will currently not be displayed, # because we don't differentiate between # components that are not enabled or that just # lack the "Packages" files (but are in sources.list) self.set_button_label(_("Update Now")) # 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) 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 """ def __init__(self, key, info_keys): Gtk.HBox.__init__(self) self.set_spacing(StockEms.LARGE) self.key = key self.info_keys = info_keys self.info_keys.append(key) self.value_label = Gtk.Label() self.value_label.set_selectable(True) self.value_label.set_line_wrap(True) self.value_label.set_alignment(0, 0.5) self.a11y = self.get_accessible() self.connect('realize', self._on_realize) 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_alignment(1, 0) # determine max width of all keys max_lw = 0 for key in self.info_keys: l = self.create_pango_layout("") l.set_markup(key_markup % key, -1) max_lw = max(max_lw, l.get_pixel_extents()[1].width) del l k.set_size_request(max_lw, -1) self.pack_start(k, False, False, 0) # value self.pack_start(self.value_label, False, False, 0) # a11y kacc = k.get_accessible() vacc = self.value_label.get_accessible() kacc.add_relationship(Atk.RelationType.LABEL_FOR, vacc) vacc.add_relationship(Atk.RelationType.LABELLED_BY, kacc) self.set_property("can-focus", True) self.show_all() def set_width(self, width): 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 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) class Addon(Gtk.HBox): """ Widget to select addons: CheckButton - Icon - Title (pkgname) """ def __init__(self, db, icons, pkgname): Gtk.HBox.__init__(self) self.set_spacing(StockEms.SMALL) self.set_border_width(2) # data self.app = Application("", pkgname) self.app_details = self.app.get_details(db) # checkbutton self.checkbutton = Gtk.CheckButton() self.checkbutton.pkgname = self.app.pkgname self.pack_start(self.checkbutton, False, False, 12) self.connect('realize', self._on_realize, icons, pkgname) def _on_realize(self, widget, icons, pkgname): # icon hbox = Gtk.HBox(spacing=6) self.icon = Gtk.Image() proposed_icon = self.app_details.icon if not proposed_icon or not icons.has_icon(proposed_icon): proposed_icon = Icons.MISSING_APP try: pixbuf = icons.load_icon(proposed_icon, 22, 0) if pixbuf: pixbuf.scale_simple(22, 22, GdkPixbuf.InterpType.BILINEAR) self.icon.set_from_pixbuf(pixbuf) except: LOG.warning("cant set icon for '%s' " % pkgname) hbox.pack_start(self.icon, False, False, 0) # name title = self.app_details.display_name if len(title) >= 2: title = title[0].upper() + title[1:] self.title = Gtk.Label() context = self.get_style_context() context.save() context.add_class("subtle") color = color_to_hex(context.get_color(Gtk.StateFlags.NORMAL)) context.restore() self.title.set_markup( title + ' (%s)' % (color, pkgname)) self.title.set_alignment(0.0, 0.5) self.title.set_line_wrap(True) self.title.set_ellipsize(Pango.EllipsizeMode.END) hbox.pack_start(self.title, False, False, 0) loader = self.get_ancestor(AppDetailsView).review_loader stats = loader.get_review_stats(self.app) if stats is not None: rating = Star() #~ rating.set_visible_window(False) rating.set_size_small() self.pack_start(rating, False, False, 0) rating.set_rating(stats.ratings_average) self.checkbutton.add(hbox) self.show_all() def get_active(self): return self.checkbutton.get_active() def set_active(self, is_active): self.checkbutton.set_active(is_active) def set_width(self, width): pass class AddonsTable(Gtk.VBox): """ Widget to display a table of addons. """ __gsignals__ = {'table-built': (GObject.SignalFlags.RUN_FIRST, None, ()), } def __init__(self, addons_manager): Gtk.VBox.__init__(self) self.set_spacing(12) self.addons_manager = addons_manager self.cache = self.addons_manager.view.cache self.db = self.addons_manager.view.db self.icons = self.addons_manager.view.icons self.recommended_addons = None self.suggested_addons = None self.label = Gtk.Label() self.label.set_alignment(0, 0.5) markup = '%s' % _('Optional add-ons') self.label.set_markup(markup) self.pack_start(self.label, False, False, 0) def get_addons(self): # filter all children widgets and return only Addons return [w for w in self if isinstance(w, Addon)] def clear(self): for addon in self.get_addons(): addon.destroy() def addons_set_sensitive(self, is_sensitive): for addon in self.get_addons(): addon.set_sensitive(is_sensitive) def set_addons(self, addons): self.recommended_addons = sorted(addons[0]) self.suggested_addons = sorted(addons[1]) if not self.recommended_addons and not self.suggested_addons: self.addons_manager.view.addons_hbar.hide() return self.addons_manager.view.addons_hbar.show() # clear any existing addons self.clear() # set the new addons exists = set() for addon_name in self.recommended_addons + self.suggested_addons: if not addon_name in self.cache or addon_name in exists: continue addon = Addon(self.db, self.icons, addon_name) #addon.pkgname.connect("clicked", not yet suitable for use) addon.set_active(self.cache[addon_name].installed is not None) addon.checkbutton.connect("toggled", self.addons_manager.mark_changes) self.pack_start(addon, False, False, 0) exists.add(addon_name) self.show_all() self.emit('table-built') return False class AddonsStatusBar(StatusBar): """ Statusbar for the addons. 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 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")) self.button_apply.connect("clicked", self._on_button_apply_clicked) self.button_cancel = Gtk.Button(_("Cancel")) self.button_cancel.connect("clicked", self.addons_manager.restore) self.hbox.pack_end(self.button_apply, False, False, 0) self.hbox.pack_end(self.button_cancel, False, False, 0) #self.hbox.pack_start(self.hbuttonbox, False, False, 0) def configure(self): LOG.debug("AddonsStatusBarConfigure") # FIXME: addons are not always free, but the old implementation # of determining price was buggy if (not self.addons_manager.addons_to_install and not self.addons_manager.addons_to_remove): self.hide() else: sensitive = network_state_is_connected() 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) self.button_cancel.set_sensitive(False) # these two lines are the magic that make it work self.view.addons_to_install = self.addons_manager.addons_to_install self.view.addons_to_remove = self.addons_manager.addons_to_remove LOG.debug("ApplyButtonClicked: inst=%s rm=%s" % ( self.view.addons_to_install, self.view.addons_to_remove)) # apply app_manager = get_appmanager() app_manager.apply_changes(self.view.app, self.view.addons_to_install, self.view.addons_to_remove) class AddonsManager(object): """ Addons manager component. This component deals with keeping track of what is marked for install or removal. """ def __init__(self, view): self.view = view # add-on handling self.table = AddonsTable(self) self.status_bar = AddonsStatusBar(self) self.addons_to_install = [] self.addons_to_remove = [] def mark_changes(self, checkbutton): LOG.debug("mark_changes") addon = checkbutton.pkgname installed = self.view.cache[addon].installed if checkbutton.get_active(): if addon not in self.addons_to_install and not installed: self.addons_to_install.append(addon) if addon in self.addons_to_remove: self.addons_to_remove.remove(addon) else: if addon not in self.addons_to_remove and installed: self.addons_to_remove.append(addon) if addon in self.addons_to_install: self.addons_to_install.remove(addon) self.status_bar.configure() self.view.update_totalsize() def configure(self, pkgname, update_addons=True): self.addons_to_install = [] self.addons_to_remove = [] if update_addons: self.addons = self.view.cache.get_addons(pkgname) self.table.set_addons(self.addons) self.status_bar.configure() sensitive = network_state_is_connected() self.table.addons_set_sensitive(sensitive) def restore(self, *button): self.addons_to_install = [] self.addons_to_remove = [] self.configure(self.view.app.pkgname) self.view.update_totalsize() _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 ? # 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 # multiple-inheriting __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): Viewport.__init__(self) # basic stuff self.db = db self.distro = distro self.icons = icons self.cache = cache # app specific data self.app = None self.app_details = None self.pkg_state = None self.cache.connect("query-total-size-on-install-done", self._on_query_total_size_on_install_done) self.backend = get_install_backend() self.cache.connect("cache-ready", self._on_cache_ready) self.connect("destroy", self._on_destroy) self.datadir = softwarecenter.paths.datadir self.addons_to_install = [] self.addons_to_remove = [] self.properties_helper = AppPropertiesHelper( self.db, self.cache, self.icons) # reviews self.review_loader = get_review_loader(self.cache, self.db) self.review_loader.connect( "get-reviews-finished", self._on_reviews_ready_callback) self.review_loader.connect( "remove-review", self._on_remove_review_callback) self.review_loader.connect( "replace-review", self._on_replace_review_callback) self.review_loader.connect("update-usefulness-votes", self._on_update_usefulness_votes_callback) # ui specific stuff self.set_shadow_type(Gtk.ShadowType.NONE) self.set_name("view") self.section = None self.reviews = UIReviewsList(self) self.adjustment_value = None # atk self.a11y = self.get_accessible() self.a11y.set_name("app_details pane") # aptdaemon self.backend.connect( "transaction-started", self._on_transaction_started) self.backend.connect( "transaction-stopped", self._on_transaction_stopped) self.backend.connect( "transaction-finished", self._on_transaction_finished) self.backend.connect( "transaction-progress-changed", self._on_transaction_progress_changed) # network status watcher watcher = get_network_watcher() watcher.connect("changed", self._on_net_state_changed) # addons manager self.addons_manager = AddonsManager(self) self.addons_statusbar = self.addons_manager.status_bar self.addons_to_install = self.addons_manager.addons_to_install self.addons_to_remove = self.addons_manager.addons_to_remove # reviews self._reviews_server_page = 1 self._reviews_server_language = None self._reviews_relaxed = False self._review_sort_method = 0 # switches self._show_overlay = False # page elements are packed into our very own lovely viewport self._layout_page() self._cache_art_assets() self.connect('realize', self._on_realize) self.loaded = True def _on_destroy(self, widget): self.cache.disconnect_by_func(self._on_cache_ready) self.review_loader.disconnect_by_func(self._on_reviews_ready_callback) self.review_loader.disconnect_by_func(self._on_remove_review_callback) self.review_loader.disconnect_by_func(self._on_replace_review_callback) def _cache_art_assets(self): global _asset_cache if _asset_cache: return _asset_cache assets = _asset_cache # cache the bg pattern surf = cairo.ImageSurface.create_from_png(self.BACKGROUND) ptrn = cairo.SurfacePattern(surf) ptrn.set_extend(cairo.EXTEND_REPEAT) assets["bg"] = ptrn return assets def _on_net_state_changed(self, watcher, state): if state in NetState.NM_STATE_DISCONNECTED_LIST: self._check_for_reviews() elif state in NetState.NM_STATE_CONNECTED_LIST: GLib.timeout_add(500, self._check_for_reviews) # set addon table and action button states based on sensitivity sensitive = state in NetState.NM_STATE_CONNECTED_LIST self.pkg_statusbar.button.set_sensitive(sensitive) self.addon_view.addons_set_sensitive(sensitive) self.addons_statusbar.button_apply.set_sensitive(sensitive) self.addons_statusbar.button_cancel.set_sensitive(sensitive) def _update_recommendations(self, pkgname): self.recommended_for_app_panel.set_pkgname(pkgname) # FIXME: should we just this with _check_for_reviews? def _update_reviews(self, app_details): self.reviews.clear() self._check_for_reviews() def _check_for_reviews(self): # self.app may be undefined on network state change events # (LP: #742635) if not self.app: return # review stats is fast and synchronous stats = self.review_loader.get_review_stats(self.app) self._update_review_stats_widget(stats) # individual reviews is slow and async so we just queue it here self._do_load_reviews() 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 self._review_sort_method = sort_method self.reviews.clear() self._do_load_reviews() def _on_reviews_in_different_language_clicked(self, uilist, language): self._reviews_server_page = 1 self._reviews_relaxed = False self._reviews_server_language = language self.reviews.clear() self._do_load_reviews() def _do_load_reviews(self): self.reviews.show_spinner_with_message(_('Checking for reviews...')) self.review_loader.get_reviews( self.app, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, relaxed=self._reviews_relaxed) def _update_review_stats_widget(self, stats): if 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) self.review_stats_widget.set_nr_reviews(stats.ratings_total) self.review_stats_widget.show() else: self.review_stats_widget.hide() def _submit_reviews_done_callback(self, spawner, error): self.reviews.new_review.enable() def _on_remove_review_callback(self, loader, app, review): self.reviews.remove_review(review) self.reviews.configure_reviews_ui() def _on_replace_review_callback(self, loader, app, review): self.reviews.replace_review(review) self.reviews.configure_reviews_ui() def _on_update_usefulness_votes_callback(self, loader, my_votes): self.reviews.update_useful_votes(my_votes) self.reviews.configure_reviews_ui() def _on_reviews_ready_callback(self, loader, app, reviews_data): """ callback when new reviews are ready, cleans out the old ones """ LOG.debug("_review_ready_callback: %s " % app) # avoid possible race if we already moved to a new app when # the reviews become ready # (we only check for pkgname currently to avoid breaking on # software-center totem) if self.app is None or self.app.pkgname != app.pkgname: return # Start fetching relaxed reviews if we retrieved no data # (and if we weren't already relaxed) if not reviews_data and not self._reviews_relaxed: self._reviews_relaxed = True self._reviews_server_page = 1 self.review_loader.get_reviews( self.app, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, relaxed=self._reviews_relaxed) return # update the stats (if needed). the caching can make them # wrong, so if the reviews we have in the list are more than the # stats we update manually old_stats = self.review_loader.get_review_stats(self.app) if ((old_stats is None and len(reviews_data) > 0) or (old_stats is not None and old_stats.ratings_total < len(reviews_data))): # generate new stats stats = ReviewStats(app) stats.ratings_total = len(reviews_data) 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)) # update UI self._update_review_stats_widget(stats) # update global stats cache as well self.review_loader.update_review_stats(app, stats) curr_list = set(self.reviews.get_all_review_ids()) retrieved_a_new_review = False for review in reviews_data: if not review.id in curr_list: retrieved_a_new_review = True self.reviews.add_review(review) if reviews_data and not retrieved_a_new_review: # We retrieved data, but nothing new. Keep going. self._reviews_server_page += 1 self.review_loader.get_reviews( self.app, page=self._reviews_server_page, language=self._reviews_server_language, sort=self._review_sort_method, relaxed=self._reviews_relaxed) self.reviews.configure_reviews_ui() def on_weblive_progress(self, weblive, progress): """ When receiving connection progress, update button """ self.test_drive.set_label(_("Connection ... (%s%%)") % (progress)) def on_weblive_connected(self, weblive, can_disconnect): """ When connected, update button """ if can_disconnect: self.test_drive.set_label(_("Disconnect")) self.test_drive.set_sensitive(True) else: self.test_drive.set_label(_("Connected")) def on_weblive_disconnected(self, weblive): """ When disconnected, reset button """ self.test_drive.set_label(_("Test drive")) self.test_drive.set_sensitive(True) def on_weblive_exception(self, weblive, exception): """ When receiving an exception, reset button and show the error """ 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) def on_test_drive_clicked(self, button): if self.weblive.client.state == "disconnected": # get exec line exec_line = get_exec_line_from_desktop(self.desktop_file) # split away any arguments, gedit for example as %U cmd = exec_line.split()[0] # Get the list of servers servers = self.weblive.get_servers_for_pkgname(self.app.pkgname) if len(servers) == 0: error(None, "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) button.set_sensitive(False) else: d = ShowWebLiveServerChooserDialog(servers, self.app.pkgname) serverid = None if d.run() == Gtk.ResponseType.OK: for server in d.servers_vbox: if server.get_active(): serverid = server.serverid break d.destroy() if serverid: self.weblive.create_automatic_user_and_run_session( session=cmd, serverid=serverid) button.set_sensitive(False) elif self.weblive.client.state == "connected": button.set_sensitive(False) self.weblive.client.disconnect_session() def _on_addon_table_built(self, table): if not table.get_parent(): self.info_vb.pack_start(table, False, False, 0) self.info_vb.reorder_child(table, 0) if not table.get_property('visible'): table.show_all() def _on_realize(self, widget): self.addons_statusbar.hide() # the install button gets initial focus self.pkg_statusbar.button.grab_focus() def _on_homepage_clicked(self, label, link): import webbrowser webbrowser.open_new_tab(link) return True def _layout_page(self): # setup widgets vb = Gtk.VBox() vb.set_spacing(StockEms.MEDIUM) vb.set_border_width(StockEms.MEDIUM) self.add(vb) # header hb = Gtk.HBox() hb.set_spacing(StockEms.MEDIUM) vb.pack_start(hb, False, False, 0) # the app icon self.icon = Gtk.Image() hb.pack_start(self.icon, False, False, 0) # the app title/summary self.title = Gtk.Label() self.subtitle = Gtk.Label() self.title.set_alignment(0, 0.5) self.subtitle.set_alignment(0, 0.5) self.title.set_line_wrap(True) self.title.set_selectable(True) self.subtitle.set_line_wrap(True) self.subtitle.set_selectable(True) vb_inner = Gtk.VBox() vb_inner.pack_start(self.title, False, False, 0) vb_inner.pack_start(self.subtitle, False, False, 0) # star rating box/widget self.review_stats_widget = StarRatingsWidget() self.review_stats = Gtk.HBox() vb_inner.pack_start( self.review_stats, False, False, StockEms.SMALL) self.review_stats.pack_start( self.review_stats_widget, False, False, 0) #~ vb_inner.set_property("can-focus", True) self.title.a11y = vb_inner.get_accessible() self.title.a11y.set_role(Atk.Role.PANEL) hb.pack_start(vb_inner, False, False, 0) # a warning bar (e.g. for HW incompatible packages) self.pkg_warningbar = WarningStatusBar(self) vb.pack_start(self.pkg_warningbar, False, False, 0) # the package status bar self.pkg_statusbar = PackageStatusBar(self) vb.pack_start(self.pkg_statusbar, False, False, 0) # installed where widget self.installed_where_hbox = Gtk.HBox() self.installed_where_hbox.set_spacing(6) 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 # thumbnail on the right body_hb = Gtk.HBox() body_hb.set_spacing(12) vb.pack_start(body_hb, False, False, 0) # append the description widget, hold the formatted long description self.desc = AppDescription() self.desc.description.set_property("can-focus", True) self.desc.description.a11y = self.desc.description.get_accessible() body_hb.pack_start(self.desc, True, True, 0) # the thumbnail/screenshot self.screenshot = ScreenshotGallery(get_distro(), self.icons) right_vb = Gtk.VBox() right_vb.set_spacing(6) body_hb.pack_start(right_vb, False, False, 0) frame = SmallBorderRadiusFrame() frame.add(self.screenshot) right_vb.pack_start(frame, False, False, 0) # video mini_hb = Gtk.HBox() self.videoplayer = VideoPlayer() mini_hb.pack_start(self.videoplayer, False, False, 0) # add a empty label here to ensure bg is set properly mini_hb.pack_start(Gtk.Label(), True, True, 0) right_vb.pack_start(mini_hb, False, False, 0) # the weblive test-drive stuff self.weblive = get_weblive_backend() if self.weblive.client is not None: self.test_drive = Gtk.Button(_("Test drive")) self.test_drive.connect("clicked", self.on_test_drive_clicked) right_vb.pack_start(self.test_drive, False, False, 0) # attach to all the WebLive events self.weblive.client.connect("progress", self.on_weblive_progress) self.weblive.client.connect("connected", self.on_weblive_connected) self.weblive.client.connect( "disconnected", self.on_weblive_disconnected) self.weblive.client.connect("exception", self.on_weblive_exception) self.weblive.client.connect("warning", self.on_weblive_warning) # homepage link button 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") self.support_btn.connect('activate-link', self._on_homepage_clicked) # add the links footer to the description widget footer_hb = Gtk.HBox(spacing=6) footer_hb.pack_start(self.homepage_btn, False, False, 0) footer_hb.pack_start(self.support_btn, False, False, 0) self.desc.pack_start(footer_hb, False, False, 0) self._hbars = [HBar(), HBar(), HBar()] vb.pack_start(self._hbars[0], False, False, 0) self.info_vb = info_vb = Gtk.VBox() info_vb.set_spacing(12) vb.pack_start(info_vb, False, False, 0) # add-on handling self.addon_view = self.addons_manager.table info_vb.pack_start(self.addon_view, False, False, 0) self.addons_statusbar = self.addons_manager.status_bar self.addon_view.pack_end(self.addons_statusbar, False, False, 0) self.addon_view.connect('table-built', self._on_addon_table_built) self.addons_hbar = self._hbars[1] info_vb.pack_start(self.addons_hbar, False, False, StockEms.SMALL) # package info self.info_keys = [] # info header #~ self.info_header = Gtk.Label() #~ self.info_header.set_markup('%s' % _("Details")) #~ self.info_header.set_alignment(0, 0.5) #~ self.info_header.set_padding(0, 6) #~ self.info_header.set_use_markup(True) #~ info_vb.pack_start(self.info_header, False, False, 0) self.version_info = PackageInfo(_("Version"), self.info_keys) info_vb.pack_start(self.version_info, False, False, 0) self.hardware_info = PackageInfoHW(_("Also requires"), self.info_keys) info_vb.pack_start(self.hardware_info, False, False, 0) self.totalsize_info = PackageInfo(_("Total size"), self.info_keys) info_vb.pack_start(self.totalsize_info, False, False, 0) self.license_info = PackageInfo(_("License"), self.info_keys) info_vb.pack_start(self.license_info, False, False, 0) self.support_info = PackageInfo(_("Updates"), self.info_keys) info_vb.pack_start(self.support_info, False, False, 0) vb.pack_start(self._hbars[2], False, False, 0) # recommendations self.recommended_for_app_panel = RecommendationsPanelDetails( self.db, self.properties_helper) self.recommended_for_app_panel.connect( "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) # 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("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._on_reviews_in_different_language_clicked) 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) self.show_all() # signals! 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) def _on_review_delete(self, button, review_id): self._review_delete(review_id) def _on_review_report_abuse(self, button, review_id): self._review_report_abuse(str(review_id)) def _on_review_submit_usefulness(self, button, review_id, is_useful): 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 # icon (also fixed). font_size = em(1.6) * Pango.SCALE markup = '%s' markup = markup % (font_size, appname) self.title.set_markup(markup) self.title.a11y.set_name(appname + '. ' + summary) self.subtitle.set_markup(summary) def _update_app_icon(self, app_details): pb = self._get_icon_as_pixbuf(app_details) # should we show the green tick? # self._show_overlay = app_details.pkg_state == PkgStates.INSTALLED w, h = pb.get_width(), pb.get_height() 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) def _update_layout_error_status(self, pkg_error): # if we have an error or if we need to enable a source # then hide everything else 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() for hbar in self._hbars: hbar.hide() 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() def _update_app_description(self, app_details, appname): # format new app description description = app_details.description if not description: description = " " self.desc.set_description(description, appname) # a11y for description self.desc.description.a11y.set_name(description) 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() self.homepage_btn.set_markup("%s" % ( self.app_details.website, _('Developer Web Site'))) self.homepage_btn.set_tooltip_text(app_details.website) else: self.homepage_btn.hide() # support too if app_details.supportsite: self.support_btn.show() self.support_btn.set_markup("%s" % ( self.app_details.supportsite, _('Support Web Site'))) self.support_btn.set_tooltip_text(app_details.supportsite) else: self.support_btn.hide() def _update_app_video(self, app_details): self.videoplayer.uri = app_details.video_url if app_details.video_url: self.videoplayer.show() else: self.videoplayer.hide() def _update_app_screenshot(self, app_details): # get screenshot urls and configure the ScreenshotView... if app_details.thumbnail and app_details.screenshot: self.screenshot.fetch_screenshots(app_details) def _update_weblive(self, app_details): 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 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() def _update_warning_bar(self, app_details): # generic error wins over HW issue if app_details.pkg_state == PkgStates.ERROR: self.pkg_warningbar.show() self.pkg_warningbar.label.set_text(app_details.error) return if (app_details.hardware_requirements_satisfied and app_details.region_requirements_satisfied): self.pkg_warningbar.hide() else: s = get_hw_missing_long_description( app_details.hardware_requirements) if not app_details.region_requirements_satisfied: if len(s) > 0: s += "\n" + REGION_WARNING_STRING else: s = REGION_WARNING_STRING self.pkg_warningbar.label.set_text(s) self.pkg_warningbar.show() def _update_pkg_info_table(self, app_details): # set the strings in the package info table if app_details.version: version = '%s %s' % (app_details.pkgname, app_details.version) else: version = utf8(_("%s (unknown version)")) % utf8( app_details.pkgname) if app_details.license: license = GLib.markup_escape_text(app_details.license) else: license = _("Unknown") if app_details.maintenance_status: support = GLib.markup_escape_text( app_details.maintenance_status) else: support = _("Unknown") # regular label updates self.version_info.set_value(version) self.license_info.set_value(license) self.support_info.set_value(support) # this is slightly special as its not using a label but a special # widget self.hardware_info.set_value(app_details.hardware_requirements) if self.app_details.hardware_requirements: self.hardware_info.show() else: self.hardware_info.hide() def _update_addons(self, app_details): # refresh addons interface self.addon_view.hide() if self.addon_view.get_parent(): self.info_vb.remove(self.addon_view) if not app_details.error: self.addons_manager.configure(app_details.pkgname) # Update total size label self.update_totalsize() # Update addons state bar self.addons_statusbar.configure() def _update_all(self, app_details, skip_update_addons=False): # reset view to top left vadj = self.get_vadjustment() hadj = self.get_hadjustment() if vadj: vadj.set_value(0) if hadj: hadj.set_value(0) # set button sensitive again self.pkg_statusbar.button.set_sensitive(True) pkg_ambiguous_error = app_details.pkg_state in (PkgStates.NOT_FOUND, PkgStates.NEEDS_SOURCE) appname = GLib.markup_escape_text(app_details.display_name) if app_details.pkg_state == PkgStates.NOT_FOUND: summary = app_details._error_not_found else: summary = GLib.markup_escape_text(app_details.display_summary) if not summary: summary = "" # depending on pkg install state set action labels self.pkg_statusbar.configure(app_details, app_details.pkg_state) self._update_layout_error_status(pkg_ambiguous_error) self._update_title_markup(appname, summary) self._update_app_icon(app_details) self._update_app_description(app_details, app_details.pkgname) self._update_description_footer_links(app_details) self._update_app_screenshot(app_details) self._update_app_video(app_details) self._update_weblive(app_details) self._update_pkg_info_table(app_details) self._update_warning_bar(app_details) if not skip_update_addons: self._update_addons(app_details) else: self.addon_view.hide() if self.addon_view.get_parent(): self.info_vb.remove(self.addon_view) self.update_totalsize() self._update_recommendations(app_details.pkgname) self._update_reviews(app_details) # show where it is self._configure_where_is_it() def _update_minimal(self, app_details): self._update_app_icon(app_details) self._update_pkg_info_table(app_details) self._update_warning_bar(app_details) # self._update_addons_minimal(app_details) self._update_app_video(app_details) # depending on pkg install state set action labels self.pkg_statusbar.configure(app_details, app_details.pkg_state) # # show where it is self._configure_where_is_it() def _add_where_is_it_commandline(self, pkgname): cmdfinder = CmdFinder(self.cache) cmds = cmdfinder.find_cmds_from_pkgname(pkgname) if not cmds: return vb = Gtk.VBox() vb.set_spacing(12) self.installed_where_hbox.pack_start(vb, False, False, 0) msg = gettext.ngettext( _('This program is run from a terminal: '), _('These programs are run from a terminal: '), len(cmds)) title = Gtk.Label() title.set_alignment(0, 0) title.set_markup(msg) title.set_line_wrap(True) #~ 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.set_selectable(True) cmd_label.set_use_markup(True) cmd_label.set_alignment(0, 0.5) cmd_label.set_padding(12, 0) cmd_label.set_line_wrap(True) vb.pack_start(cmd_label, False, False, 0) self.installed_where_hbox.show_all() def _add_where_is_it_launcher(self, desktop_file): searcher = GMenuSearcher() where = searcher.get_main_menu_path(desktop_file) if not where: return # display launcher location label = Gtk.Label(label=_("Find it in the menu: ")) self.installed_where_hbox.pack_start(label, False, False, 0) if is_gnome_shell_running(): class ActivitiesPseudoItem(object): def get_icon(self): return "" def get_name(self): return _("Activities") where.insert(0, ActivitiesPseudoItem()) for (i, item) in enumerate(where): icon = None iconname = None if hasattr(item, "get_icon"): icon = item.get_icon() elif hasattr(item, "get_app_info"): app_info = item.get_app_info() icon = app_info.get_icon() if icon: iconinfo = self.icons.lookup_by_gicon(icon, 18, 0) iconname = iconinfo.get_filename() # we get the right name from the lookup we did before if iconname and os.path.exists(iconname): image = Gtk.Image() pb = GdkPixbuf.Pixbuf.new_from_file_at_size(iconname, 18, 18) if pb: image.set_from_pixbuf(pb) self.installed_where_hbox.pack_start(image, False, False, 0) label_name = Gtk.Label() if hasattr(item, "get_name"): label_name.set_text(item.get_name()) elif hasattr(item, "get_app_info"): app_info = item.get_app_info() 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, False, False, 0) # create our a11y text a11y_text = "" for widget in self.installed_where_hbox: if isinstance(widget, Gtk.Label): a11y_text += ' > ' + widget.get_text() self.installed_where_hbox.a11y.set_name(a11y_text) self.installed_where_hbox.set_property("can-focus", True) self.installed_where_hbox.show_all() def _configure_where_is_it(self): def get_desktop_file(): # we should know the desktop file if self.app_details.desktop_file: return self.app_details.desktop_file # fallback mode pkgname = self.app_details.pkgname desktop_file = "/usr/share/applications/%s.desktop" % pkgname if not os.path.exists(desktop_file): return None return desktop_file # remove old content self.installed_where_hbox.foreach(lambda w, d: w.destroy(), None) self.installed_where_hbox.set_property("can-focus", False) self.installed_where_hbox.a11y.set_name('') # exit here early if unity is running (but still # show commandline args) if is_unity_running(): # but still display available commands, even in unity # because these are not easily discoverable and we don't # offer a launcher if not get_desktop_file(): self._add_where_is_it_commandline(self.app_details.pkgname) return # 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 # the package # try to show menu location if there is a desktop file, but # never show commandline programs for apps with a desktop file # to cover cases like "file-roller" that have NoDisplay=true desktop_file = get_desktop_file() if desktop_file: self._add_where_is_it_launcher(desktop_file) # if there is no desktop file, show commandline else: self._add_where_is_it_commandline(self.app_details.pkgname) # public API def show_app(self, app, force=False): LOG.debug("AppDetailsView.show_app '%s'" % app) if app is None: LOG.debug("no app selected") return same_app = (self.app and self.app.pkgname and self.app.appname == app.appname and self.app.pkgname == app.pkgname) #print 'SameApp:', same_app # init data self.app = app self.app_details = app.get_details(self.db) # check if app just became available and if so, force full # refresh if same_app: # if the app was in one of the states, force refresh if its # no longer in this state for state in [PkgStates.NEEDS_SOURCE, PkgStates.NOT_FOUND]: if (self.pkg_state == state and self.app_details.pkg_state != state): force = True self.pkg_state = self.app_details.pkg_state # update content # layout page if same_app and not force: self._update_minimal(self.app_details) else: # reset reviews_page self._reviews_server_page = 1 # 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 = isinstance(self.app, DebFileApplication) self._update_all(self.app_details, 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 # around that self.pkg_statusbar.button.grab_focus() self.emit("selected", self.app) 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, _("Version unknown"), _("The version of the application can not " "be detected. Entering a review is not " "possible.")) return # gather data pkg = self.cache[self.app.pkgname] version = pkg.candidate.version origin = self.cache.get_origin(self.app.pkgname) # FIXME: probably want to not display the ui if we can't review it if not origin: dialogs.error(None, _("Origin unknown"), _("The origin of the application can not " "be detected. Entering a review is not " "possible.")) return if pkg.installed: version = pkg.installed.version # 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() self.review_loader.spawn_write_new_review_ui( self.app, version, self.app_details.icon, origin, parent_xid, self.datadir) def _review_report_abuse(self, review_id): parent_xid = '' #parent_xid = get_parent_xid(self) self.review_loader.spawn_report_abuse_ui( review_id, parent_xid, self.datadir) def _review_submit_usefulness(self, review_id, is_useful): parent_xid = '' #parent_xid = get_parent_xid(self) self.review_loader.spawn_submit_usefulness_ui( review_id, is_useful, parent_xid, self.datadir) def _review_modify(self, review_id): parent_xid = '' #parent_xid = get_parent_xid(self) self.review_loader.spawn_modify_review_ui( parent_xid, self.app_details.icon, self.datadir, review_id) def _review_delete(self, review_id): parent_xid = '' #parent_xid = get_parent_xid(self) self.review_loader.spawn_delete_review_ui( review_id, parent_xid, self.datadir) # internal callbacks def _on_cache_ready(self, cache): # re-show the application if the cache changes, it may affect the # current application LOG.debug("on_cache_ready") self.show_app(self.app) def _update_interface_on_trans_ended(self, result): state = self.pkg_statusbar.pkg_state # handle purchase: install purchased has multiple steps 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 result and result.pkgname): self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) # reset the reviews UI now that we have installed the package self.reviews.configure_reviews_ui() # normal states elif state == PkgStates.REMOVING: 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: self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) # addons modified, order is important here elif self.addons_statusbar.applying: self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) self.addons_manager.configure(self.app_details.name, False) self.addons_statusbar.configure() # cancellation of dependency dialog elif state == PkgStates.INSTALLED: self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLED) # 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.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, trans_type): if self.addons_statusbar.applying: self.pkg_statusbar.configure(self.app_details, AppActions.APPLY) return 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, PkgStates.INSTALLING_PURCHASED) elif (state == PkgStates.UNINSTALLED or state == PkgStates.FORCE_VERSION): 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) # 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) def _on_transaction_stopped(self, backend, result): self.pkg_statusbar.progress.hide() self._update_interface_on_trans_ended(result) def _on_transaction_finished(self, backend, result): self.pkg_statusbar.progress.hide() self._update_interface_on_trans_ended(result) def _on_transaction_progress_changed(self, backend, pkgname, progress): if (self.app_details and self.app_details.pkgname and self.app_details.pkgname == pkgname): if not self.pkg_statusbar.progress.get_property('visible'): self.pkg_statusbar.button.hide() 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) if progress >= 100: self.pkg_statusbar.progress.set_fraction(1) adj = self.get_vadjustment() if adj: self.adjustment_value = adj.get_value() def get_app_icon_details(self): """ helper for unity dbus support to provide details about the application icon as it is displayed on-screen """ icon_size = self._get_app_icon_size_on_screen() (icon_x, icon_y) = self._get_app_icon_xy_position_on_screen() return (icon_size, icon_x, icon_y) def _get_app_icon_size_on_screen(self): """ helper for unity dbus support to get the size of the maximum side for the application icon as it is displayed on-screen """ icon_size = self.APP_ICON_SIZE if self.icon.get_storage_type() == Gtk.ImageType.PIXBUF: pb = self.icon.get_pixbuf() if pb.get_width() > pb.get_height(): icon_size = pb.get_width() 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 position cannot be determined for any reason, then the value (0,0) is returned """ # find top-level parent parent = self while parent.get_parent(): parent = parent.get_parent() # get x, y relative to top-level try: (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) # get top-level window position (px, py) = parent.get_position() 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): try: return self.icons.load_icon(app_details.icon, self.APP_ICON_SIZE, 0) except GObject.GError as e: logging.warn("failed to load '%s': %s" % ( app_details.icon, e)) return self.icons.load_icon(Icons.MISSING_APP, self.APP_ICON_SIZE, 0) elif app_details.icon_url: 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 # view with the downloaded one 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 # and after that this callback is called (wouldn't # it be nice if gtk would do that automatically?) 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)) 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 # if we need to purchase/enable the report use the agent info if self.app_details.pkg_state in ( PkgStates.NEEDS_SOURCE, PkgStates.NEEDS_PURCHASE, PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED): if self.app_details.size: self.totalsize_info.set_value( GLib.format_size(self.app_details.size)) return self.totalsize_info.set_value(_("Calculating...")) while Gtk.events_pending(): Gtk.main_iteration() self.cache.query_total_size_on_install( self.app_details.pkgname, self.addons_manager.addons_to_install, self.addons_manager.addons_to_remove, self.app.archive_suite) def _on_query_total_size_on_install_done(self, pkginfo, pkgname, total_download_size, total_install_size): if not self.app or self.app.pkgname != pkgname: return label_string = "" if (total_download_size == 0 and total_install_size == 0 and isinstance(self.app, DebFileApplication)): total_install_size = self.app_details.installed_size if total_download_size > 0: download_size = GLib.format_size(total_download_size) label_string += _("%s to download, ") % (download_size) if total_install_size > 0: install_size = GLib.format_size(total_install_size) label_string += _("%s when installed") % (install_size) elif (total_install_size == 0 and self.app_details and self.app_details.pkg_state == PkgStates.INSTALLED and not self.addons_manager.addons_to_install and not self.addons_manager.addons_to_remove): pkg_version = self.cache[self.app_details.pkgname].installed # we may not always get a pkg_version returned (LP: #870822), # in that case, we'll just have to display "Unknown" if pkg_version: install_size = GLib.format_size(pkg_version.installed_size) # FIXME: this is not really a good indication of the size # on disk label_string += _("%s on disk") % (install_size) 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 software-center-13.10/softwarecenter/ui/gtk3/views/lobbyview.py0000664000202700020270000003254712151440100025102 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Matthew McGowan # 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 gettext from gi.repository import Gtk, GLib import logging import webbrowser import xapian from gettext import gettext as _ from softwarecenter.db.application import Application from softwarecenter.enums import ( TOP_RATED_CAROUSEL_LIMIT, WHATS_NEW_CAROUSEL_LIMIT, ) from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.widgets.containers import ( FramedHeaderBox, FramedBox, TileGrid) from softwarecenter.ui.gtk3.widgets.exhibits import ( ExhibitBanner, FeaturedExhibit) from softwarecenter.ui.gtk3.widgets.recommendations import ( RecommendationsPanelLobby) from softwarecenter.ui.gtk3.widgets.buttons import LabelTile from softwarecenter.db.appfilter import get_global_filter from softwarecenter.db.enquire import AppEnquire from softwarecenter.db.categories import (Category, CategoriesParser, get_category_by_name, categories_sorted_by_name) from softwarecenter.distro import get_distro from softwarecenter.backend.scagent import SoftwareCenterAgent from softwarecenter.backend.reviews import get_review_loader LOG = logging.getLogger(__name__) from .catview import CategoriesView _asset_cache = {} class LobbyView(CategoriesView): def __init__(self, cache, db, icons, apps_filter, apps_limit=0): CategoriesView.__init__(self, cache, db, icons, apps_filter, apps_limit=0) self.top_rated = None self.exhibit_banner = None # sections self.departments = None self.appcount = None # get categories self.categories_parser = CategoriesParser(db) self.categories = self.categories_parser.parse_applications_menu() # build before connecting the signals to avoid race self.build() # ensure that on db-reopen we refresh the whats-new titles self.db.connect("reopen", self._on_db_reopen) # ensure that updates to the stats are reflected in the UI self.reviews_loader = get_review_loader(self.cache) self.reviews_loader.connect( "refresh-review-stats-finished", self._on_refresh_review_stats) def _on_db_reopen(self, db): self._update_whats_new_content() def _on_refresh_review_stats(self, reviews_loader, review_stats): self._update_top_rated_content() def _build_homepage_view(self): # these methods add sections to the page # changing order of methods changes order that they appear in the page self._append_banner_ads() 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.add(self.top_hbox) self.vbox.pack_start(top_hbox_alignment, False, False, 0) self._append_departments() self.right_column = Gtk.Box.new(Gtk.Orientation.VERTICAL, self.SPACING) self.top_hbox.pack_start(self.right_column, True, True, 0) self.bottom_hbox = Gtk.HBox(spacing=StockEms.SMALL) bottom_hbox_alignment = Gtk.Alignment() bottom_hbox_alignment.set_padding( StockEms.SMALL, 0, StockEms.MEDIUM - 2, StockEms.MEDIUM - 2) bottom_hbox_alignment.add(self.bottom_hbox) self.vbox.pack_start(bottom_hbox_alignment, False, False, 0) self._append_whats_new() self._append_top_rated() self._append_recommended_for_you() self._append_appcount() def _on_show_exhibits(self, exhibit_banner, exhibit): pkgs = exhibit.package_names.split(",") url = exhibit.click_url if url: webbrowser.open_new_tab(url) elif len(pkgs) == 1: app = Application("", pkgs[0]) self.emit("application-activated", app) else: query = self.db.get_query_for_pkgnames(pkgs) title = exhibit.title_translated untranslated_name = exhibit.package_names # create a temp query cat = Category(untranslated_name, title, None, query, flags=['nonapps-visible']) self.emit("category-selected", cat) def _filter_and_set_exhibits(self, sca_client, exhibit_list): result = [] # filter out those exhibits that are not available in this run for exhibit in exhibit_list: if not exhibit.package_names: result.append(exhibit) else: available = all(self.db.is_pkgname_known(p) for p in exhibit.package_names.split(',')) if available: result.append(exhibit) else: LOG.warn("skipping exhibit for: '%r' not available" % ( exhibit.package_names)) # its ok if result is empty, since set_exhibits() will ignore # empty lists self.exhibit_banner.set_exhibits(result) def _append_banner_ads(self): self.exhibit_banner = ExhibitBanner() self.exhibit_banner.set_exhibits([FeaturedExhibit()]) self.exhibit_banner.connect( "show-exhibits-clicked", self._on_show_exhibits) # query using the agent scagent = SoftwareCenterAgent() scagent.connect("exhibits", self._filter_and_set_exhibits) scagent.query_exhibits() a = Gtk.Alignment() a.set_padding(0, StockEms.SMALL, 0, 0) a.add(self.exhibit_banner) self.vbox.pack_start(a, False, False, 0) def _append_departments(self): # 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) # sort Category.name's alphabetically sorted_cats = categories_sorted_by_name(self.categories) mrkup = "%s" for cat in sorted_cats: if 'carousel-only' in cat.flags: continue category_name = mrkup % GLib.markup_escape_text(cat.name) label = LabelTile(category_name, None) label.label.set_margin_left(StockEms.SMALL) label.label.set_margin_right(StockEms.SMALL) label.label.set_alignment(0.0, 0.5) label.label.set_use_markup(True) label.connect('clicked', self.on_category_clicked, cat) cat_vbox.pack_start(label, False, False, 0) return # FIXME: _update_{top_rated,whats_new,recommended_for_you}_content() # duplicates a lot of code def _update_top_rated_content(self): # remove any existing children from the grid widget self.top_rated.remove_all() # get top_rated category and docs top_rated_cat = get_category_by_name( self.categories, u"Top Rated") # untranslated name if top_rated_cat: docs = top_rated_cat.get_documents(self.db) self.top_rated.add_tiles(self.properties_helper, docs, TOP_RATED_CAROUSEL_LIMIT) self.top_rated.show_all() return top_rated_cat def _append_top_rated(self): self.top_rated = TileGrid() self.top_rated.connect("application-activated", self.on_application_activated) #~ self.top_rated.row_spacing = StockEms.SMALL self.top_rated_frame = FramedHeaderBox() self.top_rated_frame.set_header_label(_("Top Rated")) self.top_rated_frame.add(self.top_rated) self.bottom_hbox.pack_start(self.top_rated_frame, True, True, 0) top_rated_cat = self._update_top_rated_content() # 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) def _update_whats_new_content(self): # remove any existing children from the grid widget 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 if whats_new_cat: docs = whats_new_cat.get_documents(self.db) self.whats_new.add_tiles(self.properties_helper, docs, WHATS_NEW_CAROUSEL_LIMIT) self.whats_new.show_all() return whats_new_cat def _append_whats_new(self): self.whats_new = TileGrid() self.whats_new.connect("application-activated", self.on_application_activated) self.whats_new_frame = FramedHeaderBox() self.whats_new_frame.set_header_label(_(u"What\u2019s New")) self.whats_new_frame.add(self.whats_new) whats_new_cat = self._update_whats_new_content() if whats_new_cat is not None: # only add to the visible right_frame if we actually have it 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) def _update_recommended_for_you_content(self): if (self.recommended_for_you_panel and self.recommended_for_you_panel.get_parent()): # disconnect listeners self.recommended_for_you_panel.disconnect_by_func( self.on_application_activated) self.recommended_for_you_panel.disconnect_by_func( self.on_category_clicked) # and remove the panel self.right_column.remove(self.recommended_for_you_panel) self.recommended_for_you_panel = RecommendationsPanelLobby( self.db, self.properties_helper) self.recommended_for_you_panel.connect("application-activated", self.on_application_activated) self.recommended_for_you_panel.connect( 'more-button-clicked', self.on_category_clicked) # until bug #1048912 with the testcase in # tests/gtk3/test_lp1048912.py # is fixed this workaround for the drawing code in FramedHeaderBox # is needed self.recommended_for_you_panel.connect( "size-allocate", self._on_recommended_for_you_panel_size_allocate) self.right_column.pack_start(self.recommended_for_you_panel, True, True, 0) def _on_recommended_for_you_panel_size_allocate(self, rec_panel, stuff): """This workaround can go once the root cause for bug #1048912 is found, see also tests/gtk3/test_lp1048912.py """ self.queue_draw() def _append_recommended_for_you(self): # update will (re)create the widget from scratch self.recommended_for_you_panel = None self._update_recommended_for_you_content() def _update_appcount(self): enq = AppEnquire(self.cache, self.db) distro = get_distro() if get_global_filter().supported_only: query = distro.get_supported_query() else: query = xapian.Query('') length = enq.get_estimated_matches_count(query) text = gettext.ngettext("%(amount)s item", "%(amount)s items", length ) % {'amount': length} self.appcount.set_text(text) def _append_appcount(self): self.appcount = Gtk.Label() self.appcount.set_alignment(0.5, 0.5) self.appcount.set_margin_top(1) self.appcount.set_margin_bottom(4) self.vbox.pack_start(self.appcount, False, True, 0) self._update_appcount() return def build(self): self.header = _('Departments') self._build_homepage_view() self.show_all() return def refresh_apps(self): supported_only = get_global_filter().supported_only if (self._supported_only == supported_only): return self._supported_only = supported_only self._update_top_rated_content() self._update_whats_new_content() self._update_recommended_for_you_content() self._update_appcount() return software-center-13.10/softwarecenter/ui/gtk3/views/catview.py0000664000202700020270000003515512151440100024540 0ustar dobeydobey00000000000000# Copyright (C) 2009 Canonical # # Authors: # Matthew McGowan # 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 cairo import gettext from gi.repository import Gtk, GObject, GLib import logging import os import xapian from gettext import gettext as _ import softwarecenter.paths from softwarecenter.enums import ( NonAppVisibility, SortMethods, TOP_RATED_CAROUSEL_LIMIT, ) from softwarecenter.utils import wait_for_apt_cache_ready from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.ui.gtk3.widgets.viewport import Viewport from softwarecenter.ui.gtk3.widgets.containers import ( FramedHeaderBox, FramedBox, TileGrid) from softwarecenter.ui.gtk3.widgets.recommendations import ( RecommendationsPanelCategory) from softwarecenter.ui.gtk3.widgets.buttons import CategoryTile from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.db.appfilter import AppFilter, get_global_filter from softwarecenter.db.enquire import AppEnquire from softwarecenter.db.categories import ( Category, categories_sorted_by_name) from softwarecenter.distro import get_distro LOG = logging.getLogger(__name__) _asset_cache = {} class CategoriesView(Viewport): __gsignals__ = { "category-selected": (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, (),) } SPACING = PADDING = 3 # art stuff STIPPLE = os.path.join(softwarecenter.paths.datadir, "ui/gtk3/art/stipple.png") def __init__(self, cache, db, icons, apps_filter=None, # FIXME: kill this, its not needed anymore? apps_limit=0): """ init the widget, takes db - a Database object icons - a Gtk.IconTheme apps_filter - ? apps_limit - the maximum amount of items to display to query for """ self.cache = cache self.db = db self.icons = icons self.properties_helper = AppPropertiesHelper( self.db, self.cache, self.icons) self.section = None Viewport.__init__(self) self.set_name("category-view") # setup base widgets # we have our own viewport so we know when the viewport grows/shrinks # setup widgets self.vbox = Gtk.VBox() self.add(self.vbox) # atk stuff atk_desc = self.get_accessible() atk_desc.set_name(_("Departments")) # appstore stuff self.categories = [] self.header = "" #~ self.apps_filter = apps_filter self.apps_limit = apps_limit # for comparing on refreshes self._supported_only = False # more stuff self._poster_sigs = [] self._allocation = None self._cache_art_assets() #~ assets = self._cache_art_assets() #~ self.vbox.connect("draw", self.on_draw, assets) self._prev_alloc = None self.connect("size-allocate", self.on_size_allocate) return def on_size_allocate(self, widget, _): a = widget.get_allocation() prev = self._prev_alloc if prev is None or a.width != prev.width or a.height != prev.height: self._prev_alloc = a self.queue_draw() return def _cache_art_assets(self): global _asset_cache if _asset_cache: return _asset_cache assets = _asset_cache # cache the bg pattern surf = cairo.ImageSurface.create_from_png(self.STIPPLE) ptrn = cairo.SurfacePattern(surf) ptrn.set_extend(cairo.EXTEND_REPEAT) assets["stipple"] = ptrn return assets def on_application_activated(self, btn, app): """ pass the application-activated signal along when an application is clicked-through in one of the tiles """ def timeout_emit(): self.emit("application-activated", app) return False GLib.timeout_add(50, timeout_emit) def on_category_clicked(self, btn, cat): """emit the category-selected signal when a category was clicked""" def timeout_emit(): self.emit("category-selected", cat) return False GLib.timeout_add(50, timeout_emit) def do_draw(self, cr): cr.save() cr.set_source(_asset_cache["stipple"]) cr.paint_with_alpha(0.5) cr.restore() for child in self: self.propagate_draw(child, cr) def set_section(self, section): self.section = section def refresh_apps(self): raise NotImplementedError class SubCategoryView(CategoriesView): def __init__(self, cache, db, icons, apps_filter, apps_limit=0, root_category=None): CategoriesView.__init__(self, cache, db, icons, apps_filter, apps_limit) # state self._built = False # data self.root_category = root_category self.enquire = AppEnquire(self.cache, self.db) self.properties_helper = AppPropertiesHelper( self.db, self.cache, self.icons) # sections self.current_category = None self.departments = None self.top_rated = 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_top(StockEms.MEDIUM) return def _get_sub_top_rated_content(self, category): app_filter = AppFilter(self.db, self.cache) self.enquire.set_query(category.query, limit=TOP_RATED_CAROUSEL_LIMIT, sortmode=SortMethods.BY_TOP_RATED, filter=app_filter, nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE, nonblocking_load=False) return self.enquire.get_documents() @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': GLib.markup_escape_text(self.header)} self.top_rated_frame.set_header_label(m) docs = self._get_sub_top_rated_content(category) self.top_rated.add_tiles(self.properties_helper, docs, TOP_RATED_CAROUSEL_LIMIT) return def _append_sub_top_rated(self): self.top_rated = TileGrid() self.top_rated.connect("application-activated", self.on_application_activated) self.top_rated.set_row_spacing(6) self.top_rated.set_column_spacing(6) self.top_rated_frame = FramedHeaderBox() self.top_rated_frame.pack_start(self.top_rated, True, True, 0) 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.recommended_for_you_in_cat.disconnect_by_func( self.on_application_activated) self.vbox.remove(self.recommended_for_you_in_cat) self.recommended_for_you_in_cat = RecommendationsPanelCategory( self.db, self.properties_helper, category) self.recommended_for_you_in_cat.connect("application-activated", self.on_application_activated) self.recommended_for_you_in_cat.connect( 'more-button-clicked', self.on_category_clicked) # 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 % GLib.markup_escape_text( self.header)) # sort Category.name's alphabetically sorted_cats = categories_sorted_by_name(self.categories) enquire = xapian.Enquire(self.db.xapiandb) app_filter = AppFilter(self.db, self.cache) distro = get_distro() supported_only = get_global_filter().supported_only for cat in sorted_cats: # add the subcategory if and only if it is non-empty if supported_only: enquire.set_query(xapian.Query(xapian.Query.OP_AND, cat.query, distro.get_supported_query())) else: enquire.set_query(cat.query) 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) # partially work around a (quite rare) corner case if num_items == 0: enquire.set_query(xapian.Query(xapian.Query.OP_AND, category.query, xapian.Query("ATapplication"))) # assuming that we only want apps is not always correct ^^^ tmp_matches = enquire.get_mset(0, len(self.db), None, app_filter) 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) name = GLib.markup_escape_text('%s %s' % (_("All"), num_items)) tile = CategoryTile(name, "category-show-all") tile.connect('clicked', self.on_category_clicked, all_cat) self.departments.add_child(tile) self.departments.queue_draw() return num_items def _append_subcat_departments(self): self.subcat_label = Gtk.Label() self.subcat_label.set_alignment(0, 0.5) self.departments = TileGrid(paint_grid_pattern=False) self.departments.set_row_spacing(StockEms.SMALL) self.departments.set_column_spacing(StockEms.SMALL) self.departments_frame = FramedBox(spacing=StockEms.MEDIUM, padding=StockEms.MEDIUM) # set x/y-alignment and x/y-expand self.departments_frame.set(0.5, 0.0, 1.0, 1.0) self.departments_frame.pack_start(self.subcat_label, False, False, 0) self.departments_frame.pack_start(self.departments, True, True, 0) # append the departments section to the page self.vbox.pack_start(self.departments_frame, False, True, 0) return def _update_appcount(self, appcount): text = gettext.ngettext("%(amount)s item available", "%(amount)s items available", appcount) % {'amount': appcount} self.appcount.set_text(text) return def _append_appcount(self): self.appcount = Gtk.Label() self.appcount.set_alignment(0.5, 0.5) self.appcount.set_margin_top(1) self.appcount.set_margin_bottom(4) self.vbox.pack_end(self.appcount, False, False, 0) return def _build_subcat_view(self): # these methods add sections to the page # 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 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 def set_subcategory(self, root_category, num_items=0): # nothing to do if (root_category is None or self.categories == root_category.subcategories): return self._set_subcategory(root_category, num_items) def _set_subcategory(self, root_category, num_items): self.current_category = root_category self.header = root_category.name self.categories = root_category.subcategories if not self._built: self._build_subcat_view() self._update_subcat_view(root_category, num_items) GLib.idle_add(self.queue_draw) return def refresh_apps(self): supported_only = get_global_filter().supported_only if (self.current_category is None or self._supported_only == supported_only): return self._supported_only = supported_only if not self._built: self._build_subcat_view() self._update_subcat_view(self.current_category) GLib.idle_add(self.queue_draw) return software-center-13.10/softwarecenter/ui/gtk3/views/appview.py0000664000202700020270000002613112151440100024543 0ustar dobeydobey00000000000000# Copyright (C) 2009,2010 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 import logging from gi.repository import Gtk, GObject from gettext import gettext as _ from softwarecenter.enums import SortMethods from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.models.appstore2 import AppTreeStore from softwarecenter.ui.gtk3.widgets.apptreeview import AppTreeView from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.utils import ExecutionTime LOG = logging.getLogger(__name__) class AppView(Gtk.VBox): __gsignals__ = { "sort-method-changed": (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, SortMethods.BY_TOP_RATED, SortMethods.BY_CATALOGED_TIME, SortMethods.BY_SEARCH_RANKING, ) # indices that relate to the above tuple _SORT_BY_ALPHABET = 0 _SORT_BY_TOP_RATED = 1 _SORT_BY_NEWEST_FIRST = 2 _SORT_BY_SEARCH_RANKING = 3 def __init__(self, db, cache, icons, show_ratings): Gtk.VBox.__init__(self) #~ self.set_name("app-view") # app properties helper with ExecutionTime("Appview.__init__ create AppPropertiesHelper"): self.helper = AppPropertiesHelper(db, cache, icons) # misc internal containers self.header_hbox = Gtk.HBox() self.header_hbox.set_border_width(StockEms.MEDIUM) self.pack_start(self.header_hbox, False, False, 0) self.tree_view_scroll = Gtk.ScrolledWindow() self.pack_start(self.tree_view_scroll, True, True, 0) # category label self.header_label = Gtk.Label() self.header_label.set_use_markup(True) self.header_hbox.pack_start(self.header_label, False, False, 0) # sort methods comboboxs # variant 1 includes sort by search relevance self.sort_methods_combobox = self._get_sort_methods_combobox() combo_alignment = Gtk.Alignment.new(0.5, 0.5, 1.0, 0.0) combo_alignment.add(self.sort_methods_combobox) self.header_hbox.pack_end(combo_alignment, False, False, 0) # content views self.tree_view = AppTreeView(self, db, icons, show_ratings, store=None) self.tree_view_scroll.add(self.tree_view) self.appcount = None self.vadj = 0.0 # list view sorting stuff self._force_default_sort_method = True self._handler = self.sort_methods_combobox.connect( "changed", self.on_sort_method_changed) #~ 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", #~ appcount) % { 'amount' : appcount, } #~ elif mode == self.DIFF_MODE: #~ text = gettext.ngettext("%(amount)s item", #~ "%(amount)s items", #~ 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) #~ self.appcount.set_margin_top(4) #~ self.appcount.set_margin_bottom(3) #~ self.appcount.connect("draw", self.on_draw) #~ self.vbox.pack_start(self.appcount, False, False, 0) #~ self.appcount.set_text(text) #~ self.appcount.show() pass def on_sort_method_changed(self, *args): self.vadj = 0.0 self.emit("sort-method-changed", self.sort_methods_combobox) def _get_sort_methods_combobox(self): combo = Gtk.ComboBoxText.new() combo.append_text(_("By Name")) combo.append_text(_("By Top Rated")) combo.append_text(_("By Newest First")) combo.append_text(_("By Relevance")) combo.set_active(self._SORT_BY_TOP_RATED) return combo def _get_combo_children(self): return len(self.sort_methods_combobox.get_model()) def _use_combobox_with_sort_by_search_ranking(self): if self._get_combo_children() == 4: return self.sort_methods_combobox.append_text(_("By Relevance")) 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) 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) def set_allow_user_sorting(self, do_allow): self.sort_methods_combobox.set_visible(do_allow) def set_header_labels(self, first_line, second_line): if second_line: markup = '%s\n%s' % (first_line, second_line) else: markup = "%s" % first_line return self.header_label.set_markup(markup) def set_model(self, model): self.tree_view.set_model(model) def get_model(self): return self.tree_view.appmodel def display_matches(self, matches, is_search=False): # FIXME: installedpane handles display of the trees intimately, # so for the time being lets just return None in the case of our # TreeView displaying an AppTreeStore ... ;( # ... also we don't currently support user sorting in the # installedview, so issue is somewhat moot for the time being... if isinstance(self.get_model(), AppTreeStore): LOG.debug("display_matches called on AppTreeStore, ignoring") return model = self.get_model() # disconnect the model from the view before running # set_from_matches to ensure that the _cell_data_func_cb is not # run when the placeholder items are set, otherwise the purpose # of the "load-on-demand" is gone and it leads to bugs like # LP: #964433 self.set_model(None) if model: model.set_from_matches(matches) self.set_model(model) adj = self.tree_view_scroll.get_vadjustment() if adj: adj.set_lower(self.vadj) adj.set_value(self.vadj) def reset_default_sort_mode(self): """ force the appview to reset to the default sort method without doing a refresh or sending any signals """ self._force_default_sort_method = True def configure_sort_method(self, is_search=False): """ configures the sort method UI appropriately based on current conditions, including whether a search is in progress. Note that this will not change the users current sort method, if that is the intention, call reset_default_sort_mode() """ # figure out what combobox we need if is_search: self._use_combobox_with_sort_by_search_ranking() else: self._use_combobox_without_sort_by_search_ranking() # and what sorting if self._force_default_sort_method: # always reset this, its the job of the user of the appview # to call reset_default_sort_mode() to reset this self._force_default_sort_method = False # and now set the default sort depending on if its a view or not if is_search: self.set_sort_method_with_no_signal( self._SORT_BY_SEARCH_RANKING) else: self.set_sort_method_with_no_signal( self._SORT_BY_TOP_RATED) def clear_model(self): return self.tree_view.clear_model() 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 """ icon_size = self._get_app_icon_size_on_screen() (icon_x, icon_y) = self._get_app_icon_xy_position_on_screen() return (icon_size, icon_x, icon_y) def _get_app_icon_size_on_screen(self): """ helper for unity dbus support to get the size of the maximum side for the application icon as it is displayed on-screen """ icon_size = 32 if (self.tree_view.selected_row_renderer and self.tree_view.selected_row_renderer.icon): pb = self.tree_view.selected_row_renderer.icon if pb.get_width() > pb.get_height(): icon_size = pb.get_width() 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 """ # find top-level parent parent = self while parent.get_parent(): parent = parent.get_parent() # get top-level window position (px, py) = parent.get_position() # and return the coordinate values if self.tree_view.selected_row_renderer: return (px + self.tree_view.selected_row_renderer.icon_x_offset, py + self.tree_view.selected_row_renderer.icon_y_offset) else: return (px, py) software-center-13.10/softwarecenter/ui/gtk3/views/purchaseview.py0000664000202700020270000003433212151440100025577 0ustar dobeydobey00000000000000# 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 GObject from gi.repository import GLib from gi.repository import Gtk from gi.repository import Gdk import logging import os import json from gi.repository import WebKit as webkit from gettext import gettext as _ from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.ui.gtk3.dialogs import show_accept_tos_dialog from softwarecenter.config import get_config from softwarecenter.ui.gtk3.utils import get_parent LOG = logging.getLogger(__name__) class PurchaseView(Gtk.VBox): """ View that displays the webkit-based UI for purchasing an item. """ LOADING_HTML = """

%s

""" % _("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, ()), 'terms-of-service-declined': (GObject.SignalFlags.RUN_LAST, None, ()), 'purchase-needs-spinner': (GObject.SignalFlags.RUN_LAST, None, (bool, )), } def __init__(self): super(PurchaseView, self).__init__() self.wk = None self._wk_handlers_blocked = False self._oauth_token = None self.config = get_config() def _log_debug_output(self, *args): LOG.info("uri changed: '%s'", self.wk.webkit.get_property("uri")) def init_view(self): if self.wk is None: # delaying this import make the window appear on the raspi # 3s quicker from softwarecenter.ui.gtk3.widgets.webkit import ( ScrolledWebkitWindow) self.wk = ScrolledWebkitWindow() self.pack_start(self.wk, True, True, 0) # automatically fill in the email in the login page self.wk.webkit.set_auto_insert_email(self.config.email) #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) # check if the user wants url debugging if os.environ.get("SOFTWARE_CENTER_DEBUG_WEBKIT"): self.wk.webkit.connect("notify::uri", self._log_debug_output) # 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._unblock_wk_handlers() def _ask_for_tos_acceptance_if_needed(self): accepted_tos = self.config.user_accepted_tos 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.user_accepted_tos = True 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 """ if not self._ask_for_tos_acceptance_if_needed(): self.emit("terms-of-service-declined") return False self.init_view() self.app = app self.iconname = iconname self.wk.webkit.load_html_string(self.LOADING_HTML, "file:///") self.wk.show() context = GLib.main_context_default() while context.pending(): context.iteration() if url: self.wk.webkit.load_uri(url) elif html: self.wk.webkit.load_html_string(html, "file:///") else: self.wk.webkit.load_html_string(DUMMY_HTML, "file:///") # only for debugging if os.environ.get("SOFTWARE_CENTER_DEBUG_BUY"): GLib.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 subprocess.Popen(['xdg-open', request.get_uri()]) return True def _on_close_web_view(self, view): win = view.parent_win win.destroy() return True def _on_create_web_view(self, view, frame): from softwarecenter.ui.gtk3.widgets.webkit import ( ScrolledWebkitWindow) win = Gtk.Window() win.set_size_request(400, 400) wk = ScrolledWebkitWindow(include_progress_ui=True) wk.webkit.connect("close-web-view", self._on_close_web_view) win.add(wk) win.show_all() # make sure close will work later wk.webkit.parent_win = win # find and set parent w = self.wk.get_parent() while w.get_parent(): w = w.get_parent() win.set_transient_for(w) return wk.webkit def _on_console_message(self, view, message, line, source_id): try: # load the token from the console message self._oauth_token = json.loads(message) # compat with the regular oauth naming self._oauth_token["token"] = self._oauth_token["token_key"] except ValueError: pass for k in ["token_key", "token_secret", "consumer_secret"]: if k in message: LOG.debug( "skipping console message that contains sensitive data") return True LOG.debug("_on_console_message '%s'" % message) return False def _on_script_alert(self, view, frame, message): self._process_json(message) # stop further processing to avoid actually showing the alter return True def _on_title_changed(self, view, frame, title): #print "on_title_changed", view, frame, title # see wkwidget.py _on_title_changed() for a code example self._process_json(title) def _on_load_status_changed(self, view, property_spec): """ helper to give visual feedback while the page is loading """ prop = view.get_property(property_spec.name) window = self.get_window() if prop == webkit.LoadStatus.PROVISIONAL: self.emit("purchase-needs-spinner", True) if window: window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) elif (prop == webkit.LoadStatus.FIRST_VISUALLY_NON_EMPTY_LAYOUT or prop == webkit.LoadStatus.FAILED or prop == webkit.LoadStatus.FINISHED): self.emit("purchase-needs-spinner", False) if window: window.set_cursor(None) def _process_json(self, json_string): try: LOG.debug("server returned: '%s'" % json_string) res = json.loads(json_string) #print res except: LOG.debug("error processing json: '%s'" % json_string) return if res["successful"] is False: if (res.get("user_canceled", False) or # note the different spelling res.get("user_cancelled", False) or # COMPAT with older clients that do not send the user # canceled property (LP: #696861), this msg appears # to be not translated "CANCELLED" in res.get("failures", "")): self.emit("purchase-cancelled-by-user") self._block_wk_handlers() return # this is what the agent implements elif "failures" in res: 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") self._block_wk_handlers() return else: self.emit("purchase-succeeded") self._block_wk_handlers() # gather data from response deb_line = res["deb_line"] signing_key_id = res["signing_key_id"] license_key = res.get("license_key") license_key_path = res.get("license_key_path") # 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, 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 # title_change on reloads (see LP: #696861) if not self._wk_handlers_blocked: self.wk.webkit.handler_block_by_func(self._on_script_alert) 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_handlers_blocked = False # just used for testing -------------------------------------------- DUMMY_HTML = """

Purchase test page

To buy Frobunicator for 99$ you need to enter your credit card info

Enter your credit card info

""" # 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 } for key in s: event = Gdk.Event(Gdk.KEY_PRESS) event.window = view.window if key.isdigit(): 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" # for some reason the "space" key before on checkbox does not work when # the event is generated, so this needs to be done manually :/ 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', ''), ] def _generate_events(view): global STATES (state, title, keys) = STATES[0] print("_generate_events: in state %s" % state) current_title = view.wk.webkit.get_property("title") if current_title and current_title.startswith(title): print("found state %s" % state) _send_keys(view, keys) STATES.pop(0) return True # # for debugging only # def _on_key_press(dialog, event): # print event, event.keyval software-center-13.10/softwarecenter/ui/gtk3/app.py0000664000202700020270000015464612200237544022543 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # 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 # order is import here, otherwise test/gtk3/test_purchase.py is unhappy from gi.repository import GObject from gi.repository import GLib from gi.repository import Gtk import atexit import collections import dbus import dbus.service import gettext import glob import logging import os import re import subprocess import sys import time import webbrowser import xapian from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) from os.path import normpath from gettext import gettext as _ # purely to initialize the netstatus import softwarecenter.netstatus # make pyflakes shut up softwarecenter.netstatus.NETWORK_STATE from softwarecenter.backend.ubuntusso import UbuntuSSO # db imports from softwarecenter.db.application import Application from softwarecenter.db import ( DebFileApplication, DebFileOpenError, ) from softwarecenter.db.utils import run_software_center_agent from softwarecenter.i18n import init_locale # misc imports from softwarecenter.plugin import PluginManager from softwarecenter.paths import SOFTWARE_CENTER_PLUGIN_DIRS from softwarecenter.enums import ( AppActions, DB_SCHEMA_VERSION, Icons, MOUSE_EVENT_FORWARD_BUTTON, MOUSE_EVENT_BACK_BUTTON, PkgStates, SearchSeparators, SOFTWARE_CENTER_DEBUG_TABS, SOFTWARE_CENTER_NAME_KEYRING, SOFTWARE_CENTER_TOS_LINK, ViewPages, ) from softwarecenter.utils import ( clear_token_from_ubuntu_sso_sync, get_http_proxy_string_from_gsettings, wait_for_apt_cache_ready, ExecutionTime, is_unity_running, ) from softwarecenter.ui.gtk3.utils import ( get_sc_icon_theme, init_sc_css_provider, ) from softwarecenter.version import VERSION from softwarecenter.db.database import ( StoreDatabase, get_reinstall_previous_purchases_query, ) try: from aptd_gtk3 import InstallBackendUI InstallBackendUI # pyflakes except: from softwarecenter.backend.installbackend import InstallBackendUI # ui imports import softwarecenter.ui.gtk3.dialogs.deauthorize_dialog as deauthorize_dialog import softwarecenter.ui.gtk3.dialogs as dialogs from softwarecenter.ui.gtk3.SimpleGtkbuilderApp import SimpleGtkbuilderApp with ExecutionTime("import InstalledPane"): from softwarecenter.ui.gtk3.panes.installedpane import InstalledPane with ExecutionTime("import AvailablePane"): from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane from softwarecenter.ui.gtk3.panes.historypane import HistoryPane from softwarecenter.ui.gtk3.panes.globalpane import GlobalPane from softwarecenter.ui.gtk3.panes.pendingpane import PendingPane from softwarecenter.ui.gtk3.session.appmanager import ( ApplicationManager, 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.installbackend import get_install_backend from softwarecenter.backend.login import get_login_backend from softwarecenter.backend.recagent import RecommenderAgent 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.distro import get_distro from softwarecenter.db.pkginfo import get_pkg_info from gi.repository import Gdk LOG = logging.getLogger(__name__) PACKAGE_PREFIX = 'apt:' FILE_PREFIX = 'file:' # "apt:///" is a valid prefix for 'apt:pkgname' in alt+F2 in gnome PACKAGE_PREFIX_REGEX = re.compile('^%s(?:/{2,3})*' % PACKAGE_PREFIX) SEARCH_PREFIX = 'search:' # py3 compat def callable(func): return isinstance(func, collections.Callable) def parse_packages_args(packages): search_text = '' app = None # avoid treating strings as sequences ('foo' should not be 'f', 'o', 'o') if isinstance(packages, basestring): packages = (packages,) if not isinstance(packages, collections.Iterable): LOG.warning('show_available_packages: argument is not an iterable %r', packages) return search_text, app items = [] # make a copy of the given sequence for arg in packages: # support both "pkg1 pkg" and "pkg1,pkg2" (and "pkg1,pkg2 pkg3") if "," in arg: items.extend(arg.split(SearchSeparators.PACKAGE)) else: items.append(arg) if len(items) > 0: # allow s-c to be called with a search term if items[0].startswith(SEARCH_PREFIX): # remove the initial search prefix items[0] = items[0].replace(SEARCH_PREFIX, '', 1) search_text = SearchSeparators.REGULAR.join(items) elif items[0].startswith(FILE_PREFIX): # strip away the initial file: prefix, if present items[0] = items[0].replace(FILE_PREFIX, '', 1) # normalize the path to strip duplicate path separators, etc items[0] = normpath(items[0]) else: # strip away the initial apt: prefix, if present items[0] = re.sub(PACKAGE_PREFIX_REGEX, '', items[0]) if len(items) > 1: # turn multiple packages into a search with "," as separator search_text = SearchSeparators.PACKAGE.join(items) if not search_text and len(items) == 1: request = items[0] # are we dealing with a path? if os.path.exists(request) and not os.path.isdir(request): if not request.startswith('/'): # we may have been given a relative path request = os.path.abspath(request) # will raise DebOpenFileError if request is invalid app = DebFileApplication(request) else: # package from archive # if there is a "/" in the string consider it as tuple # of (pkgname, appname) for exact matching (used by # e.g. unity (pkgname, sep, appname) = request.partition("/") if pkgname or appname: app = Application(appname, pkgname) else: LOG.warning('show_available_packages: received %r but ' 'can not build an Application from it.', request) return search_text, app class SoftwarecenterDbusController(dbus.service.Object): """ This is a helper to provide the SoftwarecenterIFace It provides only a bringToFront method that takes additional arguments about what packages to show """ def __init__(self, parent, bus_name, object_path='/com/ubuntu/Softwarecenter'): dbus.service.Object.__init__(self, bus_name, object_path) self.parent = parent def stop(self): """ stop the dbus controller and remove from the bus """ self.remove_from_connection() @dbus.service.method('com.ubuntu.SoftwarecenterIFace', utf8_strings=True) def bringToFront(self, args): if args != 'nothing-to-show': self.parent.show_available_packages(args) self.parent.window_main.present() return True @dbus.service.method('com.ubuntu.SoftwarecenterIFace') def triggerDatabaseReopen(self): self.parent.db.emit("reopen") @dbus.service.method('com.ubuntu.SoftwarecenterIFace') def triggerCacheReload(self): self.parent.cache.emit("cache-ready") @dbus.service.method('com.ubuntu.SoftwarecenterIFace') def writeMemoryDump(self): self.parent.write_memory_dump() class SoftwareCenterAppGtk3(SimpleGtkbuilderApp): WEBLINK_URL = "http://apt.ubuntu.com/p/%s" # the size of the icon for dialogs APP_ICON_SIZE = Gtk.IconSize.DIALOG START_DBUS = True def __init__(self, options, args=None): self.dbusController = None if self.START_DBUS: # setup dbus and exit if there is another instance already running self.setup_dbus_or_bring_other_instance_to_front(args) self.datadir = softwarecenter.paths.datadir super(SoftwareCenterAppGtk3, self).__init__( os.path.join(self.datadir, "ui", "gtk3", "SoftwareCenter.ui"), "software-center") gettext.bindtextdomain("software-center", "/usr/share/locale") gettext.textdomain("software-center") init_locale() if SOFTWARE_CENTER_DEBUG_TABS: self.notebook_view.set_show_tabs(True) # distro specific stuff self.distro = get_distro() # setup proxy self._setup_proxy_initially() # Disable software-properties if it does not exist if not os.path.exists("/usr/bin/software-properties-gtk"): self.menuitem_software_sources.set_sensitive(False) with ExecutionTime("opening the pkginfo"): # a main iteration friendly apt cache self.cache = get_pkg_info() # cache is opened later in run() self.cache.connect("cache-broken", self._on_apt_cache_broken) xapian_base_path = softwarecenter.paths.XAPIAN_BASE_PATH with ExecutionTime("opening the xapiandb"): pathname = os.path.join(xapian_base_path, "xapian") self._use_axi = not options.disable_apt_xapian_index try: self.db = StoreDatabase(pathname, self.cache) 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())) if os.access(pathname, os.W_OK): self._rebuild_and_reopen_local_db(pathname) 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): self._rebuild_and_reopen_local_db(pathname) except xapian.DatabaseCorruptError: LOG.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) # additional icons come from app-install-data with ExecutionTime("building the icon cache"): self.icons = get_sc_icon_theme() # backend 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("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) # misc state self._block_menuitem_view = False # for use when viewing previous purchases self.scagent = None self.sso = None self.available_for_me_query = get_reinstall_previous_purchases_query() Gtk.Window.set_default_icon_name("softwarecenter") # inhibit the error-bell, Bug #846138... settings = Gtk.Settings.get_default() settings.set_property("gtk-error-bell", False) # initial css loading init_sc_css_provider(self.window_main, settings, Gdk.Screen.get_default()) # wire up the css provider to reconfigure on theme-changes self.window_main.connect("style-updated", self._on_style_updated, init_sc_css_provider, settings, Gdk.Screen.get_default()) # workaround broken engines (LP: #1021308) self.window_main.emit("style-updated") # register view manager and create view panes/widgets self.view_manager = ViewManager(self.notebook_view, options) with ExecutionTime("building panes"): self.global_pane = GlobalPane(self.view_manager, self.db, self.cache, self.icons) self.vbox1.pack_start(self.global_pane, False, False, 0) self.vbox1.reorder_child(self.global_pane, 1) # start with the toolbar buttons insensitive and don't make them # sensitive until the panel elements are ready self.global_pane.view_switcher.set_sensitive(False) # available pane self.available_pane = AvailablePane(self.cache, self.db, self.distro, self.icons, 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) # installed pane (view not fully initialized at this point) self.installed_pane = InstalledPane(self.cache, self.db, self.distro, self.icons) 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, self.db, self.distro, self.icons) self.view_manager.register(self.history_pane, ViewPages.HISTORY) # pending pane 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()) # specify the smallest allowable window size self.window_main.set_size_request(730, 470) # reviews with ExecutionTime("create review loader"): self.review_loader = get_review_loader(self.cache, self.db) self.review_loader.connect( "refresh-review-stats-finished", self.on_review_stats_loaded) self.review_loader.refresh_review_stats() #load usefulness votes from server when app starts 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.load_plugins() # setup window name and about information (needs branding) name = self.distro.get_app_name() self.window_main.set_title(name) self.aboutdialog.set_program_name(name) about_description = self.distro.get_app_description() self.aboutdialog.set_comments(about_description) # about dialog self.aboutdialog.connect("response", lambda dialog, rid: dialog.hide()) 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 self.menuitem_view_supported_only.set_label( self.distro.get_supported_filter_name()) # this will be set sensitive once a the availablepane is available self.menuitem_recommendations.set_sensitive(False) if not self.distro.DEVELOPER_URL: self.menu_help.remove(self.separator_developer) self.menu_help.remove(self.menuitem_developer) # Check if oneconf is available och = is_oneconf_available() if not och: self.menu_file.remove(self.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 if is_unity_running(): self.menuitem_add_to_launcher.set_active( self.config.add_to_unity_launcher) else: self.menu_view.remove(self.add_to_launcher_separator) self.menu_view.remove(self.menuitem_add_to_launcher) # run s-c-agent update if options.disable_buy or not self.distro.PURCHASE_APP_URL: self.menu_file.remove(self.menuitem_reinstall_purchases) else: # running the agent will trigger a db reload so we do it later GLib.timeout_add_seconds(3, self._run_software_center_agent) # keep the cache clean GLib.timeout_add_seconds(15, self._run_expunge_cache_helper) # check to see if a new recommendations profile upload is # needed and upload if necessary GLib.timeout_add_seconds(45, self._upload_recommendations_profile) # TODO: Remove the following two lines once we have remove repository # support in aptdaemon (see LP: #723911) self.menu_file.remove(self.menuitem_deauthorize_computer) # keep track of the current active pane self.active_pane = self.available_pane # launchpad integration help, its ok if that fails try: from gi.repository import LaunchpadIntegration LaunchpadIntegration.set_sourcepackagename("software-center") LaunchpadIntegration.add_items(self.menu_help, 3, True, False) 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 """ run_software_center_agent(self.db) def _run_expunge_cache_helper(self): """ helper that expires the piston-mini-client cache """ sc_expunge_cache = os.path.join( self.datadir, "expunge-cache.py") (pid, stdin, stdout, stderr) = GLib.spawn_async( [sc_expunge_cache, "--by-unsuccessful-http-states", softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR, ]) def _rebuild_and_reopen_local_db(self, pathname): """ helper that rebuilds a db and reopens it """ from softwarecenter.db.update import rebuild_database LOG.info("building local database") # debian_sources is a bit misnamed, it will look for annotated # desktop files in /usr/share/app-install - enabling this on # non-debian systems will do no harm debian_sources = True # the appstream sources, enabling this on non-appstream systems # will do no harm appstream_sources = True rebuild_database(pathname, debian_sources, appstream_sources) self.db = StoreDatabase(pathname, self.cache) self.db.open(use_axi=self._use_axi) def _setup_proxy_initially(self): from gi.repository import Gio self._setup_proxy() self._gsettings = Gio.Settings.new("org.gnome.system.proxy.http") self._gsettings.connect("changed", self._setup_proxy) def _setup_proxy(self, setting=None, key=None): try: proxy = get_http_proxy_string_from_gsettings() LOG.info("setting up proxy '%s'" % proxy) if proxy: os.environ["http_proxy"] = proxy else: os.environ.pop("http_proxy", None) 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_available_pane_created(self, widget): self.available_pane.searchentry.grab_focus() self._update_recommendations_menuitem( opted_in=self._get_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( "recommendations-opt-in", self._on_recommendations_opt_in) self.available_pane.cat_view.recommended_for_you_panel.connect( "recommendations-opt-out", self._on_recommendations_opt_out) self.menuitem_recommendations.set_sensitive(True) # set the main toolbar buttons sensitive self.global_pane.view_switcher.set_sensitive(True) def on_installed_pane_created(self, widget): # set the main toolbar buttons sensitive self.global_pane.view_switcher.set_sensitive(True) def _on_recommendations_opt_in(self, rec_panel): self._update_recommendations_menuitem(opted_in=True) def _on_recommendations_opt_out(self, rec_panel): self._update_recommendations_menuitem(opted_in=False) def _update_recommendations_menuitem(self, opted_in): if opted_in: self.menuitem_recommendations.set_label( _(u"Turn Off Recommendations")) else: self.menuitem_recommendations.set_label( _(u"Turn On Recommendations…")) @wait_for_apt_cache_ready def _upload_recommendations_profile(self): recommender_agent = self._get_recommender_agent() if recommender_agent.is_opted_in(): recommender_agent.post_submit_profile(self.db) def _get_recommender_agent(self): if not hasattr(self, "_recommender_agent"): self._recommender_agent = RecommenderAgent() return self._recommender_agent def on_review_stats_loaded(self, loader, reviews): LOG.debug("on_review_stats_loaded: '%s'" % len(reviews)) def destroy(self): """Destroy this instance and every used resource.""" self.window_main.destroy() # remove global instances of Managers self.app_manager.destroy() self.view_manager.destroy() if self.dbusController is not None: # ensure that the dbus controller is really gone self.dbusController.stop() def close_app(self): """ perform tasks like save-state etc when the application is exited """ # this may happen during the early initialization # when "app.run()" was called but has not finished setting up the # stuff yet, in this case its ok to just exit if Gtk.main_level() == 0: LOG.debug("closing before the regular main loop was run") sys.exit(0) # hide the main window early even if saving the state etc takes # a wee bit self.window_main.hide() self.save_state() self.destroy() # this will not throw exceptions in pygi but "only" log via g_critical # to the terminal but it might in the future so we add a handler here try: Gtk.main_quit() except: LOG.exception("Gtk.main_quit failed") # exit here explicitly to ensure that no further gtk event loops or # threads run and cause havoc on exit (LP: #914393) sys.exit(0) def on_window_main_key_press_event(self, widget, event): """ Define all the accelerator keys here - slightly messy, but the ones defined in the menu don't seem to work.. """ # close if ((event.keyval == Gdk.keyval_from_name("w") or event.keyval == Gdk.keyval_from_name("q")) and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_close.activate() # undo/redo if (event.keyval == Gdk.keyval_from_name("z") and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_edit.activate() if self.menuitem_undo.get_sensitive(): self.menuitem_undo.activate() if (event.keyval == Gdk.keyval_from_name("Z") and event.state == (Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK)): self.menuitem_edit.activate() if self.menuitem_redo.get_sensitive(): self.menuitem_redo.activate() # cut/copy/paste if (event.keyval == Gdk.keyval_from_name("x") and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_edit.activate() if (event.keyval == Gdk.keyval_from_name("c") and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_edit.activate() if self.menuitem_copy.get_sensitive(): self.menuitem_copy.activate() # copy web link if (event.keyval == Gdk.keyval_from_name("C") and 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() # select all if (event.keyval == Gdk.keyval_from_name("a") and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_edit.activate() if self.menuitem_select_all.get_sensitive(): self.menuitem_select_all.activate() # search if (event.keyval == Gdk.keyval_from_name("f") and event.state == Gdk.ModifierType.CONTROL_MASK): self.menuitem_edit.activate() if self.menuitem_search.get_sensitive(): self.menuitem_search.activate() # back if ((event.keyval == Gdk.keyval_from_name("bracketleft") and event.state == Gdk.ModifierType.CONTROL_MASK) or ((event.keyval == Gdk.keyval_from_name("Left") or event.keyval == Gdk.keyval_from_name("KP_Left")) and event.state == Gdk.ModifierType.MOD1_MASK)): # using the backspace key to navigate back has been disabled as it # has started to show dodgy side effects which I can't figure how # to deal with self.menuitem_view.activate() if self.menuitem_go_back.get_sensitive(): self.menuitem_go_back.activate() # forward if ((event.keyval == Gdk.keyval_from_name("bracketright") and event.state == Gdk.ModifierType.CONTROL_MASK) or ((event.keyval == Gdk.keyval_from_name("Right") or event.keyval == Gdk.keyval_from_name("KP_Right")) and event.state == Gdk.ModifierType.MOD1_MASK)): self.menuitem_view.activate() if self.menuitem_go_forward.get_sensitive(): self.menuitem_go_forward.activate() def on_window_main_button_press_event(self, widget, event): """ Implement back/forward navigation via mouse navigation keys using the same button codes as used in Nautilus. """ if event.button == MOUSE_EVENT_BACK_BUTTON: self.menuitem_view.activate() if self.menuitem_go_back.get_sensitive(): self.menuitem_go_back.activate() elif event.button == MOUSE_EVENT_FORWARD_BUTTON: self.menuitem_view.activate() if self.menuitem_go_forward.get_sensitive(): self.menuitem_go_forward.activate() def _on_style_updated(self, widget, init_css_callback, *args): init_css_callback(widget, *args) 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) return iconinfo.get_filename() # File Menu def on_menu_file_activate(self, menuitem): """Enable/disable install/remove""" LOG.debug("on_menu_file_activate") # reset it all self.menuitem_install.set_sensitive(False) self.menuitem_remove.set_sensitive(False) # get our active pane vm = get_viewmanager() if vm is None: return False self.active_pane = vm.get_view_widget(vm.get_active_view()) if self.active_pane is None: return False # determine the current app app = self.active_pane.get_current_app() if not app: return False # wait for the cache to become ready (if needed) if not self.cache.ready: GLib.timeout_add( 100, lambda: self.on_menu_file_activate(menuitem)) return False # 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: 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): return False 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: self.menuitem_remove.set_sensitive(True) elif pkg_state == PkgStates.UNINSTALLED and not error: self.menuitem_install.set_sensitive(True) 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 error): # when does this happen? pkg = self.cache[app.pkgname] installed = bool(pkg.installed) self.menuitem_install.set_sensitive(not installed) self.menuitem_remove.set_sensitive(installed) # return False to ensure that a possible GLib.timeout_add ends return False def _on_reinstall_purchased_login(self, sso, oauth_result): self._sso_login_successful = True # appmanager needs to know about the oauth token for the reinstall # previous purchases add_license_key call self.app_manager.oauth_token = oauth_result # the software-center-agent will ensure the previous-purchases # get merged in self._run_software_center_agent() # show spinner as this may take some time, the spinner will # automatically go away when a DB refreshes self.available_pane.show_appview_spinner() # show previous purchased self.available_pane.on_previous_purchases_activated( self.available_for_me_query) 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() # its ok to use the sync version here to get the token, this is # very quick helper = UbuntuSSO() token = helper.find_oauth_token_sync() if token: # trigger a software-center-agent run to ensure the merged in # purchases are fresh self._run_software_center_agent() # we already have the list of available items, so just show it # (no need for spinner here) self.available_pane.on_previous_purchases_activated( self.available_for_me_query) else: # see bug #773214 for the rationale, do not translate the appname #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.") #window = self.window_main.get_window() #xid = self.get_window().xid xid = 0 self.sso = get_login_backend(xid, appname, help_text) self.sso.connect( "login-successful", self._on_reinstall_purchased_login) self.sso.login() def on_menuitem_recommendations_activate(self, menu_item): rec_panel = self.available_pane.cat_view.recommended_for_you_panel if self._get_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_deauthorize_computer_activate(self, menuitem): # FIXME: need Ubuntu SSO username here # account_name = get_person_from_config() account_name = None # get a list of 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_purchases) if deauthorize: # clear the ubuntu SSO token for this account # FIXME: as this is a sync call it maybe slow so we should # probably provide a async() version of this as well clear_token_from_ubuntu_sso_sync(SOFTWARE_CENTER_NAME_KEYRING) # 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_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 # (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) def on_menuitem_sync_between_computers_activate(self, menuitem): if self.view_manager.get_active_view() != ViewPages.INSTALLED: pane = self.view_manager.set_active_view(ViewPages.INSTALLED) state = pane.state.copy() state.channel = AllInstalledChannel() page = None self.view_manager.display_page(pane, page, state) self.installed_pane.refresh_apps() get_oneconf_handler().sync_between_computers(True) def on_menuitem_install_activate(self, menuitem): app = self.active_pane.get_current_app() get_appmanager().request_action(app, [], [], AppActions.INSTALL) def on_menuitem_remove_activate(self, menuitem): app = self.active_pane.get_current_app() get_appmanager().request_action(app, [], [], AppActions.REMOVE) def on_menuitem_close_activate(self, widget): self.close_app() def on_window_main_delete_event(self, widget, event): self.close_app() # Edit Menu def on_menu_edit_activate(self, menuitem): """ Check whether the search field is focused and if so, focus some items """ edit_menu_items = [self.menuitem_undo, self.menuitem_redo, self.menuitem_copy, self.menuitem_copy_web_link, self.menuitem_paste, self.menuitem_delete, self.menuitem_select_all, self.menuitem_search] for item in edit_menu_items: item.set_sensitive(False) # get our active pane vm = get_viewmanager() if vm is None: return False self.active_pane = vm.get_view_widget(vm.get_active_view()) if (self.active_pane and self.active_pane.searchentry and self.active_pane.searchentry.get_visible()): # undo, redo, cut, copy, paste, delete, select_all sensitive # if searchentry is focused (and other more specific conditions) if self.active_pane.searchentry.is_focus(): if len(self.active_pane.searchentry._undo_stack) > 1: self.menuitem_undo.set_sensitive(True) if len(self.active_pane.searchentry._redo_stack) > 0: self.menuitem_redo.set_sensitive(True) self.menuitem_paste.set_sensitive(True) if self.active_pane.searchentry.get_text(): self.menuitem_delete.set_sensitive(True) self.menuitem_select_all.set_sensitive(True) # search sensitive if searchentry is not focused else: self.menuitem_search.set_sensitive(True) # weblink if self.active_pane: app = self.active_pane.get_current_app() if app and app.pkgname: self.menuitem_copy_web_link.set_sensitive(True) # details view if (self.active_pane and self.active_pane.is_app_details_view_showing()): self.menuitem_select_all.set_sensitive(True) desc = self.active_pane.app_details_view.desc if desc.get_selected_text(): self.menuitem_copy.set_sensitive(True) def on_menuitem_undo_activate(self, menuitem): self.active_pane.searchentry.undo() def on_menuitem_redo_activate(self, menuitem): self.active_pane.searchentry.redo() def on_menuitem_copy_activate(self, menuitem): if (self.active_pane and self.active_pane.is_app_details_view_showing()): self.active_pane.app_details_view.desc.copy_clipboard() def on_menuitem_paste_activate(self, menuitem): self.active_pane.searchentry.paste_clipboard() def on_menuitem_delete_activate(self, menuitem): self.active_pane.searchentry.set_text("") def on_menuitem_select_all_activate(self, menuitem): if (self.active_pane and self.active_pane.is_app_details_view_showing()): self.active_pane.app_details_view.desc.select_all() self.active_pane.app_details_view.desc.grab_focus() elif self.active_pane: self.active_pane.searchentry.select_region(0, -1) def on_menuitem_copy_web_link_activate(self, menuitem): app = self.active_pane.get_current_app() if app: display = Gdk.Display.get_default() selection = Gdk.Atom.intern("CLIPBOARD", False) clipboard = Gtk.Clipboard.get_for_display(display, selection) clipboard.set_text(self.WEBLINK_URL % app.pkgname, -1) def on_menuitem_search_activate(self, widget): if self.active_pane: self.active_pane.searchentry.grab_focus() self.active_pane.searchentry.select_region(0, -1) def on_menuitem_software_sources_activate(self, widget): # run software-properties-gtk window = self.window_main.get_window() if hasattr(window, 'xid'): xid = window.xid else: xid = 0 p = subprocess.Popen( ["/usr/bin/software-properties-gtk", "-n", "-t", str(xid)]) # Monitor the subprocess regularly GLib.timeout_add(100, self._poll_software_sources_subprocess, p) def _poll_software_sources_subprocess(self, popen): ret = popen.poll() if ret is None: # Keep monitoring return True # A return code of 1 means that the sources have changed if ret == 1: self.run_update_cache() # Stop monitoring return False # View Menu def on_menu_view_activate(self, menuitem): vm = get_viewmanager() if vm is None: self.menuitem_view_all.set_sensitive(False) self.menuitem_view_supported_only.set_sensitive(False) self.menuitem_go_back.set_sensitive(False) self.menuitem_go_forward.set_sensitive(False) return False left_sensitive = vm.back_forward.left.get_sensitive() self.menuitem_go_back.set_sensitive(left_sensitive) right_sensitive = vm.back_forward.right.get_sensitive() self.menuitem_go_forward.set_sensitive(right_sensitive) self.menuitem_view.blocked = True # get our active pane self.active_pane = vm.get_view_widget(vm.get_active_view()) if (self.active_pane and self.active_pane == self.available_pane or self.active_pane == self.installed_pane): self.menuitem_view_all.set_sensitive(True) self.menuitem_view_supported_only.set_sensitive(True) from softwarecenter.db.appfilter import get_global_filter supported_only = get_global_filter().supported_only self.menuitem_view_all.set_active(not supported_only) self.menuitem_view_supported_only.set_active(supported_only) else: self.menuitem_view_all.set_sensitive(False) self.menuitem_view_supported_only.set_sensitive(False) self.menuitem_view.blocked = False def on_menuitem_view_all_activate(self, widget): if self.menuitem_view.blocked: return from softwarecenter.db.appfilter import get_global_filter if get_global_filter().supported_only is True: get_global_filter().supported_only = False self.available_pane.refresh_apps() try: self.installed_pane.refresh_apps() except: # may not be initialised pass def on_menuitem_view_supported_only_activate(self, widget): if self.menuitem_view.blocked: return from softwarecenter.db.appfilter import get_global_filter if get_global_filter().supported_only is False: get_global_filter().supported_only = True self.available_pane.refresh_apps() try: self.installed_pane.refresh_apps() 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 len(ap.app_view.get_model()) == 0: #~ ap.navigation_bar.navigate_up_twice() #~ else: #~ ap.navigation_bar.navigate_up() #~ ap.on_application_selected(None, None) #~ # navigate up if the list page is empty #~ elif (ap and ap.is_applist_view_showing() and #~ len(ap.app_view.get_model()) == 0): #~ ap.navigation_bar.navigate_up() #~ ap.on_application_selected(None, None) def on_navhistory_back_action_activate(self, navhistory_back_action=None): vm = get_viewmanager() vm.nav_back() def on_navhistory_forward_action_activate(self, navhistory_forward_action=None): vm = get_viewmanager() vm.nav_forward() def on_menuitem_add_to_launcher_toggled(self, menu_item): self.config.add_to_unity_launcher = menu_item.get_active() # Help Menu def on_menuitem_about_activate(self, widget): self.aboutdialog.set_version(VERSION) self.aboutdialog.set_transient_for(self.window_main) self.aboutdialog.show() def on_menuitem_help_activate(self, menuitem): # run browser (pid, stdin, stdout, stderr) = GLib.spawn_async( ["yelp", "help: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) def _ask_and_repair_broken_cache(self): # wait until the window window is available if self.window_main.props.visible is False: GLib.timeout_add_seconds(1, self._ask_and_repair_broken_cache) return if dialogs.confirm_repair_broken_cache(self.window_main, self.datadir): self.backend.fix_broken_depends() def _on_apt_cache_broken(self, aptcache): self._ask_and_repair_broken_cache() def _on_transaction_finished(self, backend, result): """ callback when an application install/remove transaction (or a cache reload) has finished """ self.cache.open() def on_channels_changed(self, backend, res): """ callback when the set of software channels has changed """ LOG.debug("on_channels_changed %s" % res) if res: # reopen the database, this will ensure that the right signals # are send and triggers "refresh_apps" # and refresh the displayed app in the details as well self.db.reopen() # helper def run_update_cache(self): """update the apt cache (e.g. after new sources where added """ self.backend.reload() def update_app_list_view(self, channel=None): """Helper that updates the app view list """ if self.active_pane is None: return if channel is None and self.active_pane.is_category_view_showing(): return if channel: self.channel_pane.set_channel(channel) self.active_pane.refresh_apps() def _on_database_rebuilding_handler(self, is_rebuilding): LOG.debug("_on_database_rebuilding_handler %s" % is_rebuilding) self._database_is_rebuilding = is_rebuilding if is_rebuilding: pass else: # we need to reopen when the database finished updating self.db.reopen() def setup_database_rebuilding_listener(self): """ Setup system bus listener for database rebuilding """ self._database_is_rebuilding = False # get dbus try: bus = dbus.SystemBus() except: LOG.exception("could not get system bus") return # check if its currently rebuilding (most likely not, so we # just ignore errors from dbus because the interface try: proxy_obj = bus.get_object("com.ubuntu.Softwarecenter", "/com/ubuntu/Softwarecenter") iface = dbus.Interface(proxy_obj, "com.ubuntu.Softwarecenter") 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) # add signal handler bus.add_signal_receiver(self._on_database_rebuilding_handler, "DatabaseRebuilding", "com.ubuntu.Softwarecenter") def setup_dbus_or_bring_other_instance_to_front(self, args): """ This sets up a dbus listener """ try: bus = dbus.SessionBus() except: LOG.exception("could not initiate dbus") return # if there is another Softwarecenter running bring it to front # and exit, otherwise install the dbus controller try: proxy_obj = bus.get_object('com.ubuntu.Softwarecenter', '/com/ubuntu/Softwarecenter') iface = dbus.Interface(proxy_obj, 'com.ubuntu.SoftwarecenterIFace') if args: res = iface.bringToFront(args) else: # None can not be transported over dbus res = iface.bringToFront('nothing-to-show') # ensure that the running s-c is working if res is not True: LOG.info("found a running software-center on dbus, " "reconnecting") sys.exit() except dbus.DBusException: bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter', bus) self.dbusController = SoftwarecenterDbusController(self, bus_name) @wait_for_apt_cache_ready def show_app(self, app): """Show 'app' in the installed pane if is installed. If 'app' is not installed, show it in the available pane. """ if (app.pkgname in self.cache and self.cache[app.pkgname].installed): with ExecutionTime("installed_pane.init_view()"): self.installed_pane.init_view() with ExecutionTime("installed_pane.show_app()"): self.installed_pane.show_app(app) else: self.available_pane.init_view() self.available_pane.show_app(app) def show_available_packages(self, packages): """ Show packages given as arguments in the available_pane If the list of packages is only one element long show that, otherwise turn it into a comma separated search """ try: search_text, app = parse_packages_args(packages) except DebFileOpenError as e: LOG.exception("show_available_packages: can not open %r, error:", packages) dialogs.error(None, _("Error"), _("The file “%s†could not be opened.") % e.path) search_text = app = None LOG.debug('show_available_packages: search_text is %r, app is %r.', search_text, app) if search_text: self.available_pane.init_view() self.available_pane.searchentry.set_text(search_text) elif app is not None: self.show_app(app) else: # normal startup, show the lobby (it will have a spinner when # its not ready yet) - it will also initialize the view self.view_manager.set_active_view(ViewPages.AVAILABLE) def restore_state(self): (x, y) = self.config.app_window_size if x > 0 and y > 0: self.window_main.set_default_size(x, 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) screen_height = Gdk.Screen.height() screen_width = Gdk.Screen.width() self.window_main.set_default_size( min(int(.85 * screen_width), 1200), min(int(.85 * screen_height), 800)) if self.config.app_window_maximized: self.window_main.maximize() def save_state(self): LOG.debug("save_state") # this happens on a delete event, we explicitly save_state() there window = self.window_main.get_window() if window is None: return maximized = window.get_state() & Gdk.WindowState.MAXIMIZED self.config.app_window_maximized = maximized if not maximized: # size only matters when non-maximized size = self.window_main.get_size() self.config.app_window_size = [size[0], size[1]] self.config.write() def write_memory_dump(self, fname=None): # trigger this e.g. with: # dbus-send --print-reply --session --dest=com.ubuntu.Softwarecenter \ # /com/ubuntu/Softwarecenter \ # com.ubuntu.SoftwarecenterIFace.writeMemoryDump if fname is None: fname = "software-center_%s.meliae" % time.strftime( "%Y%m%d_%H%M%S") try: from meliae import scanner scanner.dump_all_objects(fname) except Exception: LOG.exception("write_memory_dump failed") def run(self, args): # show window as early as possible self.window_main.show_all() # delay cache open GLib.timeout_add(1, self.cache.open) # support both "pkg1 pkg" and "pkg1,pkg2" (and pkg1,pkg2 pkg3) if args: for (i, arg) in enumerate(args[:]): if "," in arg: args.extend(arg.split(",")) del args[i] # FIXME: make this more predictable and less random # show args when the app is ready self.show_available_packages(args) atexit.register(self.save_state) software-center-13.10/softwarecenter/ui/gtk3/__init__.py0000664000202700020270000000000012151440100023455 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/drawing.py0000664000202700020270000000475612151440100023377 0ustar dobeydobey00000000000000from math import pi as PI 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) def color_floats(spec): rgba = Gdk.RGBA() 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) 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. - fgcolor: an rgb of floats - bgcolor: an rgb of floats - mix_alpha: (0.0 - 1.0) the proportion of fgcolor mixed into bgcolor """ src_r, src_g, src_b = fgcolor.red, fgcolor.green, fgcolor.blue bg_r, bg_g, bg_b = bgcolor.red, bgcolor.green, bgcolor.blue # Source: http://en.wikipedia.org/wiki/Alpha_compositing r = ((1 - mix_alpha) * bg_r) + (mix_alpha * src_r) g = ((1 - mix_alpha) * bg_g) + (mix_alpha * src_g) 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.close_path() return def rounded_rect2(cr, x, y, w, h, radii): nw, ne, se, sw = radii nw = max(0, nw) ne = max(0, ne) se = max(0, se) sw = max(0, sw) 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() 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) cr.arc(r + x, r + y, r, 0, 360 * PI_OVER_180) cr.close_path() software-center-13.10/softwarecenter/ui/gtk3/models/0000755000202700020270000000000012224614354022656 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/models/__init__.py0000664000202700020270000000000012151440100024740 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/models/appstore2.py0000664000202700020270000004621312151440100025140 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2009,2010 Canonical # # Authors: # Michael Vogt, Matthew McGowan # # 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 GLib from gi.repository import Gtk, GdkPixbuf import logging import os from gettext import gettext as _ from softwarecenter.enums import (Icons, XapianValues) from softwarecenter.utils import ( ExecutionTime, SimpleFileDownloader, split_icon_ext, capitalize_first_word, utf8, unescape, ) from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.backend.reviews import get_review_loader from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR from softwarecenter.db.categories import ( category_subcat, category_cat, CategoriesParser, ) # global cache icons to speed up rendering _app_icon_cache = {} LOG = logging.getLogger(__name__) _FREE_AS_IN_BEER = ("0.00", "") class CategoryRowReference: """ A simple container for Category properties to be displayed in a AppListStore or AppTreeStore """ def __init__(self, untranslated_name, display_name, subcats, pkg_count): self.untranslated_name = untranslated_name self.display_name = GLib.markup_escape_text(utf8(display_name)) #self.subcategories = subcats self.pkg_count = pkg_count self.vis_count = pkg_count def __repr__(self): return "[CategoryRowReference: name=%s]" % self.untranslated_name class UncategorisedRowRef(CategoryRowReference): def __init__(self, untranslated_name=None, display_name=None, pkg_count=0): if untranslated_name is None: untranslated_name = 'Uncategorised' if display_name is None: display_name = _("Uncategorized") CategoryRowReference.__init__(self, untranslated_name, display_name, None, pkg_count) def __repr__(self): return "[UncategorizedRowReference: name=%s]" % self.untranslated_name class AppPropertiesHelper(GObject.GObject): """ Baseclass that contains common functions for our liststore/treestore, only useful for subclassing """ __gsignals__ = { "needs-refresh": (GObject.SignalFlags.RUN_LAST, None, (str, ), ), } def __init__(self, db, cache, icons, icon_size=48, global_icon_cache=False): GObject.GObject.__init__(self) self.db = db self.cache = cache # get all categories cat_parser = CategoriesParser(db) with ExecutionTime("cat_parser.parse_applications_menu()"): self.all_categories = cat_parser.parse_applications_menu() # reviews stats loader self.review_loader = get_review_loader(cache, db) # icon jazz self.icons = icons self.icon_size = icon_size self._missing_icon = None # delay this until actually needed if global_icon_cache: self.icon_cache = _app_icon_cache else: self.icon_cache = {} def _on_image_download_complete( self, downloader, image_file_path, pkgname): LOG.debug("download for '%s' complete" % image_file_path) try: pb = GdkPixbuf.Pixbuf.new_from_file_at_size(image_file_path, self.icon_size, self.icon_size) except GObject.GError as e: LOG.warn("Failed to get image file for '%s' (%s)", image_file_path, e) return # 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) 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) if url is not None: icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, icon_file_name) image_downloader = SimpleFileDownloader() image_downloader.connect('file-download-complete', self._on_image_download_complete, pkgname) image_downloader.download_file(url, icon_file_path) @property def missing_icon(self): # cache the 'missing icon' used in treeviews for apps without an icon if self._missing_icon is None: self._missing_icon = self.icons.load_icon(Icons.MISSING_APP, self.icon_size, 0) return self._missing_icon def update_availability(self, doc): doc.available = None doc.installed = None doc.purchasable = None self.is_installed(doc) def is_available(self, doc): if doc.available is None: pkgname = self.get_pkgname(doc) doc.available = ( (pkgname in self.cache and # compare here instead of assigning .candidate to # .available to avoid leaking candidates self.cache[pkgname].candidate is not None) or self.is_purchasable(doc)) return doc.available def is_installed(self, doc): if doc.installed is None: pkgname = self.get_pkgname(doc) doc.installed = (self.is_available(doc) and pkgname in self.cache and self.cache[pkgname].is_installed) return doc.installed def is_purchasable(self, doc): if doc.purchasable is None: doc.purchasable = (doc.get_value(XapianValues.PRICE) not in _FREE_AS_IN_BEER) return doc.purchasable def get_pkgname(self, doc): return self.db.get_pkgname(doc) def get_application(self, doc): return self.db.get_application(doc) def get_appname(self, doc): app = self.db.get_application(doc) return app.get_display_name(self.db, doc) def get_markup(self, doc): app = self.db.get_application(doc) # the logic is that "apps" are displayed normally # but "packages" are displayed with their summary as name if app.appname: appname = self.get_appname(doc) summary = capitalize_first_word(self.db.get_summary(doc)) else: appname = capitalize_first_word(self.db.get_summary(doc)) summary = self.get_pkgname(doc) return "%s\n%s" % ( GLib.markup_escape_text(appname), GLib.markup_escape_text(summary)) def get_display_price(self, doc): app = self.db.get_application(doc) details = app.get_details(self.db) return details.price def get_icon(self, doc): try: full_icon_file_name = self.db.get_iconname(doc) icon_file_name = split_icon_ext(full_icon_file_name) if icon_file_name: icon_name = icon_file_name if icon_name in self.icon_cache: return self.icon_cache[icon_name] # 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) if icon: self.icon_cache[icon_name] = icon return icon elif self.db.get_icon_download_url(doc): url = self.db.get_icon_download_url(doc) self._download_icon_and_show_when_ready( url, self.get_pkgname(doc), full_icon_file_name) # display the missing icon while the real one downloads self.icon_cache[icon_name] = self.missing_icon except GObject.GError as e: LOG.debug("get_icon returned '%s'" % e) return self.missing_icon def get_review_stats(self, doc): return self.review_loader.get_review_stats(self.get_application(doc)) def get_transaction_progress(self, doc): pkgname = self.get_pkgname(doc) if pkgname in self.backend.pending_transactions: return self.backend.pending_transactions[pkgname].progress return -1 def _category_translate(self, catname): """ 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 for cat in self.all_categories: if cat.untranslated_name == catname: return cat.name # try normal translation first translated_catname = _(catname) if translated_catname == catname: # if no normal translation is found, try to find a escaped # translation (LP: #872760) translated_catname = _(GLib.markup_escape_text(catname)) # the parent expect the string unescaped translated_catname = unescape(translated_catname) return translated_catname def get_categories(self, doc): categories = doc.get_value(XapianValues.CATEGORIES).split(';') or [] if categories and categories[0].startswith('DEPARTMENT:'): return _(categories[0].split('DEPARTMENT:')[1]) for key in category_subcat: if key in categories: visible_category = category_subcat[key].split(';')[1] return self._category_translate(visible_category) for key in category_cat: if key in categories: visible_category = category_cat[key] return self._category_translate(visible_category) if categories: return _('System') else: return '' def get_icon_at_size(self, doc, width, height): pixbuf = self.get_icon(doc) pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR) return pixbuf class AppGenericStore(AppPropertiesHelper): # column types COL_TYPES = (GObject.TYPE_PYOBJECT,) # column id COL_ROW_DATA = 0 # default icon size displayed in the treeview ICON_SIZE = 32 # the amount of items to initially lo LOAD_INITIAL = 75 def __init__(self, db, cache, icons, icon_size, global_icon_cache): 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) # keep track of paths for transactions in progress self.transaction_path_map = {} # active row path self.active_row = None self._in_progress = False self._break = False # other stuff self.active = False # FIXME: port from @property def installable_apps(self): return [] @property def existing_apps(self): return [] def notify_action_request(self, doc, path): pkgname = str(self.get_pkgname(doc)) self.transaction_path_map[pkgname] = (path, self.get_iter(path)) 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): #~ self._refresh_transaction_map() pass def _on_transaction_progress_changed(self, backend, pkgname, progress): if pkgname in self.transaction_path_map: path, it = self.transaction_path_map[pkgname] self.row_changed(path, it) def _on_transaction_finished(self, backend, result): pkgname = str(result.pkgname) if pkgname in self.transaction_path_map: path, it = self.transaction_path_map[pkgname] doc = self.get_value(it, self.COL_ROW_DATA) self.update_availability(doc) self.row_changed(path, it) del self.transaction_path_map[pkgname] def buffer_icons(self): def buffer_icons(): #~ print "Buffering icons ..." #t0 = GObject.get_current_time() if self.current_matches is None: return False db = self.db.xapiandb for m in self.current_matches: doc = db.get_document(m.docid) # calling get_icon is enough to cache the icon self.get_icon(doc) while Gtk.events_pending(): Gtk.main_iteration() #~ import sys #~ t_lapsed = round(GObject.get_current_time() - t0, 3) #~ 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) return False # remove from sources on completion if self.current_matches is not None: GLib.idle_add(buffer_icons) def load_range(self, indices, step): # stub pass class AppListStore(Gtk.ListStore, AppGenericStore): """ use for flat applist views. for large lists this appends rows approx three times faster than the AppTreeStore equivalent """ __gsignals__ = { "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, ), ), } 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.ListStore.__init__(self) self.set_column_types(self.COL_TYPES) self.current_matches = None def set_from_matches(self, matches): """ set the content of the liststore based on a list of xapian.MSetItems """ LOG.debug("set_from_matches len(matches)='%s'" % len(matches)) self.current_matches = matches n_matches = len(matches) if n_matches == 0: return extent = min(self.LOAD_INITIAL, n_matches) with ExecutionTime("store.append_initial"): for doc in [m.document for m in matches][:extent]: doc.available = doc.installed = doc.purchasable = None self.append((doc,)) if n_matches == extent: return with ExecutionTime("store.append_placeholders"): for i in range(n_matches - extent): self.append() self.emit('appcount-changed', len(matches)) self.buffer_icons() def load_range(self, indices, step): LOG.debug("load_range: %s %s" % (indices, step)) db = self.db.xapiandb matches = self.current_matches n_matches = len(matches or []) start = indices[0] end = start + step if end >= n_matches: end = n_matches for i in range(start, end): try: row_content = self[(i,)][0] except IndexError as e: LOG.warn("failed to load rows: '%s'" % e) break if row_content: continue doc = db.get_document(matches[i].docid) doc.available = doc.installed = doc.purchasable = None self[(i,)][0] = doc def clear(self): # reset the transaction map because it will now be invalid self.transaction_path_map = {} self.current_matches = None Gtk.ListStore.clear(self) class AppTreeStore(Gtk.TreeStore, AppGenericStore): """ A treestore based application model """ __gsignals__ = { # meh, this is a signal from AppPropertiesHelper "needs-refresh": (GObject.SignalFlags.RUN_LAST, None, (str, ), ), } 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) def set_documents(self, parent, documents): for doc in documents: doc.available = None doc.installed = doc.purchasable = None self.append(parent, (doc,)) self.transaction_path_map = {} def set_category_documents(self, cat, documents): category = CategoryRowReference(cat.untranslated_name, cat.name, cat.subcategories, len(documents)) it = self.append(None, (category,)) self.set_documents(it, documents) return it def set_nocategory_documents(self, documents, untranslated_name=None, display_name=None): category = UncategorisedRowRef(untranslated_name, display_name, len(documents)) it = self.append(None, (category,)) self.set_documents(it, documents) return it def clear(self): # reset the transaction map because it will now be invalid self.transaction_path_map = {} Gtk.TreeStore.clear(self) software-center-13.10/softwarecenter/ui/gtk3/models/pendingstore.py0000664000202700020270000001772512151440100025730 0ustar dobeydobey00000000000000 from gi.repository import GLib from gi.repository import Gtk from gi.repository import GdkPixbuf import logging from softwarecenter.utils import get_icon_from_theme, utf8 from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.backend.transactionswatcher import get_transactions_watcher from gettext import gettext as _ class PendingStore(Gtk.ListStore): # column names (COL_TID, COL_ICON, COL_NAME, COL_STATUS, COL_PROGRESS, COL_PULSE, COL_CANCEL) = range(7) # column types column_types = (str, # COL_TID GdkPixbuf.Pixbuf, # COL_ICON 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 ICON_SIZE = 24 # for the progress pulse if a transaction is in the waiting state DO_PROGRESS_PULSE = 1 STOP_PROGRESS_PULSE = -1 def __init__(self, icons): # icon, status, progress Gtk.ListStore.__init__(self) self.set_column_types(self.column_types) self._transactions_watcher = get_transactions_watcher() self._transactions_watcher.connect("lowlevel-transactions-changed", self._on_lowlevel_transactions_changed) # data self.icons = icons # the apt-daemon stuff self.backend = get_install_backend() self._signals = [] # let the pulse helper run GLib.timeout_add(500, self._pulse_purchase_helper) def clear(self): super(PendingStore, self).clear() for sig in self._signals: GLib.source_remove(sig) 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))) self.clear() for tid in [current_tid] + pending_tids: if not tid: continue # we do this synchronous (it used to be a reply_handler) # otherwise we run into a race that # when we get two on_transaction_changed closely after each # other clear() is run before the "_append_transaction" handler # is run and we end up with two (or more) _append_transactions trans = self._transactions_watcher.get_transaction(tid) if trans: self._append_transaction(trans) # 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) 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]) def _pulse_purchase_helper(self): for item in self: if item[self.COL_PULSE] > 0: self[-1][self.COL_PULSE] += 1 return True def _append_transaction(self, trans): """Extract information about the transaction and append it to the store. """ logging.debug("_append_transaction %s (%s)" % (trans.tid, trans)) self._signals.append( trans.connect( "progress-details-changed", self._on_progress_details_changed)) self._signals.append( trans.connect("progress-changed", self._on_progress_changed)) self._signals.append( trans.connect("status-changed", self._on_status_changed)) self._signals.append( trans.connect( "cancellable-changed", self._on_cancellable_changed)) if "sc_appname" in trans.meta_data: appname = trans.meta_data["sc_appname"] elif "sc_pkgname" in trans.meta_data: appname = trans.meta_data["sc_pkgname"] else: #FIXME: Extract information from packages property appname = trans.get_role_description() self._signals.append( trans.connect("role-changed", self._on_role_changed)) try: iconname = trans.meta_data["sc_iconname"] 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) # if transaction is waiting, switch to indeterminate progress if trans.is_waiting(): status = trans.status_details pulse = self.DO_PROGRESS_PULSE else: status = trans.get_status_description() pulse = self.STOP_PROGRESS_PULSE 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), pulse, cancel_icon]) def _on_cancellable_changed(self, trans, cancellable): #print "_on_allow_cancel: ", trans, allow_cancel for row in self: if row[self.COL_TID] == trans.tid: row[self.COL_CANCEL] = self._get_cancel_icon(cancellable) def _get_cancel_icon(self, cancellable): if cancellable: return self.PENDING_STORE_ICON_CANCEL else: return self.PENDING_STORE_ICON_NO_CANCEL def _on_role_changed(self, trans, role): #print "_on_progress_changed: ", trans, role for row in self: if row[self.COL_TID] == trans.tid: row[self.COL_NAME] = trans.get_role_description(role) or "" def _on_progress_details_changed(self, trans, current_items, total_items, current_bytes, total_bytes, current_cps, eta): #print "_on_progress_details_changed: ", trans, progress for row in self: if row[self.COL_TID] == trans.tid: if trans.is_downloading(): name = row[self.COL_NAME] current_bytes_str = GLib.format_size(current_bytes) 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) def _on_progress_changed(self, trans, progress): # print "_on_progress_changed: ", trans, progress for row in self: if row[self.COL_TID] == trans.tid: if progress: row[self.COL_PROGRESS] = float(progress) def _on_status_changed(self, trans, status): #print "_on_progress_changed: ", trans, status for row in self: if row[self.COL_TID] == trans.tid: # FIXME: the spaces around %s are poor mans padding because # setting xpad on the cell-renderer seems to not work name = row[self.COL_NAME] if trans.is_waiting(): st = trans.status_details row[self.COL_PULSE] = self.DO_PROGRESS_PULSE else: st = trans.get_status_description(status) row[self.COL_PULSE] = self.STOP_PROGRESS_PULSE row[self.COL_STATUS] = self._render_status_text(name, st) def _render_status_text(self, name, status): if not name: name = "" return "%s\n%s" % (utf8(name), utf8(status)) software-center-13.10/softwarecenter/ui/gtk3/utils.py0000664000202700020270000001104412151440100023070 0ustar dobeydobey00000000000000# 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 #import gi #gi.require_version("Gtk", "3.0") import os import logging from gi.repository import Gtk import softwarecenter.paths from softwarecenter.paths import ICON_PATH, SOFTWARE_CENTER_ICON_CACHE_DIR 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 = softwarecenter.paths.datadir context = toplevel.get_style_context() theme_name = settings.get_property("gtk-theme-name").lower() if hasattr(toplevel, '_css_provider'): # check old provider, see if we can skip setting or remove old # style provider if toplevel._css_provider._theme_name == theme_name: return else: # clean up old css provider if exists context.remove_provider_for_screen(screen, toplevel._css_provider) # munge css path for theme-name css_path = os.path.join( datadir, "ui/gtk3/css/softwarecenter.%s.css" % theme_name) # if no css for theme-name try fallback css if not os.path.exists(css_path): css_path = os.path.join(datadir, "ui/gtk3/css/softwarecenter.css") if not os.path.exists(css_path): # 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!") LOG.warn(msg % css_path) return # things seem ok, now set the css provider for softwarecenter msg = "Softwarecenter style provider for %s Gtk theme: %s" LOG.debug(msg % (theme_name, css_path)) provider = Gtk.CssProvider() provider._theme_name = theme_name toplevel._css_provider = provider provider.load_from_path(css_path) context.add_provider_for_screen(screen, provider, 800) return css_path def get_sc_icon_theme(): # additional icons come from app-install-data datadir = softwarecenter.paths.datadir 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")) # 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 # themes contain all the icons we need # this *shouldn't* lead to any performance regressions path = '/usr/share/icons/Humanity' if os.path.exists(path): for subpath in os.listdir(path): subpath = os.path.join(path, subpath) if os.path.isdir(subpath): for subsubpath in os.listdir(subpath): subsubpath = os.path.join(subpath, subsubpath) if os.path.isdir(subsubpath): icons.append_search_path(subsubpath) # add the gnome-packagekit icons path = '/usr/share/gnome-packagekit/icons' if os.path.exists(path): icons.append_search_path(path) # make the local cache directory if it doesn't already exist icon_cache_dir = SOFTWARE_CENTER_ICON_CACHE_DIR if not os.path.exists(icon_cache_dir): try: # possible race, see lp #962927 os.makedirs(icon_cache_dir) except OSError: pass icons.append_search_path(icon_cache_dir) return icons software-center-13.10/softwarecenter/ui/gtk3/dialogs/0000755000202700020270000000000012224614354023015 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py0000664000202700020270000001023012151440100027166 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 Gtk import logging from gettext import gettext as _ import softwarecenter.paths from softwarecenter.ui.gtk3.dialogs import SimpleGtkbuilderDialog from softwarecenter.distro import get_distro from softwarecenter.enums import Icons from softwarecenter.ui.gtk3.views.pkgnamesview import PackageNamesView LOG = logging.getLogger(__name__) #FIXME: These need to come from the main app ICON_SIZE = 24 # for the unittests only _DIALOG = None def confirm_install(parent, 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) """ cache = db._aptcache distro = get_distro() appdetails = app.get_details(db) if not appdetails.pkg: return True 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, app, db, icons, primary, button_text, depends, cache) def confirm_remove(parent, app, db, icons): """ Confirm removing of the given app """ cache = db._aptcache distro = get_distro() appdetails = app.get_details(db) # FIXME: use # backend = get_install_backend() # backend.simulate_remove(app.pkgname) # once it works if not appdetails.pkg: return True if app.pkgname in distro.IMPORTANT_METAPACKAGES: displayed_depends = set([app.pkgname]) else: displayed_depends = cache.get_packages_removed_on_remove( appdetails.pkg) if not displayed_depends: return True (primary, button_text) = distro.get_removal_warning_text( db._aptcache, appdetails.pkg, app.name, displayed_depends) return _confirm_internal(parent, app, db, icons, primary, button_text, displayed_depends, cache) def _get_confirm_internal_dialog(parent, app, db, icons, primary, button_text, depends, cache): glade_dialog = SimpleGtkbuilderDialog( softwarecenter.paths.datadir, domain="software-center") dialog = glade_dialog.dialog_dependency_alert dialog.set_resizable(True) dialog.set_transient_for(parent) dialog.set_default_size(360, -1) # get icon for the app appdetails = app.get_details(db) icon_name = appdetails.icon 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, Gtk.IconSize.DIALOG) # set the texts 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.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 result = dialog.run() dialog.hide() return result == Gtk.ResponseType.ACCEPT software-center-13.10/softwarecenter/ui/gtk3/dialogs/__init__.py0000664000202700020270000001164312151440100025116 0ustar dobeydobey00000000000000# 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 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.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 unit testing only _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.set_default_size(380, -1) dialog.set_transient_for(parent) result = dialog.run() dialog.destroy() if result == Gtk.ResponseType.ACCEPT: return True return False class DetailsMessageDialog(Gtk.MessageDialog): """Message dialog with optional details expander""" def __init__(self, parent=None, title="", primary=None, secondary=None, details=None, buttons=Gtk.ButtonsType.OK, type=Gtk.MessageType.INFO): Gtk.MessageDialog.__init__(self, parent, 0, type, buttons, primary) self.set_title(title) if secondary: self.format_secondary_markup(secondary) if details: textview = Gtk.TextView() 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.add(textview) expand = Gtk.Expander().new(_("Details")) expand.add(scroll) expand.show_all() self.get_content_area().pack_start(expand, True, True, 0) if parent: self.set_modal(True) self.set_property("skip-taskbar-hint", True) def messagedialog(parent=None, title="", primary=None, secondary=None, details=None, 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, buttons=buttons) global _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, secondary=secondary, details=details, type=Gtk.MessageType.ERROR, alternative_action=alternative_action) if __name__ == "__main__": 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") print "res: ", res software-center-13.10/softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py0000664000202700020270000001234412151440100027220 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # 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 Gtk import logging from gettext import gettext as _ from softwarecenter.ui.gtk3.dialogs import SimpleGtkbuilderDialog from softwarecenter.distro import get_distro from softwarecenter.enums import Icons from softwarecenter.ui.gtk3.views.pkgnamesview import PackageNamesView import softwarecenter.ui.gtk3.dialogs as dialogs LOG = logging.getLogger(__name__) #FIXME: These need to come from the main app ICON_SIZE = 24 def deauthorize_computer(parent, datadir, db, icons, account_name, purchased_packages): """ Display a dialog to deauthorize the current computer for purchases """ cache = db._aptcache distro = get_distro() (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 dialog.set_resizable(True) dialog.set_transient_for(parent) dialog.set_default_size(360, -1) # use the icon for software-center in the dialog icon_name = "softwarecenter" 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, Gtk.IconSize.DIALOG) # set the texts 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.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: return True return False if __name__ == "__main__": import sys import os 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() Gtk.Window.set_default_icon_name("softwarecenter") from softwarecenter.db.pkginfo import get_pkg_info cache = get_pkg_info() cache.open() # xapian import xapian from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.db.database import StoreDatabase 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) purchased_packages = set() purchased_packages.add('file-roller') purchased_packages.add('alarm-clock') purchased_packages.add('pitivi') purchased_packages.add('chromium-browser') purchased_packages.add('cheese') purchased_packages.add('aisleriot') account_name = "max.fischer@rushmoreacademy.edu" deauthorize_computer(None, "./data", db, icons, account_name, purchased_packages) software-center-13.10/softwarecenter/ui/gtk3/dialogs/dialog_tos.py0000664000202700020270000000571412151440100025505 0ustar dobeydobey00000000000000# 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.widgets.spinner import SpinnerNotebook from softwarecenter.ui.gtk3.widgets.webkit import ScrolledWebkitWindow from softwarecenter.enums import SOFTWARE_CENTER_TOS_LINK_NO_HEADER 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(SOFTWARE_CENTER_TOS_LINK_NO_HEADER) 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 software-center-13.10/softwarecenter/ui/gtk3/review_gui_helper.py0000775000202700020270000015504412151440100025450 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # 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, Gtk, Gdk, GLib import datetime import gettext import logging import os import json import sys import tempfile import time import threading # py3 try: from urllib.request import urlopen urlopen # pyflakes from queue import Queue 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 import piston_mini_client from softwarecenter.paths import SOFTWARE_CENTER_CONFIG_DIR 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 import get_login_backend from softwarecenter.backend.reviews import Review from softwarecenter.db.database import Application from softwarecenter.gwibber_helper import GwibberHelper, GwibberHelperMock from softwarecenter.i18n import get_language from softwarecenter.ui.gtk3.SimpleGtkbuilderApp import SimpleGtkbuilderApp from softwarecenter.ui.gtk3.dialogs import SimpleGtkbuilderDialog from softwarecenter.ui.gtk3.widgets.stars import ReactiveStar from softwarecenter.utils import make_string_from_list, utf8 from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.backend.piston.rnrclient_pristine import ReviewRequest # get current distro and set default server root distro = get_distro() SERVER_ROOT = distro.REVIEWS_SERVER # server status URL 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" 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, ), ), # send when a transmit was successful "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), ), } def __init__(self, token): super(GRatingsAndReviews, self).__init__() # piston worker thread self.worker_thread = Worker(token) self.worker_thread.start() GLib.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.worker_thread._transmit_error_str) self.worker_thread._transmit_state = TRANSMIT_STATE_NONE return True class Worker(threading.Thread): def __init__(self, token): # init parent threading.Thread.__init__(self) self.pending_reviews = Queue() self.pending_reports = Queue() self.pending_usefulness = Queue() self.pending_modify = Queue() self.pending_delete = Queue() self.pending_server_status = Queue() self._shutdown = False # FIXME: instead of a binary value we need the state associated # with each request from the queue self._transmit_state = TRANSMIT_STATE_NONE 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"]) # change default server to the SSL one distro = get_distro() service_root = distro.REVIEWS_SERVER self.rnrclient = RatingsAndReviewsAPI(service_root=service_root, auth=auth) def run(self): """Main thread run interface, logs into launchpad and waits for commands """ logging.debug("worker thread run") # loop self._wait_for_commands() def shutdown(self): """Request shutdown""" self._shutdown = True def _wait_for_commands(self): """internal helper that waits for commands""" while True: #logging.debug("worker: _wait_for_commands") self._submit_reviews_if_pending() self._submit_reports_if_pending() self._submit_usefulness_if_pending() self._submit_modify_if_pending() self._submit_delete_if_pending() time.sleep(0.2) if (self._shutdown and self.pending_reviews.empty() and self.pending_usefulness.empty() and self.pending_reports.empty() and self.pending_modify.empty() and self.pending_delete.empty()): return def _submit_usefulness_if_pending(self): """ the actual usefulness function """ while not self.pending_usefulness.empty(): logging.debug("POST usefulness") self._transmit_state = TRANSMIT_STATE_INPROGRESS (review_id, is_useful) = self.pending_usefulness.get() try: res = self.rnrclient.submit_usefulness( review_id=review_id, useful=str(is_useful)) self._transmit_state = TRANSMIT_STATE_DONE sys.stdout.write(json.dumps(res)) except Exception as e: logging.exception("submit_usefulness failed") err_str = self._get_error_messages(e) self._transmit_error_str = err_str self._write_exception_html_log_if_needed(e) self._transmit_state = TRANSMIT_STATE_ERROR self.pending_usefulness.task_done() def _submit_modify_if_pending(self): """ the actual modify function """ while not self.pending_modify.empty(): logging.debug("_modify_review") self._transmit_state = TRANSMIT_STATE_INPROGRESS (review_id, review) = self.pending_modify.get() summary = review['summary'] review_text = review['review_text'] rating = review['rating'] try: res = self.rnrclient.modify_review(review_id=review_id, summary=summary, review_text=review_text, rating=rating) self._transmit_state = TRANSMIT_STATE_DONE sys.stdout.write(json.dumps(vars(res))) except Exception as e: logging.exception("modify_review") err_str = self._get_error_messages(e) 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() def _submit_delete_if_pending(self): """ the actual deletion """ while not self.pending_delete.empty(): logging.debug("POST delete") self._transmit_state = TRANSMIT_STATE_INPROGRESS review_id = self.pending_delete.get() try: res = self.rnrclient.delete_review(review_id=review_id) self._transmit_state = TRANSMIT_STATE_DONE sys.stdout.write(json.dumps(res)) except Exception as e: logging.exception("delete_review failed") self._write_exception_html_log_if_needed(e) self._transmit_error_str = _("Failed to delete review") self._transmit_state = TRANSMIT_STATE_ERROR self.pending_delete.task_done() def _submit_reports_if_pending(self): """ the actual report function """ while not self.pending_reports.empty(): logging.debug("POST report") self._transmit_state = TRANSMIT_STATE_INPROGRESS (review_id, summary, text) = self.pending_reports.get() try: res = self.rnrclient.flag_review(review_id=review_id, reason=summary, text=text) self._transmit_state = TRANSMIT_STATE_DONE sys.stdout.write(json.dumps(res)) except Exception as e: logging.exception("flag_review failed") err_str = self._get_error_messages(e) self._transmit_error_str = err_str self._write_exception_html_log_if_needed(e) self._transmit_state = TRANSMIT_STATE_ERROR self.pending_reports.task_done() def _write_exception_html_log_if_needed(self, e): # write out a "oops.html" if type(e) is piston_mini_client.APIError: 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 if hasattr(e, "body") and e.body: f.write(e.body) else: f.write(str(e)) # reviews def queue_review(self, review): """ queue a new review for sending to LP """ logging.debug("queue_review %s" % review) self.pending_reviews.put(review) def _submit_reviews_if_pending(self): """ the actual submit function """ while not self.pending_reviews.empty(): logging.debug("_submit_review") self._transmit_state = TRANSMIT_STATE_INPROGRESS review = self.pending_reviews.get() piston_review = ReviewRequest() piston_review.package_name = review.app.pkgname piston_review.app_name = review.app.appname piston_review.summary = review.summary piston_review.version = review.package_version piston_review.review_text = review.text piston_review.date = str(review.date) piston_review.rating = review.rating piston_review.language = review.language piston_review.arch_tag = get_current_arch() piston_review.origin = review.origin piston_review.distroseries = distro.get_codename() try: res = self.rnrclient.submit_review(review=piston_review) self._transmit_state = TRANSMIT_STATE_DONE # output the resulting ReviewDetails object as json so # that the parent can read it sys.stdout.write(json.dumps(vars(res))) except Exception as e: logging.exception("submit_review") err_str = self._get_error_messages(e) self._write_exception_html_log_if_needed(e) 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: logging.warning(e.body) error_msg = json.loads(e.body)['errors'] errs = error_msg["__all__"] err_str = _("Server's response was:") 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") logging.warning(e) else: 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 this method should be overridden if clients talk to a different server than rnr """ try: resp = urlopen(SERVER_STATUS_URL).read() if resp != "ok": return False except Exception as e: logging.error("exception from '%s': '%s'" % (SERVER_STATUS_URL, e)) return False return True class BaseApp(SimpleGtkbuilderApp): def __init__(self, datadir, uifile): SimpleGtkbuilderApp.__init__( self, os.path.join(datadir, "ui/gtk3", uifile), "software-center") # generic data self.token = None self.display_name = None self._login_successful = False self._whoami_token_reset_nr = 0 #persistent config configfile = os.path.join( SOFTWARE_CENTER_CONFIG_DIR, "submit_reviews.cfg") 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.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]) #submit error image self.submit_error_img = Gtk.Image() 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) #submit warn image self.submit_warn_img = Gtk.Image() 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]) def _get_parent_xid_for_login_window(self): # no get_xid() yet in gir world #return self.submit_window.get_window().get_xid() return "" def run(self): # initially display a 'Connecting...' page self.main_notebook.set_current_page(0) self.login_status_label.set_markup(_(u"Signing in\u2026")) self.status_spinner.start() self.submit_window.show() # now run the loop self.login() def quit(self, exitcode=0): sys.exit(exitcode) def _add_spellcheck_to_textview(self, textview): """ adds a spellchecker (if available) to the given Gtk.textview """ pass #~ try: #~ import gtkspell #~ # mvo: gtkspell.get_from_text_view() is broken, so we use this #~ # method instead, the second argument is the language to #~ # use (that is directly passed to pspell) #~ spell = gtkspell.Spell(textview, None) #~ except: #~ return #~ return spell def login(self, show_register=True): logging.debug("login()") login_window_xid = self._get_parent_xid_for_login_window() help_text = _("To review software or to report abuse you need to " "sign in to a Ubuntu Single Sign-On account.") self.sso = get_login_backend(login_window_xid, 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: self.sso.login_or_register() else: self.sso.login() def _login_canceled(self, sso): self.status_spinner.hide() 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 """ 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 # if its expired self.ssoapi.whoami() def _whoami_done(self, ssologin, result): logging.debug("_whoami_done") self.display_name = result["displayname"] self._create_gratings_api() self.login_successful(self.display_name) def _whoami_error(self, ssologin, e): logging.error("whoami error '%s'" % e) # show error self.status_spinner.hide() self.login_status_label.set_markup( '%s' % _("Failed to log in")) def login_successful(self, display_name): """ callback when the login was successful """ pass def on_button_cancel_clicked(self, button=None): # bring it down gracefully if hasattr(self, "api"): self.api.shutdown() while Gtk.events_pending(): Gtk.main_iteration() self.quit(1) def _create_gratings_api(self): self.api = GRatingsAndReviews(self.token) self.api.connect("transmit-start", self.on_transmit_start) self.api.connect("transmit-success", self.on_transmit_success) self.api.connect("transmit-failure", self.on_transmit_failure) 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)) 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.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 """ 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) self.submit_spinner.show() self.submit_spinner.start() self.label_transmit_status.set_text(message) elif type == "fail": self.status_hbox.pack_start(self.submit_error_img, False, False, 0) 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.detail_expander.show() elif type == "success": 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) elif type == "warning": self.status_hbox.pack_start(self.submit_warn_img, False, False, 0) self.status_hbox.reorder_child(self.submit_warn_img, 0) self.submit_warn_img.show() self.label_transmit_status.set_text(message) def _clear_status_imagery(self): self.detail_expander.hide() self.detail_expander.set_expanded(False) #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) if self.submit_error_img.get_window(): self.status_hbox.remove(self.submit_error_img) if self.submit_success_img.get_window(): self.status_hbox.remove(self.submit_success_img) if self.submit_warn_img.get_window(): self.status_hbox.remove(self.submit_warn_img) class SubmitReviewsApp(BaseApp): """ review a given application or package """ 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) SUMMARY_CHAR_LIMITS = (80, 60, 70) REVIEW_CHAR_LIMITS = (5000, 4900, 4950) #alert colours for character warning labels NORMAL_COLOUR = "000000" ERROR_COLOUR = "FF0000" SUBMIT_MESSAGE = _("Submitting Review") 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): 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) # additional icons come from app-install-data self.icons = Gtk.IconTheme.get_default() self.icons.append_search_path("/usr/share/app-install/icons/") self.submit_window.connect("destroy", self.on_button_cancel_clicked) self._add_spellcheck_to_textview(self.textview_review) self.star_rating = ReactiveStar() alignment = Gtk.Alignment.new(0.0, 0.5, 1.0, 1.0) 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() alignment.show_all() self.rating_hbox.pack_start(alignment, True, True, 0) self.rating_hbox.reorder_child(alignment, 0) self.rating_hbox.pack_start(self.star_caption, False, False, 0) self.rating_hbox.reorder_child(self.star_caption, 1) self.review_buffer = self.textview_review.get_buffer() self.detail_expander.hide() self.retrieve_api = RatingsAndReviewsAPI() # data self.app = app self.version = version self.origin = origin self.iconname = iconname self.action = action self.review_id = int(review_id) # parent xid #~ if parent_xid: #~ win = Gdk.Window.foreign_new(int(parent_xid)) #~ wnck_get_xid_from_pid(os.getpid()) #~ win = '' #~ self.review_buffer.set_text(str(win)) #~ if win: #~ self.submit_window.realize() #~ self.submit_window.get_window().set_transient_for(win) self.submit_window.set_position(Gtk.WindowPosition.MOUSE) self._confirm_cancel_yes_handler = 0 self._confirm_cancel_no_handler = 0 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.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() #cells[0].set_property("ellipsize", pango.ELLIPSIZE_END) self.gwibber_hbox.pack_start(self.gwibber_combo, True, True, 0) if "SOFTWARE_CENTER_GWIBBER_MOCK_USERS" in os.environ: 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 self.gwibber_prefs = self._get_gwibber_prefs() # gwibber stuff self._setup_gwibber_gui() #now setup rest of app based on whether submit or modify if self.action == "submit": self._init_submit() 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)) def _init_modify(self): self._populate_review() self.submit_window.set_title(_("Modify Your %(appname)s Review") % { 'appname': gettext.dgettext("app-install-data", self.app.name)}) self.button_post.set_label(_("Modify")) self.SUBMIT_MESSAGE = _("Updating your review") 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) 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 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 except piston_mini_client.APIError: 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): if app is None: logging.warning('Can not setup details since app is None.') return # icon shazam 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) self.review_appicon.set_from_pixbuf(icon) # title app = utf8(gettext.dgettext("app-install-data", app.name)) version = utf8(version) self.review_title.set_markup( '%s\n%s' % (app, version)) # review label 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'))) def _has_user_started_reviewing(self): summary_chars = self.review_summary_entry.get_text_length() review_chars = self.review_buffer.get_char_count() return summary_chars > 0 or review_chars > 0 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() if (summary_chars and summary_chars <= self.SUMMARY_CHAR_LIMITS[0] and review_chars and review_chars <= self.REVIEW_CHAR_LIMITS[0] and int(self.star_rating.get_rating()) > 0): self.button_post.set_sensitive(True) self._change_status("clear", "") 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 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)): 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): return False #compare review 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): return False return True def _check_summary_character_count(self): 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], summary_chars) self.summary_char_label.set_markup(markup) else: self.summary_char_label.set_text('') 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.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 current value between the min and max returns a pango color string """ markup = '%s' if curr > cmax: 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)) 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)) def _convert_rgb_to_html(self, r, g, b): return "%s%s%s" % ("%02X" % r, "%02X" % g, "%02X" % b) def on_button_post_clicked(self, button): logging.debug("enter_review ok button") review = Review(self.app) 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 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} self.api.modify_review(self.review_id, changes) def login_successful(self, display_name): self.main_notebook.set_current_page(1) self._setup_details(self.submit_window, self.app, self.iconname, self.version, display_name) self.textview_review.grab_focus() def _setup_gwibber_gui(self): self.gwibber_accounts = self.gwibber_helper.accounts() list_length = len(self.gwibber_accounts) if list_length == 0: self._on_no_gwibber_accounts() elif list_length == 1: self._on_one_gwibber_account() else: self._on_multiple_gwibber_accounts() def _get_gwibber_prefs(self): send = self.config.reviews_post_via_gwibber account_id = self.config.reviews_gwibber_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'])) 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() # 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']) 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"}) # reapply preferences self.gwibber_checkbutton.set_active(self.gwibber_prefs['gwibber_send']) gwibber_active_account = 0 for account in self.gwibber_accounts: if account['id'] == self.gwibber_prefs['account_id']: gwibber_active_account = self.gwibber_accounts.index(account) self.gwibber_combo.set_active(gwibber_active_account) def _post_to_one_gwibber_account(self, msg, account): """ little helper to facilitate posting message to twitter account passed in """ status_text = _("Posting to %s") % utf8( account['service'].capitalize()) self._change_status("progress", status_text) 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) def _on_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: self._confirm_cancellation() def _confirm_cancellation(self): if (self._has_user_started_reviewing() and not self._displaying_cancel_confirmation): 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) self.main_notebook.set_current_page(1) self.response_hbuttonbox.set_visible(False) self.confirm_cancel_yes.grab_focus() self.main_notebook.set_current_page(2) self._displaying_cancel_confirmation = True if not self._confirm_cancel_yes_handler: tag = self.confirm_cancel_yes.connect("clicked", do_cancel) self._confirm_cancel_yes_handler = tag if not self._confirm_cancel_no_handler: tag = self.confirm_cancel_no.connect("clicked", undo_cancel) self._confirm_cancel_no_handler = tag else: self.submit_window.destroy() self.quit() 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 """ 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): """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 = [] for account in send_accounts: if account["id"] != "pseudo-sc-all": if not self._post_to_one_gwibber_account(msg, account): failed_accounts.append(account) return failed_accounts def _run_gwibber_submits(self, api, trans): """check if gwibber send should occur and send via gwibber if so""" gwibber_success = True using_gwibber = self.gwibber_checkbutton.get_active() if using_gwibber: i = self.gwibber_combo.get_active() msg = (self._gwibber_message()) 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) if len(failed_accounts) > 0: gwibber_success = False #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 # in config if the checkbutton was not selected self._save_gwibber_state(False, None) # run parent handler on gwibber success, otherwise this will be dealt # with in _on_gwibber_fail if gwibber_success: self._success_status() BaseApp.on_transmit_success(self, api, trans) def _gwibber_retry_some(self, api, trans, accounts): """ perform selective retrying of gwibber posting, using only accounts passed in """ 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 """ 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")) #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") 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 # "factbook and twister" error_str = gettext.ngettext( "There was a problem posting this review to %s.", "There was a problem posting this review to %s.", len(failed_services)) error_str = make_string_from_list(error_str, failed_services) dialog.set_markup(error_str) dialog.format_secondary_text(error) result = dialog.run() dialog.destroy() if result == Gtk.RESPONSE_ACCEPT: 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): self.config.reviews_post_via_gwibber = gwibber_send if account_id: self.config.reviews_gwibber_account_id = account_id self.config.write() 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} rating = self.star_rating.get_rating() rating_string = '' #fill star ratings for string 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 running ubuntu #app_link = "http://apt.ubuntu.com/p/%s" % self.app.pkgname 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) new_char_count = len(review_summary_text) - chars_to_reduce 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") def __init__(self, review_id, parent_xid, datadir): BaseApp.__init__(self, datadir, "report_abuse.ui") # status self._add_spellcheck_to_textview(self.textview_report) ## make button sensitive when textview has content self.textview_report.get_buffer().connect( "changed", self._enable_or_disable_report_button) # data self.review_id = review_id # title self.submit_window.set_title(_("Flag as Inappropriate")) # parent xid #if parent_xid: # #win = Gtk.gdk.window_foreign_new(int(parent_xid)) # if win: # self.submit_window.realize() # self.submit_window.window.set_transient_for(win) # mousepos 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.reorder_child(self.combobox_report_summary, 2) self.combobox_report_summary.show() 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?'. # This text refers to a possible reason for why the corresponding # review is being flagged as inappropriate. _("Offensive language"), # TRANSLATORS: The following is one entry in a combobox that is # 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"), # TRANSLATORS: The following is one entry in a combobox that is # 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"), # TRANSLATORS: The following is one entry in a combobox that is # 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")]: 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) def _enable_or_disable_report_button(self, widget): if (self.textview_report.get_buffer().get_char_count() > 0 and self.combobox_report_summary.get_active() != 0): self.button_post.set_sensitive(True) else: self.button_post.set_sensitive(False) def _setup_details(self, widget, display_name): # report label self.report_label.set_markup(_('Please give details:')) # review summary label self.report_summary_label.set_markup( _('Why is this review inappropriate?')) #error detail link label self.label_expander.set_markup('%s' % (_('Error Details'))) def on_button_post_clicked(self, button): logging.debug("report_abuse ok button") report_summary = self.combobox_report_summary.get_active_text() text_buffer = self.textview_report.get_buffer() report_text = text_buffer.get_text(text_buffer.get_start_iter(), text_buffer.get_end_iter(), include_hidden_chars=False) self.api.report_abuse(self.review_id, report_summary, report_text) def login_successful(self, display_name): logging.debug("login_successful") self.main_notebook.set_current_page(1) #self.label_reporter.set_text(display_name) self._setup_details(self.submit_window, display_name) 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 self.review_id = review_id self.is_useful = bool(is_useful) # no UI except for error conditions self.parent_xid = parent_xid # override behavior of baseapp here as we don't actually # have a UI by default def _get_parent_xid_for_login_window(self): return self.parent_xid def login_successful(self, display_name): 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() self.quit(2) # override parents run to only trigger login (and subsequent # events) but no UI, if this is commented out, there is some # stub ui that can be useful for testing def run(self): self.login() # 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 # (a) it isn't shown and (b) it's similar in usage BaseApp.__init__(self, datadir, "submit_usefulness.ui") # data self.review_id = review_id # no UI except for error conditions self.parent_xid = parent_xid # override behavior of baseapp here as we don't actually # have a UI by default def _get_parent_xid_for_login_window(self): return self.parent_xid def login_successful(self, display_name): logging.debug("delete review") self.main_notebook.set_current_page(1) self.api.delete_review(self.review_id) def on_transmit_failure(self, api, trans, error): logging.warn("exiting - error: %s" % error) self.api.shutdown() self.quit(2) # override parents run to only trigger login (and subsequent # events) but no UI, if this is commented out, there is some # stub ui that can be useful for testing def run(self): self.login() # 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 software-center-13.10/softwarecenter/ui/gtk3/aptd_gtk3.py0000664000202700020270000000512212200237544023623 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 Gtk from aptdaemon.gtk3widgets import (AptMediumRequiredDialog, AptConfigFileConflictDialog) from softwarecenter.backend.installbackend import InstallBackendUI class InstallBackendUI(InstallBackendUI): def ask_config_file_conflict(self, old, new): dia = AptConfigFileConflictDialog(old, new) res = dia.run() dia.hide() dia.destroy() # send result to the daemon if res == Gtk.ResponseType.YES: return "replace" else: return "keep" def ask_medium_required(self, medium, drive): dialog = AptMediumRequiredDialog(medium, drive) res = dialog.run() dialog.hide() if res == Gtk.ResponseType.YES: return True else: return False def error(self, parent, primary, secondary, details=None, alternative_action=None): from dialogs import error res = "ok" res = error(parent=parent, primary=primary, secondary=secondary, details=details, alternative_action=alternative_action) if res == Gtk.ResponseType.YES: res = "yes" return res if __name__ == "__main__": from softwarecenter.backend.installbackend import get_install_backend from mock import Mock aptd = get_install_backend() aptd.ui = InstallBackendUI() # test config file prompt trans = Mock() res = aptd._config_file_conflict(trans, "/etc/group", "/etc/group-") print (res) # test medium required trans = Mock() res = aptd._medium_required(trans, "medium", "drive") print (res) # test error dialog trans = Mock() trans.error_code = 102 trans.error_details = "details" enum = 101 res = aptd._show_transaction_failed_dialog(trans, enum) print (res) software-center-13.10/softwarecenter/ui/gtk3/widgets/0000755000202700020270000000000012224614354023041 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/widgets/oneconfviews.py0000664000202700020270000001130712151440100026105 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Didier Roche # # 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 GdkPixbuf, GObject, Gtk import logging LOG = logging.getLogger(__name__) 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, (), ), } (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.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) self.col.add_attribute(hosticon_renderer, 'pixbuf', self.COL_ICON) self.col.pack_start(hostname_renderer, True) self.col.add_attribute(hostname_renderer, 'text', self.COL_HOSTNAME) self.append_column(self.col) self.current_hostid = None self.hostids = [] # 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() if not model: return if hostid in self.hostids: return hostid = hostid or '' # bug 905605 self.hostids.append(hostid) LOG.debug("register new computer: %s, %s" % (hostname, hostid)) 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 ''' 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) return iter_id = model.get_iter_first() while iter_id: if model.get_value(iter_id, self.COL_HOSTID) == hostid: model.remove(iter_id) self.hostids.remove(hostid) break iter_id = model.iter_next(iter_id) def on_cursor_changed(self, widget): (path, column) = self.get_cursor() if not path: return model = self.get_model() if not model: 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 else: return -1 software-center-13.10/softwarecenter/ui/gtk3/widgets/webkit.py0000664000202700020270000001671412151440100024674 0ustar dobeydobey00000000000000# 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 logging import os 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 from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR from softwarecenter.enums import WEBKIT_USER_AGENT_SUFFIX from softwarecenter.utils import get_oem_channel_descriptor from gi.repository import Soup from gi.repository import WebKit LOG = logging.getLogger(__name__) def global_webkit_init(): """ this sets the defaults for webkit, its important that this gets run if you want a secure webkit session """ session = WebKit.get_default_session() # add security by default (see bugzilla #666280 and #666276) # enable certificates validation in webkit views unless specified otherwise if not "SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK" in os.environ: session = webkit.get_default_session() session.set_property( "ssl-ca-file", "/etc/ssl/certs/ca-certificates.crt") else: # WARN the user!! Do not remove this LOG.warning("SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK " + "has been specified, all purchase transactions " + "are now INSECURE and UNENCRYPTED!!") # cookies by default fname = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "cookies.txt") # clear cookies again in a new session, see #1018347 comment #4 # there is no "logout" support right now on any of the USC pages try: os.remove(fname) except OSError: pass cookie_jar = Soup.CookieJarText.new(fname, False) session.add_feature(cookie_jar) # optional session debugging if "SOFTWARE_CENTER_DEBUG_WEBKIT" in os.environ: # alternatively you can use HEADERS, BODY here logger = Soup.Logger.new(Soup.LoggerLogLevel.BODY, -1) logger.attach(session) # ALWAYS run this or get insecurity by default global_webkit_init() class SoftwareCenterWebView(webkit.WebView): """ A customized version of the regular webview It will: - send Accept-Language headers from the users language - disable plugins - send a custom user-agent string - auto-fill in id_email in login.ubuntu.com """ # javascript to auto fill email login on login.ubuntu.com AUTO_FILL_SERVER = "https://login.ubuntu.com" AUTO_FILL_EMAIL_JS = """ document.getElementById("id_email").value="%s"; document.getElementById("id_password").focus(); """ def __init__(self): # actual webkit init webkit.WebView.__init__(self) self.connect("resource-request-starting", self._on_resource_request_starting) self.connect("notify::load-status", self._on_load_status_changed) settings = self.get_settings() settings.set_property("enable-plugins", False) settings.set_property("user-agent", self._get_user_agent_string()) self._auto_fill_email = "" def set_auto_insert_email(self, email): self._auto_fill_email = email def _get_user_agent_string(self): settings = self.get_settings() user_agent_string = settings.get_property("user-agent") user_agent_string += " %s " % WEBKIT_USER_AGENT_SUFFIX user_agent_string += get_oem_channel_descriptor() return user_agent_string 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) def _maybe_auto_fill_in_username(self): uri = self.get_uri() if self._auto_fill_email and uri.startswith(self.AUTO_FILL_SERVER): self.execute_script( self.AUTO_FILL_EMAIL_JS % self._auto_fill_email) # ensure that we have the keyboard focus self.grab_focus() def _on_load_status_changed(self, view, pspec): prop = pspec.name status = view.get_property(prop) if status == webkit.LoadStatus.FINISHED: self._maybe_auto_fill_in_username() class ScrolledWebkitWindow(Gtk.VBox): def __init__(self, include_progress_ui=False): super(ScrolledWebkitWindow, self).__init__() # get webkit self.webkit = SoftwareCenterWebView() # 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 irrelevant for the purchase view, but it is # interesting 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() software-center-13.10/softwarecenter/ui/gtk3/widgets/apptreeview.py0000664000202700020270000005747112151440100025747 0ustar dobeydobey00000000000000from gi.repository import Gtk, Gdk, GLib import logging from gettext import gettext as _ from softwarecenter.ui.gtk3.session.appmanager import get_appmanager from cellrenderers import (CellRendererAppView, CellButtonRenderer, CellButtonIDs) from softwarecenter.ui.gtk3.em import em, StockEms from softwarecenter.enums import (AppActions, Icons) from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.netstatus import (get_network_watcher, network_state_is_connected) from softwarecenter.ui.gtk3.models.appstore2 import ( AppGenericStore, CategoryRowReference) LOG = logging.getLogger(__name__) class AppTreeView(Gtk.TreeView): """Treeview based view component that takes a AppStore and displays it""" VARIANT_INFO = 0 VARIANT_REMOVE = 1 VARIANT_INSTALL = 2 VARIANT_PURCHASE = 3 ACTION_BTNS = (VARIANT_REMOVE, VARIANT_INSTALL, VARIANT_PURCHASE) def __init__(self, app_view, db, icons, show_ratings, store=None): Gtk.TreeView.__init__(self) self._logger = logging.getLogger("softwarecenter.view.appview") self.app_view = app_view self.db = db self.pressed = False self.focal_btn = None self._action_block_list = [] self._needs_collapse = [] self.expanded_path = None self.selected_row_renderer = None # pixbuf for the icon that is displayed in the selected row self.selected_row_icon = None #~ # if this hacked mode is available everything will be fast #~ # and we can set fixed_height mode and still have growing rows #~ # (see upstream gnome #607447) try: 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.set_headers_visible(False) # our custom renderer self._renderer = CellRendererAppView(icons, self.create_pango_layout(''), show_ratings, Icons.INSTALLED_OVERLAY) self._renderer.set_pixbuf_width(32) self._renderer.set_button_spacing(em(0.3)) # create buttons and set initial strings info = CellButtonRenderer(self, name=CellButtonIDs.INFO) info.set_markup_variants( {self.VARIANT_INFO: _('More Info')}) action = CellButtonRenderer(self, name=CellButtonIDs.ACTION) action.set_markup_variants( {self.VARIANT_INSTALL: _('Install'), self.VARIANT_REMOVE: _('Remove'), self.VARIANT_PURCHASE: _(u'Buy\u2026')}) self._renderer.button_pack_start(info) self._renderer.button_pack_end(action) self._column = Gtk.TreeViewColumn( "Applications", self._renderer, application=AppGenericStore.COL_ROW_DATA) self._column.set_cell_data_func( self._renderer, self._cell_data_func_cb) self._column.set_fixed_width(200) self._column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.append_column(self._column) # network status watcher watcher = get_network_watcher() watcher.connect("changed", self._on_net_state_changed, self._renderer) # custom cursor self._cursor_hand = Gdk.Cursor.new(Gdk.CursorType.HAND2) self.connect("style-updated", self._on_style_updated, self._renderer) # workaround broken engines (LP: #1021308) self.emit("style-updated") # button and motion are "special" self.connect("button-press-event", self._on_button_press_event, self._renderer) self.connect("button-release-event", self._on_button_release_event, self._renderer) self.connect("key-press-event", self._on_key_press_event, self._renderer) self.connect("key-release-event", self._on_key_release_event, self._renderer) self.connect("motion-notify-event", self._on_motion, self._renderer) self.connect("cursor-changed", self._on_cursor_changed, self._renderer) # our own "activate" handler self.connect("row-activated", self._on_row_activated, self._renderer) self.backend = get_install_backend() self._transactions_connected = False self.connect('realize', self._on_realize, self._renderer) @property def appmodel(self): model = self.get_model() # FIXME: would be nice if that would be less ugly, # because we use a treefilter we need to get the "real" # model first if isinstance(model, Gtk.TreeModelFilter): return model.get_model() return model def clear_model(self): vadjustment = self.get_scrolled_window_vadjustment() if vadjustment: vadjustment.set_value(0) self.expanded_path = None self._needs_collapse = [] if self.appmodel: # before clearing the model, disconnect it from the view. this # avoids that the model gets a "cursor_changed" signal for each # removed row and consequently that _update_selected_row is called # for rows that are not really selected (LP: #969050) model = self.get_model() self.set_model(None) model.clear() self.set_model(model) def set_model(self, model): # the set_cell_data_func() calls here are a workaround for bug # LP: #986186 - once that is fixed upstream we can revert this # and remove the entire "set_model" again self._column.set_cell_data_func(self._renderer, None) Gtk.TreeView.set_model(self, model) self._column.set_cell_data_func( self._renderer, self._cell_data_func_cb) 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" % type(path)) model = self.get_model() old = self.expanded_path self.expanded_path = path if old is not None: start, end = self.get_visible_range() or (None, None) if ((start and start.compare(old) != -1) or (end and end.compare(old) != 1)): self._needs_collapse.append(old) else: try: # try... a lazy solution to Bug #846204 model.row_changed(old, model.get_iter(old)) except: LOG.exception( "apptreeview.expand_path: Supplied 'old' " "path is an invalid tree path: '%s'" % old) if path is None: return model.row_changed(path, model.get_iter(path)) def get_scrolled_window_vadjustment(self): ancestor = self.get_ancestor(Gtk.ScrolledWindow) if ancestor: return ancestor.get_vadjustment() def get_rowref(self, model, path): if path is not None: return model[path][AppGenericStore.COL_ROW_DATA] def rowref_is_category(self, rowref): return isinstance(rowref, CategoryRowReference) def reset_action_button(self): """ Set the current row's action button sensitivity to the specified value """ if self.selected_row_renderer: action_btn = self.selected_row_renderer.get_button_by_name( CellButtonIDs.ACTION) if action_btn: action_btn.set_sensitive(True) pkgname = self.db.get_pkgname(self.selected_doc) self._check_remove_pkg_from_blocklist(pkgname) def _on_realize(self, widget, tr): # 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) self._transactions_connected = True def _calc_row_heights(self, tr): ypad = StockEms.SMALL tr.set_property('xpad', StockEms.MEDIUM) tr.set_property('ypad', ypad) for btn in tr.get_buttons(): # recalc button geometry and cache btn.configure_geometry(self.create_pango_layout("")) btn_h = btn.height tr.normal_height = max(32 + 2 * ypad, em(2.5) + ypad) tr.selected_height = tr.normal_height + btn_h + StockEms.MEDIUM def _on_style_updated(self, widget, tr): self._calc_row_heights(tr) def _on_motion(self, tree, event, tr): window = self.get_window() x, y = int(event.x), int(event.y) if not self._xy_is_over_focal_row(x, y): window.set_cursor(None) return path = tree.get_path_at_pos(x, y) if not path: window.set_cursor(None) return rowref = self.get_rowref(tree.get_model(), path[0]) if not rowref: return if self.rowref_is_category(rowref): window.set_cursor(None) return model = tree.get_model() app = model[path[0]][AppGenericStore.COL_ROW_DATA] if (not network_state_is_connected() and not self.appmodel.is_installed(app)): for btn_id in self.ACTION_BTNS: btn_id = tr.get_button_by_name(CellButtonIDs.ACTION) btn_id.set_sensitive(False) use_hand = False for btn in tr.get_buttons(): if btn.state == Gtk.StateFlags.INSENSITIVE: continue if btn.point_in(x, y): use_hand = True if self.focal_btn is btn: btn.set_state(Gtk.StateFlags.ACTIVE) elif not self.pressed: btn.set_state(Gtk.StateFlags.PRELIGHT) else: if btn.state != Gtk.StateFlags.NORMAL: btn.set_state(Gtk.StateFlags.NORMAL) if use_hand: window.set_cursor(self._cursor_hand) else: window.set_cursor(None) def _on_cursor_changed(self, view, tr): model = view.get_model() sel = view.get_selection() path = view.get_cursor()[0] rowref = self.get_rowref(model, path) if not rowref: return if self.has_focus(): self.grab_focus() if self.rowref_is_category(rowref): self.expand_path(None) return sel.select_path(path) self._update_selected_row(view, tr, path) def _update_selected_row(self, view, tr, path=None): # keep track of the currently selected row renderer and associated # doc for use when updating the widgets and for use with the Unity # integration feature self.selected_row_renderer = tr self.selected_doc = tr.application sel = view.get_selection() if not sel: return False model, rows = sel.get_selected_rows() if not rows: return False row = rows[0] if self.rowref_is_category(row): return False # update active app, use row-ref as argument self.expand_path(row) app = model[row][AppGenericStore.COL_ROW_DATA] # make sure this is not a category (LP: #848085) if self.rowref_is_category(app): return False action_btn = tr.get_button_by_name( CellButtonIDs.ACTION) #if not action_btn: return False if self.appmodel.is_installed(app): action_btn.set_variant(self.VARIANT_REMOVE) action_btn.set_sensitive(True) action_btn.show() elif self.appmodel.is_available(app): if self.appmodel.is_purchasable(app): action_btn.set_variant(self.VARIANT_PURCHASE) else: action_btn.set_variant(self.VARIANT_INSTALL) action_btn.set_sensitive(True) action_btn.show() if not network_state_is_connected(): action_btn.set_sensitive(False) self.app_view.emit("application-selected", self.appmodel.get_application(app)) return else: action_btn.set_sensitive(False) action_btn.hide() self.app_view.emit("application-selected", self.appmodel.get_application(app)) return if self.appmodel.get_transaction_progress(app) > 0: action_btn.set_sensitive(False) elif self.pressed and self.focal_btn == action_btn: action_btn.set_state(Gtk.StateFlags.ACTIVE) else: action_btn.set_state(Gtk.StateFlags.NORMAL) self.app_view.emit( "application-selected", self.appmodel.get_application(app)) return False def _on_row_activated(self, view, path, column, tr): rowref = self.get_rowref(view.get_model(), path) if not rowref: return elif self.rowref_is_category(rowref): return x, y = self.get_pointer() for btn in tr.get_buttons(): if btn.point_in(x, y): return app = self.appmodel.get_application(rowref) if app: self.app_view.emit("application-activated", app) def _on_button_event_get_path(self, view, event, allow_categories=False): if event.button != 1: return False res = view.get_path_at_pos(int(event.x), int(event.y)) 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: return False if is_cat: if allow_categories: return path else: return False # only act when the selection is already there selection = view.get_selection() if not selection.path_is_selected(path): return False return path def _on_button_press_event(self, view, event, tr): path = self._on_button_event_get_path(view, event, allow_categories=False) if not path: path = self._on_button_event_get_path(view, event, allow_categories=True) if path: if view.row_expanded(path): view.collapse_row(path) else: view.expand_row(path, True) return True # swallow event to avoid double action when # clicking on the expander arrow itself 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)): self.focal_btn = btn btn.set_state(Gtk.StateFlags.ACTIVE) view.queue_draw() return self.focal_btn = None def _on_button_release_event(self, view, event, tr): path = self._on_button_event_get_path(view, event) 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)): btn.set_state(Gtk.StateFlags.NORMAL) self.get_window().set_cursor(self._cursor_hand) if self.focal_btn is not btn: break self._init_activated(btn, view.get_model(), path) view.queue_draw() break self.focal_btn = None def _on_key_press_event(self, widget, event, tr): kv = event.keyval #print kv r = False if kv == Gdk.KEY_Right: # right-key btn = tr.get_button_by_name(CellButtonIDs.ACTION) 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 btn = tr.get_button_by_name(CellButtonIDs.ACTION) if btn is None: return # Bug #846779 btn.has_focus = False btn = tr.get_button_by_name(CellButtonIDs.INFO) btn.has_focus = True elif kv == Gdk.KEY_space: # spacebar for btn in tr.get_buttons(): if (btn is not None and btn.has_focus and btn.state != Gtk.StateFlags.INSENSITIVE): btn.set_state(Gtk.StateFlags.ACTIVE) sel = self.get_selection() model, it = sel.get_selected() path = model.get_path(it) if path: #self._init_activated(btn, self.get_model(), path) r = True break self.queue_draw() return r def _on_key_release_event(self, widget, event, tr): kv = event.keyval r = False if kv == 32: # spacebar for btn in tr.get_buttons(): if btn.has_focus and btn.state != Gtk.StateFlags.INSENSITIVE: btn.set_state(Gtk.StateFlags.NORMAL) sel = self.get_selection() model, it = sel.get_selected() path = model.get_path(it) if path: self._init_activated(btn, model, path) btn.has_focus = False r = True break self.queue_draw() return r def _init_activated(self, btn, model, path): app = model[path][AppGenericStore.COL_ROW_DATA] s = Gtk.Settings.get_default() GLib.timeout_add(s.get_property("gtk-timeout-initial"), self._app_activated_cb, btn, btn.name, app, path) def _cell_data_func_cb(self, col, cell, model, it, user_data): path = model.get_path(it) # this will pre-load data *only* on a AppListStore, it has # no effect with a AppTreeStore if model[path][0] is None: indices = path.get_indices() model.load_range(indices, 5) if path in self._needs_collapse: # collapse rows that were outside the visible range and # thus not immediately collapsed when expand_path was called cell.set_property('isactive', False) i = self._needs_collapse.index(path) del self._needs_collapse[i] model.row_changed(path, it) return # update active property cell.set_property('isactive', path == self.expanded_path) # update "text" property for a11y raw = model[path][AppGenericStore.COL_ROW_DATA] if self.rowref_is_category(raw): text = raw.display_name elif raw: text = self.db.get_pkgname(raw) else: # this can happen for empty/not-yet-loaded row, LP: #981992 text = "" cell.set_property('text', text) def _app_activated_cb(self, btn, btn_id, app, path): if self.rowref_is_category(app): return model = self.appmodel # don't continue if we don't have a valid model (LP: #969907) if not model: return pkgname = model.get_pkgname(app) if btn_id == CellButtonIDs.INFO: self.app_view.emit("application-activated", model.get_application(app)) elif btn_id == CellButtonIDs.ACTION: btn.set_sensitive(False) model.row_changed(path, model.get_iter(path)) app_manager = get_appmanager() # be sure we don't request an action for a pkg with # pre-existing actions if pkgname in self._action_block_list: logging.debug("Action already in progress for package:" " '%s'" % pkgname) return False self._action_block_list.append(pkgname) if model.is_installed(app): action = AppActions.REMOVE elif model.is_purchasable(app): app_manager.buy_app(model.get_application(app)) model.notify_action_request(app, path) return else: action = AppActions.INSTALL model.notify_action_request(app, path) app_manager.request_action( model.get_application(app), [], [], action) return False def _set_cursor(self, btn, cursor): # make sure we have a window instance (LP: #617004) window = self.get_window() if isinstance(window, Gdk.Window): x, y = self.get_pointer() 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 """ 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 """ # need to send a cursor-changed so the row button is properly updated self.emit("cursor-changed") # 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: action_btn.set_sensitive(True) 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 """ # 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 if action_btn.current_variant == self.VARIANT_INSTALL: action_btn.set_markup(self.VARIANT_REMOVE) action_btn.set_sensitive(True) self._set_cursor(action_btn, self._cursor_hand) def _on_net_state_changed(self, watcher, state, tr): self._update_selected_row(self, tr) # queue a draw just to be sure the view is looking right self.queue_draw() def _check_remove_pkg_from_blocklist(self, pkgname): if pkgname in self._action_block_list: i = self._action_block_list.index(pkgname) del self._action_block_list[i] def _xy_is_over_focal_row(self, x, y): res = self.get_path_at_pos(x, y) #cur = self.get_cursor() if not res: return False return self.get_path_at_pos(x, y)[0] == self.get_cursor()[0] software-center-13.10/softwarecenter/ui/gtk3/widgets/stars.py0000664000202700020270000003675012216112732024556 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # # 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 cairo import logging import gettext from gettext import gettext as _ from gi.repository import Gtk, Gdk, GObject from softwarecenter.ui.gtk3.em import StockEms, em, small_em, big_em _star_surface_cache = {} LOG = logging.getLogger(__name__) class StarSize: SMALL = 1 NORMAL = 2 BIG = 3 PIXEL_VALUE = 4 class StarFillState: FULL = 10 EMPTY = 20 class StarRenderHints: NORMAL = 1 REACTIVE = -1 class ShapeStar(object): 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): ShapeStar.__init__(self, 5, 0.6) self.size = StarSize.NORMAL self.n_stars = 5 self.spacing = 1 self.rounded = True self.rating = 3 self.hints = StarRenderHints.NORMAL self.pixel_value = None self._size_map = {StarSize.SMALL: small_em, StarSize.NORMAL: em, StarSize.BIG: big_em, StarSize.PIXEL_VALUE: self.get_pixel_size} # private def _get_mangled_keys(self, size): keys = (size * self.hints + StarFillState.FULL, size * self.hints + StarFillState.EMPTY) return keys # public def create_normal_surfaces(self, context, vis_width, vis_height, star_width): rgba1 = context.get_border_color(Gtk.StateFlags.NORMAL) rgba0 = context.get_color(Gtk.StateFlags.ACTIVE) lin = cairo.LinearGradient(0, 0, 0, vis_height) lin.add_color_stop_rgb(0, rgba0.red, rgba0.green, rgba0.blue) lin.add_color_stop_rgb(1, rgba1.red, rgba1.green, rgba1.blue) # paint full full_surf = cairo.ImageSurface( cairo.FORMAT_ARGB32, vis_width, vis_height) cr = cairo.Context(full_surf) cr.set_source(lin) cr.set_line_width(1) if self.rounded: cr.set_line_join(cairo.LINE_CAP_ROUND) for i in range(self.n_stars): x = 1 + i * (star_width + self.spacing) self.layout(cr, x + 1, 1, star_width - 2, vis_height - 2) cr.stroke_preserve() cr.fill() del cr # paint empty empty_surf = cairo.ImageSurface( cairo.FORMAT_ARGB32, vis_width, vis_height) cr = cairo.Context(empty_surf) cr.set_source(lin) cr.set_line_width(1) if self.rounded: cr.set_line_join(cairo.LINE_CAP_ROUND) for i in range(self.n_stars): x = 1 + i * (star_width + self.spacing) self.layout(cr, x + 1, 1, star_width - 2, vis_height - 2) cr.stroke() del cr return full_surf, empty_surf def create_reactive_surfaces(self, context, vis_width, vis_height, star_width): # paint full full_surf = cairo.ImageSurface( cairo.FORMAT_ARGB32, vis_width, vis_height) cr = cairo.Context(full_surf) if self.rounded: cr.set_line_join(cairo.LINE_CAP_ROUND) for i in range(self.n_stars): x = 1 + i * (star_width + self.spacing) self.layout(cr, x + 2, 2, star_width - 4, vis_height - 4) line_color = context.get_border_color(Gtk.StateFlags.NORMAL) cr.set_source_rgb(line_color.red, line_color.green, line_color.blue) cr.set_line_width(3) cr.stroke_preserve() cr.clip() context.save() context.add_class("button") context.set_state(Gtk.StateFlags.NORMAL) Gtk.render_background(context, cr, 0, 0, vis_width, vis_height) context.restore() for i in range(self.n_stars): x = 1 + i * (star_width + self.spacing) self.layout(cr, x + 1.5, 1.5, star_width - 3, vis_height - 3) cr.set_source_rgba(1, 1, 1, 0.8) cr.set_line_width(1) cr.stroke() del cr # paint empty empty_surf = cairo.ImageSurface( cairo.FORMAT_ARGB32, vis_width, vis_height) cr = cairo.Context(empty_surf) if self.rounded: cr.set_line_join(cairo.LINE_CAP_ROUND) line_color = context.get_border_color(Gtk.StateFlags.NORMAL) cr.set_source_rgb(line_color.red, line_color.green, line_color.blue) for i in range(self.n_stars): x = 1 + i * (star_width + self.spacing) self.layout(cr, x + 2, 2, star_width - 4, vis_height - 4) cr.set_line_width(3) cr.stroke() del cr return full_surf, empty_surf def update_cache_surfaces(self, context, size): LOG.debug('update cache') global _star_surface_cache star_width = vis_height = self._size_map[size]() vis_width = (star_width + self.spacing) * self.n_stars if self.hints == StarRenderHints.NORMAL: surfs = self.create_normal_surfaces(context, vis_width, vis_height, star_width) elif self.hints == StarRenderHints.REACTIVE: surfs = self.create_reactive_surfaces( context, vis_width, vis_height, star_width) # dict keys full_key, empty_key = self._get_mangled_keys(size) # save surfs to dict _star_surface_cache[full_key] = surfs[0] _star_surface_cache[empty_key] = surfs[1] return surfs def lookup_surfaces_for_size(self, size): full_key, empty_key = self._get_mangled_keys(size) if full_key not in _star_surface_cache: return None, None full_surf = _star_surface_cache[full_key] empty_surf = _star_surface_cache[empty_key] return full_surf, empty_surf def render_star(self, context, cr, x, y): size = self.size full, empty = self.lookup_surfaces_for_size(size) if full is None: full, empty = self.update_cache_surfaces(context, size) fraction = self.rating / self.n_stars stars_width = star_height = full.get_width() full_width = round(fraction * stars_width, 0) cr.rectangle(x, y, full_width, star_height) cr.clip() cr.set_source_surface(full, x, y) cr.paint() cr.reset_clip() if fraction < 1.0: empty_width = stars_width - full_width cr.rectangle(x + full_width, y, empty_width, star_height) cr.clip() cr.set_source_surface(empty, x, y) cr.paint() cr.reset_clip() def get_pixel_size(self): return self.pixel_value def get_visible_size(self, context): surf, _ = self.lookup_surfaces_for_size(self.size) if surf is None: surf, _ = self.update_cache_surfaces(context, self.size) return surf.get_width(), surf.get_height() class Star(Gtk.EventBox, StarRenderer): def __init__(self, size=StarSize.NORMAL): Gtk.EventBox.__init__(self) StarRenderer.__init__(self) self.set_name("featured-star") self.label = None self.size = size self.xalign = 0.5 self.yalign = 0.5 self._render_allocation_bbox = False self.set_visible_window(False) self.connect("draw", self.on_draw) self.connect("style-updated", self.on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") def do_get_preferred_width(self): context = self.get_style_context() pref_w, _ = self.get_visible_size(context) return pref_w, pref_w def do_get_preferred_height(self): context = self.get_style_context() _, pref_h = self.get_visible_size(context) return pref_h, pref_h def set_alignment(self, xalign, yalign): self.xalign = xalign self.yalign = yalign self.queue_draw() #~ def set_padding(*args): #~ return def get_alignment(self): return self.xalign, self.yalign #~ def get_padding(*args): #~ return def on_style_updated(self, widget): global _star_surface_cache _star_surface_cache = {} self.queue_draw() def on_draw(self, widget, cr): self.render_star(widget.get_style_context(), cr, 0, 0) if self._render_allocation_bbox: a = widget.get_allocation() cr.rectangle(0, 0, a.width, a.height) cr.set_source_rgb(1, 0, 0) cr.set_line_width(2) cr.stroke() def set_n_stars(self, n_stars): if n_stars == self.n_stars: return self.n_stars = n_stars global _star_surface_cache _star_surface_cache = {} self.queue_draw() def set_rating(self, rating): self.rating = float(rating) self.queue_draw() def set_avg_rating(self, rating): # compat for ratings container return self.set_rating(rating) def set_size(self, size): self.size = size self.queue_draw() def set_size_big(self): return self.set_size(StarSize.BIG) def set_size_small(self): return self.set_size(StarSize.SMALL) def set_size_normal(self): return self.set_size(StarSize.NORMAL) def set_use_rounded_caps(self, use_rounded): self.rounded = use_rounded global _star_surface_cache _star_surface_cache = {} self.queue_draw() def set_size_as_pixel_value(self, pixel_value): if pixel_value == self.pixel_value: return global _star_surface_cache keys = (StarSize.PIXEL_VALUE + StarFillState.FULL, StarSize.PIXEL_VALUE + StarFillState.EMPTY) for key in keys: if key in _star_surface_cache: del _star_surface_cache[key] self.pixel_value = pixel_value self.set_size(StarSize.PIXEL_VALUE) class StarRatingsWidget(Gtk.HBox): def __init__(self): Gtk.Box.__init__(self) self.set_spacing(StockEms.SMALL) self.stars = Star() self.stars.set_size_small() self.pack_start(self.stars, False, False, 0) self.label = Gtk.Label() self.label.set_alignment(0, 0.5) self.pack_start(self.label, False, False, 0) def set_avg_rating(self, rating): # compat for ratings container return self.stars.set_rating(rating) def set_nr_reviews(self, nr_reviews): s = gettext.ngettext( "%(nr_ratings)i rating", "%(nr_ratings)i ratings", nr_reviews) % {'nr_ratings': nr_reviews} # FIXME don't use fixed color m = '(%s)' self.label.set_markup(m % s) class ReactiveStar(Star): __gsignals__ = { "changed": (GObject.SignalFlags.RUN_LAST, None, (),) } def __init__(self, size=StarSize.BIG): Star.__init__(self, size) self.hints = StarRenderHints.REACTIVE self.set_rating(0) self.set_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) # signal handlers def on_enter_notify(self, widget, event): pass def on_leave_notify(self, widget, event): pass def on_button_press(self, widget, event): pass def on_button_release(self, widget, event): star_index = self.get_star_at_xy(event.x, event.y) if star_index is None: return self.set_rating(star_index) self.emit('changed') def on_key_press(self, widget, event): pass def on_key_release(self, widget, event): pass def on_focus_in(self, widget, event): pass def on_focus_out(self, widget, event): pass # public def get_rating(self): return self.rating def render_star(self, widget, cr, x, y): # paint focus StarRenderer.render_star(self, widget, cr, x, y) # if a star is hovered paint prelit star def get_star_at_xy(self, x, y, half_star_precision=False): star_width = self._size_map[self.size]() star_index = x / star_width remainder = 1.0 if half_star_precision: if round((x % star_width) / star_width, 1) <= 0.5: remainder = 0.5 if star_index > self.n_stars: return None return int(star_index) + remainder class StarRatingSelector(Gtk.Box): RATING_WORDS = [_('Hint: Click a star to rate this app'), # unrated _('Awful'), # 1 star rating _('Poor'), # 2 star rating _('Adequate'), # 3 star rating _('Good'), # 4 star rating _('Excellent')] # 5 star rating INIT_RATING = 3 N_STARS = 5 def __init__(self): Gtk.Box.__init__(self) self.set_spacing(StockEms.SMALL) self.selector = ReactiveStar() self.selector.set_n_stars(self.N_STARS) self.selector.set_rating(self.INIT_RATING) self.selector.set_size_as_pixel_value(big_em(3)) text = self.RATING_WORDS[self.INIT_RATING] self.caption = Gtk.Label.new(text) self.set_orientation(Gtk.Orientation.HORIZONTAL) self.pack_start(self.selector, False, False, 0) self.pack_start(self.caption, False, False, 0) software-center-13.10/softwarecenter/ui/gtk3/widgets/__init__.py0000664000202700020270000000000012151440100025123 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/widgets/searchentry.py0000664000202700020270000001233612151440100025732 0ustar dobeydobey00000000000000# coding: utf-8 # # SearchEntry - An enhanced search entry with timeout # # Copyright (C) 2007 Sebastian Heinlein # 2007-2009 Canonical Ltd. # # Authors: # Sebastian Heinlein # # 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, GObject, GLib from gettext import gettext as _ from softwarecenter.ui.gtk3.em import em class SearchEntry(Gtk.Entry): # FIXME: we need "can-undo", "can-redo" signals __gsignals__ = {'terms-changed': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,))} SEARCH_TIMEOUT = 600 def __init__(self, icon_theme=None): """ Creates an enhanced IconEntry that triggers a timeout when typing """ Gtk.Entry.__init__(self) self.set_width_chars(25) self.set_size_request(0, em(1.7)) if not icon_theme: icon_theme = Gtk.IconTheme.get_default() self._handler_changed = self.connect_after("changed", self._on_changed) self.connect("icon-press", self._on_icon_pressed) self.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, 'edit-find-symbolic') self.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None) # set sensible atk name atk_desc = self.get_accessible() atk_desc.set_name(_("Search")) # data self._timeout_id = 0 self._undo_stack = [""] self._redo_stack = [] def _on_icon_pressed(self, widget, icon, mouse_button): """ Emit the terms-changed signal without any time out when the clear button was clicked """ if icon == Gtk.EntryIconPosition.SECONDARY: # clear with no signal and emit manually to avoid the # search-timeout self.clear_with_no_signal() self.grab_focus() self.emit("terms-changed", "") elif icon == Gtk.EntryIconPosition.PRIMARY: self.select_region(0, -1) self.grab_focus() def undo(self): if len(self._undo_stack) <= 1: return # pop top element and push on redo stack text = self._undo_stack.pop() self._redo_stack.append(text) # the next element is the one we want to display text = self._undo_stack.pop() self.set_text(text) self.set_position(-1) def redo(self): if not self._redo_stack: return # just reply the redo stack text = self._redo_stack.pop() self.set_text(text) self.set_position(-1) def clear(self): self.set_text("") self._check_style() def set_text(self, text, cursor_to_end=True): Gtk.Entry.set_text(self, text) self.emit("move-cursor", Gtk.MovementStep.BUFFER_ENDS, 1, False) def set_text_with_no_signal(self, text): """Clear and do not send a term-changed signal""" self.handler_block(self._handler_changed) self.set_text(text) self.emit("move-cursor", Gtk.MovementStep.BUFFER_ENDS, 1, False) self.handler_unblock(self._handler_changed) def clear_with_no_signal(self): """Clear and do not send a term-changed signal""" self.handler_block(self._handler_changed) self.clear() self.handler_unblock(self._handler_changed) def _emit_terms_changed(self): text = self.get_text() # add to the undo stack once a term changes self._undo_stack.append(text) self.emit("terms-changed", text) def _on_changed(self, widget): """ Call the actual search method after a small timeout to allow the user to enter a longer search term """ self._check_style() if self._timeout_id > 0: GLib.source_remove(self._timeout_id) self._timeout_id = GLib.timeout_add(self.SEARCH_TIMEOUT, self._emit_terms_changed) def _check_style(self): """ Show the clear icon whenever the field is not empty """ if self.get_text() != "": self.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_CLEAR) # reverse the icon if we are in an rtl environment if self.get_direction() == Gtk.TextDirection.RTL: pb = self.get_icon_pixbuf( Gtk.EntryIconPosition.SECONDARY).flip(True) self.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, pb) else: self.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None) software-center-13.10/softwarecenter/ui/gtk3/widgets/spinner.py0000664000202700020270000001137112151440100025057 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # Authors: # Gary Lasker # Natalia Bidart # # 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, GLib from softwarecenter.enums import SOFTWARE_CENTER_DEBUG_TABS class SpinnerView(Gtk.Viewport): """A panel that contains a spinner with an optional legend. The spinner can be specified in one of two sizes, and defaults to the larger size. An optional label_text value can be specified for display with the spinner. """ # define spinner size options (LARGE, SMALL) = range(2) def __init__(self, label_text="", spinner_size=LARGE): Gtk.Viewport.__init__(self) self.spinner = Gtk.Spinner() if spinner_size not in (self.SMALL, self.LARGE): raise ValueError('The value of spinner_size must be ' 'one of SpinnerView.SMALL or SpinnerView.LARGE') if spinner_size == self.LARGE: self.spinner.set_size_request(48, 48) else: self.spinner.set_size_request(24, 24) # use a table for the spinner (otherwise the spinner is massive!) spinner_table = Gtk.Table(3, 3, False) self.spinner_label = Gtk.Label() self.spinner_label.set_markup('%s' % label_text) spinner_vbox = Gtk.VBox() spinner_vbox.pack_start(self.spinner, True, True, 0) spinner_vbox.pack_start(self.spinner_label, True, True, 10) spinner_table.attach(spinner_vbox, 1, 2, 1, 2, Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.EXPAND) #~ self.modify_bg(Gtk.StateType.NORMAL, Gdk.Color(1.0, 1.0, 1.0)) self.add(spinner_table) self.set_shadow_type(Gtk.ShadowType.NONE) def start_and_show(self): """Start the spinner and show it.""" self.spinner.start() self.spinner.show() def stop_and_hide(self): """Stop the spinner and hide it.""" self.spinner.stop() self.spinner.hide() def set_text(self, spinner_text=""): """Add/remove/change this spinner's label text.""" self.spinner_label.set_markup('%s' % spinner_text) def get_text(self): """Return the spinner's currently set label text.""" return self.spinner_label.get_text() class SpinnerNotebook(Gtk.Notebook): """ this provides a Gtk.Notebook that contains a content page and a spinner page. """ (CONTENT_PAGE, SPINNER_PAGE) = range(2) def __init__(self, content, msg="", spinner_size=SpinnerView.LARGE): Gtk.Notebook.__init__(self) self._last_timeout_id = None self.spinner_view = SpinnerView(msg, spinner_size) # its critical to show() the spinner early as otherwise # gtk_notebook_set_active_page() will not switch to it self.spinner_view.show() if not SOFTWARE_CENTER_DEBUG_TABS: self.set_show_tabs(False) self.set_show_border(False) self.append_page(content, Gtk.Label("content")) self.append_page(self.spinner_view, Gtk.Label("spinner")) def _unmask_view_spinner(self): # start is actually start_and_show() self.spinner_view.start_and_show() self.set_current_page(self.SPINNER_PAGE) self._last_timeout_id = None return False def show_spinner(self, msg=""): """ show the spinner page with a alternative message """ if msg: self.spinner_view.set_text(msg) # delay showing the spinner view prevent it from flashing into # view in the case of short delays where it isn't actually needed # (but only if its not already visible anyway) if self.get_current_page() == self.CONTENT_PAGE: self.spinner_view.stop_and_hide() self._last_timeout_id = GLib.timeout_add( 250, self._unmask_view_spinner) def hide_spinner(self): """ hide the spinner page again and show the content page """ if self._last_timeout_id is not None: GLib.source_remove(self._last_timeout_id) self._last_timeout_id = None self.spinner_view.stop_and_hide() self.set_current_page(self.CONTENT_PAGE) software-center-13.10/softwarecenter/ui/gtk3/widgets/weblivedialog.py0000664000202700020270000001063512151440100026220 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Michael Vogt # Stephane Graber # # 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 import sys from gettext import gettext as _ class ShowWebLiveServerChooserDialog(Gtk.Dialog): """A dialog to choose between multiple server""" def __init__(self, supplied_servers, pkgname, parent=None): Gtk.Dialog.__init__(self) #self.set_has_separator(False) # find parent window for the dialog if not parent: parent = self.get_parent() while parent: parent = parent.get_parent() # servers self.servers_vbox = Gtk.VBox(homogeneous=False, spacing=0) # Merge duplicate servers, keep the one with the most space servers = [] for server in supplied_servers: duplicate = False for otherserver in servers: if server.title == otherserver.title: percent_server = ((float(server.current_users) / float(server.userlimit)) * 100.0) percent_otherserver = ((float(otherserver.current_users) / float(otherserver.userlimit)) * 100.0) for package in server.packages: if package.pkgname == pkgname: autoinstall_server = package.autoinstall for package in otherserver.packages: if package.pkgname == pkgname: autoinstall_otherserver = package.autoinstall # Replace existing server if: # current server has more free slots and we don't switch # to a server requiring autoinstall # or doesn't need autoinstall but existing one does if ((percent_otherserver > percent_server and not autoinstall_otherserver < autoinstall_server)or autoinstall_otherserver > autoinstall_server): servers.remove(otherserver) servers.append(server) duplicate = True if duplicate: continue servers.append(server) if len(servers) == 1: self.show_dialog = False else: self.show_dialog = True button = Gtk.RadioButton() for server in sorted(servers, key=lambda server: server.title): button = Gtk.RadioButton.new_from_widget(button) button.set_label("%s - %s" % (server.title, server.description)) button.serverid = server.name self.servers_vbox.pack_start(button, True, True, 0) # dialog self.set_transient_for(parent) self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.get_content_area().add(self.servers_vbox) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) self.set_resizable(False) self.set_title(_("Choose your distribution")) self.set_border_width(8) def run(self): if self.show_dialog is False: return Gtk.ResponseType.OK self.show_all() # and run the real thing return Gtk.Dialog.run(self) if __name__ == "__main__": sys.path.append('../../../') from softwarecenter.backend.weblive_pristine import WebLive weblive = WebLive('https://weblive.stgraber.org/weblive/json', True) servers = weblive.list_everything() d = ShowWebLiveServerChooserDialog(servers, "gimp") if d.run() == Gtk.ResponseType.OK: for server in d.servers_vbox: if server.get_active(): print(server.serverid) break d.destroy() software-center-13.10/softwarecenter/ui/gtk3/widgets/actionbar.py0000664000202700020270000003271612151440100025351 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # Authors: # Jacob Johan Edwards # # 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 from gi.repository import Gtk, Gdk, GLib 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. 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 """ PADDING = 4 ANIMATE_START_DELAY = 50 ANIMATE_STEP_INTERVAL = 10 ANIMATE_STEP = 8 def __init__(self): super(ActionBar, self).__init__(spacing=self.PADDING) self._btns = Gtk.HBox(spacing=self.PADDING) self._btns.set_border_width(self.PADDING) self._label_hbox = Gtk.HBox() self._label_hbox.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.add(self._btns) # Buttons go on the right, labels on the left (in LTR mode) super(ActionBar, self).pack_start(self._label_hbox, False, False, 10) super(ActionBar, self).pack_end(self._btn_bin, False, True, 0) # Don't show_all() by default. self.set_no_show_all(True) self._label_hbox.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) self._is_sliding_in = False self._is_sliding_out = False self._target_height = None self.connect("draw", self._on_draw) def add_button(self, id, label, result, *result_args): """Adds a button and shows the bar. Keyword arguments: id -- A unique identifier for the button label -- A string for the button label result -- A function to be called on button click result_args -- Any arguments for the result function """ overwrite = self.get_button(id) if overwrite: self._btns.remove(overwrite) btn = Gtk.Button(label) btn.connect("clicked", self._callback(result, result_args)) btn.id = id btn.show() self._btns.pack_start(btn, False, True, 0) if not self._visible: # always animate with buttons self._show(animate=True) def remove_button(self, id): """Removes a button. Hides bar if no buttons left.""" 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 self.set_size_request(-1, self.get_allocation().height) self._btns.remove(child) if len(children) == 1 and not len(self._label_hbox): # always animate with buttons self._hide(animate=True) return def set_label(self, text, link_result=None, *link_result_args): """ Places a string on the left and shows the actionbar. Note the "_" symbol acts to delimit sections treated as a link. For example, in text="Lorem _ ipsum_ dolor _sit amat", clicking the section " ipsum" or "sit amat" will trigger the link_result. Keyword arguments: text -- a string optionally with "_" to indicate a link button. link_result -- A function to be called on link click link_result_args -- Any arguments for the result function """ sections = text.split("_") LOG.debug("got sections '%s'" % sections) self._label_text = text for i, text_for_label in enumerate(sections): action_bar_item = Gtk.Label(text_for_label) # every second item in the bar is a clickable link, # this is because the text.split("_") earlier (all links # are put into "_foo_" # FIXME: actually remove the "_" and use proper " # in the label, but this requires a string change # so we need to do it after 12.04 if not i % 2: markup = text_for_label action_bar_item.set_markup(markup) else: markup = '%s' % text_for_label action_bar_item.set_markup(markup) action_bar_item.connect("activate-link", self._callback(link_result, link_result_args)) self._label_hbox.pack_start(action_bar_item, True, True, 0) self._label_hbox.show_all() self._show(animate=False) def unset_label(self): """ Removes the currently set label, hiding the actionbar if no buttons are displayed. """ self._label_text = "" # Destroy all event boxes holding text segments. while len(self._label_hbox): last = self._label_hbox.get_children()[-1] self._label_hbox.remove(last) window = self.get_window() if window: window.set_cursor(None) # Then hide if there's nothing else visible. if not len(self._btns): self._hide(animate=False) def get_button(self, id): """Returns a button, or None if `id` links to no button.""" for child in self._btns.get_children(): if child.id == id: return child def clear(self): """Removes all contents and hides the bar.""" animate = len(self._btns.get_children()) > 0 self._hide(animate) for child in self._btns.get_children(): self._btns.remove(child) self.unset_label() # The following Gtk.Container methods are deimplemented to prevent # overwriting of the _elems children box, and because the only # intended contents of an ActionBar are buttons and perhaps a text # label. def add(self, widget): """Not Implemented: Do not call.""" raise NotImplementedError def add_with_properties(self, widget): """Not Implemented: Do not call.""" raise NotImplementedError def remove(self, widget): """Not Implemented: Do not call.""" raise NotImplementedError def pack_start(self, *args): """Not Implemented: Do not call.""" raise NotImplementedError def pack_default_start(self, *args): """Not Implemented: Do not call.""" raise NotImplementedError def pack_end(self, *args): """Not Implemented: Do not call.""" raise NotImplementedError def pack_default_end(self, *args): """Not Implemented: Do not call.""" raise NotImplementedError # Internal methods def _show(self, animate): if self._visible or self._is_sliding_in: return self._visible = True if animate: self._slide_in() else: super(ActionBar, self).show() def _hide(self, animate): if not self._visible or self._is_sliding_out: return if animate: self._slide_out() else: self.set_size_request(-1, -1) self._visible = False super(ActionBar, self).hide() def _slide_in(self): self._is_sliding_in = True self._target_height = self.get_size_request()[1] self._current_height = 0 self.set_size_request(-1, self._current_height) super(ActionBar, self).show() GLib.timeout_add(self.ANIMATE_START_DELAY, self._slide_in_cb) def _slide_out(self): self._is_sliding_out = True self._target_height = 0 self._current_height = self.get_size_request()[1] GLib.timeout_add(self.ANIMATE_START_DELAY, self._slide_out_cb) def _slide_in_cb(self): if (self._is_sliding_in and self._current_height < self._target_height): new_height = self._current_height + self.ANIMATE_STEP if new_height > self._target_height: new_height = self._target_height self.set_size_request(-1, new_height) else: self._is_sliding_in = False def _slide_out_cb(self): if (self._is_sliding_out and self._current_height > self._target_height): new_height = self._current_height - self.ANIMATE_STEP if new_height <= self._target_height: new_height = self._target_height self._is_sliding_out = False self.set_size_request(-1, -1) self._visible = False super(ActionBar, self).hide() else: self.set_size_request(-1, new_height) def _on_size_allocate(self, widget, allocation): # FIXME: mvo: allocation.height is no longer accessible with gtk3 # THIS SEEMS TO BREAK THE ANIMATION #height = allocation.height height = widget.get_allocation().height if self._is_sliding_in: self._current_height = height GLib.timeout_add(self.ANIMATE_STEP_INTERVAL, self._slide_in_cb, priority=100) elif self._is_sliding_out: self._current_height = height GLib.timeout_add(self.ANIMATE_STEP_INTERVAL, self._slide_out_cb, priority=100) else: self.queue_draw() def _on_draw(self, widget, cr): a = widget.get_allocation() context = widget.get_style_context() color = context.get_background_color(widget.get_state_flags()) cr.set_source_rgb(color.red, color.green, color.blue) cr.paint() color = context.get_border_color(widget.get_state_flags()) 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.stroke() def _callback(self, function, args): # Disposes of the 'widget' argument that # Gtk.Widget.connect() prepends to calls. callback = lambda *trash_args: function(*args) return callback def _hover_link(self, linkbox, *args): # Makes the link in given eventbox bold # label = linkbox.get_child() # label.set_markup("%s" % label.get_text()) window = self.get_window() window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2)) def _unhover_link(self, linkbox, *args): # Sets the link in given eventbox to its base state # label = linkbox.get_child() # label.set_markup("%s" % label.get_text()) window = self.get_window() window.set_cursor(None) if __name__ == "__main__": win = Gtk.Window() win.set_size_request(600, 400) box = Gtk.VBox() control_box = Gtk.HBox() tree = Gtk.TreeView() bar = ActionBar() btns = 0 def perform(btn): print("Clicked 'Perform %i'" % btn) def perform_lbl(): print("Clicked label link") return True def add_func(*args): global btns btns += 1 bar.add_button(btns, "Perform %i action" % btns, perform, btns) def rmv_func(*args): global btns if btns > 0: bar.remove_button(btns) btns -= 1 def set_func(*args): # example with multiple links bar.set_label("This label _has a link_ and _another one_", perform_lbl) def unset_func(*args): bar.unset_label() addbtn = Gtk.Button("Add Button") rmvbtn = Gtk.Button("Remove Button") setbtn = Gtk.Button("Set Label") unsetbtn = Gtk.Button("Unset Label") addbtn.connect("clicked", add_func) rmvbtn.connect("clicked", rmv_func) setbtn.connect("clicked", set_func) unsetbtn.connect("clicked", unset_func) control_box.pack_start(addbtn, True, True, 0) control_box.pack_start(rmvbtn, True, True, 0) control_box.pack_start(setbtn, True, True, 0) control_box.pack_start(unsetbtn, True, True, 0) win.add(box) box.pack_start(control_box, False, True, 0) box.pack_start(tree, True, True, 0) box.pack_start(bar, False, True, 5) win.connect("destroy", Gtk.main_quit) win.show_all() Gtk.main() software-center-13.10/softwarecenter/ui/gtk3/widgets/videoplayer.py0000664000202700020270000001412012151440100025717 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 import logging import subprocess from gettext import gettext as _ from gi.repository import Gdk # FIXME: remove this try/except and add a dependency on gir1.2-gstreamer-0.10 # if we (ever) start using VideoPlayerGtk3 try: from gi.repository import Gst except ImportError: pass from gi.repository import Gtk from gi.repository import WebKit LOG = logging.getLogger(__name__) class VideoPlayer(Gtk.VBox): def __init__(self): super(VideoPlayer, self).__init__() self.set_size_request(400, 255) self.webkit = WebKit.WebView() settings = self.webkit.get_settings() # this disables the flash and other plugins so that we force html5 # video on the system. This is works currently (11/2011) fine with # dailymotion and vimeo but youtube is opt-in only so we need # to monitor the situation settings.set_property("enable-plugins", False) # on navigation/new window etc, just use the proper browser self.webkit.connect( "new-window-policy-decision-requested", self._on_new_window) self.webkit.connect("create-web-view", self._on_create_web_view) self.pack_start(self.webkit, True, True, 0) self._uri = "" # helper required to follow ToS about the "back" link (flash version) def _on_new_window(self, view, frame, request, action, policy): subprocess.Popen(['xdg-open', request.get_uri()]) return True # helper for the embedded html5 viewer def _on_create_web_view(self, view, frame): # mvo: this is not ideal, the trouble is that we do not get the # url that the new view points to until after the view was # created. But we don't want to be a full blow internal # webbrowser so we simply go back to the youtube url here # and the user needs to click "youtube" there again :/ uri = frame.get_uri() subprocess.Popen(['xdg-open', uri]) # uri property def _set_uri(self, v): self._uri = v or "" if self._uri: # only load the uri if it's defined, otherwise we may get: # Program received signal SIGSEGV, Segmentation fault. # webkit_web_frame_load_uri () from /usr/lib/libwebkitgtk-3.0.so.0 self.webkit.load_uri(self._uri) def _get_uri(self): return self._uri uri = property(_get_uri, _set_uri, None, "uri property") def load_html_string(self, html): """ Instead of a video URI use a html embedded video like e.g. youtube or vimeo. Note that on a default install not all video codecs will play (no flash!), so be careful! """ # FIXME: add something more useful here base_uri = "http://www.ubuntu.com" self.webkit.load_html_string(html, base_uri) def stop(self): self.webkit.load_uri('') # AKA the-segfault-edition-with-no-documentation class VideoPlayerGtk3(Gtk.VBox): def __init__(self): super(VideoPlayerGtk3, self).__init__() self.uri = "" # gtk ui self.movie_window = Gtk.DrawingArea() self.pack_start(self.movie_window, True, True, 0) self.button = Gtk.Button(_("Play")) self.pack_start(self.button, False, True, 0) self.button.connect("clicked", self.on_play_clicked) # player self.player = Gst.ElementFactory.make("playbin2", "player") # bus stuff bus = self.player.get_bus() bus.add_signal_watch() bus.enable_sync_message_emission() bus.connect("message", self.on_message) # FIXME: no sync messages currently so no playing in the widget :/ # the former appears to be not working anymore with GIR, the # later is not exported (introspectable=0 in the GIR) bus.connect("sync-message", self.on_sync_message) #bus.set_sync_handler(self.on_sync_message) def on_play_clicked(self, button): if self.button.get_label() == _("Play"): self.button.set_label("Stop") print(self.uri) self.player.set_property("uri", self.uri) self.player.set_state(Gst.State.PLAYING) else: self.player.set_state(Gst.State.NULL) self.button.set_label(_("Play")) def on_message(self, bus, message): print("message: %s" % bus, message) if message is None: return t = message.type print(t) if t == Gst.MessageType.EOS: self.player.set_state(Gst.State.NULL) self.button.set_label(_("Play")) elif t == Gst.MessageType.ERROR: self.player.set_state(Gst.State.NULL) err, debug = message.parse_error() LOG.error("Error playing video: %s (%s)" % (err, debug)) self.button.set_label(_("Play")) def on_sync_message(self, bus, message): print("sync: %s" % bus, message) if message is None or message.structure is None: return message_name = message.structure.get_name() if message_name == "prepare-xwindow-id": imagesink = message.src imagesink.set_property("force-aspect-ratio", True) Gdk.threads_enter() # FIXME: this is the way to do it, *but* get_xid() is not # exported in the GIR xid = self.player.movie_window.get_window().get_xid() imagesink.set_xwindow_id(xid) Gdk.threads_leave() software-center-13.10/softwarecenter/ui/gtk3/widgets/recommendations.py0000664000202700020270000005171412151440100026575 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2012 Canonical # # Authors: # 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 Gtk, GObject, GLib import logging from gettext import gettext as _ from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.widgets.containers import (FramedHeaderBox, TileGrid) from softwarecenter.ui.gtk3.utils import get_parent_xid from softwarecenter.db.categories import (RecommendedForYouCategory, AppRecommendationsCategory) from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.backend.recagent import RecommenderAgent from softwarecenter.backend.login import get_login_backend from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend from softwarecenter.enums import ( LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT, DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT, SOFTWARE_CENTER_NAME_KEYRING, RecommenderFeedbackActions, TransactionTypes, ) from softwarecenter.utils import utf8 from softwarecenter.netstatus import network_state_is_connected 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,), ), } def __init__(self): FramedHeaderBox.__init__(self) self.recommender_agent = RecommenderAgent() # keep track of applications that have been viewed via a # recommendation so that we can detect when a recommended app # has been installed self.recommended_apps_viewed = set() self.backend = get_install_backend() self.backend.connect("transaction-started", self._on_transaction_started) self.backend.connect("transaction-finished", self._on_transaction_finished) def _on_application_activated(self, tile, app): self.emit("application-activated", app) # we only report on items if the user has opted-in to the # recommendations service if self.recommender_agent.is_opted_in(): self.recommended_apps_viewed.add(app.pkgname) if network_state_is_connected(): # let the recommendations service know that a # recommended item has been viewed (if it is # subsequently installed we will send an additional # signal to indicate that, in on_transaction_finished) # (there is no need to monitor for an error, etc., for this) self.recommender_agent.post_implicit_feedback( app.pkgname, RecommenderFeedbackActions.VIEWED) def _on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): if (trans_type != TransactionTypes.INSTALL and pkgname in self.recommended_apps_viewed): # if the transaction is not an installation we don't want to # track it as a recommended item self.recommended_apps_viewed.remove(pkgname) def _on_transaction_finished(self, backend, result): if result.pkgname in self.recommended_apps_viewed: self.recommended_apps_viewed.remove(result.pkgname) if network_state_is_connected(): # let the recommendations service know that a # recommended item has been successfully installed # (there is no need to monitor for an error, etc., for this) self.recommender_agent.post_implicit_feedback( result.pkgname, RecommenderFeedbackActions.INSTALLED) def _on_recommender_agent_error(self, agent, msg): LOG.warn("Error while accessing the recommender agent: %s" % msg) # this can happen if: # - there is a real error from the agent # - no cached data is available # hide the panel on an error self.hide() class RecommendationsPanelCategory(RecommendationsPanel): """ Panel for use in the category view that displays recommended apps for the given category """ __gsignals__ = { "more-button-clicked": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, ), ), } def __init__(self, db, properties_helper, subcategory): RecommendationsPanel.__init__(self) self.db = db self.properties_helper = properties_helper self.subcategory = subcategory if self.subcategory: self.set_header_label(GLib.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() 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 = TileGrid() self.recommended_for_you_content.connect( "application-activated", self._on_application_activated) 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( self.db, 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() self.more.connect("clicked", self._on_more_button_clicked, self.recommended_for_you_cat) docs = cat.get_documents(self.db) # display the recommendations if len(docs) > 0: self.recommended_for_you_content.add_tiles( self.properties_helper, docs, LOBBY_RECOMMENDATIONS_CAROUSEL_LIMIT) self.recommended_for_you_content.show_all() self.spinner_notebook.hide_spinner() self.header.queue_draw() self.show_all() else: # hide the panel if we have no recommendations to show self.hide() def _on_more_button_clicked(self, btn, category): self.emit("more-button-clicked", category) 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 """ __gsignals__ = { "recommendations-opt-in": (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 a list " "of software currently installed.") NO_NETWORK_RECOMMENDATIONS_TEXT = _(u"Recommendations will appear " "when next online.") def __init__(self, db, properties_helper): RecommendationsPanel.__init__(self) self.db = db self.properties_helper = properties_helper self.subcategory = None self.set_header_label(_(u"Recommended For You")) self.recommended_for_you_content = None # .is_opted_in() means either "successfully opted-in" or # "requested opt-in" (but not done yet) if self.recommender_agent.is_opted_in(): if network_state_is_connected(): self._try_sso_login() else: if self.recommender_agent.opt_in_requested: # the user has opted in but has not yet completed the # initial recommender profile upload, therefore there # are no cached values available yet to display self._show_no_network_view() else: # display cached recommendations self._update_recommended_for_you_content() else: self._show_opt_in_view() def _show_opt_in_view(self): # opt in box self.recommended_for_you_content = Gtk.Box.new( Gtk.Orientation.VERTICAL, StockEms.MEDIUM) self.recommended_for_you_content.set_border_width(StockEms.MEDIUM) self.add(self.recommended_for_you_content) # opt in button self.opt_in_button = Gtk.Button(_(self.TURN_ON_RECOMMENDATIONS_TEXT)) self.opt_in_button.connect("clicked", self._on_opt_in_button_clicked) hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL) hbox.pack_start(self.opt_in_button, False, False, 0) self.recommended_for_you_content.pack_start(hbox, False, False, 0) # opt in text text = _(self.RECOMMENDATIONS_OPT_IN_TEXT) self.opt_in_label = self._create_opt_in_label(text) self.recommended_for_you_content.pack_start(self.opt_in_label, False, False, 0) def _show_no_network_view(self): # display network not available message if not self.recommended_for_you_content: self.recommended_for_you_content = Gtk.Box.new( Gtk.Orientation.VERTICAL, StockEms.MEDIUM) self.recommended_for_you_content.set_border_width(StockEms.MEDIUM) self.add(self.recommended_for_you_content) text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT) self.opt_in_label = self._create_opt_in_label(text) self.recommended_for_you_content.pack_start(self.opt_in_label, False, False, 0) else: self.opt_in_button.hide() text = _(self.NO_NETWORK_RECOMMENDATIONS_TEXT) self.opt_in_label.set_markup(self.opt_in_label_markup % text) def _create_opt_in_label(self, label_text): opt_in_label = Gtk.Label() opt_in_label.set_use_markup(True) self.opt_in_label_markup = '%s' opt_in_label.set_name("subtle-label") opt_in_label.set_markup(self.opt_in_label_markup % label_text) opt_in_label.set_alignment(0, 0.5) opt_in_label.set_line_wrap(True) return opt_in_label def _on_opt_in_button_clicked(self, button): self.opt_in_to_recommendations_service() def opt_in_to_recommendations_service(self): # first we verify the ubuntu sso login/oath status, and if that is good # 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) if network_state_is_connected(): self._try_sso_login() else: self._show_no_network_view() self.recommender_agent.recommender_opt_in_requested(True) self.emit("recommendations-opt-in") 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") self._disconnect_recommender_listeners() def _try_sso_login(self): # display the SSO login dialog if needed # FIXME: consider improving the text in the SSO dialog, for now # we simply reuse the opt-in text from the panel since we # are well past string freeze self.spinner_notebook.show_spinner() self.sso = get_login_backend(get_parent_xid(self), SOFTWARE_CENTER_NAME_KEYRING, self.RECOMMENDATIONS_OPT_IN_TEXT) self.sso.connect("login-successful", self._maybe_login_successful) self.sso.connect("login-failed", self._login_failed) self.sso.connect("login-canceled", self._login_canceled) self.sso.login_or_register() def _maybe_login_successful(self, sso, 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 keyring token and retrigger # login (once) if its expired self.ssoapi.whoami() def _whoami_done(self, ssologin, result): # we are all squared up with SSO login, now we can proceed with the # recommendations display, or the profile upload if this is an # initial opt-in if not self.recommender_agent.recommender_uuid: self._upload_user_profile_and_get_recommendations() if self.recommender_agent.recommender_opt_in_requested: self.recommender_agent.recommender_opt_in_requested(False) else: self._update_recommended_for_you_content() def _whoami_error(self, ssologin, e): self.spinner_notebook.hide_spinner() # FIXME: there is a race condition here if the network state changed # between the call and this check, to fix this properly the # spawn_helper/piston-generic-helper will need to return # better error information though if not network_state_is_connected(): # if there is an error in the SSO whois, first just check if we # have network access and if we do no, just hide the panel self._show_no_network_view() else: # an error that is not network related indicates that the user's # token has likely been revoked or invalidated on the server, for # this case we want to reset the user's opt-in status self.opt_out_of_recommendations_service() def _login_failed(self, sso): # if the user cancels out of the SSO dialog, reset everything to the # opt-in view state self.spinner_notebook.hide_spinner() self.opt_out_of_recommendations_service() def _login_canceled(self, sso): # if the user cancels out of the SSO dialog, reset everything to the # opt-in view state self.spinner_notebook.hide_spinner() self.opt_out_of_recommendations_service() 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(_(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.db) def _on_profile_submitted(self, agent, profile): # after the user profile data has been uploaded, make the request # and load the the recommended_for_you content LOG.debug("The updated profile was successfully submitted to the " "recommender service") # only detect the very first profile upload as that indicates # the user's initial opt-in self._update_recommended_for_you_content() self._disconnect_recommender_listeners() self.emit("recommendations-opt-in") 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 # detect the very first profile upload as that indicates # the user's initial opt-in self._disconnect_recommender_listeners() self.hide() def _disconnect_recommender_listeners(self): 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 class RecommendationsPanelDetails(RecommendationsPanel): """ Panel for use in the details view to display recommendations for a given application """ def __init__(self, db, properties_helper): RecommendationsPanel.__init__(self) self.db = db self.properties_helper = properties_helper self.set_header_label(_(u"People Also Installed")) self.app_recommendations_content = TileGrid() self.app_recommendations_content.connect( "application-activated", self._on_application_activated) 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): 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.db, self.pkgname) self.app_recommendations_cat.connect( 'needs-refresh', 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.db) # display the recommendations if len(docs) > 0: self.app_recommendations_content.add_tiles( self.properties_helper, docs, DETAILS_RECOMMENDATIONS_CAROUSEL_LIMIT) self.show_all() self.spinner_notebook.hide_spinner() else: 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) software-center-13.10/softwarecenter/ui/gtk3/widgets/buttons.py0000664000202700020270000005040212151440100025075 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # # 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 cairo from gi.repository import Gtk, Gdk, Pango, GObject, GdkPixbuf, GLib from gettext import gettext as _ from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.enums import Icons from softwarecenter.ui.gtk3.em import StockEms, em from softwarecenter.ui.gtk3.drawing import darken from softwarecenter.ui.gtk3.widgets.stars import Star, StarSize _HAND = Gdk.Cursor.new(Gdk.CursorType.HAND2) def _update_icon(image, icon, icon_size): if isinstance(icon, GdkPixbuf.Pixbuf): image = image.set_from_pixbuf(icon) elif isinstance(icon, Gtk.Image): image = image.set_from_pixbuf(icon.get_pixbuf()) elif isinstance(icon, str): image = image.set_from_icon_name(icon, icon_size) elif icon is None: image = Gtk.Image() else: msg = "Acceptable icon values: None, GdkPixbuf, GtkImage or str" raise TypeError(msg) return image class _Tile(object): MIN_WIDTH = em(7) def __init__(self): self.set_focus_on_click(False) self.set_relief(Gtk.ReliefStyle.NONE) self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) self.box.set_size_request(self.MIN_WIDTH, -1) self.add(self.box) def build_default(self, label, icon, icon_size): if icon is not None: if isinstance(icon, Gtk.Image): self.image = icon else: self.image = Gtk.Image() _update_icon(self.image, icon, icon_size) self.box.pack_start(self.image, True, True, 0) self.label = Gtk.Label.new(label) self.box.pack_start(self.label, True, True, 0) class TileButton(Gtk.Button, _Tile): def __init__(self): Gtk.Button.__init__(self) _Tile.__init__(self) class TileToggleButton(Gtk.RadioButton, _Tile): def __init__(self): Gtk.RadioButton.__init__(self) self.set_mode(False) _Tile.__init__(self) class LabelTile(TileButton): MIN_WIDTH = -1 def __init__(self, label, icon, icon_size=Gtk.IconSize.MENU): TileButton.__init__(self) self.build_default(label, icon, icon_size) self.label.set_line_wrap(True) context = self.label.get_style_context() context.add_class("label-tile") self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) def do_draw(self, cr): cr.save() A = self.get_allocation() if self.has_focus(): Gtk.render_focus(self.get_style_context(), cr, 3, 3, A.width - 6, A.height - 6) cr.restore() for child in self: self.propagate_draw(child, cr) def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) class CategoryTile(TileButton): def __init__(self, label, icon, icon_size=Gtk.IconSize.DIALOG): TileButton.__init__(self) self.set_size_request(em(8), -1) self.build_default(label, icon, icon_size) self.label.set_justify(Gtk.Justification.CENTER) self.label.set_alignment(0.5, 0.0) self.label.set_line_wrap(True) self.box.set_border_width(StockEms.SMALL) context = self.label.get_style_context() context.add_class("category-tile") self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) def do_draw(self, cr): cr.save() A = self.get_allocation() if self.has_focus(): Gtk.render_focus(self.get_style_context(), cr, 3, 3, A.width - 6, A.height - 6) cr.restore() for child in self: self.propagate_draw(child, cr) def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) _global_featured_tile_width = em(11) class FeaturedTile(TileButton): INSTALLED_OVERLAY_SIZE = 22 _MARKUP = '%s' def __init__(self, helper, doc, icon_size=48): TileButton.__init__(self) self._pressed = False label = helper.get_appname(doc) icon = helper.get_icon_at_size(doc, icon_size, icon_size) stats = helper.get_review_stats(doc) self.helper = helper helper.update_availability(doc) helper.connect("needs-refresh", self._on_needs_refresh, doc, icon_size) self.is_installed = helper.is_installed(doc) self._overlay = helper.icons.load_icon(Icons.INSTALLED_OVERLAY, self.INSTALLED_OVERLAY_SIZE, 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_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) self.image = Gtk.Image() _update_icon(self.image, icon, icon_size) self.content_left.pack_start(self.image, False, False, 0) self.title = Gtk.Label.new(self._MARKUP % GLib.markup_escape_text(label)) self.title.set_alignment(0.0, 0.5) self.title.set_use_markup(True) self.title.set_tooltip_text(label) self.title.set_ellipsize(Pango.EllipsizeMode.END) self.content_right.pack_start(self.title, False, False, 0) categories = helper.get_categories(doc) if categories is not None: self.category = Gtk.Label.new('%s' % (em(0.6), GLib.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) self.content_right.pack_start(self.category, False, False, 4) stats_a11y = None if stats is not None: 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.pack_start(self.stars, False, False, 0) self.n_ratings = Gtk.Label.new( ' (%i)' % ( em(0.45), stats.ratings_total)) self.n_ratings.set_use_markup(True) self.n_ratings.set_name("subtle-label") self.n_ratings.set_alignment(0.0, 0.5) self.rating_box.pack_start(self.n_ratings, False, False, 0) self.content_right.pack_start(self.rating_box, False, False, 0) # TRANSLATORS: this is an accessibility description for eg orca and # 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 req_width = (self.stars.size_request().width + self.image.size_request().width + self.n_ratings.size_request().width + StockEms.MEDIUM * 3 ) global _global_featured_tile_width _global_featured_tile_width = max(_global_featured_tile_width, req_width) # TRANSLATORS: Free here means Gratis price = helper.get_display_price(doc) self.price = Gtk.Label.new( '%s' % (em(0.6), price)) self.price.set_use_markup(True) self.price.set_name("subtle-label") self.price.set_alignment(0.0, 0.5) self.content_right.pack_start(self.price, False, False, 0) self.set_name("featured-tile") a11y_name = '. '.join([t for t in [label, categories, stats_a11y, price] if t]) self.get_accessible().set_name(a11y_name) self.backend = get_install_backend() self.backend.connect("transaction-finished", self.on_transaction_finished, helper, doc) self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) self.connect("button-press-event", self.on_press) self.connect("button-release-event", self.on_release) def destroy(self): # the disconnect the suff connected to "self" is taken care # of by this super() super(FeaturedTile, self).destroy() self.backend.disconnect_by_func(self.on_transaction_finished) self.helper.disconnect_by_func(self._on_needs_refresh) 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 return w, w def do_draw(self, cr): # draw icons first for child in self: self.propagate_draw(child, cr) # then draw focus/installed overlay on top cr.save() A = self.get_allocation() if self._pressed: cr.translate(1, 1) if self.has_focus(): Gtk.render_focus(self.get_style_context(), cr, 3, 3, A.width - 6, A.height - 6) if self.is_installed: # paint installed tick overlay if self.get_direction() != Gtk.TextDirection.RTL: x = y = 36 else: x = A.width - 56 y = 36 Gdk.cairo_set_source_pixbuf(cr, self._overlay, x, y) cr.paint() cr.restore() def on_transaction_finished(self, backend, result, helper, doc): trans_pkgname = str(result.pkgname) pkgname = helper.get_pkgname(doc) if trans_pkgname != pkgname: return # update installed state helper.update_availability(doc) self.is_installed = helper.is_installed(doc) self.queue_draw() def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) return True def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) self._pressed = False return True def on_press(self, widget, event): self._pressed = True def on_release(self, widget, event): if not self._pressed: return self.emit("clicked") self._pressed = False class ChannelSelector(Gtk.Button): PADDING = 0 def __init__(self, section_button): Gtk.Button.__init__(self) alignment = Gtk.Alignment.new(0.5, 0.5, 0.0, 1.0) alignment.set_padding(self.PADDING, self.PADDING, self.PADDING, self.PADDING) self.add(alignment) self.arrow = Gtk.Arrow.new(Gtk.ArrowType.DOWN, Gtk.ShadowType.IN) alignment.add(self.arrow) # vars self.parent_style_type = Gtk.Toolbar self.section_button = section_button self.popup = None self.connect("button-press-event", self.on_button_press) def do_draw(self, cr): cr.save() parent_style = self.get_ancestor(self.parent_style_type) context = parent_style.get_style_context() color = darken(context.get_border_color(Gtk.StateFlags.ACTIVE), 0.2) cr.set_line_width(1) a = self.get_allocation() lin = cairo.LinearGradient(0, 0, 0, a.height) lin.add_color_stop_rgba(0.1, color.red, color.green, color.blue, 0.0) # alpha lin.add_color_stop_rgba(0.5, color.red, color.green, color.blue, 1.0) # alpha lin.add_color_stop_rgba(1.0, color.red, color.green, color.blue, 0.1) # alpha cr.set_source(lin) cr.move_to(0.5, 0.5) cr.rel_line_to(0, a.height) cr.stroke() cr.move_to(a.width - 0.5, 0.5) cr.rel_line_to(0, a.height) cr.stroke() cr.restore() 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) #~ #~ 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) def show_channel_sel_popup(self, widget, event): def position_func(menu, (window, a)): if self.get_direction() != Gtk.TextDirection.RTL: tmpx = a.x else: tmpx = a.x + a.width - self.popup.get_allocation().width x, y = window.get_root_coords(tmpx, a.y + a.height) return (x, y, False) a = self.section_button.get_allocation() window = self.section_button.get_window() self.popup.popup(None, None, position_func, (window, a), event.button, event.time) def set_build_func(self, build_func): self.build_func = build_func 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) class SectionSelector(TileToggleButton): MIN_WIDTH = em(5) _MARKUP = '%s' def __init__(self, label, icon, icon_size=Gtk.IconSize.DIALOG): TileToggleButton.__init__(self) markup = self._MARKUP % label self.build_default(markup, icon, icon_size) self.label.set_use_markup(True) self.label.set_justify(Gtk.Justification.CENTER) context = self.get_style_context() context.add_class("section-sel-bg") context = self.label.get_style_context() context.add_class("section-sel") self.draw_hint_has_channel_selector = False self._alloc = None self._bg_cache = {} self.connect('size-allocate', self.on_size_allocate) self.connect('style-updated', self.on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") 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 = {} def on_style_updated(self, *args): # also reset the bg cache self._bg_cache = {} def _cache_bg_for_state(self, state): a = self.get_allocation() # tmp surface on which we render the button bg as per the gtk # theme engine _surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height) cr = cairo.Context(_surf) context = self.get_style_context() context.save() context.set_state(state) Gtk.render_background(context, cr, -5, -5, a.width + 10, a.height + 10) Gtk.render_frame(context, cr, -5, -5, a.width + 10, a.height + 10) del cr # new surface which will be cached which surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height) cr = cairo.Context(surf) # 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) cr.set_source_surface(_surf, 0, 0) cr.mask(lin) del cr # cache the resulting surf... self._bg_cache[state] = surf def do_draw(self, cr): cr.save() state = self.get_state_flags() if self.get_active(): if state not in self._bg_cache: self._cache_bg_for_state(state) cr.set_source_surface(self._bg_cache[state], 0, 0) cr.paint() cr.restore() for child in self: self.propagate_draw(child, cr) class Link(Gtk.Label): __gsignals__ = { "clicked": (GObject.SignalFlags.RUN_LAST, None, (),) } def __init__(self, markup="", uri="none"): Gtk.Label.__init__(self) self._handler = 0 self.set_markup(markup, uri) 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) # synonyms for set_markup def set_label(self, label): return self.set_markup(label) def set_text(self, text): return self.set_markup(text) def on_activate_link(self, uri, data): self.emit("clicked") def disable(self): self.set_sensitive(False) self.set_name("subtle-label") def enable(self): self.set_sensitive(True) self.set_name("label") class MoreLink(Gtk.Button): _MARKUP = '%s' _MORE = _("More") def __init__(self): Gtk.Button.__init__(self) self.label = Gtk.Label() self.label.set_padding(StockEms.SMALL, 0) self.label.set_markup(self._MARKUP % _(self._MORE)) self.add(self.label) self._init_event_handling() context = self.get_style_context() context.add_class("more-link") def _init_event_handling(self): self.connect("enter-notify-event", self.on_enter) self.connect("leave-notify-event", self.on_leave) def do_draw(self, cr): if self.has_focus(): layout = self.label.get_layout() a = self.get_allocation() 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) for child in self: self.propagate_draw(child, cr) def on_enter(self, widget, event): window = self.get_window() window.set_cursor(_HAND) def on_leave(self, widget, event): window = self.get_window() window.set_cursor(None) software-center-13.10/softwarecenter/ui/gtk3/widgets/navlog.py0000664000202700020270000000443512151440100024672 0ustar dobeydobey00000000000000from gi.repository import Gtk, GLib class NavLog(Gtk.TreeView): _COLUMN_NAMES = ("Pane name", "View name", "DisplayState repr") _COLUMN_TYPES = (str, str, str) def __init__(self, navhistory): Gtk.TreeView.__init__(self) self.navhistory = navhistory model = Gtk.ListStore() model.set_column_types(self._COLUMN_TYPES) self._build_tree_view(model) self.connect("button-press-event", self.on_press_event) return def on_press_event(self, *args): return True def _build_tree_view(self, model): for i, name in enumerate(self._COLUMN_NAMES): renderer = Gtk.CellRendererText() renderer.set_property("wrap-width", 200) column = Gtk.TreeViewColumn(name, renderer, markup=i) self.append_column(column) self.set_model(model) return def notify_append(self, nav_item): model = self.get_model() pane_name = GLib.markup_escape_text( str(nav_item.pane.pane_name)) if nav_item.page >= 0: view_name = nav_item.pane.Pages.NAMES[nav_item.page] else: view_name = str(nav_item.page) it = model.append([pane_name, view_name, str(nav_item.view_state)]) self.set_cursor(model.get_path(it), None, False) return def notify_step_back(self): path, _ = self.get_cursor() path.prev() self.set_cursor(path, None, False) return def notify_step_forward(self): path, _ = self.get_cursor() path.next() self.set_cursor(path, None, False) return def notify_clear_forward_items(self): model = self.get_model() cursor, _ = self.get_cursor() for row in model: if row.path > cursor: model.remove(row.iter) return class NavLogUtilityWindow(Gtk.Window): def __init__(self, navhistory): Gtk.Window.__init__(self) self.set_default_size(600, 300) self.set_title("Software Center Navigation Log") scroll = Gtk.ScrolledWindow() self.add(scroll) self.log = NavLog(navhistory) scroll.add(self.log) self.show_all() return software-center-13.10/softwarecenter/ui/gtk3/widgets/separators.py0000664000202700020270000000222312151440100025560 0ustar dobeydobey00000000000000from gi.repository import Gtk, Gdk class HBar(Gtk.VBox): def __init__(self): Gtk.VBox.__init__(self) context = self.get_style_context() context.add_class("item-view-separator") self.connect("style-updated", self.on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") return def on_style_updated(self, widget): context = widget.get_style_context() border = context.get_border(Gtk.StateFlags.NORMAL) widget.set_size_request(-1, max(1, max(border.top, border.bottom))) return def do_draw(self, cr): context = self.get_style_context() bc = context.get_border_color(self.get_state_flags()) cr.save() Gdk.cairo_set_source_rgba(cr, bc) width = self.get_property("height-request") a = self.get_allocation() cr.move_to(0, 0) cr.rel_line_to(a.width, 0) cr.set_dash((width, 2 * width), 0) cr.set_line_width(2 * width) cr.stroke() cr.restore() for child in self: self.propagate_draw(child, cr) software-center-13.10/softwarecenter/ui/gtk3/widgets/labels.py0000664000202700020270000000770312151440100024647 0ustar dobeydobey00000000000000# Copyright (C) 2012 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 Gtk from gettext import gettext as _ 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 """ SUPPORTED_SYM = { 'yes': u'\u2713', 'no': u'\u2718' } # 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 # "hardware-supported" string, where sym is # either a unicode checkmark or a cross # and hardware is the short hardware description # Note that the trailing "," LABEL = _("%(sym)s%(hardware)s,") def __init__(self, last_item=True): super(HardwareRequirementsLabel, self).__init__() self.tag = None self.result = None self.last_item = last_item self._build_ui() def _build_ui(self): self._label = Gtk.Label() self._label.set_selectable(True) 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 if self.last_item: label_text = self.LABEL_LAST_ITEM else: label_text = self.LABEL short_descr = get_hw_short_description(self.tag) # this needs to be unicode as the translation for zh_CN contains # special chars for the "," (LP: #967036) label_text = unicode(_(label_text), "utf8", "ignore") % { "sym": sym, # we need unicode() here instead of utf8 or str because # the %s in "label_text" will cause str() to be called on the # encoded string, but it will not know what encoding to use "hardware": unicode(short_descr, "utf8", "ignore") } return label_text 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 """ def __init__(self): super(HardwareRequirementsBox, self).__init__() def clear(self): for w in self.get_children(): self.remove(w) def set_hardware_requirements(self, hw_requirements_result): self.clear() for i, (tag, sup) in enumerate(hw_requirements_result.iteritems()): # ignore unknown for now if not sup in ("yes", "no"): continue 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) @property def hw_labels(self): return self.get_children() software-center-13.10/softwarecenter/ui/gtk3/widgets/exhibits.py0000664000202700020270000005156612151440100025232 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 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 import cairo import logging import os from gettext import gettext as _ from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GLib from gi.repository import GObject from gi.repository import GdkPixbuf from gi.repository import WebKit from urlparse import urlparse from softwarecenter.utils import SimpleFileDownloader from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.drawing import rounded_rect from softwarecenter.ui.gtk3.utils import point_in import softwarecenter.paths LOG = logging.getLogger(__name__) _asset_cache = {} _HAND = Gdk.Cursor.new(Gdk.CursorType.HAND2) EXHIBIT_HTML = """ """ 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.title_translated = _("Our star apps") self.published = True self.banner_urls = ["file:%s" % (os.path.abspath(os.path.join( softwarecenter.paths.datadir, "default_banner/fallback.png")))] self.html = EXHIBIT_HTML % { 'banner_url': self.banner_urls[0], 'title': _("Our star apps"), 'subtitle': _("Come and explore our favourites"), } self.click_url = "" # 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") class _HtmlRenderer(Gtk.OffscreenWindow): __gsignals__ = { "render-finished": (GObject.SignalFlags.RUN_LAST, None, (), ) } def __init__(self): Gtk.OffscreenWindow.__init__(self) self.view = WebKit.WebView() settings = self.view.get_settings() settings.set_property("enable-java-applet", False) settings.set_property("enable-plugins", False) settings.set_property("enable-scripts", False) self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT) self.add(self.view) self.show_all() self.loader = SimpleFileDownloader() self.loader.connect("file-download-complete", self._on_one_download_complete) self.loader.connect("error", self._on_download_error) self.exhibit = None self._downloaded_banner_images = [] self.view.connect( "notify::load-status", self._on_internal_renderer_load_status) def set_exhibit(self, exhibit): LOG.debug("set_exhibit: '%s'" % exhibit) self._downloaded_banner_images = [] self.exhibit = exhibit self._download_next_banner_image() def _on_download_error(self, loader, exception, error): LOG.warn("download failed: '%s', '%s'" % (exception, error)) def _on_one_download_complete(self, loader, path): LOG.debug("downloading of '%s' finished" % path) self._downloaded_banner_images.append(path) if len(self._downloaded_banner_images) < len(self.exhibit.banner_urls): self._download_next_banner_image() self._on_all_banner_images_downloaded() def _on_all_banner_images_downloaded(self): LOG.debug("downloading of all banner images finished") html = self.exhibit.html cache_dir = os.path.join( softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR, "download-cache") for url, local_file in zip(self.exhibit.banner_urls, self._downloaded_banner_images): # no need to mangle local urls if url.startswith("file"): continue scheme, netloc, server_path, para, query, frag = urlparse(url) image_name = os.path.basename(local_file) # replace the server side path with the local image name, this # assumes that the image always comes from the same server as # the html html = html.replace(server_path, image_name) self.exhibit.html = html LOG.debug("mangled html: '%s'" % html) self.view.load_string(html, "text/html", "UTF-8", "file:%s/" % cache_dir) def _download_next_banner_image(self): LOG.debug("_download_next_banner_image") self.loader.download_file( self.exhibit.banner_urls[len(self._downloaded_banner_images)], use_cache=True, simple_quoting_for_webkit=True) def _on_internal_renderer_load_status(self, view, prop): """Called when the rendering of the html banner is done""" if view.get_property("load-status") == WebKit.LoadStatus.FINISHED: # this needs to run with a timeout because otherwise the # status is emitted before the offscreen image is finished GLib.timeout_add(100, lambda: self.emit("render-finished")) class ExhibitButton(Gtk.Button): def __init__(self): Gtk.Button.__init__(self) self.DROPSHADOW = GdkPixbuf.Pixbuf.new_from_file( os.path.join(softwarecenter.paths.datadir, "ui/gtk3/art/circle-dropshadow.png")) self.set_focus_on_click(False) self.set_name("exhibit-button") self._dropshadow = None # is the current active "page" self.is_active = False self.connect("size-allocate", self.on_size_allocate) def on_size_allocate(self, *args): a = self.get_allocation() if (self._dropshadow is not None and a.width == self._dropshadow.get_width() and a.height == self._dropshadow.get_height()): return self._dropshadow = self.DROPSHADOW.scale_simple( a.width, a.width, GdkPixbuf.InterpType.BILINEAR) self._margin = int(float(a.width) / self.DROPSHADOW.get_width() * 15) def do_draw(self, cr): cr.save() a = self.get_allocation() #state = self.get_state_flags() context = self.get_style_context() ds_h = self._dropshadow.get_height() y = (a.height - ds_h) / 2 Gdk.cairo_set_source_pixbuf(cr, self._dropshadow, 0, y) cr.paint() # 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: color = context.get_background_color(Gtk.StateFlags.INSENSITIVE) Gdk.cairo_set_source_rgba(cr, color) cr.fill() cr.restore() for child in self: self.propagate_draw(child, cr) class ExhibitArrowButton(ExhibitButton): 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)) self.add(a) class ExhibitBanner(Gtk.EventBox): # 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,), ) } DROPSHADOW_HEIGHT = 11 MAX_HEIGHT = 200 # pixels TIMEOUT_SECONDS = 10 def __init__(self): Gtk.EventBox.__init__(self) vbox = Gtk.VBox() vbox.set_margin_bottom(StockEms.SMALL) vbox.set_margin_left(StockEms.LARGE) vbox.set_margin_right(StockEms.LARGE) self.add(vbox) # defined to make overriding softwarecenter.paths.datadir possible self.NORTHERN_DROPSHADOW = os.path.join( softwarecenter.paths.datadir, "ui/gtk3/art/exhibit-dropshadow-n.png") self.SOUTHERN_DROPSHADOW = os.path.join( softwarecenter.paths.datadir, "ui/gtk3/art/exhibit-dropshadow-s.png") self.FALLBACK = os.path.join( softwarecenter.paths.datadir, "default_banner/fallback.png") hbox = Gtk.HBox(spacing=StockEms.SMALL) vbox.pack_end(hbox, False, False, 0) next = ExhibitArrowButton(Gtk.ArrowType.RIGHT) previous = ExhibitArrowButton(Gtk.ArrowType.LEFT) self.nextprev_hbox = Gtk.HBox() self.nextprev_hbox.pack_start(previous, False, False, 0) self.nextprev_hbox.pack_start(next, False, False, 0) hbox.pack_end(self.nextprev_hbox, False, False, 0) self.index_hbox = Gtk.HBox(spacing=StockEms.SMALL) alignment = Gtk.Alignment.new(1.0, 1.0, 0.0, 1.0) alignment.add(self.index_hbox) hbox.pack_end(alignment, False, False, 0) self.cursor = 0 self._timeout = 0 self.pressed = False self.alpha = 1.0 self.image = None self.old_image = None self.renderer = _HtmlRenderer() self.renderer.connect("render-finished", self.on_banner_rendered) self.set_visible_window(False) self.set_size_request(-1, self.MAX_HEIGHT) self.exhibits = [] next.connect('clicked', self.on_next_clicked) previous.connect('clicked', self.on_previous_clicked) self._dotsigs = [] self._cache_art_assets() self._init_event_handling() # fill this in later self._toplevel_window = None 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 | 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) def _init_mouse_pointer(self): window = self.get_window() if not window: return if not self.exhibits[self.cursor].package_names: window.set_cursor(None) else: window.set_cursor(_HAND) def on_enter_notify(self, *args): self._init_mouse_pointer() def on_leave_notify(self, *args): window = self.get_window() window.set_cursor(None) def on_button_release(self, widget, event): if not point_in(self.get_allocation(), int(event.x), int(event.y)): self.pressed = False return exhibit = self.exhibits[self.cursor] if (exhibit.package_names or exhibit.click_url) and self.pressed: self.emit("show-exhibits-clicked", exhibit) self.pressed = False def on_button_press(self, widget, event): if point_in(self.get_allocation(), int(event.x), int(event.y)): self.pressed = True def on_key_press(self, widget, event): # activate if (event.keyval == Gdk.keyval_from_name("space") or event.keyval == Gdk.keyval_from_name("Return") or event.keyval == Gdk.keyval_from_name("KP_Enter")): exhibit = self.exhibits[self.cursor] if exhibit.package_names: self.emit("show-exhibits-clicked", exhibit) return True # previous if (event.keyval == Gdk.keyval_from_name("Left") or event.keyval == Gdk.keyval_from_name("KP_Left")): self.on_previous_clicked() return True # next if (event.keyval == Gdk.keyval_from_name("Right") or event.keyval == Gdk.keyval_from_name("KP_Right")): self.on_next_clicked() return True return False def on_next_clicked(self, *args): self.next_exhibit() self.queue_next() def on_previous_clicked(self, *args): self.previous() self.queue_next() def cleanup_timeout(self): if self._timeout > 0: GLib.source_remove(self._timeout) self._timeout = 0 def _render_exhibit_at_cursor(self): # init the mouse pointer self._init_mouse_pointer() # check the cursor is within range n_exhibits = len(self.exhibits) cursor = self.cursor if n_exhibits == 0 or (cursor < 0 and cursor >= n_exhibits): return # copy old image for the fade if self.image: self.old_image = self.image.copy() # set the right one self.renderer.set_exhibit(self.exhibits[cursor]) # make sure the active button is having a different color for i, w in enumerate(self.index_hbox): w.is_active = (i == cursor) def on_paging_dot_clicked(self, dot, index): self.cursor = index self._render_exhibit_at_cursor() # next() is a special function in py3 so we call this next_exhibit def next_exhibit(self): if len(self.exhibits) - 1 == self.cursor: self.cursor = 0 else: self.cursor += 1 self._render_exhibit_at_cursor() return False def previous(self): if self.cursor == 0: self.cursor = len(self.exhibits) - 1 else: self.cursor -= 1 self._render_exhibit_at_cursor() return False def queue_next(self): self.cleanup_timeout() self._timeout = GLib.timeout_add_seconds( self.TIMEOUT_SECONDS, self.next_exhibit) return self._timeout def on_banner_rendered(self, renderer): self.image = renderer.get_pixbuf() if self.image.get_width() == 1: # the offscreen window is not really as such content not # correctly rendered GLib.timeout_add(500, self.on_banner_rendered, renderer) return from gi.repository import Atk 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() return False def _fade_in(self, step=0.05): self.alpha = 0.0 def fade_step(): retval = True self.alpha += step if self.alpha >= 1.0: self.alpha = 1.0 self.old_image = None retval = False self.queue_draw() return retval GLib.timeout_add(50, fade_step) def _cache_art_assets(self): global _asset_cache assets = _asset_cache if assets: return assets #~ surf = cairo.ImageSurface.create_from_png(self.NORTHERN_DROPSHADOW) #~ ptrn = cairo.SurfacePattern(surf) #~ ptrn.set_extend(cairo.EXTEND_REPEAT) #~ assets["n"] = ptrn surf = cairo.ImageSurface.create_from_png(self.SOUTHERN_DROPSHADOW) ptrn = cairo.SurfacePattern(surf) ptrn.set_extend(cairo.EXTEND_REPEAT) assets["s"] = ptrn return assets def do_draw(self, cr): # ensure that we pause the exhibits carousel if the window goes # into the background self._init_pause_handling_if_needed() # hide the next/prev buttons if needed if len(self.exhibits) == 1: self.nextprev_hbox.hide() else: self.nextprev_hbox.show() # do the actual drawing cr.save() a = self.get_allocation() cr.set_source_rgb(1, 1, 1) cr.paint() # workaround a really odd bug in the offscreen window of the # webkit view that leaves a 8px white border around the rendered # image OFFSET = -8 if self.old_image is not None: #x = (a.width - self.old_image.get_width()) / 2 x = OFFSET y = OFFSET Gdk.cairo_set_source_pixbuf(cr, self.old_image, x, y) cr.paint() if self.image is not None: #x = (a.width - self.image.get_width()) / 2 x = OFFSET y = OFFSET Gdk.cairo_set_source_pixbuf(cr, self.image, x, y) cr.paint_with_alpha(self.alpha) if self.has_focus(): # paint a focal border/frame context = self.get_style_context() context.save() context.add_class(Gtk.STYLE_CLASS_HIGHLIGHT) highlight = context.get_background_color(self.get_state_flags()) context.restore() 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() # paint dropshadows last #~ cr.rectangle(0, 0, a.width, self.DROPSHADOW_HEIGHT) #~ cr.clip() #~ cr.set_source(assets["n"]) #~ cr.paint() #~ cr.reset_clip() 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.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.stroke() cr.restore() for child in self: self.propagate_draw(child, cr) def _init_pause_handling_if_needed(self): # nothing to do if we have the toplevel already if self._toplevel_window: return # find toplevel Gtk.Window w = self while w.get_parent(): w = w.get_parent() # paranoia, should never happen if not isinstance(w, Gtk.Window): return # connect to property changes for the toplevel focus w.connect("notify::has-toplevel-focus", self._on_main_window_is_active_changed) self._toplevel_window = w def _on_main_window_is_active_changed(self, win, gparamspec): """ this tracks focus of the main window and pauses the exhibits cycling if the window does not have the toplevel focus """ is_active = win.get_property("has-toplevel-focus") if is_active: self.queue_next() else: self.cleanup_timeout() def set_exhibits(self, exhibits_list): if not exhibits_list: return self.exhibits = exhibits_list self.cursor = 0 for child in self.index_hbox: child.destroy() for sigid in self._dotsigs: GLib.source_remove(sigid) self._dotsigs = [] if len(self.exhibits) > 1: for i, exhibit in enumerate(self.exhibits): dot = ExhibitButton() dot.set_size_request(StockEms.LARGE, StockEms.LARGE) self._dotsigs.append( dot.connect("clicked", self.on_paging_dot_clicked, 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() software-center-13.10/softwarecenter/ui/gtk3/widgets/symbolic_icons.py0000664000202700020270000001477612151440100026431 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # # 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 cairo import os from math import pi as PI from gi.repository import Gtk, Gdk, GLib, PangoCairo import softwarecenter.paths from softwarecenter.ui.gtk3.em import em from softwarecenter.ui.gtk3.drawing import rounded_rect # pi constants _2PI = 2 * PI PI_OVER_180 = PI / 180 def radian(deg): return PI_OVER_180 * deg class SymbolicIcon(Gtk.Image): DROPSHADOW = "%s-dropshadow.png" ICON = "%s.png" def __init__(self, name): Gtk.Image.__init__(self) context = self.get_style_context() context.add_class("symbolic-icon") # get base dir SYMBOLIC_DIR = os.path.join( softwarecenter.paths.datadir, "ui/gtk3/art/icons/") drop_shadow_path = SYMBOLIC_DIR + self.DROPSHADOW % name self.drop_shadow = cairo.ImageSurface.create_from_png(drop_shadow_path) icon_path = SYMBOLIC_DIR + self.ICON % name self.icon = cairo.ImageSurface.create_from_png(icon_path) self.drop_shadow_x_offset = 0 self.drop_shadow_y_offset = 1 self.connect("draw", self.on_draw, self.drop_shadow, self.icon, self.drop_shadow_x_offset, self.drop_shadow_y_offset) def do_get_preferred_width(self): ds = self.drop_shadow return ds.get_width(), ds.get_width() def do_get_preferred_height(self): ds = self.drop_shadow return ds.get_height(), ds.get_height() def on_draw(self, widget, cr, drop_shadow, icon, ds_xo, ds_yo, xo=0, yo=0): a = widget.get_allocation() # dropshadow x = (a.width - drop_shadow.get_width()) * 0.5 + ds_xo + xo y = (a.height - drop_shadow.get_height()) * 0.5 + ds_yo + yo cr.set_source_surface(drop_shadow, int(x), int(y)) cr.paint_with_alpha(0.4) # colorised icon state = widget.get_state_flags() context = widget.get_style_context() color = context.get_color(state) Gdk.cairo_set_source_rgba(cr, color) x = (a.width - icon.get_width()) * 0.5 + xo y = (a.height - icon.get_height()) * 0.5 + yo cr.mask_surface(icon, int(x), int(y)) class RotationAboutCenterAnimation(object): NEW_FRAME_DELAY = 50 # msec ROTATION_INCREMENT = radian(5) # 5 degrees -> radians def __init__(self): self.rotation = 0 self.animator = None self._stop_requested = False def new_frame(self): _continue = True self.rotation += self.ROTATION_INCREMENT if self.rotation >= _2PI: self.rotation = 0 if self._stop_requested: self.animator = None self._stop_requested = False _continue = False self.queue_draw() return _continue def start(self): if not self.is_animating(): self.animator = GLib.timeout_add(self.NEW_FRAME_DELAY, self.new_frame) def stop(self): if self.is_animating(): self._stop_requested = True def is_animating(self): return self.animator is not None class PendingSymbolicIcon(SymbolicIcon, RotationAboutCenterAnimation): BUBBLE_MAX_BORDER_RADIUS = em() BUBBLE_XPADDING = 5 BUBBLE_YPADDING = 2 BUBBLE_FONT_DESC = "Bold 8.5" def __init__(self, name): SymbolicIcon.__init__(self, name) RotationAboutCenterAnimation.__init__(self) # for painting the trans count bubble self.layout = self.create_pango_layout("") self.transaction_count = 0 def on_draw(self, widget, cr, *args, **kwargs): cr.save() if self.is_animating(): # translate to the center, then set the rotation a = widget.get_allocation() cr.translate(a.width * 0.5, a.height * 0.5) cr.rotate(self.rotation) # pass on the translation details kwargs['xo'] = -(a.width * 0.5) kwargs['yo'] = -(a.height * 0.5) # do icon drawing SymbolicIcon.on_draw(self, widget, cr, *args, **kwargs) cr.restore() if not self.is_animating() or not self.transaction_count: return # paint transactions bubble # get the layout extents and calc the bubble size ex = self.layout.get_pixel_extents()[1] x = ((a.width - self.icon.get_width()) / 2 + self.icon.get_width() - ex.width + 2) y = ((a.height - self.icon.get_height()) / 2 + self.icon.get_height() - ex.height + 2) w = ex.width + 2 * self.BUBBLE_XPADDING h = ex.height + 2 * self.BUBBLE_YPADDING border_radius = w / 3 if border_radius > self.BUBBLE_MAX_BORDER_RADIUS: border_radius = self.BUBBLE_MAX_BORDER_RADIUS # paint background context = widget.get_style_context() context.save() color = context.get_background_color(Gtk.StateFlags.SELECTED) rounded_rect(cr, x + 1, y + 1, w - 2, h - 2, border_radius) Gdk.cairo_set_source_rgba(cr, color) cr.fill() context.restore() # paint outline rounded_rect(cr, x + 1.5, y + 1.5, w - 3, h - 3, border_radius - 1) cr.set_source_rgb(1, 1, 1) cr.set_line_width(1) cr.stroke() # paint layout cr.save() cr.translate(x + (w - ex.width) * 0.5, y + (h - ex.height) * 0.5) cr.move_to(0, 1) PangoCairo.layout_path(cr, self.layout) cr.set_source_rgba(0, 0, 0, 0.6) cr.fill() Gtk.render_layout(context, cr, 0, 0, self.layout) cr.restore() def set_transaction_count(self, count): if count == self.transaction_count: return self.transaction_count = count m = ('%i' % (self.BUBBLE_FONT_DESC, "white", count)) self.layout.set_markup(m, -1) self.queue_draw() software-center-13.10/softwarecenter/ui/gtk3/widgets/thumbnail.py0000664000202700020270000003743312151440100025373 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # # 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, Gdk, Atk, Gio, GObject, GdkPixbuf, GLib import logging from softwarecenter.utils import SimpleFileDownloader from imagedialog import SimpleShowImageDialog from gettext import gettext as _ LOG = logging.getLogger(__name__) class ScreenshotData(GObject.GObject): __gsignals__ = {"screenshots-available": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (),), } def __init__(self): GObject.GObject.__init__(self) self._sig = 0 self._screenshots = [] def set_app_details(self, app_details): if self._sig > 0: GLib.source_remove(self._sig) self.app_details = app_details self.appname = app_details.display_name self.pkgname = app_details.pkgname self._sig = self.app_details.connect( "screenshots-available", self._on_screenshots_available) self._screenshots = [] self.app_details.query_multiple_screenshots() def _on_screenshots_available(self, screenshot_data, screenshots): self._screenshots = screenshots self.emit("screenshots-available") def get_n_screenshots(self): return len(self._screenshots) def get_nth_large_screenshot(self, index): return self._screenshots[index]['large_image_url'] def get_nth_small_screenshot(self, index): return self._screenshots[index]['small_image_url'] class ScreenshotButton(Gtk.Button): def __init__(self): Gtk.Button.__init__(self) self.set_focus_on_click(False) self.set_valign(Gtk.Align.CENTER) self.image = Gtk.Image() self.add(self.image) def do_draw(self, cr): if self.has_focus(): context = self.get_style_context() _a = self.get_allocation() a = self.image.get_allocation() pb = self.image.get_pixbuf() pbw, pbh = pb.get_width(), pb.get_height() Gtk.render_focus( context, cr, a.x - _a.x + (a.width - pbw) / 2 - 4, a.y - _a.y + (a.height - pbh) / 2 - 4, pbw + 8, pbh + 8) for child in self: self.propagate_draw(child, cr) class ScreenshotGallery(Gtk.VBox): """ Widget that displays screenshot availability, download progress, and eventually the screenshot itself. """ MAX_SIZE_CONSTRAINTS = 300, 250 SPINNER_SIZE = 32, 32 ZOOM_ICON = "stock_zoom-page" NOT_AVAILABLE_STRING = _('No screenshot available') USE_CACHING = True def __init__(self, distro, icons): Gtk.VBox.__init__(self) # data self.distro = distro self.icons = icons self.data = ScreenshotData() self.data.connect( "screenshots-available", self._on_screenshots_available) # state tracking self.ready = False self.screenshot_pixbuf = None self.screenshot_available = False self._thumbnail_sigs = [] self._height = 0 # zoom cursor try: zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0) # FIXME self._zoom_cursor = Gdk.Cursor.new_from_pixbuf( Gdk.Display.get_default(), zoom_pb, 0, 0) # x, y except: self._zoom_cursor = None # convenience class for handling the downloading (or not) of # any screenshot self.loader = SimpleFileDownloader() self.loader.connect( 'error', self._on_screenshot_load_error) self.loader.connect( 'file-url-reachable', self._on_screenshot_query_complete) self.loader.connect( 'file-download-complete', self._on_screenshot_download_complete) self._build_ui() # add cleanup handler to avoid signals after we are destroyed self.connect("destroy", self._on_destroy) def _on_destroy(self, widget): # we need to disconnect here otherwise gtk segfaults when it # tries to set a already destroyed gtk image self.loader.disconnect_by_func( self._on_screenshot_download_complete) self.loader.disconnect_by_func( self._on_screenshot_query_complete) self.loader.disconnect_by_func( self._on_screenshot_load_error) # overrides def do_get_preferred_width(self): if self.data.get_n_screenshots() <= 1: pb = self.main_screenshot.image.get_pixbuf() if pb: width = pb.get_width() + 20 return width, width return 320, 320 def do_get_preferred_height(self): pb = self.main_screenshot.image.get_pixbuf() if pb: height = pb.get_height() if self.data.get_n_screenshots() <= 1: height += 20 height = max(self._height, height) self._height = height return height, height else: height += 110 height = max(self._height, height) self._height = height return height, height self._height = max(self._height, 250) return self._height, self._height # private def _build_ui(self): self.set_border_width(3) # the frame around the screenshot (placeholder) self.screenshot_frame = Gtk.VBox() self.pack_start(self.screenshot_frame, True, True, 0) self.spinner = Gtk.Spinner() self.spinner.set_size_request(*self.SPINNER_SIZE) self.spinner.set_valign(Gtk.Align.CENTER) self.spinner.set_halign(Gtk.Align.CENTER) self.screenshot_frame.add(self.spinner) # main big clickable screenshot button self.main_screenshot = ScreenshotButton() self.screenshot_frame.pack_start(self.main_screenshot, True, False, 0) # unavailable layout self.unavailable = Gtk.Label(label=_(self.NOT_AVAILABLE_STRING)) self.unavailable.set_alignment(0.5, 0.5) # force the label state to INSENSITIVE so we get the nice # subtle etched in look self.unavailable.set_state(Gtk.StateType.INSENSITIVE) self.screenshot_frame.add(self.unavailable) self.thumbnails = ThumbnailGallery(self) self.thumbnails.set_margin_top(5) self.thumbnails.set_halign(Gtk.Align.CENTER) self.pack_end(self.thumbnails, False, False, 0) self.thumbnails.connect( "thumb-selected", self.on_thumbnail_selected) self.main_screenshot.connect("clicked", self.on_clicked) self.main_screenshot.connect('enter-notify-event', self._on_enter) self.main_screenshot.connect('leave-notify-event', self._on_leave) self.show_all() def _on_enter(self, widget, event): if self.get_is_actionable(): self.get_window().set_cursor(self._zoom_cursor) def _on_leave(self, widget, event): self.get_window().set_cursor(None) def _on_key_press(self, widget, event): # react to spacebar, enter, numpad-enter if (event.keyval in ( Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter) and self.get_is_actionable()): self.set_state(Gtk.StateType.ACTIVE) def _on_key_release(self, widget, event): # react to spacebar, enter, numpad-enter if (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter) and self.get_is_actionable()): self.set_state(Gtk.StateType.NORMAL) self._show_image_dialog() def _show_image_dialog(self): """ Displays the large screenshot in a separate dialog window """ if self.data and self.screenshot_pixbuf: title = _("%s - Screenshot") % self.data.appname toplevel = self.get_toplevel() d = SimpleShowImageDialog( title, self.screenshot_pixbuf, toplevel) d.run() d.destroy() def _on_screenshots_available(self, screenshots): self.thumbnails.set_thumbnails_from_data(screenshots) def _on_screenshot_download_complete(self, loader, screenshot_path): try: self.screenshot_pixbuf = GdkPixbuf.Pixbuf.new_from_file( screenshot_path) except Exception, e: LOG.exception("Pixbuf.new_from_file() failed") self.loader.emit('error', GObject.GError, e) return False #context = self.main_screenshot.get_style_context() tw, th = self.MAX_SIZE_CONSTRAINTS pb = self._downsize_pixbuf(self.screenshot_pixbuf, tw, th) self.main_screenshot.image.set_from_pixbuf(pb) self.ready = True self.display_image() def _on_screenshot_load_error(self, loader, err_type, err_message): self.set_screenshot_available(False) self.ready = True def _on_screenshot_query_complete(self, loader, reachable): self.set_screenshot_available(reachable) if not reachable: self.ready = True def _downsize_pixbuf(self, pb, target_w, target_h): w = pb.get_width() h = pb.get_height() sf = min(float(target_w) / w, float(target_h) / h) sw = int(w * sf) sh = int(h * sf) return pb.scale_simple(sw, sh, GdkPixbuf.InterpType.BILINEAR) # public def download_and_display_from_url(self, url): self.loader.download_file(url, use_cache=self.USE_CACHING) def clear(self): """ All state trackers are set to their initial states, and the old screenshot is cleared from the view. """ self._height = 0 self.clear_main_screenshot() self.thumbnails.clear() def clear_main_screenshot(self): self.screenshot_available = True self.ready = False self.display_spinner() def display_spinner(self): self.main_screenshot.image.clear() self.main_screenshot.hide() self.unavailable.hide() self.spinner.show() self.spinner.start() def display_unavailable(self): self.spinner.hide() self.spinner.stop() self.unavailable.show() self.main_screenshot.hide() acc = self.get_accessible() acc.set_name(_(self.NOT_AVAILABLE_STRING)) acc.set_role(Atk.Role.LABEL) def display_image(self): self.unavailable.hide() self.spinner.stop() self.spinner.hide() self.main_screenshot.show_all() self.thumbnails.show() def get_is_actionable(self): """ Returns true if there is a screenshot available and the download has completed """ return self.screenshot_available and self.ready def set_screenshot_available(self, available): """ Configures the ScreenshotView depending on whether there is a screenshot available. """ if not available: self.display_unavailable() elif available and self.unavailable.get_property("visible"): self.display_spinner() self.screenshot_available = available def on_clicked(self, button): if self.get_is_actionable(): self._show_image_dialog() def fetch_screenshots(self, app_details): """ Called to configure the screenshotview for a new application. The existing screenshot is cleared and the process of fetching a new screenshot is instigated. """ self.clear() acc = self.get_accessible() acc.set_name(_('Fetching screenshot ...')) self.data.set_app_details(app_details) self.display_spinner() self.download_and_display_from_url(app_details.screenshot) def on_thumbnail_selected(self, gallery, id_): self.clear_main_screenshot() large_url = self.data.get_nth_large_screenshot(id_) self.download_and_display_from_url(large_url) def draw(self, widget, cr): """ Draws the thumbnail frame """ pass class Thumbnail(Gtk.Button): def __init__(self, id_, url, cancellable, gallery): Gtk.Button.__init__(self) self.id_ = id_ def download_complete_cb(loader, path): width, height = ThumbnailGallery.THUMBNAIL_SIZE_CONSTRAINTS pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( path, width, height, # width, height constraints True) # respect image proportionality im = Gtk.Image.new_from_pixbuf(pixbuf) self.add(im) self.show_all() loader = SimpleFileDownloader() loader.connect("file-download-complete", download_complete_cb) loader.download_file( url, use_cache=ScreenshotGallery.USE_CACHING) self.connect("draw", self.on_draw) def on_draw(self, thumb, cr): state = self.get_state_flags() if self.has_focus() or (state & Gtk.StateFlags.ACTIVE) > 0: return for child in self: self.propagate_draw(child, cr) return True class ThumbnailGallery(Gtk.HBox): __gsignals__ = { "thumb-selected": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (int,),), } THUMBNAIL_SIZE_CONSTRAINTS = 90, 80 THUMBNAIL_MAX_COUNT = 3 def __init__(self, gallery): Gtk.HBox.__init__(self) self.gallery = gallery self.distro = gallery.distro self.icons = gallery.icons self.cancel = Gio.Cancellable() self._prev = None self._handlers = [] def clear(self): self.cancel.cancel() self.cancel.reset() for sig in self._handlers: GLib.source_remove(sig) for child in self: child.destroy() def set_thumbnails_from_data(self, data): self.clear() # if there are multiple screenshots n = data.get_n_screenshots() if n <= 1: return # pick the first ones, the data is sorted by most appropriate # version first - if at some later point we have a lot of # screenshots we could consider randomizing again for i in range(min(n, ThumbnailGallery.THUMBNAIL_MAX_COUNT)): url = data.get_nth_small_screenshot(i) self._create_thumbnail_for_url(i, url) # set first child to selected self._prev = self.get_children()[0] self._prev.set_state_flags(Gtk.StateFlags.SELECTED, False) self.show_all() def _create_thumbnail_for_url(self, index, url): thumbnail = Thumbnail(index, url, self.cancel, self.gallery) self.pack_start(thumbnail, False, False, 0) sig = thumbnail.connect("clicked", self.on_clicked) self._handlers.append(sig) def on_clicked(self, thumb): if self._prev is not None: self._prev.set_state_flags(Gtk.StateFlags.NORMAL, True) thumb.set_state_flags(Gtk.StateFlags.SELECTED, False) self._prev = thumb self.emit("thumb-selected", thumb.id_) software-center-13.10/softwarecenter/ui/gtk3/widgets/menubutton.py0000664000202700020270000001024012151440100025573 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Didier Roche # # 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 class MenuButton(Gtk.Button): def __init__(self, menu, icon=None, label=None): super(MenuButton, self).__init__() box = Gtk.Box() self.add(box) if icon: 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) def get_menu(self): '''Return menu attached to the button''' 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) def on_keyboard_clicked(self, button, menu): 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()) # if pressed by the mouse, center the X position to it if x_cursor_pos: x_position += x_cursor_pos x_position = x_position - menu.get_allocated_width() * 0.5 # computer current monitor height current_screen = button.get_screen() 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 monitor_geo.height < y_position + menu.get_allocated_height(): y_position = (y_position - button.get_allocated_height() - menu.get_allocated_height()) return (x_position, y_position, True) if __name__ == "__main__": win = Gtk.Window() win.set_size_request(200, 300) menu = Gtk.Menu() menuitem = Gtk.MenuItem(label="foo") menuitem2 = Gtk.MenuItem(label="long long long bar message") menu.append(menuitem) menu.append(menuitem2) menuitem.show() menuitem2.show() box1 = Gtk.Box() 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) 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) 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() software-center-13.10/softwarecenter/ui/gtk3/widgets/backforward.py0000664000202700020270000001232712151440100025670 0ustar dobeydobey00000000000000# Copyright (C) 2010 Matthew McGowan # # Authors: # Matthew McGowan # # 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, either version 3 of the License, or # (at your option) any later version. # # 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, see . from gi.repository import Atk from gi.repository import Gtk from gi.repository import GObject from gettext import gettext as _ DEFAULT_PART_SIZE = (28, -1) class BackForwardButton(Gtk.HBox): __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) context = self.get_style_context() context.add_class(Gtk.STYLE_CLASS_LINKED) atk_obj = self.get_accessible() atk_obj.set_name(_('History Navigation')) atk_obj.set_description(_('Navigate forwards and backwards.')) atk_obj.set_role(Atk.Role.PANEL) self._build_left_right_buttons() self.pack_start(self.left, True, True, 0) self.pack_end(self.right, True, True, 0) self.left.connect("clicked", self.on_clicked) self.right.connect("clicked", self.on_clicked) 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.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) context = self.left.get_style_context() context.add_class("backforward-right-button") context = self.right.get_style_context() context.add_class("backforward-left-button") self.set_button_atk_info_rtl() def on_clicked(self, button): self.emit(button.signal_name) def set_button_atk_info_ltr(self): # left button atk_obj = self.left.get_accessible() atk_obj.set_name(_('Back Button')) atk_obj.set_description(_('Navigates back.')) # right button atk_obj = self.right.get_accessible() atk_obj.set_name(_('Forward Button')) atk_obj.set_description(_('Navigates forward.')) def set_button_atk_info_rtl(self): # right button atk_obj = self.right.get_accessible() atk_obj.set_name(_('Back Button')) atk_obj.set_description(_('Navigates back.')) atk_obj.set_role(Atk.Role.PUSH_BUTTON) # left button atk_obj = self.left.get_accessible() atk_obj.set_name(_('Forward Button')) atk_obj.set_description(_('Navigates forward.')) atk_obj.set_role(Atk.Role.PUSH_BUTTON) def set_use_hand_cursor(self, use_hand): self.use_hand = use_hand class ButtonPart(Gtk.Button): def __init__(self, signal_name, arrow_type): Gtk.Button.__init__(self) #~ self.set_relief(Gtk.ReliefStyle.NORMAL) self.arrow_type = arrow_type if self.arrow_type == Gtk.ArrowType.LEFT: self.arrow = Gtk.Image.new_from_icon_name('stock_left', Gtk.IconSize.BUTTON) elif self.arrow_type == Gtk.ArrowType.RIGHT: self.arrow = Gtk.Image.new_from_icon_name('stock_right', Gtk.IconSize.BUTTON) self.arrow.set_margin_left(2) self.arrow.set_margin_right(2) self.add(self.arrow) self.signal_name = signal_name def do_draw(self, cr): context = self.get_style_context() context.save() state = self.get_state_flags() if (state & Gtk.StateFlags.NORMAL) == 0: state = Gtk.StateFlags.PRELIGHT context.set_state(state) a = self.get_allocation() x = 0 y = 0 width = a.width height = a.height border = context.get_border(Gtk.StateFlags.PRELIGHT) if self.arrow_type == Gtk.ArrowType.LEFT: width += 2 * border.right elif self.arrow_type == Gtk.ArrowType.RIGHT: x -= border.left width += border.left Gtk.render_background(context, cr, x, y, width, height) Gtk.render_frame(context, cr, x, y, width, height) context.restore() for child in self: self.propagate_draw(child, cr) software-center-13.10/softwarecenter/ui/gtk3/widgets/description.py0000664000202700020270000011206712151440100025730 0ustar dobeydobey00000000000000# Copyright (C) 2010 Matthew McGowan # # Authors: # Matthew McGowan # # 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, either version 3 of the License, or # (at your option) any later version. # # 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, see . from gi.repository import Gtk, Gdk from gi.repository import GLib from gi.repository import Pango from softwarecenter.utils import normalize_package_description from softwarecenter.ui.gtk3.drawing import color_to_hex from softwarecenter.ui.gtk3.utils import point_in _PS = Pango.SCALE class _SpecialCasePreParsers(object): def preparse(self, k, desc): if k is None: return desc func_name = '_%s_preparser' % k.lower().replace('-', '_') if not hasattr(self, func_name): return desc f = getattr(self, func_name) return f(desc) # special case pre-parsers def _skype_preparser(self, desc): return desc.replace('. *', '.\n*') def _texlive_fonts_extra_preparser(self, desc): return desc.replace(')\n', ').\n').replace('--\n', '--\n\n') class EventHelper(dict): # FIXME: workaround for broken event.copy() class ButtonEvent(object): def __init__(self, event): self.x = event.x self.y = event.y self.type = event.type self.button = event.button VALID_KEYS = ( 'event', 'layout', 'index', 'within-selection', 'drag-active', 'drag-context') def __init__(self): dict.__init__(self) self.new_press(None, None, None, False) def __setitem__(self, k, v): if k not in EventHelper.VALID_KEYS: raise KeyError('\"%s\" is not a valid key' % k) return False return dict.__setitem__(self, k, v) def new_press(self, event, layout, index, within_sel): if event is None: self['event'] = None else: # this should be simply event.copy() but that appears broken # currently(?) self['event'] = EventHelper.ButtonEvent(event) self['layout'] = layout self['index'] = index self['within-selection'] = within_sel self['drag-active'] = False self['drag-context'] = None class PangoLayoutProxy(object): """ Because i couldn't figure out how to inherit from pygi's Pango.Layout... """ def __init__(self, context): self._layout = Pango.Layout.new(context) def xy_to_index(self, x, y): return self._layout.xy_to_index(x, y) def index_to_pos(self, *args): return self._layout.index_to_pos(*args) # 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) def set_font_description(self, font_desc): return self._layout.set_font_description(font_desc) def set_wrap(self, wrap_mode): return self._layout.set_wrap(wrap_mode) def set_width(self, width): return self._layout.set_width(width) # getter proxies def get_text(self): return self._layout.get_text() def get_pixel_extents(self): return self._layout.get_pixel_extents()[1] def get_cursor_pos(self, index): return self._layout.get_cursor_pos(index) def get_iter(self): return self._layout.get_iter() def get_extents(self): return self._layout.get_extents() class Layout(PangoLayoutProxy): def __init__(self, widget, text=""): PangoLayoutProxy.__init__(self, widget.get_pango_context()) self.widget = widget self.length = 0 self.indent = 0 self.vspacing = None self.is_bullet = False self.index = 0 self.allocation = Gdk.Rectangle() self._default_attrs = True self.set_markup(text) def __len__(self): return self.length def set_text(self, text): PangoLayoutProxy.set_markup(self, text) self.length = len(self.get_text()) def set_allocation(self, x, y, w, h): a = self.allocation a.x = x a.y = y a.width = w a.height = h def get_position(self): return self.allocation.x, self.allocation.y def cursor_up(self, cursor, target_x=-1): layout = self.widget.order[cursor.paragraph] pos = layout.index_to_pos(cursor.index) x, y = pos.x, pos.y if target_x >= 0: x = target_x y -= _PS * self.widget.line_height return layout.xy_to_index(x, y), (x, y) def cursor_down(self, cursor, target_x=-1): layout = self.widget.order[cursor.paragraph] pos = layout.index_to_pos(cursor.index) x, y = pos.x, pos.y if target_x >= 0: x = target_x 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) 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 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)) #~ self.set_attributes(attrs) # XXX: workaround text = self.get_text() new_text = (text[:start] + '' % (bg, fg)) new_text += text[start:end] new_text += '' + text[end:] self.set_markup(new_text) self._default_attrs = False def highlight_all(self, 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, 0, -1)) #~ attrs.insert(Pango.AttrForeground(fg.red, fg.green, fg.blue, 0, -1)) #~ self.set_attributes(attrs) # XXX: workaround text = self.get_text() self.set_markup('%s' % (bg, fg, text)) self._default_attrs = False class Cursor(object): WORD_TERMINATORS = (' ',) # empty space. suggestions recommended... def __init__(self, parent): self.parent = parent self.index = 0 self.paragraph = 0 def is_min(self, cursor): return self.get_position() <= cursor.get_position() def is_max(self, cursor): return self.get_position() >= cursor.get_position() def switch(self, cursor): this_pos = self.get_position() other_pos = cursor.get_position() self.set_position(*other_pos) cursor.set_position(*this_pos) def same_line(self, cursor): return self.get_current_line()[0] == cursor.get_current_line()[0] def get_current_line(self): keep_going = True i, it = self.index, self.parent.order[self.paragraph].get_iter() ln = 0 while keep_going: l = it.get_line() ls = l.start_index le = ls + l.length if i >= ls and i <= le: if not it.at_last_line(): le -= 1 return (self.paragraph, ln), (ls, le) ln += 1 keep_going = it.next_line() return None, None, None def get_current_word(self): keep_going = True layout = self.parent.order[self.paragraph] text = layout.get_text() i, it = self.index, layout.get_iter() start = 0 while keep_going: j = it.get_index() if j >= i and text[j] in self.WORD_TERMINATORS: return self.paragraph, (start, j) elif text[j] in self.WORD_TERMINATORS: start = j + 1 keep_going = it.next_char() return self.paragraph, (start, len(layout)) def set_position(self, paragraph, index): self.index = index self.paragraph = paragraph def get_position(self): return self.paragraph, self.index class PrimaryCursor(Cursor): def __init__(self, parent): Cursor.__init__(self, parent) def __repr__(self): return 'Cursor: ' + str((self.paragraph, self.index)) def get_rectangle(self, layout, a): if self.index < len(layout): pos = layout.get_cursor_pos(self.index)[1] 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 def draw(self, cr, layout, a): cr.set_source_rgb(0, 0, 0) cr.rectangle(*self.get_rectangle(layout, a)) cr.fill() def zero(self): self.index = 0 self.paragraph = 0 class SelectionCursor(Cursor): def __init__(self, cursor): Cursor.__init__(self, cursor.parent) self.cursor = cursor self.target_x = None self.target_x_indent = 0 self.restore_point = None def __repr__(self): return 'Selection: ' + str(self.get_range()) def __nonzero__(self): c = self.cursor return (self.paragraph, self.index) != (c.paragraph, c.index) @property def min(self): c = self.cursor return min((self.paragraph, self.index), (c.paragraph, c.index)) @property def max(self): c = self.cursor return max((self.paragraph, self.index), (c.paragraph, c.index)) def clear(self, key=None): self.index = self.cursor.index self.paragraph = self.cursor.paragraph self.restore_point = None if key not in (Gdk.KEY_uparrow, Gdk.KEY_downarrow): self.target_x = None self.target_x_indent = 0 def set_target_x(self, x, indent): self.target_x = x self.target_x_indent = indent def get_range(self): return self.min, self.max def within_selection(self, pos): l = list(self.get_range()) l.append(pos) l.sort() # sort the list, see if pos is in between the extents of the selection # range, if it is, pos is within the selection if pos in l: return l.index(pos) == 1 return False class TextBlock(Gtk.EventBox): PAINT_PRIMARY_CURSOR = False DEBUG_PAINT_BBOXES = False BULLET_POINT = u' \u2022 ' def __init__(self): Gtk.EventBox.__init__(self) self.set_visible_window(False) 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 | Gdk.EventMask.POINTER_MOTION_MASK) self._is_new = False self.order = [] self.cursor = cur = PrimaryCursor(self) self.selection = sel = SelectionCursor(self.cursor) self.clipboard = None #~ event_helper = EventHelper() self._update_cached_layouts() self._test_layout = self.create_pango_layout('') #self._xterm = Gdk.Cursor.new(Gdk.XTERM) # popup menu and menuitems self.copy_menuitem = Gtk.ImageMenuItem.new_from_stock( Gtk.STOCK_COPY, None) self.select_all_menuitem = Gtk.ImageMenuItem.new_from_stock( Gtk.STOCK_SELECT_ALL, None) self.menu = Gtk.Menu() self.menu.attach_to_widget(self, None) self.menu.append(self.copy_menuitem) 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) #~ Gtk.drag_source_set(self, Gdk.ModifierType.BUTTON1_MASK, #~ None, Gdk.DragAction.COPY) #~ Gtk.drag_source_add_text_targets(self) #~ self.connect('drag-begin', self._on_drag_begin) #~ self.connect('drag-data-get', self._on_drag_data_get, sel) 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('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) self.connect('focus-out-event', self._on_focus_out) self.connect("size-allocate", self.on_size_allocate) self.connect('style-updated', self._on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") def on_size_allocate(self, *args): allocation = self.get_allocation() width = allocation.width x = y = 0 for layout in self.order: 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) else: 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 # overrides def do_get_request_mode(self): return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH def do_get_preferred_height_for_width(self, width): height = 0 layout = self._test_layout for l in self.order: layout.set_text(l.get_text(), -1) layout.set_width(_PS * (width - l.indent)) lh = layout.get_pixel_extents()[1].height height += lh + (l.vspacing or self.line_height) height = max(50, height) return height, height def do_draw(self, cr): self.render(self, cr) def _config_colors(self): context = self.get_style_context() context.save() context.add_class(Gtk.STYLE_CLASS_HIGHLIGHT) state = self.get_state_flags() if self.has_focus(): state |= Gtk.StateFlags.FOCUSED context.set_state(state) self._bg = color_to_hex(context.get_background_color(state)) self._fg = color_to_hex(context.get_color(state)) context.restore() def _on_style_updated(self, widget): self._config_colors() self._update_cached_layouts() # def _on_drag_begin(self, widgets, context, event_helper): # print 'drag: begin' 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) def _on_focus_in(self, widget, event): self._config_colors() def _on_focus_out(self, widget, event): self._config_colors() def _on_motion(self, widget, event, event_helper, cur, sel): 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 start_x, start_y = int(press.x), int(press.y) cur_x, cur_y = int(event.x), int(event.y) if (not event_helper['drag-active'] and self.drag_check_threshold(start_x, start_y, cur_x, cur_y)): event_helper['drag-active'] = True if not event_helper['drag-active']: return #~ 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 #~ #~ event_helper['drag-context'] = ctx #~ return for layout in self.order: point_in, index = layout.index_at(cur_x, cur_y) if point_in: cur.set_position(layout.index, index) self.queue_draw() break def _on_press(self, widget, event, event_helper, cur, sel): if sel and not self.has_focus(): self.grab_focus() return # spot the difference if not self.has_focus(): self.grab_focus() if event.button == 3: self._button3_action(cur, sel, event) return elif event.button != 1: return for layout in self.order: x, y = int(event.x), int(event.y) point_in, index = layout.index_at(x, y) if point_in: within_sel = False #~ within_sel = sel.within_selection((layout.index, index)) if not within_sel: cur.set_position(layout.index, index) sel.clear() #~ event_helper.new_press(event.copy(), layout, index, #~ within_sel) event_helper.new_press(event, layout, index, within_sel) break def _on_release(self, widget, event, event_helper, cur, sel): if not event_helper['event']: return # check if a drag occurred if event_helper['drag-active']: # if so, do not handle release return # else, handle release, do click cur.set_position(event_helper['layout'].index, event_helper['index']) sel.clear() press = event_helper['event'] if (press.type == Gdk.EventType._2BUTTON_PRESS): self._2click_select(cur, sel) elif (press.type == Gdk.EventType._3BUTTON_PRESS): self._3click_select(cur, sel) self.queue_draw() def _menu_do_copy(self, item, sel): self._copy_text(sel) def _menu_do_select_all(self, item, cur, sel): self._select_all(cur, sel) def _button3_action(self, cur, sel, event): start, end = sel.get_range() self.copy_menuitem.set_sensitive(True) self.select_all_menuitem.set_sensitive(True) if not sel: self.copy_menuitem.set_sensitive(False) elif (start == (0, 0) and 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, event.button, event.time) def _on_key_press(self, widget, event, cur, sel): kv = event.keyval s, i = cur.paragraph, cur.index handled_keys = True ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK) > 0 shift = (event.state & Gdk.ModifierType.SHIFT_MASK) > 0 if (not self.PAINT_PRIMARY_CURSOR and kv in (Gdk.KEY_uparrow, Gdk.KEY_downarrow) and not sel): return False if kv == Gdk.KEY_Tab: handled_keys = False elif kv == Gdk.KEY_Left: if ctrl: self._select_left_word(cur, sel, s, i) else: self._select_left(cur, sel, s, i, shift) if shift: layout = self._get_cursor_layout() pos = layout.index_to_pos(cur.index) sel.set_target_x(pos.x, layout.indent) elif kv == Gdk.KEY_Right: if ctrl: self._select_right_word(cur, sel, s, i) else: self._select_right(cur, sel, s, i, shift) if shift: layout = self._get_cursor_layout() pos = layout.index_to_pos(cur.index) sel.set_target_x(pos.x, layout.indent) elif kv == Gdk.KEY_Up: if ctrl: if i == 0: if s > 0: cur.paragraph -= 1 cur.set_position(cur.paragraph, 0) elif sel and not shift: cur.set_position(*sel.min) else: self._select_up(cur, sel) elif kv == Gdk.KEY_Down: if ctrl: if i == len(self._get_layout(cur)): if s + 1 < len(self.order): cur.paragraph += 1 i = len(self._get_layout(cur)) cur.set_position(cur.paragraph, i) elif sel and not shift: cur.set_position(*sel.max) else: self._select_down(cur, sel) elif kv == Gdk.KEY_Home: if shift: self._select_home(cur, sel, self.order[cur.paragraph]) else: cur.set_position(0, 0) elif kv == Gdk.KEY_End: if shift: self._select_end(cur, sel, self.order[cur.paragraph]) else: cur.paragraph = len(self.order) - 1 cur.index = len(self._get_layout(cur)) else: handled_keys = False if not shift and handled_keys: sel.clear(kv) self.queue_draw() return handled_keys def _on_key_release(self, widget, event, cur, sel): ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK) > 0 if ctrl: if event.keyval == Gdk.KEY_a: self._select_all(cur, sel) elif event.keyval == Gdk.KEY_c: self._copy_text(sel) self.queue_draw() def _select_up(self, cur, sel): #~ if sel and not cur.is_min(sel) and cur.same_line(sel): #~ cur.switch(sel) s = cur.paragraph layout = self._get_layout(cur) if sel.target_x: x = sel.target_x if sel.target_x_indent: x += (sel.target_x_indent - layout.indent) * _PS (_, j, k), (x, y) = layout.cursor_up(cur, x) j += k else: (_, j, k), (x, y) = layout.cursor_up(cur) j += k sel.set_target_x(x, layout.indent) if (s, j) != cur.get_position(): cur.set_position(s, j) elif s > 0: 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) else: return False return True def _select_down(self, cur, sel): #~ if sel and not cur.is_max(sel) and cur.same_line(sel): #~ cur.switch(sel) s = cur.paragraph layout = self._get_layout(cur) if sel.target_x: x = sel.target_x if sel.target_x_indent: x += (sel.target_x_indent - layout.indent) * _PS (_, j, k), (x, y) = layout.cursor_down(cur, x) j += k else: (_, j, k), (x, y) = layout.cursor_down(cur) j += k sel.set_target_x(x, layout.indent) if (s, j) != cur.get_position(): cur.set_position(s, j) elif s < len(self.order) - 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) else: return False return True def _2click_select(self, cursor, sel): self._select_word(cursor, sel) def _3click_select(self, cursor, sel): # XXX: # _select_line seems to expose the following Pango issue: # (description.py:3892): Pango-CRITICAL **: # pango_layout_line_unref: assertion `private->ref_count > 0' # failed # ... which can result in a segfault #~ self._select_line(cursor, sel) self._select_all(cursor, sel) def _copy_text(self, sel): text = self.get_selected_text(sel) if not self.clipboard: display = Gdk.Display.get_default() selection = Gdk.Atom.intern("CLIPBOARD", False) self.clipboard = Gtk.Clipboard.get_for_display(display, selection) self.clipboard.clear() self.clipboard.set_text(text.strip(), -1) def _select_end(self, cur, sel, layout): if not cur.is_max(sel): cur.switch(sel) n, r, line = cur.get_current_line() cur_pos = cur.get_position() 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) else: # reselect the line end n, r, line = sel.get_current_line() cur.set_position(n[0], r[1]) 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])) elif cur_pos == (n[0], r[1]): # line end # select para end cur.set_position(n[0], len(self.order[n[0]])) else: # not at any end, within line somewhere # select line end if sel: sel.restore_point = cur_pos cur.set_position(n[0], r[1]) def _select_home(self, cur, sel, layout): if not cur.is_min(sel): cur.switch(sel) n, r, line = cur.get_current_line() cur_pos = cur.get_position() if cur_pos == (0, 0): # absolute home if sel.restore_point: cur.set_position(*sel.restore_point) else: n, r, line = sel.get_current_line() cur.set_position(n[0], r[0]) elif cur_pos[1] == 0: # para home cur.set_position(0, 0) elif cur_pos == (n[0], r[0]): # line home cur.set_position(n[0], 0) else: # not at any home, within line somewhere if sel: sel.restore_point = cur_pos cur.set_position(n[0], r[0]) 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) elif cur.paragraph > 0: cur.paragraph -= 1 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) def _select_left_word(self, cur, sel, s, i): if i > 0: cur.index -= 1 elif s > 0: cur.paragraph -= 1 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)) 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): 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)) def _select_word(self, cursor, sel): paragraph, word = cursor.get_current_word() if word: cursor.set_position(paragraph, word[1] + 1) sel.set_position(paragraph, word[0]) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) def _select_line(self, cursor, sel): n, r = self.cursor.get_current_line() sel.set_position(n[0], r[0]) cursor.set_position(n[0], r[1]) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) def _select_all(self, cursor, sel): layout = self.order[-1] sel.set_position(0, 0) cursor.set_position(layout.index, len(layout)) if self.get_direction() == Gtk.TextDirection.RTL: cursor.switch(sel) def _selection_copy(self, layout, sel, new_para=True): i = layout.index start, end = sel.get_range() if new_para: text = '\n\n' else: text = '' if sel and i >= start[0] and i <= end[0]: if i == start[0]: if end[0] > i: return text + layout.get_text()[start[1]: len(layout)] else: 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]] else: return text + layout.get_text()[start[1]: end[1]] else: return text + layout.get_text() return '' def _new_layout(self, text=''): layout = Layout(self, text) layout.set_wrap(Pango.WrapMode.WORD_CHAR) return layout def _update_cached_layouts(self): self._bullet = self._new_layout() self._bullet.set_markup(self.BULLET_POINT) font_desc = Pango.FontDescription() font_desc.set_weight(Pango.Weight.BOLD) self._bullet.set_font_description(font_desc) e = self._bullet.get_pixel_extents() self.indent, self.line_height = e.width, e.height def _selection_highlight(self, layout, sel, bg, fg): i = layout.index start, end = sel.get_range() if sel and i >= start[0] and i <= end[0]: if i == start[0]: if end[0] > i: layout.highlight(start[1], len(layout), bg, fg) else: layout.highlight(start[1], end[1], bg, fg) elif i == end[0]: if start[0] < i: layout.highlight(0, end[1], bg, fg) else: layout.highlight(start[1], end[1], bg, fg) else: layout.highlight_all(bg, fg) elif not layout._default_attrs: layout.reset_attrs() def _paint_bullet_point(self, cr, x, y): # draw the layout Gtk.render_layout(self.get_style_context(), cr, # state x, # x coord y, # y coord self._bullet._layout) # a Pango.Layout() def _get_layout(self, cursor): return self.order[cursor.paragraph] def _get_cursor_layout(self): return self.order[self.cursor.paragraph] def _get_selection_layout(self): return self.order[self.selection.paragraph] def render(self, widget, cr): if not self.order: return cr.save() a = self.get_allocation() for layout in self.order: lx, ly = layout.get_position() self._selection_highlight(layout, self.selection, self._bg, self._fg) if layout.is_bullet: if self.get_direction() != Gtk.TextDirection.RTL: indent = layout.indent - self.indent else: indent = a.width - layout.indent self._paint_bullet_point(cr, indent, ly) 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.stroke() # draw the layout Gtk.render_layout(self.get_style_context(), cr, lx, # x coord ly, # y coord layout._layout) # a Pango.Layout() # draw the cursor if self.PAINT_PRIMARY_CURSOR and self.has_focus(): self.cursor.draw(cr, self._get_layout(self.cursor), a) cr.restore() def append_paragraph(self, p, vspacing=None): l = self._new_layout() l.index = len(self.order) l.vspacing = vspacing l.set_text(p) self.order.append(l) def append_bullet(self, point, indent_level, vspacing=None): l = self._new_layout() l.index = len(self.order) l.indent = self.indent * (indent_level + 1) l.vspacing = vspacing l.is_bullet = True l.set_text(point) self.order.append(l) def copy_clipboard(self): self._copy_text(self.selection) def get_selected_text(self, sel=None): text = '' if not sel: sel = self.selection for layout in self.order: text += self._selection_copy(layout, sel, (layout.index > 0)) return text def select_all(self): self._select_all(self.cursor, self.selection) self.queue_draw() def finished(self): self.queue_resize() def clear(self, key=None): self.cursor.zero() self.selection.clear(key) self.order = [] class AppDescription(Gtk.VBox): TYPE_PARAGRAPH = 0 TYPE_BULLET = 1 _preparser = _SpecialCasePreParsers() def __init__(self): Gtk.VBox.__init__(self) self.description = TextBlock() self.pack_start(self.description, False, False, 0) self._prev_type = None def _part_is_bullet(self, part): # normalize_description() ensures that we only have "* " bullets i = part.find("* ") return i > -1, i def _parse_desc(self, desc, pkgname): """ 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 description if special case exists for the given pkgname desc = self._preparser.preparse(pkgname, desc) parts = normalize_package_description(desc).split('\n') for part in parts: if not part: continue is_bullet, indent = self._part_is_bullet(part) if is_bullet: self.append_bullet(part, indent) else: self.append_paragraph(part) self.description.finished() def clear(self): self.description.clear() def append_paragraph(self, p): vspacing = self.description.line_height self.description.append_paragraph(p.strip(), vspacing) self._prev_type = self.TYPE_PARAGRAPH def append_bullet(self, point, indent_level): if self._prev_type == self.TYPE_BULLET: 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) self._prev_type = self.TYPE_BULLET def set_description(self, raw_desc, pkgname): self.clear() if type(raw_desc) == str: encoded_desc = unicode(raw_desc, 'utf8').encode('utf8') else: encoded_desc = raw_desc.encode('utf8') self._text = GLib.markup_escape_text(encoded_desc) self._parse_desc(self._text, pkgname) self.show_all() # easy access to some TextBlock methods def copy_clipboard(self): return TextBlock.copy_clipboard(self.description) def get_selected_text(self): return TextBlock.get_selected_text(self.description) def select_all(self): return TextBlock.select_all(self.description) software-center-13.10/softwarecenter/ui/gtk3/widgets/searchaid.py0000664000202700020270000002573612151440100025336 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- from gi.repository import Gtk, GObject, GLib import gettext from gettext import gettext as _ from softwarecenter.ui.gtk3.em import StockEms class Suggestions(Gtk.VBox): __gsignals__ = { "activate-link": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, str), ), } def __init__(self): Gtk.VBox.__init__(self) self.set_spacing(StockEms.MEDIUM) self.title = Gtk.Label() self.title.set_line_wrap(True) self.pack_start(self.title, False, False, 0) self.xalign = 0.0 self.yalign = 0.0 self._labels = [] self._handlers = [] def on_link_activate(self, widget, uri): self.reset_all() self.emit("activate-link", widget, uri) return True # silences the gtk-warning def foreach(self, label_func, *args): for label in [self.title] + self._labels: label_func(label, *args) def set_alignment(self, xalign, yalign): self.xalign = xalign self.yalign = yalign self.foreach(Gtk.Label.set_alignment, xalign, yalign) def set_title(self, title_markup): self.title.set_markup(title_markup) def append_suggestion(self, suggestion_markup): label = Gtk.Label() label.set_alignment(self.xalign, self.yalign) label.set_markup(suggestion_markup) self.pack_start(label, False, False, 0) label.show() self._handlers.append( label.connect("activate-link", self.on_link_activate)) self._labels.append(label) def set_suggestions(self, suggestions): if self._labels: self.reset() for s in suggestions: self.append_suggestion(s) def reset(self): for label, handler in zip(self._labels, self._handlers): GLib.source_remove(handler) label.destroy() self._labels = [] self._handlers = [] def reset_all(self): self.title.set_text('') self.reset() class SearchAidLogic(object): HEADER_ICON_NAME = "face-sad" HEADER_MARKUP = '%s' #TRANSLATORS: this is the layout of an indented # line starting with a bullet point BULLET = unicode(_("\t• %s"), 'utf8').encode('utf8') def __init__(self, pane): self.pane = pane self.db = pane.db self.enquirer = pane.enquirer def is_search_aid_required(self, state): return (state.search_term and len(self.enquirer.matches) == 0) def get_correction(self, term): return self.db.get_spelling_correction(term) def get_title_text(self, term, category, state): from softwarecenter.utils import utf8 def build_category_path(): if not category: return '' if not state.subcategory: return category.name plain_text = _("%(category_name)s → %(subcategory_name)s") usable_text = unicode(plain_text, 'utf8').encode('utf8') return usable_text % {'category_name': category.name, 'subcategory_name': state.subcategory.name} if not category: sub = utf8(_(u"No items match “%sâ€")) % term else: sub = utf8(_(u"No items in %s match “%sâ€")) sub = sub % (build_category_path(), term) return self.HEADER_MARKUP % GLib.markup_escape_text(sub) def get_suggestions(self, term, category, state): correction = self.get_correction(term) suggestions = [] # offer to research in the parent category is search is # limited to a subcategory new_text = self.get_include_parent_suggestion_text( term, category, state) if new_text is not None: suggestions.append(new_text) # check if we are searching supported pkg, and offer to include # unsupported pkg's as well new_text = self.get_unsupported_suggestion_text( term, category, state) if new_text is not None: suggestions.append(new_text) # if we are in a category, suggest searching within 'All cats' if category: new_text = self.BULLET % _("Try searching across " "all categories" " instead") suggestions.append(new_text) # If spelling correction, offer alternative term(s) if correction: correction = GLib.markup_escape_text(correction) ref = "%s" % (correction, correction) new_text = self.BULLET % _("Check that your spelling is correct. " "Did you mean: %s?") % ref suggestions.append(new_text) return suggestions def get_suggestion_title_text(self, suggestions): if suggestions: return _("Suggestions:") # else, say sorry if we cannot offer any suggestions return _("Software Center was unable to come up with any " "suggestions that may aid you in your search") def get_include_parent_suggestion_text(self, term, category, state): if not state.subcategory: return enq = self.enquirer query = self.db.get_query_list_from_search_entry( term, category.query) enq.set_query(query, limit=state.limit, sortmode=self.pane.get_sort_mode(), nonapps_visible=self.pane.nonapps_visible, filter=state.filter, nonblocking_load=False) if enq.nr_apps > 0: text = self.BULLET % gettext.ngettext("Try " "the item " "in %(category)s that matches", "Try " "the %(n)d items " "in %(category)s that match", n=enq.nr_apps) % \ {'category': category.name, 'n': enq.nr_apps} return text def get_unsupported_suggestion_text(self, term, category, state): if state.filter is None: return supported_only = state.filter.get_supported_only() if not supported_only: return state.filter.set_supported_only(False) #if category: # category_query = category.query #else: # category_query = None enq = self.enquirer enq.set_query(enq.search_query, limit=self.pane.get_app_items_limit(), sortmode=self.pane.get_sort_mode(), nonapps_visible=True, filter=state.filter, nonblocking_load=False) state.filter.set_supported_only(True) if enq.nr_apps > 0: text = self.BULLET % gettext.ngettext("Try " "the %(amount)d item " "that matches in software not maintained by Canonical", "Try the %(amount)d items " "that match in software not maintained by Canonical", enq.nr_apps) % {'amount': enq.nr_apps} return text def update_search_help(self, state): # do any non toolkit logic here # ... # do toolkit stuff here if hasattr(self, 'on_update_search_help'): self.on_update_search_help(state) def reset(self): if hasattr(self, 'on_reset'): self.on_reset() class SearchAid(Gtk.Table, SearchAidLogic): def __init__(self, pane): SearchAidLogic.__init__(self, pane) # gtk box basics Gtk.Table.__init__(self) self.resize(2, 2) self.set_border_width(2 * StockEms.XLARGE) # no results (sad face) image image = Gtk.Image.new_from_icon_name(self.HEADER_ICON_NAME, Gtk.IconSize.DIALOG) self.attach(image, 0, 1, # left_attach, right_attach 0, 1, # top_attach, bottom_attach Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, StockEms.LARGE, 0) # title self.title = Gtk.Label() self.title.set_use_markup(True) self.title.set_alignment(0.0, 0.5) self.attach(self.title, 1, 2, # left_attach, right_attach 0, 1, # top_attach, bottom_attach Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, StockEms.MEDIUM, 0) # suggestion label self.suggestion = Suggestions() self.suggestion.set_alignment(0.0, 0.5) self.attach(self.suggestion, 1, 2, # left_attach, right_attach 1, 2, # top_attach, bottom_attach Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.FILL, StockEms.MEDIUM, StockEms.MEDIUM) self.suggestion.connect("activate-link", self.on_link_activate) def on_update_search_help(self, state): if not self.is_search_aid_required(state): # catchall self.pane.app_view.set_visible(True) self.set_visible(False) return self.pane.app_view.set_visible(False) self.set_visible(True) term = state.search_term category = state.category # set the title title_markup = self.get_title_text(term, category, state) self.title.set_markup(title_markup) suggestions = self.get_suggestions(term, category, state) suggestions_title = self.get_suggestion_title_text(suggestions) self.suggestion.set_title(suggestions_title) self.suggestion.set_suggestions(suggestions) def on_reset(self): self.suggestion.reset_all() def on_link_activate(self, suggestions, link, uri): markup = self.HEADER_MARKUP % _('Trying suggestion ...') self.title.set_markup(markup) GLib.timeout_add(750, self._handle_suggestion_action, uri) def _handle_suggestion_action(self, uri): self = self.pane if uri.startswith("search/"): self.searchentry.set_text(uri[len("search/"):]) elif uri.startswith("search-all/"): self.unset_current_category() self.refresh_apps() elif uri.startswith("search-parent/"): self.state.subcategory = None self.refresh_apps() elif uri.startswith("search-unsupported:"): self.state.filter.set_supported_only(False) self.refresh_apps() # FIXME: add ability to remove categories restriction here # True stops event propagation return False software-center-13.10/softwarecenter/ui/gtk3/widgets/imagedialog.py0000664000202700020270000000456012151440100025645 0ustar dobeydobey00000000000000# 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 import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, GdkPixbuf ICON_EXCEPTIONS = ["gnome"] class Url404Error(IOError): pass class Url403Error(IOError): pass class SimpleShowImageDialog(Gtk.Dialog): """A dialog that shows a image """ DEFAULT_WIDTH = 850 DEFAULT_HEIGHT = 650 def __init__(self, title, pixbuf, parent=None): Gtk.Dialog.__init__(self) # find parent window for the dialog if not parent: parent = self.get_parent() while parent: parent = parent.get_parent() # screenshot img = Gtk.Image.new_from_pixbuf(pixbuf) # scrolled window for screenshot scroll = Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.add_with_viewport(img) content_area = self.get_content_area() content_area.pack_start(scroll, True, True, 0) # dialog self.set_title(title) self.set_transient_for(parent) self.set_destroy_with_parent(True) self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) 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() Gtk.Dialog.run(self) if __name__ == "__main__": # pixbuf d = SimpleShowImageDialog("Synaptic Screenshot", GdkPixbuf.Pixbuf.new_from_file( "/usr/share/software-center/default_banner/fallback.png")) d.run() software-center-13.10/softwarecenter/ui/gtk3/widgets/viewport.py0000664000202700020270000000261412151440100025260 0ustar dobeydobey00000000000000from gi.repository import Gtk, Gdk # key values we want to respond to _uppers = (Gdk.KEY_KP_Up, Gdk.KEY_Up) _downers = (Gdk.KEY_KP_Down, Gdk.KEY_Down) class Viewport(Gtk.Viewport): def __init__(self): Gtk.Viewport.__init__(self) self.connect("key-press-event", self.on_key_press_event) return def on_key_press_event(self, widget, event): global _uppers, _downers kv = event.keyval if kv in _uppers or kv in _downers: # get the ScrolledWindow adjustments and tweak them appropriately if not self.get_parent(): return False # the ScrolledWindow vertical-adjustment scroll = self.get_ancestor('GtkScrolledWindow') if not scroll: return False v_adj = scroll.get_vadjustment() # scroll up if kv in _uppers: v = max(v_adj.get_value() - v_adj.get_step_increment(), v_adj.get_lower()) # scroll down elif kv in _downers: v = min(v_adj.get_value() + v_adj.get_step_increment(), v_adj.get_upper() - v_adj.get_page_size()) # set our new value v_adj.set_value(v) # do not share the event with other widgets return True # share the event with other widgets return False software-center-13.10/softwarecenter/ui/gtk3/widgets/cellrenderers.py0000664000202700020270000004336412151440100026241 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # 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 Gtk, Gdk, GObject, GLib, Pango from softwarecenter.utils import utf8 from softwarecenter.ui.gtk3.em import EM from softwarecenter.ui.gtk3.models.appstore2 import CategoryRowReference from stars import StarRenderer, StarSize class CellButtonIDs: INFO = 0 ACTION = 1 # custom cell renderer to support dynamic grow class CellRendererAppView(Gtk.CellRendererText): # x, y offsets for the overlay icon OVERLAY_XO = OVERLAY_YO = 2 # size of the install overlay icon OVERLAY_SIZE = 16 # ratings MAX_STARS = 5 STAR_SIZE = EM # initialize declared properties (LP: #965937) application = GObject.Property( type=GObject.TYPE_PYOBJECT, nick='document', blurb='a xapian document containing pkg information', flags=(GObject.PARAM_READWRITE | GObject.PARAM_CONSTRUCT), default=None) isactive = GObject.Property( type=bool, nick='isactive', blurb='is cell active/selected', flags=(GObject.PARAM_READWRITE | GObject.PARAM_CONSTRUCT), default=False) def __init__(self, icons, layout, show_ratings, overlay_icon_name): Gtk.CellRendererText.__init__(self) # the icon pixbuf to be displayed in the row self.icon = None # geometry-state values self.pixbuf_width = 0 self.apptitle_width = 0 self.apptitle_height = 0 self.normal_height = 0 self.selected_height = 0 self.show_ratings = show_ratings self.icon_x_offset = 0 self.icon_y_offset = 0 # button packing self.button_spacing = 0 self._buttons = { Gtk.PackType.START: [], Gtk.PackType.END: [] } self._all_buttons = {} # cache a layout self._layout = layout # star painter, paints stars self._stars = StarRenderer() self._stars.size = StarSize.SMALL # icon/overlay jazz try: self._installed = icons.load_icon(overlay_icon_name, self.OVERLAY_SIZE, 0) except GObject.GError: # icon not present in theme, probably because running uninstalled self._installed = icons.load_icon('emblem-system', self.OVERLAY_SIZE, 0) def _layout_get_pixel_width(self, layout): return layout.get_size()[0] / Pango.SCALE def _layout_get_pixel_height(self, layout): return layout.get_size()[1] / Pango.SCALE def _render_category(self, context, cr, app, cell_area, layout, xpad, ypad, is_rtl): layout.set_markup('%s' % app.display_name, -1) # work out max allowable layout width lw = self._layout_get_pixel_width(layout) lh = self._layout_get_pixel_height(layout) if not is_rtl: x = cell_area.x else: x = cell_area.x + cell_area.width - lw y = cell_area.y + (cell_area.height - lh) / 2 Gtk.render_layout(context, cr, x, y, layout) def _render_price(self, context, cr, app, layout, cell_area, xpad, ypad, is_rtl): layout.set_markup("%s" % self.model.get_display_price(app)) if is_rtl: x = cell_area.x + xpad else: x = (cell_area.x + cell_area.width - xpad - self._layout_get_pixel_width(layout)) Gtk.render_layout(context, cr, x, ypad + cell_area.y, layout) 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 if not is_rtl: x = cell_area.x + xo + xpad else: x = cell_area.x + cell_area.width + xo - self.pixbuf_width - xpad y = cell_area.y + ypad # 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: x += (self.pixbuf_width - self.OVERLAY_SIZE + self.OVERLAY_XO) else: x -= self.OVERLAY_XO y += (self.pixbuf_width - self.OVERLAY_SIZE + self.OVERLAY_YO) Gdk.cairo_set_source_pixbuf(cr, self._installed, x, y) cr.paint() def _render_summary(self, context, cr, app, cell_area, layout, xpad, ypad, star_width, is_rtl): layout.set_markup(self.model.get_markup(app), -1) # work out max allowable layout width 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 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): action_btn = self.get_button_by_name(CellButtonIDs.ACTION) max_layout_width -= (xpad + action_btn.width) if lw >= max_layout_width: layout.set_width((max_layout_width) * Pango.SCALE) layout.set_ellipsize(Pango.EllipsizeMode.END) lw = max_layout_width apptitle_extents = layout.get_line_readonly(0).get_pixel_extents()[1] self.apptitle_width = apptitle_extents.width self.apptitle_height = apptitle_extents.height if not is_rtl: x = cell_area.x + 2 * xpad + self.pixbuf_width else: x = (cell_area.x + cell_area.width - lw - self.pixbuf_width - 2 * xpad) y = cell_area.y + ypad Gtk.render_layout(context, cr, x, y, layout) 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 sr = self._stars if not is_rtl: x = (cell_area.x + 3 * xpad + self.pixbuf_width + self.apptitle_width) else: x = (cell_area.x + cell_area.width - 3 * xpad - self.pixbuf_width - self.apptitle_width - star_width) 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 layout.set_markup("%s" % s, -1) if not is_rtl: x += xpad + star_width else: 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() 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 action_btn = self.get_button_by_name(CellButtonIDs.ACTION) x, _, w, h = action_btn.allocation # shift the bar to the top edge y = cell_area.y + ypad context.save() context.add_class("trough") Gtk.render_background(context, cr, x, y, w, h) Gtk.render_frame(context, cr, x, y, w, h) context.restore() bar_size = w * percent 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() 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 spacing = self.button_spacing if not is_rtl: start = Gtk.PackType.START end = Gtk.PackType.END xs = cell_area.x + 2 * xpad + self.pixbuf_width xb = cell_area.x + cell_area.width - xpad else: start = Gtk.PackType.END end = Gtk.PackType.START xs = cell_area.x + xpad 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.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.render(context, cr, layout) xb -= spacing def set_pixbuf_width(self, w): self.pixbuf_width = w def set_button_spacing(self, spacing): self.button_spacing = spacing def get_button_by_name(self, name): if name in self._all_buttons: return self._all_buttons[name] def get_buttons(self): btns = () for k, v in self._buttons.items(): btns += tuple(v) return btns def button_pack(self, btn, pack_type=Gtk.PackType.START): self._buttons[pack_type].append(btn) self._all_buttons[btn.name] = btn def button_pack_start(self, btn): self.button_pack(btn, Gtk.PackType.START) def button_pack_end(self, btn): self.button_pack(btn, Gtk.PackType.END) def do_set_property(self, pspec, value): setattr(self, pspec.name, value) def do_get_property(self, pspec): return getattr(self, pspec.name) def do_get_preferred_height_for_width(self, treeview, width): if not self.get_properties("isactive")[0]: return self.normal_height, self.normal_height return self.selected_height, self.selected_height def do_render(self, cr, widget, bg_area, cell_area, flags): app = self.props.application if not app: return self.model = widget.appmodel context = widget.get_style_context() xpad = self.get_property('xpad') ypad = self.get_property('ypad') star_width, star_height = self._stars.get_visible_size(context) is_rtl = widget.get_direction() == Gtk.TextDirection.RTL layout = self._layout # 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 #~ if widget.has_focus(): #~ state = Gtk.StateFlags.SELECTED #~ else: #~ state = Gtk.StateFlags.ACTIVE #~ else: #~ state = Gtk.StateFlags.NORMAL context.save() #~ context.set_state(state) if isinstance(app, CategoryRowReference): self._render_category(context, cr, app, cell_area, layout, xpad, ypad, is_rtl) return self._render_icon(cr, app, cell_area, xpad, ypad, is_rtl) self._render_summary(context, cr, app, cell_area, layout, xpad, ypad, star_width, is_rtl) # only show ratings if we have one if self.show_ratings: self._render_rating(context, cr, app, cell_area, layout, xpad, ypad, star_width, star_height, is_rtl) progress = self.model.get_transaction_progress(app) if progress > 0: self._render_progress(context, cr, progress, cell_area, ypad, is_rtl) elif self.model.is_purchasable(app): self._render_price(context, cr, app, layout, cell_area, xpad, ypad, is_rtl) # below is the stuff that is only done for the active cell if not self.props.isactive: return self._render_buttons(context, cr, cell_area, layout, xpad, ypad, is_rtl) context.restore() class CellButtonRenderer(object): def __init__(self, widget, name, use_max_variant_width=True): # use_max_variant_width is currently ignored. assumed to be True self.name = name self.markup_variants = {} self.current_variant = None self.xpad = 12 self.ypad = 4 self.allocation = [0, 0, 1, 1] self.state = Gtk.StateFlags.NORMAL self.has_focus = False self.visible = True self.widget = widget def _layout_reset(self, layout): layout.set_width(-1) layout.set_ellipsize(Pango.EllipsizeMode.NONE) @property def x(self): return self.allocation[0] @property def y(self): return self.allocation[1] @property def width(self): return self.allocation[2] @property def height(self): return self.allocation[3] def configure_geometry(self, layout): self._layout_reset(layout) max_size = (0, 0) for k, variant in self.markup_variants.items(): safe_markup = GLib.markup_escape_text(utf8(variant)) layout.set_markup(safe_markup, -1) size = layout.get_size() max_size = max(max_size, size) w, h = max_size w /= Pango.SCALE h /= Pango.SCALE self.set_size(w + 2 * self.xpad, h + 2 * self.ypad) def point_in(self, px, py): x, y, w, h = self.allocation return (px >= x and px <= x + w and py >= y and py <= y + h) def get_size(self): return self.allocation[2:] def set_position(self, x, y): self.allocation[:2] = int(x), int(y) def set_size(self, w, h): self.allocation[2:] = int(w), int(h) def set_state(self, state): if not isinstance(state, Gtk.StateFlags): msg = ("state should be of type Gtk.StateFlags, got %s" % type(state)) raise TypeError(msg) elif state == self.state: return self.state = state self.widget.queue_draw_area(*self.allocation) def set_sensitive(self, is_sensitive): if is_sensitive: state = Gtk.StateFlags.PRELIGHT else: state = Gtk.StateFlags.INSENSITIVE self.set_state(state) def show(self): self.visible = True def hide(self): self.visible = False def set_markup(self, markup): self.markup_variant = (markup,) 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 self.markup_variants = markup_variants self.current_variant = markup_variants.keys()[0] def set_variant(self, current_var): self.current_variant = current_var def is_sensitive(self): return self.state is not Gtk.StateFlags.INSENSITIVE def render(self, context, cr, layout): if not self.visible: return x, y, width, height = self.allocation context.save() context.add_class("cellrenderer-button") if self.has_focus: context.set_state(self.state | Gtk.StateFlags.FOCUSED) else: context.set_state(self.state) # render background and focal frame if has-focus context.save() context.add_class(Gtk.STYLE_CLASS_BUTTON) Gtk.render_background(context, cr, x, y, width, height) context.restore() if self.has_focus: Gtk.render_focus(context, cr, x + 3, y + 3, width - 6, height - 6) # position and render layout markup context.save() context.add_class(Gtk.STYLE_CLASS_BUTTON) layout.set_markup(self.markup_variants[self.current_variant], -1) layout_width = layout.get_pixel_extents()[1].width x = x + (width - layout_width) / 2 y += self.ypad Gtk.render_layout(context, cr, x, y, layout) context.restore() context.restore() software-center-13.10/softwarecenter/ui/gtk3/widgets/containers.py0000664000202700020270000004642112151440100025552 0ustar dobeydobey00000000000000import cairo import os from math import pi as PI PI_OVER_180 = PI / 180 import softwarecenter.paths from gi.repository import Gtk, Gdk, GObject, GLib from buttons import MoreLink from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.drawing import rounded_rect from softwarecenter.ui.gtk3.widgets.spinner import (SpinnerView, SpinnerNotebook) from softwarecenter.ui.gtk3.widgets.buttons import FeaturedTile class FlowableGrid(Gtk.Fixed): MIN_HEIGHT = 100 def __init__(self, paint_grid_pattern=True): Gtk.Fixed.__init__(self) self.set_size_request(100, -1) self.row_spacing = 0 self.column_spacing = 0 self.n_columns = 0 self.n_rows = 0 self.paint_grid_pattern = paint_grid_pattern self._cell_size = None # private def _get_n_columns_for_width(self, width, cell_w, col_spacing): n_cols = width / (cell_w + col_spacing) return n_cols def _layout_children(self, a): if not self.get_visible(): return children = self.get_children() width = a.width #height = a.height col_spacing = 0 row_spacing = 0 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 cell_w = width / n_cols self.n_columns = n_cols #~ h_overhang = width - n_cols*cell_w - (n_cols-1)*col_spacing #~ if n_cols > 1: #~ xo = h_overhang / (n_cols-1) #~ else: #~ xo = h_overhang if len(children) % n_cols: self.n_rows = len(children) / n_cols + 1 else: self.n_rows = len(children) / n_cols y = 0 for i, child in enumerate(children): x = a.x + (i % n_cols) * (cell_w + col_spacing) #~ x = a.x + (i % n_cols) * (cell_w + col_spacing + xo) #~ if n_cols == 1: #~ x += xo/2 if (i % n_cols) == 0: y = a.y + (i / n_cols) * (cell_h + row_spacing) child_alloc = child.get_allocation() child_alloc.x = x child_alloc.y = y child_alloc.width = cell_w child_alloc.height = cell_h child.size_allocate(child_alloc) # overrides def do_get_request_mode(self): return Gtk.SizeRequestMode.WIDTH_FOR_HEIGHT def do_get_preferred_height_for_width(self, width): old = self.get_allocation() 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 children = self.get_children() n_rows = len(children) / n_cols # store these for use when _layout_children gets called if len(children) % n_cols: n_rows += 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 # signal handlers def do_size_allocate(self, allocation): self.set_allocation(allocation) self._layout_children(allocation) def do_draw(self, cr): 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) # public def render_grid(self, cr): context = self.get_style_context() context.save() context.add_class("grid-lines") bg = context.get_border_color(self.get_state_flags()) context.restore() cr.save() Gdk.cairo_set_source_rgba(cr, bg) cr.set_line_width(1) a = self.get_allocation() w = a.width / self.n_columns for i in range(1, self.n_columns): cr.move_to(i * w + 0.5, 0) cr.rel_line_to(0, a.height - 1) cr.stroke() w = a.height / self.n_rows for i in range(self.n_rows): cr.move_to(0, i * w + 0.5) cr.rel_line_to(a.width, 0) cr.stroke() cr.restore() def add_child(self, child): self._cell_size = None self.put(child, 0, 0) def get_cell_size(self): if self._cell_size is not None: return self._cell_size w = h = 1 for child in self.get_children(): child_pref_w = child.get_preferred_width()[0] child_pref_h = child.get_preferred_height()[0] w = max(w, child_pref_w) h = max(h, child_pref_h) self._cell_size = (w, h) return w, h def set_row_spacing(self, value): self.row_spacing = value def set_column_spacing(self, value): self.column_spacing = value self._layout_children(self.get_allocation()) def remove_all(self, destroy=True): self._cell_size = None for child in self: self.remove(child) # meh, this should not really be necessary, but e.g. the # TileButtons have pretty high refcounts even after remove (~10) if destroy: #print child.__grefcount__ child.destroy() del child class TileGrid(FlowableGrid): ''' a flowable layout widget that holds a collection of TileButtons ''' __gsignals__ = { "application-activated": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, ), ), } def add_tiles(self, properties_helper, docs, amount): '''Adds application tiles to an ApplicationTileGrid: properties_help -- an instance of the PropertiesHelper object docs -- xapian documents (apps) amount -- number of tiles to add from start of doc range ''' amount = min(len(docs), amount) for doc in docs[0:amount]: tile = FeaturedTile(properties_helper, doc) tile.connect('clicked', self._on_app_clicked, properties_helper.get_application(doc)) self.add_child(tile) # private def _on_app_clicked(self, btn, app): """emit signal when a tile is clicked""" def timeout_emit(): self.emit("application-activated", app) return False GLib.timeout_add(50, timeout_emit) # 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") def __init__(self, padding=0): Gtk.Alignment.__init__(self) # set padding + some additional padding to factor in the # dropshadow+border width # padding is (top, bottom, left, right) self.set_padding(padding, padding + 3, padding + 3, padding + 3) self._cache_art_assets() # second tier of caching, cache resultant surface of # fully composed and rendered frame self._frame_surface_cache = None self._allocation = Gdk.Rectangle() self.connect("size-allocate", self.on_size_allocate) self.connect("style-updated", self.on_style_updated) # workaround broken engines (LP: #1021308) self.emit("style-updated") def on_style_updated(self, widget): self._frame_surface_cache = None def on_size_allocate(self, *args): old = self._allocation cur = self.get_allocation() if cur.width != old.width or cur.height != old.height: self._frame_surface_cache = None self._allocation = cur return return True def _cache_art_assets(self): global _frame_asset_cache at = self.ASSET_TAG assets = _frame_asset_cache if at in assets: return assets def cache_corner_surface(tag, xo, yo): sw = sh = cnr_slice surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh) cr = cairo.Context(surf) cr.set_source_surface(border_image, xo, yo) cr.paint() assets[tag] = surf del cr def cache_edge_pattern(tag, xo, yo, sw, sh): surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh) cr = cairo.Context(surf) cr.set_source_surface(border_image, xo, yo) cr.paint() ptrn = cairo.SurfacePattern(surf) ptrn.set_extend(cairo.EXTEND_REPEAT) assets[tag] = ptrn del cr # register the asset tag within the asset_cache assets[at] = 'loaded' # the basic stuff border_image = cairo.ImageSurface.create_from_png(self.BORDER_IMAGE) assets["corner-slice"] = cnr_slice = 10 w = border_image.get_width() h = border_image.get_height() # caching .... # north-west corner of border image cache_corner_surface("%s-nw" % at, 0, 0) # northern edge pattern cache_edge_pattern("%s-n" % at, -cnr_slice, 0, w - 2 * cnr_slice, cnr_slice) # north-east corner 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) # south-east corner 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) # south-west corner 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) # all done! return assets def do_draw(self, cr): cr.save() self.on_draw(cr) cr.restore() 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) def render_frame(self, cr, a, border_radius, assets): # we cache as much of the drawing as possible # store a copy of the rendered frame surface, so we only have to # do a full redraw if the widget dimensions change if self._frame_surface_cache is None: surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, a.width, a.height) _cr = cairo.Context(surf) at = self.ASSET_TAG width = a.width height = a.height cnr_slice = assets["corner-slice"] # paint north-west corner _cr.set_source_surface(assets["%s-nw" % at], 0, 0) _cr.paint() # paint north length _cr.save() _cr.set_source(assets["%s-n" % at]) _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) _cr.paint() # paint east length _cr.save() _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.clip() _cr.paint() _cr.restore() # paint south-east corner _cr.set_source_surface(assets["%s-se" % at], width - cnr_slice, height - cnr_slice) _cr.paint() # paint south length _cr.save() _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.clip() _cr.paint() _cr.restore() # paint south-west corner _cr.set_source_surface(assets["%s-sw" % at], 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.clip() _cr.paint() _cr.restore() # fill interior 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()) Gdk.cairo_set_source_rgba(_cr, bg) _cr.fill_preserve() lin = cairo.LinearGradient(0, 0, 0, max(300, a.height)) lin.add_color_stop_rgba(0, 1, 1, 1, 0.02) lin.add_color_stop_rgba(1, 0, 0, 0, 0.06) _cr.set_source(lin) _cr.fill() self._frame_surface_cache = surf del _cr # 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 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() class SmallBorderRadiusFrame(Frame): BORDER_RADIUS = 3 ASSET_TAG = "small" BORDER_IMAGE = os.path.join( softwarecenter.paths.datadir, "ui/gtk3/art/frame-border-image-2px-border-radius.png") def __init__(self, padding=3): Frame.__init__(self, padding) class FramedBox(Frame): 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) def add(self, *args, **kwargs): return self.box.add(*args, **kwargs) def pack_start(self, *args, **kwargs): return self.box.pack_start(*args, **kwargs) def pack_end(self, *args, **kwargs): return self.box.pack_end(*args, **kwargs) class HeaderPosition: LEFT = 0.0 CENTER = 0.5 RIGHT = 1.0 class FramedHeaderBox(FramedBox): MARKUP = '%s' # pages for the spinner notebook (CONTENT, SPINNER) = range(2) def __init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0, padding=0, show_spinner=False): FramedBox.__init__(self, Gtk.Orientation.VERTICAL, spacing, padding) # make the header self.header = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing) self.header_alignment = Gtk.Alignment() self.header_alignment.add(self.header) self.box.pack_start(self.header_alignment, False, False, 0) # make the content box self.content_box = Gtk.Box.new(orientation, spacing) self.content_box.show() # finally, a notebook for the spinner and the content box to share self.spinner_notebook = SpinnerNotebook(self.content_box, spinner_size=SpinnerView.SMALL) self.box.add(self.spinner_notebook) # make the "More" button, but don't add it to the header unless/until # we get a header_implements_more_button self.more = MoreLink() def on_draw(self, cr): a = self.get_allocation() self.render_frame(cr, a, Frame.BORDER_RADIUS, _frame_asset_cache) 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) def add(self, *args, **kwargs): return self.content_box.add(*args, **kwargs) def pack_start(self, *args, **kwargs): return self.content_box.pack_start(*args, **kwargs) def pack_end(self, *args, **kwargs): return self.content_box.pack_end(*args, **kwargs) def set_header_position(self, position): alignment = self.header_alignment alignment.set(position, 0.5, alignment.get_property("xscale"), alignment.get_property("yscale")) def set_header_label(self, label): if not hasattr(self, "title"): self.title = Gtk.Label() self.title.set_padding(StockEms.MEDIUM, StockEms.SMALL) context = self.title.get_style_context() context.add_class("frame-header-title") self.header.pack_start(self.title, False, False, 0) self.title.show() self.title.set_markup(self.MARKUP % label) def header_implements_more_button(self): if self.more not in self.header: self.header.pack_end(self.more, False, False, 0) def remove_more_button(self): if self.more in self.header: self.header.remove(self.more) def render_header(self, cr, a, border_radius, assets): if self.more in self.header: context = self.get_style_context() # set the arrow fill color context = self.more.get_style_context() cr.save() bg = context.get_background_color(self.get_state_flags()) Gdk.cairo_set_source_rgba(cr, bg) # the arrow shape stuff r = Frame.BORDER_RADIUS - 1 ta = self.more.get_allocation() y = ta.y - a.y + 2 h = ta.height - 2 if self.get_direction() == Gtk.TextDirection.RTL: x = ta.x - a.x + 3 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.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) else: x = ta.x - a.x - StockEms.MEDIUM w = ta.width + StockEms.MEDIUM + 3 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.close_path() cr.fill() cr.move_to(x, y) 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) cr.set_line_width(1) cr.stroke() cr.restore() # paint the containers children for child in self: self.propagate_draw(child, cr) software-center-13.10/softwarecenter/ui/gtk3/widgets/reviews.py0000664000202700020270000011127012151440100025064 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010 Canonical # # Authors: # Matthew McGowan # 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 gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, GObject, Pango, GLib import datetime import logging import gettext from gettext import gettext as _ from stars import Star from softwarecenter.utils import ( get_person_from_config, get_nice_date_string, upstream_version_compare, upstream_version, utf8, ) from softwarecenter.i18n import ( get_languages, langcode_to_name, ) from softwarecenter.netstatus import ( network_state_is_connected, get_network_watcher, ) from softwarecenter.enums import ( PkgStates, ReviewSortMethods, ) from softwarecenter.backend.reviews import UsefulnessCache from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.widgets.buttons import Link LOG_ALLOCATION = logging.getLogger("softwarecenter.ui.Gtk.get_allocation()") LOG = logging.getLogger(__name__) (COL_LANGNAME, COL_LANGCODE) = range(2) class UIReviewsList(Gtk.VBox): __gsignals__ = { 'new-review': (GObject.SignalFlags.RUN_FIRST, None, ()), 'report-abuse': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'submit-usefulness': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT, bool)), 'modify-review': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'delete-review': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), 'more-reviews-clicked': (GObject.SignalFlags.RUN_FIRST, None, ()), 'different-review-language-clicked': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)), 'review-sort-changed': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_INT,)), } def __init__(self, parent): Gtk.VBox.__init__(self) self.set_spacing(12) self.logged_in_person = get_person_from_config() self._parent = parent # this is a list of review data (softwarecenter.backend.reviews.Review) self.reviews = [] # global review stats, this includes ratings in different languages self.global_review_stats = None # usefulness stuff self.useful_votes = UsefulnessCache() self.logged_in_person = None # add header label label = Gtk.Label() label.set_markup('%s' % _("Reviews")) label.set_padding(6, 6) label.set_use_markup(True) label.set_alignment(0, 0.5) self.header = Gtk.HBox() self.header.pack_start(label, False, False, 0) # header self.header.set_spacing(StockEms.MEDIUM) # review sort method 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.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) # change language self.review_language = Gtk.ComboBox() cell = Gtk.CellRendererText() self.review_language.pack_start(cell, True) self.review_language.add_attribute(cell, "text", COL_LANGNAME) self.review_language_model = Gtk.ListStore(str, str) for lang in get_languages(): self.review_language_model.append((langcode_to_name(lang), lang)) self.review_language_model.append((_('Any language'), 'any')) self.review_language.set_model(self.review_language_model) self.review_language.set_active(0) self.review_language.connect( "changed", self._on_different_review_language_clicked) self.header.pack_end(self.review_language, False, True, 0) self.pack_start(self.header, False, False, 0) self.reviews_info_hbox = Gtk.HBox() self.new_review = Link(_('Write your own review')) self.new_review.connect('clicked', lambda w: self.emit('new-review')) self.reviews_info_hbox.pack_start( self.new_review, False, False, StockEms.SMALL) self.pack_start(self.reviews_info_hbox, True, True, 0) # this is where the reviews end up self.vbox = Gtk.VBox() self.vbox.set_spacing(24) self.pack_end(self.vbox, True, True, 0) # ensure network state updates self.no_network_msg = None watcher = get_network_watcher() watcher.connect( "changed", lambda w, s: self._on_network_state_change()) self.show_all() def _on_network_state_change(self): is_connected = network_state_is_connected() if is_connected: self.new_review.set_sensitive(True) if self.no_network_msg: self.no_network_msg.hide() else: self.new_review.set_sensitive(False) if self.no_network_msg: self.no_network_msg.show() def _on_button_new_clicked(self, button): self.emit("new-review") def _on_sort_method_changed(self, cb): selection = self.sort_combo.get_active() if selection == self._current_sort: return else: self._current_sort = selection self.emit("review-sort-changed", selection) def update_useful_votes(self, my_votes): self.useful_votes = my_votes def _fill(self): """ take the review data object from self.reviews and build the UI vbox out of them """ self.logged_in_person = get_person_from_config() is_first_for_version = None if self.reviews: previous_review = None for r in self.reviews: pkgversion = self._parent.app_details.version if previous_review: is_first_for_version = previous_review.version != r.version else: is_first_for_version = True previous_review = r review = UIReview(r, pkgversion, self.logged_in_person, self.useful_votes, is_first_for_version) review.show_all() self.vbox.pack_start(review, True, True, 0) def _be_the_first_to_review(self): s = _('Be the first to review it') self.new_review.set_label(s) self.vbox.pack_start(NoReviewYetWriteOne(), True, True, 0) self.vbox.show_all() def _install_to_review(self): s = ('%s' % _("You need to install this before you can review it")) self.install_first_label = Gtk.Label(label=s) self.install_first_label.set_use_markup(True) self.install_first_label.set_alignment(1.0, 0.5) self.reviews_info_hbox.pack_start( self.install_first_label, False, False, 0) self.install_first_label.show() # FIXME: this needs to be smarter in the future as we will # not allow multiple reviews for the same software version def _any_reviews_current_user(self): for review in self.reviews: if self.logged_in_person == review.reviewer_username: return True return False def _add_no_network_connection_msg(self): title = _('No network connection') msg = _('Connect to the Internet to see more reviews.') m = EmbeddedMessage(title, msg, 'network-offline') self.vbox.pack_start(m, True, True, 0) return m def _clear_vbox(self, vbox): children = vbox.get_children() for child in children: child.destroy() # FIXME: instead of clear/add_reviews/configure_reviews_ui we should # provide a single show_reviews(reviews_data_list) def configure_reviews_ui(self): """ this needs to be called after add_reviews, it will actually show the reviews """ #print 'Review count: %s' % len(self.reviews) try: self.install_first_label.hide() except AttributeError: pass self._clear_vbox(self.vbox) # network sensitive stuff, only show write_review if connected, # add msg about offline cache usage if offline is_connected = network_state_is_connected() self.no_network_msg = self._add_no_network_connection_msg() # only show new_review for installed stuff is_installed = (self._parent.app_details and self._parent.app_details.pkg_state == PkgStates.INSTALLED) # show/hide new review button if is_installed: self.new_review.show() else: self.new_review.hide() # if there are no reviews, the install to review text appears # where the reviews usually are (LP #823255) if self.reviews: self._install_to_review() # always hide spinner and call _fill (fine if there is nothing to do) self.hide_spinner() self._fill() if self.reviews: # adjust label if we have reviews if self._any_reviews_current_user(): self.new_review.hide() else: self.new_review.set_label(_("Write your own review")) else: # no reviews, either offer to write one or show "none" if (self.get_active_review_language() != 'any' and self.global_review_stats and self.global_review_stats.ratings_total > 0): self.vbox.pack_start(NoReviewRelaxLanguage(), True, True, 0) elif is_installed and is_connected: self._be_the_first_to_review() else: self.vbox.pack_start(NoReviewYet(), True, True, 0) # aaronp: removed check to see if the length of reviews is divisible by # the batch size to allow proper fixing of LP: #794060 as when a review # is submitted and appears in the list, the pagination will break this # check and make it unreliable # if self.reviews and len(self.reviews) % REVIEWS_BATCH_PAGE_SIZE == 0: if self.reviews: button = Gtk.Button(_("Check for more reviews")) button.connect("clicked", self._on_more_reviews_clicked) button.show() self.vbox.pack_start(button, False, False, 0) # always run this here to make update the current ui based on the # network state self._on_network_state_change() def _on_more_reviews_clicked(self, button): # remove button and emit signal self.vbox.remove(button) self.emit("more-reviews-clicked") def _on_different_review_language_clicked(self, combo): language = self.get_active_review_language() # clean reviews so that we can show the new language self.clear() self.emit("different-review-language-clicked", language) def get_active_review_language(self): model = self.review_language.get_model() language = model[self.review_language.get_active_iter()][COL_LANGCODE] return language def get_all_review_ids(self): ids = [] for review in self.reviews: ids.append(review.id) return ids def add_review(self, review): self.reviews.append(review) def replace_review(self, review): for r in self.reviews: if r.id == review.id: pos = self.reviews.index(r) self.reviews.remove(r) self.reviews.insert(pos, review) break def remove_review(self, review): for r in self.reviews: if r.id == review.id: self.reviews.remove(r) break def clear(self): self.reviews = [] for review in self.vbox: review.destroy() self.new_review.hide() # FIXME: ideally we would have "{show,hide}_loading_notice()" to # easily allow changing from e.g. spinner to text def show_spinner_with_message(self, message): try: self.install_first_label.hide() except AttributeError: pass a = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0) hb = Gtk.HBox(spacing=12) hb.show() a.add(hb) a.show() spinner = Gtk.Spinner() spinner.start() spinner.show() hb.pack_start(spinner, False, False, 0) l = Gtk.Label() l.set_markup(message) l.set_use_markup(True) l.show() hb.pack_start(l, False, False, 0) self.vbox.pack_start(a, False, False, 0) self.vbox.show() def hide_spinner(self): for child in self.vbox.get_children(): if isinstance(child, Gtk.Alignment): child.destroy() def draw(self, cr, a): for r in self.vbox: if isinstance(r, (UIReview)): r.draw(cr, r.get_allocation()) class UIReview(Gtk.VBox): """ the UI for a individual review including all button to mark useful/inappropriate etc """ def __init__(self, review_data=None, app_version=None, logged_in_person=None, useful_votes=None, first_for_version=True): Gtk.VBox.__init__(self) self.set_spacing(StockEms.SMALL) self.version_label = Gtk.Label() self.version_label.set_alignment(0, 0.5) self.header = Gtk.HBox() self.header.set_spacing(StockEms.MEDIUM) self.body = Gtk.VBox() self.footer = Gtk.HBox() self.useful = None self.yes_like = None self.no_like = None self.status_box = Gtk.HBox() self.delete_status_box = Gtk.HBox() self.delete_error_img = Gtk.Image() self.delete_error_img.set_from_stock( Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.SMALL_TOOLBAR) self.submit_error_img = Gtk.Image() self.submit_error_img.set_from_stock( Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.SMALL_TOOLBAR) self.submit_status_spinner = Gtk.Spinner() self.submit_status_spinner.set_size_request(12, 12) self.delete_status_spinner = Gtk.Spinner() self.delete_status_spinner.set_size_request(12, 12) self.acknowledge_error = Gtk.Button() label = Gtk.Label() label.set_markup('%s' % _("OK")) self.acknowledge_error.add(label) self.delete_acknowledge_error = Gtk.Button() delete_label = Gtk.Label() delete_label.set_markup('%s' % _("OK")) self.delete_acknowledge_error.add(delete_label) self.usefulness_error = False self.delete_error = False self.modify_error = False if first_for_version: self.pack_start(self.version_label, False, False, 0) self.pack_start(self.header, False, False, 0) self.pack_start(self.body, False, False, 0) self.pack_start(self.footer, False, False, StockEms.SMALL) self.logged_in_person = logged_in_person self.person = None self.id = None self.useful_votes = useful_votes self._allocation = None if review_data: self._build(review_data, app_version, logged_in_person, useful_votes) def _on_report_abuse_clicked(self, button): reviews = self.get_ancestor(UIReviewsList) if reviews: reviews.emit("report-abuse", self.id) def _on_modify_clicked(self, button): reviews = self.get_ancestor(UIReviewsList) if reviews: reviews.emit("modify-review", self.id) def _on_useful_clicked(self, btn, is_useful): reviews = self.get_ancestor(UIReviewsList) if reviews: self._usefulness_ui_update('progress') reviews.emit("submit-usefulness", self.id, is_useful) def _on_error_acknowledged(self, button, current_user_reviewer, useful_total, useful_favorable): self.usefulness_error = False self._usefulness_ui_update('renew', current_user_reviewer, useful_total, useful_favorable) def _usefulness_ui_update(self, type, current_user_reviewer=False, useful_total=0, useful_favorable=0): self._hide_usefulness_elements() #print "_usefulness_ui_update: %s" % type if type == 'renew': self._build_usefulness_ui(current_user_reviewer, useful_total, useful_favorable, self.useful_votes) return if type == 'progress': self.status_label = Gtk.Label.new( "%s" % _(u"Submitting now\u2026")) self.status_label.set_use_markup(True) self.status_box.pack_start(self.submit_status_spinner, False, False, 0) self.submit_status_spinner.show() self.submit_status_spinner.start() self.status_label.set_padding(2, 0) self.status_box.pack_start(self.status_label, False, False, 0) self.status_label.show() if type == 'error': self.submit_error_img.show() self.status_label = Gtk.Label.new( "%s" % _("Error submitting usefulness")) self.status_label.set_use_markup(True) self.status_box.pack_start(self.submit_error_img, False, False, 0) self.status_label.set_padding(2, 0) self.status_box.pack_start(self.status_label, False, False, 0) self.status_label.show() self.acknowledge_error.show() self.status_box.pack_start(self.acknowledge_error, False, False, 0) self.acknowledge_error.connect('clicked', self._on_error_acknowledged, current_user_reviewer, useful_total, useful_favorable) self.status_box.show() self.footer.pack_start(self.status_box, False, False, 0) def _hide_usefulness_elements(self): """ hide all usefulness elements """ for attr in ["useful", "yes_like", "no_like", "submit_status_spinner", "submit_error_img", "status_box", "status_label", "acknowledge_error", "yes_no_separator" ]: widget = getattr(self, attr, None) if widget: widget.hide() def _get_datetime_from_review_date(self, raw_date_str): # example raw_date str format: 2011-01-28 19:15:21 return datetime.datetime.strptime(raw_date_str, '%Y-%m-%d %H:%M:%S') def _delete_ui_update(self, type, current_user_reviewer=False, action=None): self._hide_delete_elements() if type == 'renew': self._build_delete_flag_ui(current_user_reviewer) return if type == 'progress': self.delete_status_spinner.start() self.delete_status_spinner.show() self.delete_status_label = Gtk.Label( "%s" % _(u"Deleting now\u2026")) self.delete_status_box.pack_start(self.delete_status_spinner, False, False, 0) self.delete_status_label.set_use_markup(True) self.delete_status_label.set_padding(2, 0) self.delete_status_box.pack_start(self.delete_status_label, False, False, 0) self.delete_status_label.show() if type == 'error': self.delete_error_img.show() # build full strings for easier i18n if action == 'deleting': s = _("Error deleting review") elif action == 'modifying': s = _("Error modifying review") else: # or unknown error, but we are in string freeze, # should never happen anyway s = _("Internal Error") self.delete_status_label = Gtk.Label( "%s" % s) self.delete_status_box.pack_start(self.delete_error_img, False, False, 0) self.delete_status_label.set_use_markup(True) self.delete_status_label.set_padding(2, 0) self.delete_status_box.pack_start(self.delete_status_label, False, False, 0) self.delete_status_label.show() self.delete_acknowledge_error.show() self.delete_status_box.pack_start(self.delete_acknowledge_error, False, False, 0) self.delete_acknowledge_error.connect('clicked', self._on_delete_error_acknowledged, current_user_reviewer) self.delete_status_box.show() self.footer.pack_end(self.delete_status_box, False, False, 0) def _on_delete_clicked(self, btn): reviews = self.get_ancestor(UIReviewsList) if reviews: self._delete_ui_update('progress') reviews.emit("delete-review", self.id) def _on_delete_error_acknowledged(self, button, current_user_reviewer): self.delete_error = False self._delete_ui_update('renew', current_user_reviewer) def _hide_delete_elements(self): """ hide all delete elements """ for attr in ["complain", "edit", "delete", "delete_status_spinner", "delete_error_img", "delete_status_box", "delete_status_label", "delete_acknowledge_error", "flagbox" ]: o = getattr(self, attr, None) if o: o.hide() def _build(self, review_data, app_version, logged_in_person, useful_votes): # all the attributes of review_data may need markup escape, # depending on if they are used as text or markup self.id = review_data.id self.person = review_data.reviewer_username displayname = review_data.reviewer_displayname # example raw_date str format: 2011-01-28 19:15:21 cur_t = self._get_datetime_from_review_date(review_data.date_created) review_version = review_data.version self.useful_total = useful_total = review_data.usefulness_total useful_favorable = review_data.usefulness_favorable useful_submit_error = review_data.usefulness_submit_error delete_error = review_data.delete_error modify_error = review_data.modify_error # upstream version version = GLib.markup_escape_text(upstream_version(review_version)) # default string version_string = _("For version %(version)s") % { 'version': version, } # If its for the same version, show it as such if (review_version and app_version and upstream_version_compare(review_version, app_version) == 0): version_string = _("For this version (%(version)s)") % { 'version': version, } m = '%s' self.version_label.set_markup(m % version_string) m = self._whom_when_markup(self.person, displayname, cur_t) who_when = Gtk.Label() who_when.set_name("subtle-label") who_when.set_justify(Gtk.Justification.RIGHT) who_when.set_markup(m) summary = Gtk.Label() try: s = GLib.markup_escape_text(review_data.summary.encode("utf-8")) summary.set_markup('%s' % s) except Exception: LOG.exception("_build() failed") summary.set_text("Error parsing summary") summary.set_ellipsize(Pango.EllipsizeMode.END) summary.set_selectable(True) summary.set_alignment(0, 0.5) text = Gtk.Label() text.set_text(review_data.review_text) text.set_line_wrap(True) text.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) text.set_selectable(True) text.set_alignment(0, 0) stars = Star() stars.set_rating(review_data.rating) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(stars) self.header.pack_start(a, False, False, 0) self.header.pack_start(summary, False, False, 0) self.header.pack_end(who_when, False, False, 0) self.body.pack_start(text, False, False, 0) current_user_reviewer = False if self.person == self.logged_in_person: current_user_reviewer = True self._build_usefulness_ui(current_user_reviewer, useful_total, useful_favorable, useful_votes, useful_submit_error) self.flagbox = Gtk.HBox() self.flagbox.set_spacing(4) self._build_delete_flag_ui(current_user_reviewer, delete_error, modify_error) self.footer.pack_end(self.flagbox, False, False, 0) # connect network signals self.connect("realize", lambda w: self._on_network_state_change()) watcher = get_network_watcher() watcher.connect( "changed", lambda w, s: self._on_network_state_change()) def _build_usefulness_ui(self, current_user_reviewer, useful_total, useful_favorable, useful_votes, usefulness_submit_error=False): if usefulness_submit_error: self._usefulness_ui_update('error', current_user_reviewer, useful_total, useful_favorable) else: already_voted = useful_votes.check_for_usefulness(self.id) #get correct label based on retrieved usefulness totals and # if user is reviewer self.useful = self._get_usefulness_label( current_user_reviewer, useful_total, useful_favorable, already_voted) self.useful.set_use_markup(True) #vertically centre so it lines up with the Yes and No buttons self.useful.set_alignment(0, 0.5) self.useful.show() self.footer.pack_start(self.useful, False, False, 3) # add here, but only populate if its not the own review self.likebox = Gtk.HBox() if already_voted is None and not current_user_reviewer: m = '%s' self.yes_like = Link(m % _('Yes')) self.yes_like.set_name("subtle-label") self.no_like = Link(m % _('No')) self.no_like.set_name("subtle-label") self.yes_like.connect('clicked', self._on_useful_clicked, True) self.no_like.connect('clicked', self._on_useful_clicked, False) self.yes_no_separator = Gtk.Label() self.yes_no_separator.set_name("subtle-label") self.yes_no_separator.set_markup(m % _('/')) self.yes_like.show() self.no_like.show() self.yes_no_separator.show() self.likebox.set_spacing(4) self.likebox.pack_start(self.yes_like, False, False, 0) self.likebox.pack_start(self.yes_no_separator, False, False, 0) self.likebox.pack_start(self.no_like, False, False, 0) self.footer.pack_start(self.likebox, False, False, 0) def _on_network_state_change(self): """ show/hide widgets based on network connection state """ # FIXME: make this dynamic show/hide on network changes # FIXME2: make ti actually work, later show_all() kill it # currently if network_state_is_connected(): self.likebox.show() self.useful.show() self.flagbox.show() else: self.likebox.hide() # we hide the useful box because if its there it says something # like "10 people found this useful. Did you?" but you can't # actually submit anything without network self.useful.hide() self.flagbox.hide() def _get_usefulness_label(self, current_user_reviewer, useful_total, useful_favorable, already_voted): '''returns Gtk.Label() to be used as usefulness label depending on passed in parameters ''' if already_voted is None: if useful_total == 0 and current_user_reviewer: s = "" elif useful_total == 0: # no votes for the review yet s = _("Was this review helpful?") elif current_user_reviewer: # user has already voted for the review s = gettext.ngettext( "%(useful_favorable)s of %(useful_total)s people " "found this review helpful.", "%(useful_favorable)s of %(useful_total)s people " "found this review helpful.", useful_total) % { 'useful_total': useful_total, 'useful_favorable': useful_favorable, } else: # user has not already voted for the review s = gettext.ngettext( "%(useful_favorable)s of %(useful_total)s people " "found this review helpful. Did you?", "%(useful_favorable)s of %(useful_total)s people " "found this review helpful. Did you?", useful_total) % { 'useful_total': useful_total, 'useful_favorable': useful_favorable, } else: #only display these special strings if the user voted either way if already_voted: if useful_total == 1: s = _("You found this review helpful.") else: s = gettext.ngettext( "%(useful_favorable)s of %(useful_total)s people " "found this review helpful, including you", "%(useful_favorable)s of %(useful_total)s people " "found this review helpful, including you.", useful_total) % { 'useful_total': useful_total, 'useful_favorable': useful_favorable, } else: if useful_total == 1: s = _("You found this review unhelpful.") else: s = gettext.ngettext( "%(useful_favorable)s of %(useful_total)s people " "found this review helpful; you did not.", "%(useful_favorable)s of %(useful_total)s people " "found this review helpful; you did not.", useful_total) % { 'useful_total': useful_total, 'useful_favorable': useful_favorable, } m = '%s' label = Gtk.Label() label.set_name("subtle-label") label.set_markup(m % s) return label def _build_delete_flag_ui(self, current_user_reviewer, delete_error=False, modify_error=False): if delete_error: self._delete_ui_update('error', current_user_reviewer, 'deleting') elif modify_error: self._delete_ui_update('error', current_user_reviewer, 'modifying') else: m = '%s' if current_user_reviewer: self.edit = Link(m % _('Edit')) self.edit.set_name("subtle-label") self.delete = Link(m % _('Delete')) self.delete.set_name("subtle-label") self.flagbox.pack_start(self.edit, False, False, 0) self.flagbox.pack_start(self.delete, False, False, 0) self.edit.connect('clicked', self._on_modify_clicked) self.delete.connect('clicked', self._on_delete_clicked) else: # Translators: This link is for flagging a review as # inappropriate. To minimize repetition, if at all possible, # keep it to a single word. If your language has an obvious # verb, it won't need a question mark. self.complain = Link(m % _('Inappropriate?')) self.complain.set_name("subtle-label") self.flagbox.pack_start(self.complain, False, False, 0) self.complain.connect('clicked', self._on_report_abuse_clicked) self.flagbox.show_all() def _whom_when_markup(self, person, displayname, cur_t): nice_date = get_nice_date_string(cur_t) #dt = datetime.datetime.utcnow() - cur_t # prefer displayname if available correct_name = displayname or person if person == self.logged_in_person: m = '%s (%s), %s' % ( GLib.markup_escape_text(utf8(correct_name)), # TRANSLATORS: displayed in a review after the persons name, # e.g. "Jane Smith (that's you), 2011-02-11" utf8(_(u"that\u2019s you")), GLib.markup_escape_text(utf8(nice_date))) else: try: m = '%s, %s' % ( GLib.markup_escape_text(correct_name.encode("utf-8")), GLib.markup_escape_text(nice_date)) except Exception: LOG.exception("_who_when_markup failed") m = "Error parsing name" return m def draw(self, widget, cr): pass class EmbeddedMessage(UIReview): def __init__(self, title='', message='', icon_name=''): UIReview.__init__(self) self.label = None self.image = None a = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0) self.body.pack_start(a, False, False, 0) hb = Gtk.HBox() hb.set_spacing(12) a.add(hb) if icon_name: self.image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG) hb.pack_start(self.image, False, False, 0) self.label = Gtk.Label() self.label.set_line_wrap(True) self.label.set_alignment(0, 0.5) if title: self.label.set_markup('%s\n%s' % (title, message)) else: self.label.set_markup(message) hb.pack_start(self.label, True, True, 0) self.show_all() def draw(self, cr, a): pass class NoReviewRelaxLanguage(EmbeddedMessage): """ represents if there are no reviews yet and the app is not installed """ def __init__(self, *args, **kwargs): # TRANSLATORS: displayed if there are no reviews for the app in # the current language, but there are some in other # languages title = _("This app has not been reviewed yet in your language") msg = _('Try selecting a different language, or even "Any language"' ' in the language dropdown') EmbeddedMessage.__init__(self, title, msg) class NoReviewYet(EmbeddedMessage): """ represents if there are no reviews yet and the app is not installed """ def __init__(self, *args, **kwargs): # TRANSLATORS: displayed if there are no reviews for the app yet # and the user does not have it installed title = _("This app has not been reviewed yet") msg = _('You need to install this before you can review it') EmbeddedMessage.__init__(self, title, msg) class NoReviewYetWriteOne(EmbeddedMessage): """ represents if there are no reviews yet and the app is installed """ def __init__(self, *args, **kwargs): # TRANSLATORS: displayed if there are no reviews yet and the user # has the app installed title = _('Got an opinion?') msg = _('Be the first to contribute a review for this application') EmbeddedMessage.__init__(self, title, msg, 'text-editor') software-center-13.10/softwarecenter/ui/gtk3/panes/0000755000202700020270000000000012224614354022501 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/panes/installedpane.py0000664000202700020270000006710712151440100025674 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2009-2011 Canonical # # Authors: # Michael Vogt # Didier Roche # # 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 xapian from gi.repository import GObject, GLib, Gtk from gettext import gettext as _ from gettext import ngettext import platform from softwarecenter.enums import (NonAppVisibility, SortMethods) from softwarecenter.utils import ( wait_for_apt_cache_ready, utf8, ExecutionTime) from softwarecenter.db.categories import (CategoriesParser, categories_sorted_by_name) from softwarecenter.ui.gtk3.models.appstore2 import ( AppTreeStore, CategoryRowReference) from softwarecenter.ui.gtk3.widgets.menubutton import MenuButton from softwarecenter.ui.gtk3.widgets.oneconfviews import OneConfViews from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook from softwarecenter.ui.gtk3.views.appview import AppView from softwarecenter.ui.gtk3.panes.softwarepane import SoftwarePane from softwarecenter.backend.oneconfhandler import get_oneconf_handler from softwarecenter.db.appfilter import AppFilter LOG = logging.getLogger(__name__) def interrupt_build_and_wait(f): """ decorator that ensures that a build of the categorised installed apps is interrupted before a new build commences. expects self._build_in_progress and self._halt_build as properties """ def wrapper(*args, **kwargs): self = args[0] if self._build_in_progress: LOG.debug('Waiting for build to exit...') self._halt_build = True GLib.timeout_add(200, lambda: wrapper(*args, **kwargs)) return False # ready now self._halt_build = False f(*args, **kwargs) return False return wrapper class InstalledPane(SoftwarePane, CategoriesParser): """Widget that represents the installed panel in software-center It contains a search entry and navigation buttons """ class Pages(): # page names, useful for debugging NAMES = ('list', 'details') # the actual page id's (LIST, 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, ())} def __init__(self, cache, db, distro, icons): # parent SoftwarePane.__init__(self, cache, db, distro, icons, show_ratings=False) CategoriesParser.__init__(self, db) self.current_appview_selection = None self.icons = icons self.loaded = False self.pane_name = _("Installed Software") self.installed_apps = 0 # None is local self.current_hostid = None self.current_hostname = None self.oneconf_additional_pkg = set() self.oneconf_missing_pkg = set() # switches to terminate build in progress self._build_in_progress = False 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: return SoftwarePane.init_view(self) # 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.select_first() 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) # OneConf pane self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL) self.oneconfcontrol = Gtk.Box() self.oneconfcontrol.set_orientation(Gtk.Orientation.VERTICAL) self.computerpane.pack1(self.oneconfcontrol, False, False) # size negotiation takes everything for the first one self.oneconfcontrol.set_property('width-request', 200) self.box_app_list.pack_start(self.computerpane, True, True, 0) scroll = Gtk.ScrolledWindow() 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.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.show() oneconfpropertymenu.append(stop_oneconf_share_menuitem) self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1) self.oneconf_last_sync = Gtk.Label() self.oneconf_last_sync.set_line_wrap(True) oneconftoolbar.pack_start(self.oneconfproperty, False, False, 0) oneconftoolbar.pack_start(self.oneconf_last_sync, True, True, 1) self.notebook.append_page(self.box_app_list, Gtk.Label(label="list")) # details self.notebook.append_page(self.scroll_details, Gtk.Label(label="details")) # initial refresh self.state.search_term = "" # build models and filters self.base_model = AppTreeStore(self.db, self.cache, self.icons) self.treefilter = self.base_model.filter_new(None) 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._all_cats = self.parse_applications_menu() 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) # 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() # hacky, hide the header self.app_view.header_hbox.hide() self.hide_appview_spinner() # keep track of the current view by tracking its origin self.current_displayed_origin = None # 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: self.installed_spinner_notebook.hide_spinner() def _selected_computer_changed(self, oneconf_pickler, hostid, hostname): if self.current_hostid == hostid: return LOG.debug("Selected computer changed to %s (%s)" % (hostid, hostname)) self.current_hostid = hostid self.current_hostname = hostname menuitem = self.oneconfproperty.get_menu().get_children()[0] if 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: self._search() else: stopsync_hostname = platform.node() self.searchentry.show() menuitem.set_label(self.stopsync_label % stopsync_hostname.encode('utf-8')) self.refresh_apps() def _last_time_sync_oneconf_changed(self, oneconf_handler, msg): LOG.debug("refresh latest sync date") self.oneconf_last_sync.set_label(msg) def _show_oneconf_changed(self, oneconf_handler, oneconf_inventory_shown): LOG.debug('Share inventory status changed') if oneconf_inventory_shown: self.oneconfcontrol.show() else: self.oneconf_viewpickler.select_first() self.oneconfcontrol.hide() def _on_stop_oneconf_hostshare_clicked(self, widget): LOG.debug("Stop sharing inventory for %s" % self.current_hostname) self.oneconf_handler.sync_between_computers(False, self.current_hostid) # stop sharing another host than the local one. 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: 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): pass def _row_visibility_func(self, model, it, col): row = model.get_value(it, col) if self.visible_docids is None: if isinstance(row, CategoryRowReference): row.vis_count = row.pkg_count return True elif isinstance(row, CategoryRowReference): return row.untranslated_name in self.visible_cats.keys() 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 ((self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE) and cat.untranslated_name == 'System')): return False return True # override its SoftwarePane._hide_nonapp_pkgs... def _hide_nonapp_pkgs(self): self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE self.refresh_apps() return True def _save_treeview_state(self): # store the state expanded_rows = [] self.app_view.tree_view.map_expanded_rows( lambda view, path, data: expanded_rows.append(path.to_string()), None) va = self.app_view.tree_view_scroll.get_vadjustment() if va: vadj = va.get_value() else: vadj = 0 return expanded_rows, vadj def _restore_treeview_state(self, state): expanded_rows, vadj = state for ind in expanded_rows: path = Gtk.TreePath.new_from_string(ind) self.app_view.tree_view.expand_row(path, False) va = self.app_view.tree_view_scroll.get_vadjustment() if va: va.set_lower(vadj) va.set_value(vadj) #~ @interrupt_build_and_wait def _build_categorised_installedview(self, keep_state=False): 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() if keep_state: treeview_state = self._save_treeview_state() # disconnect the model to avoid e.g. updates of "cursor-changed" # AppTreeView.expand_path while the model is in rebuild-flux self.app_view.set_model(None) model = self.base_model # base model not treefilter model.clear() def profiled_rebuild_categorised_view(): with ExecutionTime("rebuild_categorized_view"): rebuild_categorised_view() 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 query = self.get_query_for_cat(cat) 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)) L = len(enq.matches) if L: i += L docs = enq.get_documents() 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)) L = len(enq.matches) if L: # some foo for channels # if no categorised results but in channel, then use # the channel name for the category channel_name = None if not i and self.state.channel: 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]) model.set_nocategory_documents(docs, untranslated_name=tag, display_name=channel_name) i += L if i: self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False) if i <= 10: self.app_view.tree_view.expand_all() # cache the installed app count self.installed_count = i self.app_view._append_appcount(self.installed_count, mode=AppView.INSTALLED_MODE) self.app_view.set_model(self.treefilter) if keep_state: self._restore_treeview_state(treeview_state) # 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) self.emit("app-list-changed", i) return GLib.idle_add(profiled_rebuild_categorised_view) def _build_oneconfview(self, keep_state=False): 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() if keep_state: treeview_state = self._save_treeview_state() # disconnect the model to avoid e.g. updates of "cursor-changed" # AppTreeView.expand_path while the model is in rebuild-flux self.app_view.set_model(None) model = self.base_model # base model not treefilter model.clear() def profiled_rebuild_oneconfview(): with ExecutionTime("rebuild_oneconfview"): 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("") if self.state.channel and self.state.channel.query: query = xapian.Query(xapian.Query.OP_AND, query, 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)) 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)} 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) # Second search: additional apps xfilter.set_restricted_list(self.oneconf_missing_pkg) xfilter.set_not_installed_only(False) xfilter.set_installed_only(True) enq.set_query(query, sortmode=SortMethods.BY_ALPHABET, nonapps_visible=self.nonapps_visible, filter=xfilter, nonblocking_load=False, 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)} 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) if i: self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False) if i <= 10: self.app_view.tree_view.expand_all() # cache the installed app count self.installed_count = i self.app_view._append_appcount(self.installed_count, mode=AppView.DIFF_MODE) self.app_view.set_model(self.treefilter) if keep_state: self._restore_treeview_state(treeview_state) # hide the local spinner self.hide_installed_view_spinner() if window: window.set_cursor(None) self.emit("app-list-changed", i) return GLib.idle_add(profiled_rebuild_oneconfview) 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: self.app_view.tree_view.expand_row(path, False) else: self.app_view.tree_view.collapse_row(path) it = self.treefilter.iter_next(it) def _do_search(self, terms): self.state.search_term = terms xfilter = AppFilter(self.db, self.cache) xfilter.set_installed_only(True) self.enquirer.set_query(self.get_query(), 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() self.app_view.tree_view.expand_all() def _run_channel_enquirer(self, persistent_duplicate_filter=True): xfilter = AppFilter(self.db, self.cache) xfilter.set_installed_only(True) if self.state.channel: self.enquirer.set_query( self.state.channel.query, sortmode=SortMethods.BY_ALPHABET, nonapps_visible=NonAppVisibility.MAYBE_VISIBLE, filter=xfilter, nonblocking_load=False, persistent_duplicate_filter=persistent_duplicate_filter) def _search(self, terms=None): if not terms: self.visible_docids = None self.state.search_term = "" self._clear_search() self.treefilter.refilter() self._check_expand() # run channel enquirer to ensure that the channel specific # info for show/hide nonapps is actually correct self._run_channel_enquirer() # trigger update of the show/hide self.emit("app-list-changed", 0) elif self.state.search_term != terms: self._do_search(terms) def get_query(self): # search terms return self.db.get_query_list_from_search_entry( self.state.search_term) def get_query_for_cat(self, cat): LOG.debug("self.state.channel: %s" % self.state.channel) if self.state.channel and self.state.channel.query: query = xapian.Query(xapian.Query.OP_AND, cat.query, self.state.channel.query) return query return cat.query @wait_for_apt_cache_ready def refresh_apps(self, *args, **kwargs): """refresh the applist and update the navigation bar """ logging.debug("installedpane refresh_apps") keep_state = kwargs.get("keep_state", False) if self.current_hostid: self._build_oneconfview(keep_state) else: self._build_categorised_installedview(keep_state) def _clear_search(self): # remove the details and clear the search self.searchentry.clear_with_no_signal() def on_search_terms_changed(self, searchentry, terms): """callback when the search entry widget changes""" logging.debug("on_search_terms_changed: '%s'" % terms) self._search(terms.strip()) self.state.search_term = terms self.notebook.set_current_page(self.Pages.LIST) self.hide_installed_view_spinner() def _get_vis_cats(self, visids): vis_cats = {} appcount = 0 visids = set(visids) for cat_uname, docids in self.cat_docid_map.iteritems(): children = len(docids & visids) if children: appcount += children vis_cats[cat_uname] = children self.app_view._append_appcount(appcount, mode=AppView.DIFF_MODE) return vis_cats def _refresh_on_cache_or_db_change(self): self.refresh_apps(keep_state=True) self.app_details_view.refresh_app() def on_db_reopen(self, db): super(InstalledPane, self).on_db_reopen(db) self._refresh_on_cache_or_db_change() def on_cache_ready(self, cache): LOG.debug("on_cache_ready") self._refresh_on_cache_or_db_change() def on_application_selected(self, appview, app): """Callback for when an app is selected.""" SoftwarePane.on_application_selected(self, appview, app) self.current_appview_selection = app def enter_page(self, page, state): if page == self.Pages.LIST: self.display_overview_page(state) else: self.display_details_page(state) def display_search(self): model = self.app_view.get_model() if model: self.emit("app-list-changed", len(model)) self.searchentry.show() @wait_for_apt_cache_ready def display_overview_page(self, view_state): LOG.debug("view_state: %s" % view_state) if self.current_hostid: # 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: self._search() self._build_oneconfview() elif (view_state and view_state.channel and view_state.channel.origin is not self.current_displayed_origin): # we don't need to refresh the full installed view every time it # is displayed, so we check to see if we are viewing the same # channel and if so we don't refresh the view, note that the view # *is* is refreshed whenever the contents change and this is # sufficient (see LP: #828887) self._build_categorised_installedview() self.current_displayed_origin = view_state.channel.origin if self.state.search_term: self._search(self.state.search_term) return True def get_current_app(self): """return the current active application object applicable to the context""" return self.current_appview_selection software-center-13.10/softwarecenter/ui/gtk3/panes/__init__.py0000664000202700020270000000000012151440100024563 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/panes/softwarepane.py0000664000202700020270000004053012151440100025536 0ustar dobeydobey00000000000000# 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 Atk import gettext from gi.repository import GObject from gi.repository import Gtk, Gdk #~ from gi.repository import Cairo import logging import xapian from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.db.enquire import AppEnquire from softwarecenter.enums import ( DEFAULT_SEARCH_LIMIT, NonAppVisibility, SOFTWARE_CENTER_DEBUG_TABS, SortMethods, ) from softwarecenter.utils import ( ExecutionTime, wait_for_apt_cache_ready, ) from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarecenter.ui.gtk3.widgets.actionbar import ActionBar from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook from softwarecenter.ui.gtk3.widgets.searchaid import SearchAid from softwarecenter.ui.gtk3.views.appview import AppView from softwarecenter.ui.gtk3.session.displaystate import DisplayState from basepane import BasePane LOG = logging.getLogger(__name__) class SoftwarePane(Gtk.VBox, BasePane): """ Common base class for AvailablePane and InstalledPane""" class Pages: NAMES = ('appview', 'details', 'spinner') APPVIEW = 0 DETAILS = 1 SPINNER = 2 __gsignals__ = { "app-list-changed": (GObject.SignalFlags.RUN_LAST, None, (int,), ), } PADDING = 6 def __init__(self, cache, db, distro, icons, show_ratings=True): Gtk.VBox.__init__(self) BasePane.__init__(self) # other classes we need self.enquirer = AppEnquire(cache, db) self._query_complete_handler = self.enquirer.connect( "query-complete", self.on_query_complete) self.cache = cache self.db = db self.distro = distro self.icons = icons self.show_ratings = show_ratings self.backend = get_install_backend() self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # refreshes can happen out-of-bound so we need to be sure # that we only set the new model (when its available) if # the refresh_seq_nr of the ready model matches that of the # request (e.g. people click on ubuntu channel, get impatient, click # on partner channel) self.refresh_seq_nr = 0 # this should be initialized self.apps_search_term = "" # Create the basic frame for the common view self.state = DisplayState() vm = get_viewmanager() self.searchentry = vm.get_global_searchentry() self.back_forward = vm.get_global_backforward() # a notebook below self.notebook = Gtk.Notebook() if not SOFTWARE_CENTER_DEBUG_TABS: self.notebook.set_show_tabs(False) self.notebook.set_show_border(False) # make a spinner view to display while the applist is loading self.spinner_notebook = SpinnerNotebook(self.notebook) self.pack_start(self.spinner_notebook, True, True, 0) # add a bar at the bottom (hidden by default) for contextual actions self.action_bar = ActionBar() self.pack_start(self.action_bar, False, True, 0) # 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 def init_view(self): """ Initialize those UI components that are common to all subclasses of 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) # its the job of the Child class to put it into a good location # list self.box_app_list = Gtk.VBox() # search aid self.search_aid = SearchAid(self) self.box_app_list.pack_start(self.search_aid, False, False, 0) with ExecutionTime("SoftwarePane.AppView"): 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.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.on_application_selected) self.app_view.connect("application-activated", self.on_application_activated) # details self.scroll_details = Gtk.ScrolledWindow() self.scroll_details.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) # delayed import gives ~1s speedup until visible on the raspi with ExecutionTime("import AppDetailsView"): from softwarecenter.ui.gtk3.views.appdetailsview import ( AppDetailsView) with ExecutionTime("SoftwarePane.AppDetailsView"): self.app_details_view = AppDetailsView(self.db, self.distro, self.icons, self.cache) self.app_details_view.connect( "different-application-selected", self.on_application_activated) self.scroll_details.add(self.app_details_view) # when the cache changes, refresh the app list self.cache.connect("cache-ready", self.on_cache_ready) # connect signals self.connect("app-list-changed", self.on_app_list_changed) # db reopen if self.db: self.db.connect("reopen", self.on_db_reopen) def init_atk_name(self, widget, name): """ 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 Atk.Object.set_name(widget.get_accessible(), name) def on_cache_ready(self, cache): """Refresh the application list when the cache is re-opened.""" LOG.debug("on_cache_ready") @wait_for_apt_cache_ready def on_application_activated(self, appview, app): """Callback when an app is clicked.""" LOG.debug("%r.on_application_activated: %r", self.__class__.__name__, app) self.state.application = app vm = get_viewmanager() # self.Pages will access the Page definition of each child correctly vm.display_page(self, self.Pages.DETAILS, self.state) def on_application_selected(self, widget, app): """Stub implementation.""" LOG.debug("%r.on_application_selected: %r", self.__class__.__name__, app) def enter_page(self, page, state): if page == self.page.DETAILS: self.display_details_page(state) def is_applist_view_showing(self): """Return True if we are in the applist view """ list_page = getattr(self.Pages, 'LIST', -1) return self.notebook.get_current_page() == list_page def is_app_details_view_showing(self): """Return True if we are in the app_details view.""" details_page = getattr(self.Pages, 'DETAILS', -1) return self.notebook.get_current_page() == details_page def show_app(self, app): self.on_application_activated(None, app) def on_query_complete(self, enquirer): self.emit("app-list-changed", len(enquirer.matches)) self.app_view.display_matches(enquirer.matches, self._is_in_search_mode()) self.hide_appview_spinner() def on_app_view_sort_method_changed(self, app_view, combo): if app_view.get_sort_mode() == self.enquirer.sortmode: return self.show_appview_spinner() app_view.clear_model() query = self.get_query() self._refresh_apps_with_apt_cache(query) def _is_in_search_mode(self): return (self.state.search_term and len(self.state.search_term) >= 2) def show_appview_spinner(self): """ display the spinner in the appview panel """ LOG.debug("show_appview_spinner") # FIXME: totally the wrong place! 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") self.spinner_notebook.hide_spinner() def set_section(self, section): self.section = section self.app_details_view.set_section(section) def section_sync(self): self.app_details_view.set_section(self.section) def on_app_list_changed(self, pane, length): """internal helper that keeps the the action bar up-to-date by keeping track of the app-list-changed signals """ self.show_nonapps_if_required(len(self.enquirer.matches)) self.search_aid.update_search_help(self.state) def hide_nonapps(self): """ hide non-applications control in the action bar """ self.action_bar.unset_label() def show_nonapps_if_required(self, length): """ update the state of the show/hide non-applications control in the action_bar """ enquirer = self.enquirer n_apps = enquirer.nr_apps n_pkgs = enquirer.nr_pkgs # calculate the number of apps/pkgs if enquirer.limit > 0 and enquirer.limit < n_pkgs: n_apps = min(enquirer.limit, n_apps) n_pkgs = min(enquirer.limit - n_apps, n_pkgs) if not (n_apps and n_pkgs): self.hide_nonapps() return LOG.debug("nonapps_visible value=%s (always visible: %s)" % ( self.nonapps_visible, self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE)) self.action_bar.unset_label() if self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE: LOG.debug('non-apps-ALWAYS-visible') # TRANSLATORS: the text in between the underscores acts as a link # 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} self.action_bar.set_label( 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} self.action_bar.set_label( label, link_result=self._show_nonapp_pkgs) def _show_nonapp_pkgs(self): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self.refresh_apps() return True def _hide_nonapp_pkgs(self): self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE self.refresh_apps() return True def get_query(self): channel_query = None #name = self.pane_name if self.channel: channel_query = self.channel.query #name = self.channel.display_name # search terms if self.apps_search_term: query = self.db.get_query_list_from_search_entry( self.apps_search_term, channel_query) return query # overview list # if we are in a channel, limit to that if channel_query: 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") # FIXME: make this available for all panes 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() self._refresh_apps_with_apt_cache(query) def quick_query_len(self, query): """ do a blocking query that only returns the amount of matches from this query """ with ExecutionTime("enquirer.set_query() quick query"): self.enquirer.set_query( query, limit=self.get_app_items_limit(), nonapps_visible=self.nonapps_visible, nonblocking_load=False, filter=self.state.filter) return len(self.enquirer.matches) @wait_for_apt_cache_ready def _refresh_apps_with_apt_cache(self, query): LOG.debug("softwarepane query: %s" % query) self.app_view.configure_sort_method(self._is_in_search_mode()) # a nonblocking query calls on_query_complete once finished with ExecutionTime("enquirer.set_query()"): self.enquirer.set_query( query, limit=self.get_app_items_limit(), sortmode=self.get_sort_mode(), exact=self.is_custom_list(), nonapps_visible=self.nonapps_visible, filter=self.state.filter) def display_details_page(self, view_state): self.app_details_view.show_app(view_state.application) self.action_bar.unset_label() return True def is_custom_list(self): return self.apps_search_term and ',' in self.apps_search_term def get_current_page(self): return self.notebook.get_current_page() def get_app_items_limit(self): if self.state.search_term: return DEFAULT_SEARCH_LIMIT elif self.state.subcategory and self.state.subcategory.item_limit > 0: return self.state.subcategory.item_limit elif self.state.category and self.state.category.item_limit > 0: return self.state.category.item_limit return 0 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 self.state.category.sortmode != SortMethods.BY_ALPHABET): return self.state.category.sortmode # ask the app_view for the sort-mode return self.app_view.get_sort_mode() def on_search_entry_key_press_event(self, event): """callback when a key is pressed in the search entry widget""" if not self.is_applist_view_showing(): return if ((event.keyval == Gdk.keyval_from_name("Down") or event.keyval == Gdk.keyval_from_name("KP_Down")) and self.is_applist_view_showing() and len(self.app_view.tree_view.get_model()) > 0): # select the first item in the applist search result self.app_view.tree_view.grab_focus() self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False) def on_search_terms_changed(self, terms): """Stub implementation.""" def on_db_reopen(self, db): """Stub implementation.""" LOG.debug("%r: on_db_reopen (db is %r).", self.__class__.__name__, db) def get_current_category(self): """Stub implementation.""" def unset_current_category(self): """Stub implementation.""" software-center-13.10/softwarecenter/ui/gtk3/panes/globalpane.py0000664000202700020270000000531512151440100025146 0ustar dobeydobey00000000000000from gi.repository import Gtk from softwarecenter.ui.gtk3.em import StockEms 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) class GlobalPane(Gtk.Toolbar): def __init__(self, view_manager, db, cache, icons): Gtk.Toolbar.__init__(self) context = self.get_style_context() 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 vm = get_viewmanager() self.back_forward = vm.get_global_backforward() self.back_forward.set_vexpand(False) self.back_forward.set_valign(Gtk.Align.CENTER) if self.get_direction() != Gtk.TextDirection.RTL: _widget_set_margins(self.back_forward, left=StockEms.MEDIUM, right=StockEms.MEDIUM + 2) else: _widget_set_margins(self.back_forward, 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, db, cache, icons) self._insert_as_tool_item(self.view_switcher, 1) item = Gtk.ToolItem() item.set_expand(True) self.insert(item, -1) #~ self.init_atk_name(self.searchentry, "searchentry") self.searchentry = vm.get_global_searchentry() self._insert_as_tool_item(self.searchentry, -1) # spinner self.spinner = vm.get_global_spinner() self.spinner.set_size_request(StockEms.XLARGE, StockEms.XLARGE) self._insert_as_tool_item(self.spinner, -1) if self.get_direction() != Gtk.TextDirection.RTL: _widget_set_margins(self.searchentry, right=StockEms.MEDIUM) else: _widget_set_margins(self.searchentry, left=StockEms.MEDIUM) def _insert_as_tool_item(self, widget, pos): item = Gtk.ToolItem() item.add(widget) self.insert(item, pos) return item software-center-13.10/softwarecenter/ui/gtk3/panes/viewswitcher.py0000664000202700020270000002373112151440100025567 0ustar dobeydobey00000000000000# 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 Gtk, GLib import logging from gettext import gettext as _ from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.enums import ViewPages from softwarecenter.backend.channel import (get_channels_manager, AllInstalledChannel, AllAvailableChannel) from softwarecenter.ui.gtk3.widgets.buttons import (SectionSelector, ChannelSelector) from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.widgets.symbolic_icons import ( SymbolicIcon, PendingSymbolicIcon) LOG = logging.getLogger(__name__) _last_button = None class ViewSwitcher(Gtk.Box): ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR def __init__(self, view_manager, 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) self.channel_manager = get_channels_manager(db) # backend sig handlers ... self.backend = get_install_backend() self.backend.connect("transactions-changed", self.on_transaction_changed) self.backend.connect("transaction-finished", self.on_transaction_finished) self.backend.connect("channels-changed", self.on_channels_changed) # widgetry Gtk.Box.__init__(self) self.set_orientation(Gtk.Orientation.HORIZONTAL) # Gui stuff self.view_buttons = {} self.selectors = {} self._prev_view = None # track the previous active section self._prev_item = None # track the previous active menu-item self._handlers = [] # order is important here! # first, the availablepane items icon = SymbolicIcon("available") self.append_section_with_channel_sel( ViewPages.AVAILABLE, _("All Software"), icon, self.on_get_available_channels) # the installedpane items icon = SymbolicIcon("installed") self.append_section_with_channel_sel( ViewPages.INSTALLED, _("Installed"), icon, self.on_get_installed_channels) # the historypane item icon = SymbolicIcon("history") self.append_section(ViewPages.HISTORY, _("History"), icon) # the pendingpane icon = PendingSymbolicIcon("pending") self.append_section(ViewPages.PENDING, _("Progress"), icon) # set sensible atk name atk_desc = self.get_accessible() atk_desc.set_name(_("Software sources")) def on_transaction_changed(self, backend, total_transactions): LOG.debug("on_transactions_changed '%s'" % total_transactions) pending = len(total_transactions) self.notify_icon_of_pending_count(pending) if pending > 0: self.start_icon_animation() pending_btn = self.view_buttons[ViewPages.PENDING] if not pending_btn.get_visible(): pending_btn.set_visible(True) else: self.stop_icon_animation() pending_btn = self.view_buttons[ViewPages.PENDING] 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) def start_icon_animation(self): self.view_buttons[ViewPages.PENDING].image.start() def stop_icon_animation(self): self.view_buttons[ViewPages.PENDING].image.stop() def notify_icon_of_pending_count(self, count): image = self.view_buttons[ViewPages.PENDING].image image.set_transaction_count(count) def on_transaction_finished(self, backend, result): if result.success: self.on_channels_changed() def on_section_sel_clicked(self, button, event, view_id): # mvo: this check causes bug LP: #828675 #if self._prev_view is view_id: # return True vm = self.view_manager def config_view(): # set active pane pane = vm.set_active_view(view_id) # configure DisplayState state = pane.state.copy() if view_id == ViewPages.INSTALLED: state.channel = AllInstalledChannel() else: state.channel = AllAvailableChannel() # decide which page we want to display if hasattr(pane, "Pages"): page = pane.Pages.HOME else: page = None # request page change vm.display_page(pane, page, state) return False self._prev_view = view_id GLib.idle_add(config_view) def on_get_available_channels(self, popup): return self.build_channel_list(popup, ViewPages.AVAILABLE) def on_get_installed_channels(self, popup): return self.build_channel_list(popup, ViewPages.INSTALLED) def on_channels_changed(self, backend=None, res=None): for view_id, sel in self.selectors.items(): # setting popup to None will cause a rebuild of the popup # menu the next time the selector is clicked sel.popup = None def append_section(self, view_id, label, icon): btn = SectionSelector(label, icon, self.ICON_SIZE) self.view_buttons[view_id] = btn self.pack_start(btn, False, False, 0) global _last_button if _last_button is not None: btn.join_group(_last_button) _last_button = btn # this must go last otherwise as the buttons are added # 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) return btn def append_channel_selector(self, section_btn, view_id, build_func): sel = ChannelSelector(section_btn) self.selectors[view_id] = sel sel.set_build_func(build_func) self.pack_start(sel, False, False, 0) return sel 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) return btn, sel def build_channel_list(self, popup, view_id): # clean up old signal handlers for sig in self._handlers: GLib.source_remove(sig) if view_id == ViewPages.AVAILABLE: channels = self.channel_manager.channels elif view_id == ViewPages.INSTALLED: channels = self.channel_manager.channels_installed_only else: channels = self.channel_manager.channels 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 # GtkCheckMenuItem, but as a GtkBin subclass a # 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) box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, StockEms.SMALL) box.pack_start(image, False, False, 0) box.pack_start(label, False, False, 0) item.add(box) item.show_all() self._handlers.append( item.connect( "button-release-event", self.on_channel_selected, channel, view_id ) ) popup.attach(item, 0, 1, i, i + 1) def on_channel_selected(self, item, event, channel, view_id): vm = self.view_manager def config_view(): # set active pane pane = vm.set_active_view(view_id) # configure DisplayState state = pane.state.copy() state.category = None state.subcategory = None state.channel = channel # decide which page we want to display if hasattr(pane, "Pages"): if channel.origin == "all": page = pane.Pages.HOME else: page = pane.Pages.LIST else: page = None # request page change vm.display_page(pane, page, state) return False GLib.idle_add(config_view) software-center-13.10/softwarecenter/ui/gtk3/panes/pendingpane.py0000664000202700020270000000764412151440100025341 0ustar dobeydobey00000000000000# 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 import logging from gettext import gettext as _ from gi.repository import Gtk from basepane import BasePane from softwarecenter.ui.gtk3.em import StockEms from softwarecenter.ui.gtk3.models.pendingstore import PendingStore from softwarecenter.ui.gtk3.session.displaystate import DisplayState LOG = logging.getLogger(__name__) class PendingPane(Gtk.ScrolledWindow, BasePane): CANCEL_XPAD = StockEms.MEDIUM CANCEL_YPAD = StockEms.MEDIUM def __init__(self, icons): Gtk.ScrolledWindow.__init__(self) BasePane.__init__(self) self.state = DisplayState() self.pane_name = _("Progress") self.tv = Gtk.TreeView() # customization self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.add(self.tv) self.tv.set_headers_visible(False) self.tv.connect("button-press-event", self._on_button_pressed) # icon self.icons = icons tp = Gtk.CellRendererPixbuf() tp.set_property("xpad", self.CANCEL_XPAD) tp.set_property("ypad", self.CANCEL_YPAD) column = Gtk.TreeViewColumn("Icon", tp, pixbuf=PendingStore.COL_ICON) self.tv.append_column(column) # name tr = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Name", tr, markup=PendingStore.COL_STATUS) column.set_min_width(200) column.set_expand(True) self.tv.append_column(column) # progress tp = Gtk.CellRendererProgress() tp.set_property("xpad", self.CANCEL_XPAD) tp.set_property("ypad", self.CANCEL_YPAD) tp.set_property("text", "") 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, stock_id=PendingStore.COL_CANCEL) self.tv.append_column(column) # fake columns that eats the extra space at the end tt = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Cancel", tt) self.tv.append_column(column) # 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 if event is None or event.button != 1: return res = self.tv.get_path_at_pos(int(event.x), int(event.y)) if not res: return (path, column, wx, wy) = res # no path if not path: return # wrong column if column.get_title() != "Cancel": return # not cancelable (no icon) model = self.tv.get_model() if model[path][PendingStore.COL_CANCEL] == "": return # get tid tid = model[path][PendingStore.COL_TID] trans = model._transactions_watcher.get_transaction(tid) try: trans.cancel() except Exception as e: LOG.warning("transaction cancel failed: %s" % e) software-center-13.10/softwarecenter/ui/gtk3/panes/availablepane.py0000664000202700020270000010261412216063163025642 0ustar dobeydobey00000000000000# 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 import gettext from gi.repository import GObject from gi.repository import Gtk from gi.repository import GLib import logging import xapian import os import softwarecenter.utils import softwarecenter.ui.gtk3.dialogs as dialogs from softwarecenter.ui.gtk3.models.appstore2 import AppListStore from gettext import gettext as _ from softwarecenter.config import get_config from softwarecenter.enums import ( ActionButtons, NavButtons, NonAppVisibility, DEFAULT_SEARCH_LIMIT, TransactionTypes, PURCHASE_TRANSACTION_ID, ) from softwarecenter.utils import ( convert_desktop_file_to_installed_location, is_no_display_desktop_file, get_exec_line_from_desktop, get_file_path_from_iconname, ) from softwarecenter.db.appfilter import AppFilter from softwarecenter.db.database import Application from softwarecenter.ui.gtk3.views.purchaseview import PurchaseView from softwarecenter.ui.gtk3.views.lobbyview import LobbyView from softwarecenter.ui.gtk3.views.catview import SubCategoryView from softwarepane import SoftwarePane from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarecenter.ui.gtk3.session.appmanager import get_appmanager from softwarecenter.utils import ExecutionTime from softwarecenter.backend.channel import SoftwareChannel from softwarecenter.backend.unitylauncher import (UnityLauncher, UnityLauncherInfo) from softwarecenter.backend.zeitgeist_logger import ZeitgeistLogger LOG = logging.getLogger(__name__) class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons """ class Pages(): # page names, useful for debugging NAMES = ('lobby', 'subcategory', 'list', 'details', 'purchase', ) # actual page id's (LOBBY, SUBCATEGORY, LIST, DETAILS, PURCHASE, PREVIOUS_PURCHASES) = range(6) # the default page HOME = LOBBY __gsignals__ = {'available-pane-created': (GObject.SignalFlags.RUN_FIRST, None, ())} class TransactionDetails(object): """ Simple class to keep track of aptdaemon transaction details """ def __init__(self, db, pkgname, appname, trans_id, trans_type): self.db = db self.app = Application(pkgname=pkgname, appname=appname) self.trans_id = trans_id self.trans_type = trans_type self.__app_details = None self.__real_desktop = None if trans_type != TransactionTypes.INSTALL: self.guess_final_desktop_file() @property def app_details(self): if not self.__app_details: self.__app_details = self.app.get_details(self.db) return self.__app_details @property def desktop_file(self): return self.app_details.desktop_file @property def final_desktop_file(self): return self.guess_final_desktop_file() def guess_final_desktop_file(self): if self.__real_desktop: return self.__real_desktop # convert the app-install desktop file location to the actual installed # desktop file location (or in the case of a purchased item from the # agent, generate the correct installed desktop file location) desktop_file = ( convert_desktop_file_to_installed_location(self.desktop_file, self.app.pkgname)) # we only add items to the launcher that have a desktop file if not desktop_file: return # do not add apps that have no Exec entry in their desktop file # (e.g. wine, see LP: #848437 or ubuntu-restricted-extras, # see LP: #913756), also, don't add the item if NoDisplay is # specified (see LP: #1006483) if (os.path.exists(desktop_file) and (not get_exec_line_from_desktop(desktop_file) or is_no_display_desktop_file(desktop_file))): return self.__real_desktop = desktop_file return self.__real_desktop def __init__(self, cache, db, distro, icons, navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons) self.searchentry.set_sensitive(False) # navigation history actions self.navhistory_back_action = navhistory_back_action self.navhistory_forward_action = navhistory_forward_action # configure any initial state attrs self.state.filter = AppFilter(db, cache) # the spec says we mix installed/not installed #self.apps_filter.set_not_installed_only(True) 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() # keep track of transactions self.transactions_queue = {} def init_view(self): if self.view_initialized: return self.show_appview_spinner() window = self.get_window() if window is not None: window.set_cursor(self.busy_cursor) with ExecutionTime("AvailablePane.init_view pending events"): while Gtk.events_pending(): Gtk.main_iteration() with ExecutionTime("SoftwarePane.init_view()"): SoftwarePane.init_view(self) # set the AppTreeView model, available pane uses list models with ExecutionTime("create AppListStore"): liststore = AppListStore(self.db, self.cache, self.icons) #~ def on_appcount_changed(widget, appcount): #~ self.subcategories_view._append_appcount(appcount) #~ 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()) # 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("terms-of-service-declined", self.on_terms_of_service_declined) 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, Gtk.PolicyType.AUTOMATIC) with ExecutionTime("create LobbyView"): self.cat_view = LobbyView( self.cache, self.db, self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) # sub-categories view with ExecutionTime("create SubCategoryView"): self.subcategories_view = SubCategoryView( 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( "show-category-applist", self.on_show_category_applist) self.subcategories_view.connect( "application-activated", self.on_application_activated) self.scroll_subcategories = Gtk.ScrolledWindow() self.scroll_subcategories.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll_subcategories.add(self.subcategories_view) self.notebook.append_page(self.scroll_subcategories, Gtk.Label(label=NavButtons.SUBCAT)) # app list self.notebook.append_page(self.box_app_list, Gtk.Label(label=NavButtons.LIST)) self.cat_view.connect( "category-selected", self.on_category_activated) self.cat_view.connect( "application-activated", self.on_application_activated) # 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)) # install backend # FIXME: move this out of the available pane really self.backend.connect("transaction-started", self.on_transaction_started) self.backend.connect("transactions-changed", self.on_transactions_changed) self.backend.connect("transaction-finished", self.on_transaction_complete) # a transaction error is treated the same as a cancellation self.backend.connect("transaction-stopped", self.on_transaction_cancelled) self.backend.connect("transaction-cancelled", self.on_transaction_cancelled) # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") self.show_all() self.hide_appview_spinner() # consider the view initialized here already as display_page() # may run into a endless recursion otherwise (it will call init_view()) # again (LP: #851671) self.view_initialized = True # important to "seed" the initial history stack (LP: #1005104) vm = get_viewmanager() vm.display_page(self, self.Pages.LOBBY, self.state) if window is not None: window.set_cursor(None) def on_purchase_requested(self, appmanager, app, iconname, url): if self.purchase_view.initiate_purchase(app, iconname, url): vm = get_viewmanager() vm.display_page(self, self.Pages.PURCHASE, self.state) def on_purchase_needs_spinner(self, appmanager, active): vm = get_viewmanager() vm.set_spinner_active(active) 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() def on_terms_of_service_declined(self, widget): """ The Terms of Service dialog was declined by the user, so we just reset the purchase button in case they want another chance """ if self.is_app_details_view_showing(): self.app_details_view.pkg_statusbar.button.set_sensitive(True) elif self.is_applist_view_showing(): self.app_view.tree_view.reset_action_button() def _return_to_appdetails_view(self): vm = get_viewmanager() vm.nav_back() # don't keep the purchase view in navigation history # as its contents are no longer valid vm.clear_forward_history() window = self.get_window() if window is not None: window.set_cursor(None) def get_query(self): """helper that gets the query for the current category/search mode""" # NoDisplay is a special case if self._in_no_display_category(): return xapian.Query() # get current sub-category (or category, but sub-category wins) query = None if self.state.channel and self.state.channel.query: query = self.state.channel.query elif self.state.subcategory: query = self.state.subcategory.query elif self.state.category: query = self.state.category.query # mix channel/category with the search terms and return query return self.db.get_query_list_from_search_entry( self.state.search_term, query) def _in_no_display_category(self): """return True if we are in a category with NoDisplay set in the XML""" return (self.state.category and self.state.category.dont_display and not self.state.subcategory and not self.state.search_term) def _get_header_for_view_state(self, view_state): channel = view_state.channel category = view_state.category subcategory = view_state.subcategory line1 = None line2 = None if channel is not None: name = channel.display_name or channel.name line1 = GLib.markup_escape_text(name) elif subcategory is not None: line1 = GLib.markup_escape_text(category.name) line2 = GLib.markup_escape_text(subcategory.name) elif category is not None: line1 = GLib.markup_escape_text(category.name) else: line1 = _("All Software") return line1, line2 #~ def _show_hide_subcategories(self, show_category_applist=False): #~ # check if have subcategories and are not in a subcategory #~ # view - if so, show it #~ current_page = self.notebook.get_current_page() #~ if (current_page == self.Pages.LOBBY or #~ current_page == self.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())) #~ self.notebook.set_current_page(self.Pages.SUBCATEGORY) #~ else: #~ self.notebook.set_current_page(self.Pages.LIST) def get_current_app(self): """return the current active application object""" if self.is_category_view_showing(): return None else: if self.state.subcategory: return self.current_app_by_subcategory.get( self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) def get_current_category(self): """ return the current category that is in use or None """ if self.state.subcategory: return self.state.subcategory elif self.state.category: return self.state.category def unset_current_category(self): """ unset the current showing category, but keep e.g. the current search """ self.state.category = None self.state.subcategory = None # reset the non-global filters see (LP: #985389) if self.state.filter: self.state.filter.reset() def on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): details = self.TransactionDetails(self.db, pkgname, appname, trans_id, trans_type) self.transactions_queue[pkgname] = details config = get_config() if (trans_type == TransactionTypes.INSTALL and trans_id != PURCHASE_TRANSACTION_ID and config.add_to_unity_launcher and softwarecenter.utils.is_unity_running()): self._add_application_to_unity_launcher(details) def on_transaction_cancelled(self, backend, result): """ handle a transaction that has been cancelled """ if result.pkgname: self.unity_launcher.cancel_application_to_launcher(result.pkgname) if result.pkgname in self.transactions_queue: self.transactions_queue.pop(result.pkgname) def on_transactions_changed(self, backend, pending_transactions): """internal helper that keeps the action bar up-to-date by keeping track of the transaction-started signals """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() def _add_application_to_unity_launcher(self, trans_details): # do not add apps that have no Exec entry in their desktop file # (e.g. wine, see LP: #848437 or ubuntu-restricted-extras, # see LP: #913756), also, don't add the item if NoDisplay is # specified (see LP: #1006483) if (os.path.exists(trans_details.desktop_file) and (not get_exec_line_from_desktop(trans_details.desktop_file) or is_no_display_desktop_file(trans_details.desktop_file))): return # now gather up the unity launcher info items and send the app to the # launcher service launcher_info = self._get_unity_launcher_info(trans_details) self.unity_launcher.send_application_to_launcher( trans_details.app.pkgname, launcher_info) def on_transaction_complete(self, backend, result): """ handle a transaction that has completed successfully """ if result.pkgname in self.transactions_queue: details = self.transactions_queue.pop(result.pkgname) if details.trans_type == TransactionTypes.INSTALL: ZeitgeistLogger(self.distro).log_install_event(details.final_desktop_file) elif details.trans_type == TransactionTypes.REMOVE: ZeitgeistLogger(self.distro).log_uninstall_event(details.final_desktop_file) def _get_unity_launcher_info(self, trans_details): (icon_size, icon_x, icon_y) = ( self._get_onscreen_icon_details_for_launcher_service(trans_details.app)) icon_path = get_file_path_from_iconname( self.icons, iconname=trans_details.app_details.icon) launcher_info = UnityLauncherInfo(trans_details.app.name, trans_details.app_details.icon, icon_path, icon_x, icon_y, icon_size, trans_details.desktop_file, trans_details.trans_id) return launcher_info def _get_onscreen_icon_details_for_launcher_service(self, app): if self.is_app_details_view_showing(): return self.app_details_view.get_app_icon_details() elif self.is_applist_view_showing(): return self.app_view.get_app_icon_details() else: # set a default, even though we cannot install from the other panes return (0, 0, 0) def on_app_list_changed(self, pane, length): """internal helper that keeps the status text and the action bar up-to-date by keeping track of the app-list-changed signals """ LOG.debug("applist-changed %s %s" % (pane, length)) super(AvailablePane, self).on_app_list_changed(pane, length) self._update_action_bar() def _update_action_bar(self): 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 """ if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and 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 = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. if button.get_label() != button_text: button.set_label(button_text) elif installable: # Install all not yet offered. Offer. self.action_bar.add_button(ActionButtons.INSTALL, button_text, self._install_current_appstore) else: # Install offered, but nothing to install. Clear offer. self.action_bar.remove_button(ActionButtons.INSTALL) 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. ''' apps = [] iconnames = [] self.action_bar.remove_button(ActionButtons.INSTALL) for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and pkgname not in self.backend.pending_transactions): apps.append(self.db.get_application(doc)) # add iconnames iconnames.append(self.db.get_iconname(doc)) self.backend.install_multiple(apps, iconnames) def _show_or_hide_search_combo_box(self, view_state): # show/hide the sort combobox headers if the category forces a # custom sort mode category = view_state.category allow_user_sort = category is None or not category.is_forced_sort_mode self.app_view.set_allow_user_sorting(allow_user_sort) def set_state(self, nav_item): pass def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" self.state.search_term = "" def _is_custom_list_search(self, search_term): return (search_term and ',' in search_term) # callbacks def on_cache_ready(self, cache): """ refresh the application list when the cache is re-opened """ # just re-draw in the available pane, nothing but the # "is-installed" overlay icon will change when something # is installed or removed in the available pane self.app_view.queue_draw() def on_search_terms_changed(self, widget, new_text): """callback when the search entry widget changes""" LOG.debug("on_search_terms_changed: %s" % new_text) # reset the flag in the app_view because each new search should # reset the sort criteria self.app_view.reset_default_sort_mode() 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 vm = get_viewmanager() adj = self.app_view.tree_view_scroll.get_vadjustment() if adj: adj.set_value(0.0) # yeah for special cases - as discussed on irc, mpt # wants this to return to the category screen *if* # we are searching but we are not in any category or channel if not self.state.category and not self.state.channel and not new_text: # category activate will clear search etc self.state.reset() vm.display_page(self, self.Pages.LOBBY, self.state) elif self.state.subcategory and not new_text: vm.display_page(self, self.Pages.LIST, self.state) elif (self.state.category and self.state.category.subcategories and not new_text): vm.display_page(self, self.Pages.SUBCATEGORY, self.state) else: vm.display_page(self, self.Pages.LIST, self.state) return False def on_db_reopen(self, db): """Called when the database is reopened.""" super(AvailablePane, self).on_db_reopen(db) self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() def enter_page(self, page, state): if page == self.Pages.LIST: if state.search_term: self.display_search_page(state) else: self.display_app_view_page(state) elif page == self.Pages.SUBCATEGORY: self.display_subcategory_page(state) elif page == self.Pages.DETAILS: self.display_details_page(state) elif page == self.Pages.PURCHASE: self.display_purchase(state) elif page == self.Pages.SUBCATEGORY: self.display_subcategory_page(state) elif page == self.Pages.PREVIOUS_PURCHASES: self.display_previous_purchases(state) else: # page is self.Pages.LOBBY or unknown self.display_lobby_page(state) def leave_page(self, state): # if previous page is a list view, then store the scroll positions if self.is_applist_view_showing(): # store last adjustment to use later v = self.app_view.tree_view_scroll.get_vadjustment() self.state.vadjustment = v.get_value() elif self.is_app_details_view_showing(): self.app_details_view.videoplayer.stop() def display_lobby_page(self, view_state): self.state.reset() self.hide_appview_spinner() self.emit("app-list-changed", len(self.db)) self._clear_search() self.action_bar.clear() return True def display_list_page(self, view_state): header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self._show_or_hide_search_combo_box(view_state) self.app_view.vadj = view_state.vadjustment self.refresh_apps() return True def display_search_page(self, view_state): new_text = view_state.search_term # DTRT if the search is reset if not new_text: self._clear_search() else: self.state.limit = DEFAULT_SEARCH_LIMIT return self.display_list_page(view_state) def display_subcategory_page(self, view_state): category = view_state.category self.set_category(category) if self.state.search_term or self.searchentry.get_text(): self._clear_search() self.refresh_apps() query = self.get_query() n_matches = self.quick_query_len(query) self.subcategories_view.set_subcategory(category, n_matches) self.action_bar.clear() return True def display_app_view_page(self, view_state): category = view_state.category subcategory = view_state.subcategory self.set_category(category) self.set_subcategory(subcategory) result = self.display_list_page(view_state) if view_state.search_term: self._clear_search() return result def display_details_page(self, view_state): if self.searchentry.get_text() != self.state.search_term: self.searchentry.set_text_with_no_signal(self.state.search_term) self.action_bar.clear() SoftwarePane.display_details_page(self, view_state) return True def display_purchase(self, view_state): self.notebook.set_current_page(self.Pages.PURCHASE) self.action_bar.clear() def display_previous_purchases(self, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self.notebook.set_current_page(self.Pages.LIST) # clear any search terms self._clear_search() self.refresh_apps() self.action_bar.clear() return True def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % ( category.name, category)) self.state.subcategory = category self.state.application = None page = AvailablePane.Pages.LIST vm = get_viewmanager() vm.display_page(self, page, self.state) def on_category_activated(self, lobby_view, category): """ callback when a category is selected """ LOG.debug("on_category_activated: %s %s" % ( category.name, category)) if category.subcategories: page = self.Pages.SUBCATEGORY else: page = self.Pages.LIST self.state.category = category self.state.subcategory = None self.state.application = None vm = get_viewmanager() vm.display_page(self, page, self.state) def on_application_activated(self, appview, app): """Callback for when an app is activated.""" super(AvailablePane, self).on_application_activated(appview, app) if self.state.subcategory: self.current_app_by_subcategory[self.state.subcategory] = app else: self.current_app_by_category[self.state.category] = app def on_show_category_applist(self, widget): self._show_hide_subcategories(show_category_applist=True) def on_previous_purchases_activated(self, query): """ called to activate the previous purchases view """ #print cat_view, name, query LOG.debug("on_previous_purchases_activated with query: %s" % query) self.state.channel = SoftwareChannel("Previous Purchases", "software-center-agent", None, channel_query=query) vm = get_viewmanager() vm.display_page(self, self.Pages.PREVIOUS_PURCHASES, self.state) def is_category_view_showing(self): """Return whether a category/sub-category page is being displayed.""" current_page = self.notebook.get_current_page() return current_page in (self.Pages.LOBBY, self.Pages.SUBCATEGORY) def is_purchase_view_showing(self): """Return whether a purchase page is being displayed.""" current_page = self.notebook.get_current_page() return current_page == self.Pages.PURCHASE def set_subcategory(self, subcategory): LOG.debug('set_subcategory: %s' % subcategory) self.state.subcategory = subcategory self._apply_filters_for_category_or_subcategory(subcategory) def set_category(self, category): LOG.debug('set_category: %s' % category) self.state.category = category self._apply_filters_for_category_or_subcategory(category) def _apply_filters_for_category_or_subcategory(self, category): # apply flags if category: if 'nonapps-visible' in category.flags: self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE else: self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # apply any category based filters if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) 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): self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) def refresh_apps(self, query=None): SoftwarePane.refresh_apps(self, query) # tell the lobby to update its content if self.cat_view: self.cat_view.refresh_apps() # and the subcat view as well... if self.subcategories_view: self.subcategories_view.refresh_apps() software-center-13.10/softwarecenter/ui/gtk3/panes/historypane.py0000664000202700020270000003464612151440100025420 0ustar dobeydobey00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2010 Canonical # # Authors: # Olivier Tilloy # # 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, see . from gi.repository import GObject from gi.repository import Gtk, Gdk import logging import datetime from gettext import gettext as _ from softwarecenter.ui.gtk3.em import get_em from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook from basepane import BasePane from softwarecenter.enums import Icons from softwarecenter.ui.gtk3.session.viewmanager import get_viewmanager from softwarecenter.ui.gtk3.session.displaystate 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, ()), } (COL_WHEN, COL_ACTION, COL_PKG) = range(3) COL_TYPES = (object, int, object) (ALL, INSTALLED, REMOVED, UPGRADED) = range(4) ICON_SIZE = 1.2 * get_em() PADDING = 4 # pages for the spinner notebook (PAGE_HISTORY_VIEW, PAGE_SPINNER) = range(2) def __init__(self, cache, db, distro, icons): Gtk.VBox.__init__(self) self.cache = cache self.db = db self.distro = distro self.icons = icons self.apps_filter = None self.state = DisplayState() self.pane_name = _("History") # Icon cache, invalidated upon icon theme changes self._app_icon_cache = {} self._reset_icon_cache() self.icons.connect('changed', self._reset_icon_cache) self._emblems = {} self._get_emblems(self.icons) vm = get_viewmanager() self.searchentry = vm.get_global_searchentry() self.toolbar = Gtk.Toolbar() self.toolbar.show() 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.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.join_group(all_action) installs_button = installs_action.create_tool_item() self.toolbar.insert(installs_button, 1) upgrades_action = Gtk.RadioAction( 'filter_upgrads', _('Updates'), None, None, self.UPGRADED) upgrades_action.join_group(all_action) upgrades_button = upgrades_action.create_tool_item() self.toolbar.insert(upgrades_button, 2) removals_action = Gtk.RadioAction( 'filter_removals', _('Removals'), None, None, self.REMOVED) removals_action.join_group(all_action) removals_button = removals_action.create_tool_item() self.toolbar.insert(removals_button, 3) self.toolbar.connect('draw', self.on_toolbar_draw) self._actions_list = all_action.get_group() self._set_actions_sensitive(False) self.view = Gtk.TreeView() self.view.set_headers_visible(False) self.view.show() self.history_view = Gtk.ScrolledWindow() self.history_view.set_policy(Gtk.PolicyType.AUTOMATIC, 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) self.visible_changes = 0 self.store_filter = self.store.filter_new(None) self.store_filter.set_visible_func(self.filter_row, None) 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 self.column = Gtk.TreeViewColumn(_('Date')) self.view.append_column(self.column) self.cell_icon = Gtk.CellRendererPixbuf() self.cell_icon.set_padding(self.PADDING, self.PADDING / 2) self.column.pack_start(self.cell_icon, False) self.column.set_cell_data_func(self.cell_icon, self.render_cell_icon) 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) self.cell_time = Gtk.CellRendererText() self.cell_time.set_padding(6, 0) self.cell_time.set_alignment(1.0, 0.5) self.column.pack_end(self.cell_time, False) self.column.set_cell_data_func(self.cell_time, self.render_cell_time) # busy cursor self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH) def init_view(self): if self.history is None: # 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) self.spinner_notebook.show_spinner() self.load_and_parse_history() self.spinner_notebook.hide_spinner() self._set_actions_sensitive(True) window.set_cursor(None) self.emit("history-pane-created") def on_toolbar_draw(self, widget, cr): a = widget.get_allocation() context = widget.get_style_context() color = context.get_border_color(widget.get_state_flags()) 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.stroke() def _get_emblems(self, icons): from softwarecenter.enums import USE_PACKAGEKIT_BACKEND if USE_PACKAGEKIT_BACKEND: emblem_names = ("pk-package-add", "pk-package-delete", "pk-package-update") else: emblem_names = ("package-install", "package-remove", "package-upgrade") for i, emblem in enumerate(emblem_names): pb = icons.load_icon(emblem, self.ICON_SIZE, 0) self._emblems[i + 1] = pb def _set_actions_sensitive(self, sensitive): for action in self._actions_list: action.set_sensitive(sensitive) 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) 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() # FIXME: a signal from AptHistory is nicer while not self.history.history_ready: while Gtk.events_pending(): Gtk.main_iteration() self.parse_history() self.history.set_on_update(self.parse_history) def parse_history(self): date = None when = None last_row = None day = self.store.get_iter_first() if day is not None: date = self.store.get_value(day, self.COL_WHEN) if len(self.history.transactions) == 0: logging.debug("AptHistory is currently empty") return new_last = self.history.transactions[0].start_date for trans in self.history.transactions: while Gtk.events_pending(): Gtk.main_iteration() when = trans.start_date if self.last is not None and when <= self.last: break if when.date() != date: date = when.date() day = self.store.append(None, (date, self.ALL, None)) last_row = None actions = {self.INSTALLED: trans.install, self.REMOVED: trans.remove, self.UPGRADED: trans.upgrade, } for action, pkgs in actions.items(): for pkgname in pkgs: row = (when, action, pkgname) last_row = self.store.insert_after(day, last_row, row) self.last = new_last self.update_view() def on_search_terms_changed(self, entry, terms): self.update_view() def change_filter(self, action, current): self.filter = action.get_current_value() self.update_view() def update_view(self): self.store_filter.refilter() self.view.collapse_all() # Expand all the matching rows if self.searchentry.get_text(): self.view.expand_all() # 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 noticeable 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) #self.emit('app-list-changed', self.visible_changes) def _row_matches(self, store, iter): # Whether a child row matches the current filter and the search entry pkg = store.get_value(iter, self.COL_PKG) or '' filter_values = (self.ALL, store.get_value(iter, self.COL_ACTION)) filter_matches = self.filter in filter_values search_matches = self.searchentry.get_text().lower() in pkg.lower() return filter_matches and search_matches def filter_row(self, store, iter, user_data): pkg = store.get_value(iter, self.COL_PKG) if pkg is not None: return self._row_matches(store, iter) else: i = store.iter_children(iter) while i is not None: if self._row_matches(store, i): return True i = store.iter_next(i) return False def render_cell_icon(self, column, cell, store, iter, user_data): pkg = store.get_value(iter, self.COL_PKG) if pkg is None: cell.set_visible(False) return cell.set_visible(True) when = store.get_value(iter, self.COL_WHEN) if isinstance(when, datetime.datetime): 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) #~ icon_value = doc.get_value(XapianValues.ICON) #~ if icon_value: #~ icon_name = os.path.splitext(icon_value)[0] #~ break #~ if icon_name in self._app_icon_cache: #~ icon = self._app_icon_cache[icon_name] #~ else: #~ try: #~ 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) if isinstance(when, datetime.datetime): pkg = store.get_value(iter, self.COL_PKG) text = pkg elif isinstance(when, datetime.date): today = datetime.date.today() monday = today - datetime.timedelta(days=today.weekday()) if when == today: text = _("Today") elif when >= monday: # Current week, display the name of the day text = when.strftime(_('%A')) else: if when.year == today.year: # Current year, display the day and month text = when.strftime(_('%d %B')) else: # Display the full date: day, month, year text = when.strftime(_('%d %B %Y')) cell.set_property('markup', text) def render_cell_time(self, column, cell, store, iter, user_data): when = store.get_value(iter, self.COL_WHEN) text = '' if isinstance(when, datetime.datetime): action = store.get_value(iter, self.COL_ACTION) # 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_text = when.time().strftime(_('%I:%M %p')) if self.filter is not self.ALL: action_text = time_text else: if action == self.INSTALLED: action_text = _('installed %s') % time_text elif action == self.REMOVED: action_text = _('removed %s') % time_text elif action == self.UPGRADED: action_text = _('updated %s') % time_text color = {'color': '#8A8A8A', 'action': action_text} text = '%(action)s' % color cell.set_property('markup', text) software-center-13.10/softwarecenter/ui/gtk3/panes/basepane.py0000664000202700020270000000371412151440100024621 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 class BasePane(object): """Base class for the widgets that can be registered in a ViewManager.""" def __init__(self): # stuff that is queried by app.py self.apps_filter = None self.searchentry = None # flag to indicate that the pane's view has been fully initialized self.view_initialized = False 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 is_purchase_view_showing(self): return False def get_current_app(self): """Return the current app being displayed by this pane.""" def enter_page(self, page_id, view_state): """Callback called when entering 'page_id' with 'view_state'.""" def leave_page(self, view_state): """Callback called when leaving 'page_id' with 'view_state'.""" def init_view(self): """A callback called when the pane is selected in the viewswitcher. This method can be used to delay initialization and/or setup of a BasePane subclass' view until it is actually to be displayed (aka lazy-loading). The primary purpose of this is to optimize startup time performance. """ software-center-13.10/softwarecenter/ui/gtk3/SimpleGtkbuilderApp.py0000664000202700020270000000372212151440100025643 0ustar dobeydobey00000000000000""" SimpleGladeApp.py Module that provides an object oriented abstraction to pygtk and gtkbuilder Copyright (C) 2009 Michael Vogt based on ideas from SimpleGladeBuilder by Sandino Flores Moreno """ # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; version 3. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA from gi.repository import Gtk # based on SimpleGladeApp class SimpleGtkbuilderApp(object): def __init__(self, path, domain): super(SimpleGtkbuilderApp, self).__init__() self.builder = Gtk.Builder() self.builder.set_translation_domain(domain) self.builder.add_from_file(path) 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) def run(self): """ Starts the main loop of processing events checking for Control-C. The default implementation checks whether a Control-C is pressed, then calls on_keyboard_interrupt(). Use this method for starting programs. """ try: Gtk.main() except KeyboardInterrupt: self.on_keyboard_interrupt() def on_keyboard_interrupt(self): """ This method is called by the default implementation of run() after a program is finished by pressing Control-C. """ pass software-center-13.10/softwarecenter/ui/gtk3/em.py0000664000202700020270000000201712151440100022331 0ustar dobeydobey00000000000000import gi from gi.repository import Pango from gi.repository import Gtk gi.require_version("Gtk", "3.0") import logging LOG = logging.getLogger(__name__) def get_em(size=""): # calc the height of a character, use as 1em if size: m = '<%s>M' % (size, size) else: m = 'M' l = Gtk.Label() l.set_markup(m) 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") EM = get_em() SMALL_EM = get_small_em() BIG_EM = get_big_em() LOG.debug("EM's: %s %s %s" % (EM, SMALL_EM, BIG_EM)) def em(multiplier=1, min=1): 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))) def big_em(multiplier=1, min=1): 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) software-center-13.10/softwarecenter/ui/gtk3/gmenusearch.py0000664000202700020270000001364312151440100024240 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 import os import logging from softwarecenter.enums import APP_INSTALL_PATH_DELIMITER LOG = logging.getLogger(__name__) class GMenuSearcher(object): def __init__(self): self._found = None def _search_gmenu_dir(self, dirlist, needle): if not dirlist[-1]: return from gi.repository import GMenu dir_iter = dirlist[-1].iter() current_type = dir_iter.next() while current_type is not GMenu.TreeItemType.INVALID: if current_type == GMenu.TreeItemType.DIRECTORY: self._search_gmenu_dir( 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] return # 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] return current_type = dir_iter.next() def get_main_menu_path(self, desktop_file, menu_files_list=None): if not desktop_file: return from gi.repository import GMenu from gi.repository import GObject # use the system ones by default, but allow override for # easier testing if menu_files_list is None: menu_files_list = ["applications.menu", "settings.menu"] for n in menu_files_list: if n.startswith("/"): tree = GMenu.Tree.new_for_path(n, 0) else: tree = GMenu.Tree.new(n, 0) try: tree.load_sync() except GObject.GError as e: LOG.warning("could not load GMenu path: %s" % e) return root = tree.get_root_directory() self._search_gmenu_dir([root], os.path.basename(desktop_file)) # retry search for app-install-data desktop files if not self._found and ":" in os.path.basename(desktop_file): # the desktop files in app-install-data have a layout # like "pkg:file.desktop" so we need to take that into # account when searching desktop_file = os.path.basename(desktop_file).split(":")[1] self._search_gmenu_dir([root], desktop_file) return self._found # these are the old static bindinds that are no longer required # (this is just kept here in case of problems with the dynamic # GIR and the old gtk2 gtk ui) class GMenuSearcherGtk2(object): def __init__(self): self._found = None def _search_gmenu_dir(self, dirlist, needle): if not dirlist[-1]: return import gmenu for item in dirlist[-1].get_contents(): mtype = item.get_type() if mtype == gmenu.TYPE_DIRECTORY: 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] return # 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] return def get_main_menu_path(self, desktop_file, menu_files_list=None): if not desktop_file: return import gmenu # use the system ones by default, but allow override for # easier testing if menu_files_list is None: 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()], os.path.basename(desktop_file)) if self._found: return self._found software-center-13.10/softwarecenter/ui/gtk3/session/0000755000202700020270000000000012224614354023056 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/session/displaystate.py0000664000202700020270000000627412200237544026145 0ustar dobeydobey00000000000000# 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 Gtk from softwarecenter.utils import utf8 # for DisplayState attribute type-checking from softwarecenter.db.database import Application from softwarecenter.db.categories import Category from softwarecenter.backend.channel import SoftwareChannel from softwarecenter.db.appfilter import AppFilter class DisplayState(object): """ This represents the display state for the undo history """ _attrs = {'category': (type(None), Category), 'channel': (type(None), SoftwareChannel), 'subcategory': (type(None), Category), 'search_term': (str,), 'application': (type(None), Application), 'limit': (int,), 'filter': (type(None), AppFilter), 'vadjustment': (float, ), } def __init__(self): self.category = None self.channel = None self.subcategory = None self.search_term = "" self.application = None self.limit = 0 self.filter = None self.vadjustment = 0.0 def __setattr__(self, name, val): attrs = self._attrs if name not in attrs: 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)) raise TypeError(msg) Gtk.main_quit() return object.__setattr__(self, name, val) def __str__(self): s = utf8('%s %s "%s" %s %s') % \ (self.category, self.subcategory, self.search_term, self.application, self.channel) return s def copy(self): state = DisplayState() state.channel = self.channel state.category = self.category state.subcategory = self.subcategory state.search_term = self.search_term state.application = self.application state.limit = self.limit if self.filter: state.filter = self.filter.copy() else: state.filter = None state.vadjustment = self.vadjustment return state def reset(self): self.channel = None self.category = None self.subcategory = None self.search_term = "" self.application = None self.limit = 0 if self.filter: self.filter.reset() self.vadjustment = 0.0 software-center-13.10/softwarecenter/ui/gtk3/session/viewmanager.py0000664000202700020270000001612412151440100025724 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 Gtk, GObject from navhistory import NavigationHistory, NavigationItem from softwarecenter.ui.gtk3.widgets.backforward import BackForwardButton from softwarecenter.ui.gtk3.widgets.searchentry import SearchEntry from softwarecenter.utils import ExecutionTime _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, ), ), } def __init__(self, notebook_view, options=None): GObject.GObject.__init__(self) self.notebook_view = notebook_view self.search_entry = SearchEntry() self.search_entry.connect( "terms-changed", self.on_search_terms_changed) self.search_entry.connect( "key-press-event", self.on_search_entry_key_press_event) self.back_forward = BackForwardButton() self.back_forward.connect( "left-clicked", self.on_nav_back_clicked) self.back_forward.connect( "right-clicked", self.on_nav_forward_clicked) self.navhistory = NavigationHistory(self.back_forward, options) self.spinner = Gtk.Spinner() self.all_views = {} self.view_to_pane = {} self._globalise_instance() def _globalise_instance(self): global _viewmanager if _viewmanager is not None: msg = "Only one instance of ViewManager is allowed!" raise ValueError(msg) else: _viewmanager = self def destroy(self): """Destroy the global instance.""" global _viewmanager _viewmanager = None def on_search_terms_changed(self, widget, new_text): pane = self.get_current_view_widget() if hasattr(pane, "on_search_terms_changed"): pane.on_search_terms_changed(widget, new_text) def on_nav_back_clicked(self, widget): self.nav_back() def on_nav_forward_clicked(self, widget): self.nav_forward() 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) 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 self.all_views[view_id] = page_id self.view_to_pane[view_id] = pane def get_current_view_widget(self): current_view = self.get_active_view() return self.get_view_widget(current_view) def get_view_id_from_page_id(self, page_id): for (k, v) in self.all_views.items(): if page_id == v: return k def set_spinner_active(self, active): if active: self.spinner.show() self.spinner.start() else: self.spinner.stop() self.spinner.hide() def set_active_view(self, view_id): # no views yet if not self.all_views: return # if the view switches, ensure that the global spinner is hidden self.spinner.hide() # emit signal self.emit('view-changed', view_id) page_id = self.all_views[view_id] view_widget = self.get_view_widget(view_id) # it *seems* that this shouldn't be called here if we want the history # to work, but I'm not familiar with the code, so I'll leave it here # for the mean time # view_page = view_widget.get_current_page() # view_state = view_widget.state if self.search_entry.get_text() != 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) # nav_item = NavigationItem(self, view_widget, view_page, # view_state.copy(), callback) # self.navhistory.append(nav_item) self.notebook_view.set_current_page(page_id) if view_widget: with ExecutionTime("view_widget.init_view() (%s)" % view_widget): view_widget.init_view() return view_widget def get_active_view(self): page_id = self.notebook_view.get_current_page() return self.get_view_id_from_page_id(page_id) def is_active_view(self, view_id): return view_id == self.get_active_view() 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) def get_latest_nav_item(self): return self.navhistory.stack[-1] def display_page(self, pane, page, view_state): if self.navhistory.stack: ni = self.navhistory.stack[self.navhistory.stack.cursor] ni.pane.leave_page(view_state) nav_item = NavigationItem(self, pane, page, view_state.copy()) self.navhistory.append(nav_item) text = view_state.search_term if text != self.search_entry.get_text(): self.search_entry.set_text_with_no_signal(text) pane.state = view_state pane.enter_page(page, view_state) if page is not None: pane.notebook.set_current_page(page) if self.get_current_view_widget() != pane: view_id = None for view_id, widget in self.view_to_pane.items(): if widget == pane: break self.set_active_view(view_id) if (not pane.searchentry or pane.is_app_details_view_showing() or pane.is_purchase_view_showing()): self.search_entry.hide() else: self.search_entry.show() self.spinner.hide() 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() def get_global_searchentry(self): return self.search_entry def get_global_backforward(self): return self.back_forward def get_global_spinner(self): return self.spinner software-center-13.10/softwarecenter/ui/gtk3/session/__init__.py0000664000202700020270000000000012151440100025140 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/gtk3/session/appmanager.py0000664000202700020270000001772312151440100025540 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # # 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 json try: from urllib.parse import urlencode urlencode # pyflakes except ImportError: from urllib import urlencode from gi.repository import GObject from softwarecenter.backend.ubuntusso import UbuntuSSO from softwarecenter.enums import AppActions from softwarecenter.db import DebFileApplication 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, ) _appmanager = None # the global AppManager instance def get_appmanager(): """ get a existing appmanager instance (or None if none is created yet) """ return _appmanager class ApplicationManager(GObject.GObject): __gsignals__ = { "purchase-requested": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT, str, str,) ), } def __init__(self, db, backend, icons): GObject.GObject.__init__(self) self._globalise_instance() self.db = db self.backend = backend self.distro = get_distro() self.icons = icons # its ok to use the sync version here to get the token, this is # very quick helper = UbuntuSSO() self.oauth_token = helper.find_oauth_token_sync() def _globalise_instance(self): global _appmanager if _appmanager is not None: msg = "Only one instance of ApplicationManager is allowed!" raise ValueError(msg) else: _appmanager = self def destroy(self): """Destroy the global instance.""" global _appmanager _appmanager = None def request_action(self, app, addons_install, addons_remove, action): """callback when an app action is requested from the appview, 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)) appdetails = app.get_details(self.db) if action == AppActions.REMOVE: if not dependency_dialogs.confirm_remove( None, app, self.db, self.icons): # 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 # also be removed and show a dialog for confirmation # generic removal text (fixing LP bug #554319) if not dependency_dialogs.confirm_install( None, app, self.db, self.icons): # 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 # this allows us to 'upgrade' deb files 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 = getattr(self.backend, action) if action == AppActions.INSTALL: # the package.deb path name is in the request if app.request and isinstance(app, DebFileApplication): debfile_name = app.request else: debfile_name = None action_func(app, appdetails.icon, debfile_name, addons_install, addons_remove) elif callable(action_func): action_func(app, appdetails.icon, addons_install=addons_install, addons_remove=addons_remove) #~ else: #~ LOG.error("Not a valid action in AptdaemonBackend: '%s'" % #~ action) # public interface def reload(self): """ reload the package cache, this goes straight to the backend """ self.backend.reload() def install(self, app, addons_to_install, addons_to_remove): """ install the current application, fire an action request """ self.request_action( app, addons_to_install, addons_to_remove, AppActions.INSTALL) def remove(self, app, addons_to_install, addons_to_remove): """ remove the current application, , fire an action request """ self.request_action( app, addons_to_install, addons_to_remove, AppActions.REMOVE) def upgrade(self, app, addons_to_install, addons_to_remove): """ upgrade the current application, fire an action request """ self.request_action( app, addons_to_install, addons_to_remove, AppActions.UPGRADE) def apply_changes(self, app, addons_to_install, addons_to_remove): """ apply changes concerning add-ons """ self.request_action( app, addons_to_install, addons_to_remove, AppActions.APPLY) def buy_app(self, app): """ initiate the purchase transaction """ 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() }) ) self.emit("purchase-requested", app, appdetails.icon, url) def reinstall_purchased(self, app): """ reinstall a purchased app """ #~ LOG.debug("reinstall_purchased %s" % self.app) appdetails = app.get_details(self.db) iconname = appdetails.icon deb_line = appdetails.deb_line license_key = appdetails.license_key license_key_path = appdetails.license_key_path signing_key_id = appdetails.signing_key_id oauth_token = json.dumps(self.oauth_token) self.backend.add_repo_add_key_and_install_app(deb_line, signing_key_id, app, iconname, license_key, license_key_path, oauth_token) def enable_software_source(self, app): """ enable the software source for the given app """ appdetails = app.get_details(self.db) if appdetails.channelfile and appdetails._unavailable_channel(): self.backend.enable_channel(appdetails.channelfile) elif appdetails.component: components = appdetails.component.split('&') for component in components: self.backend.enable_component(component) software-center-13.10/softwarecenter/ui/gtk3/session/navhistory.py0000664000202700020270000002304212151440100025622 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical # # Authors: # 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 softwarecenter.utils import utf8 import logging LOG = logging.getLogger(__name__) class NavigationHistory(object): """ class to manage navigation history """ MAX_NAV_ITEMS = 25 # limit number of NavItems allowed in the NavStack def __init__(self, back_forward_btn, options=None): self.stack = NavigationStack(self, self.MAX_NAV_ITEMS, options) self.back_forward = back_forward_btn self.in_replay_history_mode = False self._nav_back_set_sensitive(False) self._nav_forward_set_sensitive(False) def append(self, nav_item): """ append a new NavigationItem to the history stack """ LOG.debug("appending: '%s'" % nav_item) if self.in_replay_history_mode: return stack = self.stack # reset navigation forward stack items on a direct navigation stack.clear_forward_items() stack.append(nav_item) if stack.cursor >= 1: self._nav_back_set_sensitive(True) self._nav_forward_set_sensitive(False) def nav_forward(self): """ navigate forward one item in the history stack """ stack = self.stack nav_item, did_step = stack.step_forward() nav_item.navigate_to() self._nav_back_set_sensitive(True) if stack.at_end(): if self.back_forward.right.has_focus(): self.back_forward.left.grab_focus() self._nav_forward_set_sensitive(False) def nav_back(self): """ navigate back one item in the history stack """ stack = self.stack nav_item, did_step = stack.step_back() nav_item.navigate_to() self._nav_forward_set_sensitive(True) if stack.at_start(): 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 navigation button insensitive, useful when navigating away from a pane whose contents are no longer valid (e.g. the purchase pane after a purchase is completed, cancelled, etc.) """ self.stack.clear_forward_items() self._nav_forward_set_sensitive(False) def reset(self): """ reset the navigation history by clearing the history stack and setting the navigation UI items insensitive """ 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) def _nav_forward_set_sensitive(self, is_sensitive): self.back_forward.right.set_sensitive(is_sensitive) #~ self.navhistory_forward_action.set_sensitive(is_sensitive) class NavigationItem(object): """ class to implement navigation points to be managed in the history queues """ def __init__(self, view_manager, pane, page, view_state): self.view_manager = view_manager self.pane = pane self.page = page self.view_state = view_state def __str__(self): facet = self.pane.pane_name.replace(' ', '')[:6] result = utf8("%s:%s %s") % (facet, self.page, self.view_state) return result def navigate_to(self): """ navigate to the view that corresponds to this NavigationItem """ # make sure we are in reply history mode self.view_manager.navhistory.in_replay_history_mode = True self.view_manager.display_page(self.pane, self.page, self.view_state) # and reset this mode again self.view_manager.navhistory.in_replay_history_mode = False class NavigationStack(object): """ a navigation history stack """ def __init__(self, history, max_length, options=None): self.history = history self.max_length = max_length self.stack = [] self.cursor = 0 if not options or not options.display_navlog: return import softwarecenter.ui.gtk3.widgets.navlog as navlog self.navlog = navlog.NavLogUtilityWindow(self) def __len__(self): return len(self.stack) def __repr__(self): BOLD = "\033[1m" RESET = "\033[0;0m" s = '[' for i, item in enumerate(self.stack): if i != self.cursor: s += str(item) + ', ' else: s += BOLD + str(item) + RESET + ', ' return s + ']' def __getitem__(self, item): return self.stack[item] def _isok(self, item): if item.page is not None and item.page < 0: return False if len(self) == 0: return True 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... # hacky: special case, check for subsequent searches # if subsequent search, update previous item_vs.search_term # to current. # do import of Pages enum here: else drama! from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane # check if both current and previous views were list views if (item.page == AvailablePane.Pages.LIST and last.page == AvailablePane.Pages.LIST): # first case, previous search and new search if (item_vs.search_term and last_vs.search_term and item_vs.search_term != last_vs.search_term): # update last search term with current search_term last.view_state.search_term = item_vs.search_term # ... but return False, resulting in 'item' not being # appended return False elif (last_vs.search_term and not item_vs.search_term): LOG.debug("search has been cleared. ignoring history item") if len(self) > 1: del self.stack[-1] 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)") return False elif (item.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: del self.stack[-1] 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)") return False if item.__str__() == last.__str__(): return False return True def append(self, item): if not self._isok(item): 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 LOG.debug('A:%s' % repr(self)) #~ print ('A:%s' % repr(self)) if hasattr(self, "navlog"): self.navlog.log.notify_append(item) def step_back(self): did_step = False if self.cursor > 0: self.cursor -= 1 did_step = True else: self.cursor = 0 LOG.debug('B:%s' % repr(self)) #~ print ('B:%s' % repr(self)) if hasattr(self, "navlog") and did_step: self.navlog.log.notify_step_back() return self.stack[self.cursor], did_step def step_forward(self): did_step = False if self.cursor < len(self.stack) - 1: self.cursor += 1 did_step = True else: self.cursor = len(self.stack) - 1 LOG.debug('B:%s' % repr(self)) #~ print ('B:%s' % repr(self)) if hasattr(self, "navlog") and did_step: self.navlog.log.notify_step_forward() return self.stack[self.cursor], did_step def clear_forward_items(self): self.stack = self.stack[:(self.cursor + 1)] if hasattr(self, "navlog"): self.navlog.log.notify_clear_forward_items() def at_end(self): return self.cursor == len(self.stack) - 1 def at_start(self): return self.cursor == 0 def reset(self): self.stack = [] self.cursor = 0 software-center-13.10/softwarecenter/ui/qml/0000755000202700020270000000000012224614355021315 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/qml/star-large-full.png0000664000202700020270000000217312151440100025011 0ustar dobeydobey00000000000000‰PNG  IHDR szzôsBIT|dˆ pHYsããæß§tEXtSoftwarewww.inkscape.org›î<øIDATX…½•ßO[eÇßöü>==§í9=…ÒBéºYØ£€‘°-n‰KÜ•ÉÌ¢áÂ5þŠŠuKÍÃb§^-&^MÔè? &fY3cP`ÖQè ?[èï¶V(íië )=U£ÏÝy¾ïû|?9ïû¼¬R©©áës”Keôä¨{Tj ¹Ô#f™{©Ù¢½òëH§¥á´–ºÊTΨ6¨›è‘†¸ìf’á•ç·¿-õ„Ën&À¶¨†u&vç·óm gT7 €ÖR!øß­rHŽºØ·£ûR“™ë¯ÎëÌl¿ÛÑ}éÐT<5„+P¸:‘(¬â©¡C˜zë„3ªÏˆéœQ}fòÍã=‡ÀðÊaš£h1æ(šÑ*_ßOM™ØKè²›@+¥!{Pé@ D«3±OiôŒ¾^Ád(³_M}WØ,Æ ù¢?—ÞpWÊ••sŸ/mÖ¸s¥Öè™ç%f…Q˜…aˆ•ÃrF!ޤq– p Á÷ùÿŠâ–òm6³ùD!/$ÊB9)¥¤°%¤6s[R¡Ì×2Ϩm¼óô‘ÝmÕˆ(—Ê`ö˜/¾‘‰ýi¨; ΆŠá¹Íé™X›‹¾š eB2O®ý ù£/ö:=S;—púzÏ…–GøO9£Út˜æ±ÕÔbÈ{þÔûw¨ê·£û¬Áªû‚oÓH¯õ"¼÷‡âC6§gb;·§ ÝŽ®ýQþË&3wìaš¯ûcó‘Åø³½7gîíÎ×|~»Öõ¨þ¨ö«f‹ÖúPÌ磾ðBârßG3ÓÕšèCtïêÉžf‹öÛ–c|Çþˆ7²˜xºÿãYo-]´ùû>œù=0~9—Þ(J5Ï¥6 Á‘ÄÌëF"&‚©¡8…Õ½Ðu?!TÁ iü¸d\µKvß©¶I@0øÀ›$¸ìfW ­ÀH´Íe7+ö  d甜‚ÓKB¼áÉ€7 1: # FIXME: we really should set the text entry here pkglistmodel.setSearchQuery(sys.argv[1]) # load the main QML file into the view qmlpath = os.path.join(os.path.dirname(__file__), "sc.qml") view.setSource(QUrl.fromLocalFile(qmlpath)) # show it view.show() sys.exit(app.exec_()) software-center-13.10/softwarecenter/ui/qml/AppListView.qml0000664000202700020270000001430112151440100024220 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * 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, see . */ import QtQuick 1.0 FocusScope { id: applistview property alias count: list.count property alias currentItem: list.currentItem property alias model: list.model signal moreInfoClicked clip: true Rectangle { color: activePalette.base anchors.fill: parent } ScrollBar { id: scrollbar width: 6 height: parent.height orientation: Qt.Vertical anchors.right: parent.right position: list.visibleArea.yPosition pageSize: list.visibleArea.heightRatio visible: list.count > 0 } ListView { id: list height: parent.height anchors.left: parent.left anchors.right: scrollbar.left spacing: 5 focus: true SystemPalette { id: activePalette colorGroup: SystemPalette.Active } SystemPalette { id: inactivePalette colorGroup: SystemPalette.Inactive } delegate: Rectangle { property string appname: _appname property string pkgname: _pkgname property string icon: _icon property string summary: _summary property bool installed: _installed property string description: _description property double ratingsaverage: _ratings_average property int ratingstotal: _ratings_total property int installremoveprogress: _installremoveprogress width: parent.width height: ListView.isCurrentItem ? 75 : 40 Behavior on height { NumberAnimation { duration: 40 } } color: { if (!ListView.isCurrentItem) return activePalette.base if (list.activeFocus) return activePalette.highlight else return inactivePalette.highlight } MouseArea { anchors.fill: parent onClicked: { list.forceActiveFocus() list.currentIndex = index } } Image { id: iconimg height: 24 width: height anchors.top: parent.top anchors.topMargin: 3 anchors.left: parent.left anchors.leftMargin: 3 sourceSize.height: height sourceSize.width: width source: icon asynchronous: true Image { id: installedemblem height: 16 width: height sourceSize.height: height sourceSize.width: width source: "file:///usr/share/software-center/icons/software-center-installed.png" anchors.horizontalCenter: parent.right anchors.verticalCenter: parent.bottom asynchronous: true visible: installed } } Text { id: appnametxt height: 20 anchors.top: parent.top anchors.topMargin: 3 width: parent.width - iconimg.witdh - 15 x: iconimg.x + iconimg.width + 10 text: appname } Text { id: summarytxt height: 20 anchors.top: appnametxt.bottom width: appnametxt.width x: appnametxt.x font.pointSize: appnametxt.font.pointSize * 0.8 text: summary } Item { id: ratings anchors.top: parent.top anchors.right: parent.right anchors.margins: 5 visible: !progressbar.visible && (ratingstotal > 0) Stars { id: ratingstars anchors.top: parent.top anchors.right: parent.right height: 16 ratings_average: ratingsaverage } Text { text: qsTr("%1 Ratings").arg(ratingstotal) anchors.top: ratingstars.bottom anchors.right: parent.right } } ProgressBar { id: progressbar width: 100 anchors.top: appnametxt.top anchors.right: parent.right anchors.margins: 10 height: appnametxt.height progress: installremoveprogress visible: parent.ListView.isCurrentItem && (progress != -1) } Button { id: moreinfobtn text: qsTr("More Info") anchors.top: summarytxt.bottom x: summarytxt.x visible: parent.ListView.isCurrentItem onClicked: applistview.moreInfoClicked() } Button { text: installed ? qsTr("Remove") : qsTr("Install") anchors.top: summarytxt.bottom anchors.right: parent.right anchors.rightMargin: 10 visible: parent.ListView.isCurrentItem onClicked: { if (installed) list.model.removePackage(pkgname) else list.model.installPackage(pkgname) } } } } } software-center-13.10/softwarecenter/ui/qml/__init__.py0000664000202700020270000000000012151440100023376 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/qml/star-small-full.png0000664000202700020270000000107612151440100025030 0ustar dobeydobey00000000000000‰PNG  IHDRóÿasBIT|dˆ pHYsqq?€wtEXtSoftwarewww.inkscape.org›î<»IDAT8cüÿÿ?.ðpª}ðßßÙ” Ž,Ç¥† §n1žnŸW˜ÛõV·…6µŒû’y%”E,øEyTØ8Ùä™Y˜dYÙY x¹´~yÿíú¯¿/ÿûûÿéïŸ~zóåæ«ïŽ1ÞŸd³FAO:—ñ—ž­azqïmô›'¶‘ªùݳ[_=xÍdÑíç“ë/¢Þ=û¸‡Í»_eÖ{õ#,!)×åSÔ—^%,-àNÈæ‡WžE¶^üÂÀ€ &—?=¾þ¢àïï¿85ÿùý—áñõÅ0Í(0000Iòk3³2ã4€…•™_”ÇY ÅN^M|Îg```ààa×@1…ÃÆ¬cÿþùçûûç2000Jòdz²³p2000°²³(â4€™…Iáßßÿ?¼ü¼÷ݳ *ÅÇŽ2000Üé³Z,,%ÐÈ/ÆãÌÂÊ,ƒâ¤ÿÿÿÃñévïM´ DCÆ÷'Ù?œj·Y  ¼¼Œ¥ fIEND®B`‚software-center-13.10/softwarecenter/ui/qml/DetailsView.qml0000664000202700020270000002541012151440100024234 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Michael Vogt * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { id: detailsview Rectangle { id: detailsframe anchors.fill: parent color: activePalette.base CloudsHeader { anchors.top: parent.top anchors.left: parent. left anchors.right: parent.right } Image { id: largeiconimg height: 64 width: height anchors.top: parent.top anchors.left: parent.left anchors.margins: 15 sourceSize.height: height sourceSize.width: width source: list.currentItem != null ? list.currentItem.icon : "" asynchronous: true } Text { id: titletxt anchors.top: parent.top anchors.left: largeiconimg.right anchors.right: parent.right anchors.margins: 15 height: 25 font.pointSize: 20 font.bold: true text: list.currentItem != null ? list.currentItem.appname : "" } Text { id: headertxt anchors.top: titletxt.bottom anchors.topMargin: 5 anchors.left: largeiconimg.right anchors.right: parent.right anchors.margins: 15 height: 10 font.pointSize: 9 text: list.currentItem != null ? list.currentItem.summary : "" } Stars { id: headerstars anchors.top: parent.top anchors.right: parent.right anchors.margins: 12 height: 32 ratings_average: list.currentItem != null ? list.currentItem.ratingsaverage : 0 } Text { id: headernrreviews anchors.top: headerstars.bottom anchors.horizontalCenter: headerstars.horizontalCenter text: list.currentItem != null ? qsTr("%1 reviews").arg(list.currentItem.ratingstotal) : 0 } Rectangle { id: installrect anchors.top: largeiconimg.bottom color: "lightblue" height: installbtn.height + 12 width: parent.width Button { id: installbtn anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.rightMargin: 12 text: list.currentItem == null ? "" : list.currentItem.installed ? qsTr("Remove") : qsTr("Install") onClicked: { if (list.currentItem.installed) list.model.removePackage(list.currentItem.pkgname) else list.model.installPackage(list.currentItem.pkgname) } } ProgressBar { id: progressbar anchors.fill: installbtn width: 200 progress: list.currentItem != null ? list.currentItem.installremoveprogress : 0 visible: (list.currentItem != null) && (list.currentItem.installremoveprogress != -1) } } Text { id: desctxt anchors.top: installrect.bottom anchors.topMargin: 50 anchors.left: parent.left anchors.right: screenshotthumb.left anchors.margins: 15 text: list.currentItem != null ? list.currentItem.description : "" wrapMode: Text.Wrap } Image { id: screenshotthumb anchors.top: installrect.bottom anchors.topMargin: 50 anchors.right: parent.right anchors.margins: 15 height: 100 width: 150 sourceSize.height: height sourceSize.width: width MouseArea { anchors.fill: parent onClicked: { if (screenshotthumb.status == Image.Ready) { screenshotview.loadScreenshot("http://screenshots.ubuntu.com/screenshot/" + list.currentItem.pkgname) } } } } // reviews part Text { anchors.top: desctxt.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: 15 id: reviewsheadertxt text: qsTr("Reviews") } Rectangle { id: reviewslistframe anchors.left: parent.left anchors.right: parent.right anchors.margins: 15 anchors.top: reviewsheadertxt.bottom anchors.bottom: parent.bottom clip: true ScrollBar { id: reviewsVerticalScrollBar width: 6; height: reviewslist.height - 10 orientation: Qt.Vertical anchors.right: reviewslistframe.right position: reviewslist.visibleArea.yPosition pageSize: reviewslist.visibleArea.heightRatio } ListView { id: reviewslist spacing: 5 width: parent.width - 10 height: parent.height - 10 anchors.centerIn: parent model: reviewslistmodel delegate: Rectangle { property string summary: _summary property string review_text: _review_text property string rating: _rating property string reviewer_displayname: _reviewer_displayname property string date_created: _date_created // FIXME: can this be automatically calculated? // if I ommit it its getting cramped togehter // in funny ways height: reviewsummarytxt.height + reviewtxt.height + 20 width: reviewslist.width Stars { id: stars height: 16 ratings_average: rating } Text { id: reviewsummarytxt text: summary font.bold: true anchors.left: stars.right anchors.right: whowhentxt.left anchors.margins: 5 elide: Text.ElideRight } Text { id: whowhentxt text: qsTr("%1, %2").arg(reviewer_displayname).arg(date_created) anchors.right: parent.right color: "grey" } Text { id: reviewtxt text: review_text wrapMode: Text.Wrap anchors.left: parent.left anchors.right: parent.right anchors.top: stars.bottom anchors.topMargin: 5 } } } } } function loadThumbnail() { screenshotthumb.source = "http://screenshots.ubuntu.com/thumbnail/" + list.currentItem.pkgname } function unloadThumbnail() { screenshotthumb.source = "" } function loadReviews() { reviewslistmodel.getReviews(list.currentItem.pkgname) } function hideScreenshot() { screenshotview.fadeOut() } Rectangle { id: screenshotview width: parent.width height: parent.height anchors.left: detailsview.left opacity: 0.0 color: activePalette.window Behavior on opacity { NumberAnimation { duration: 300 } } function loadScreenshot(url) { screenshotfullimg.source = url fadeIn() } function fadeIn() { opacity = 1.0 } function fadeOut() { opacity = 0.0 } Rectangle { id: screenshotframe anchors.fill: parent anchors.margins: 10 color: activePalette.base radius: 5 Rectangle { anchors.left: parent.left anchors.right: parent.right height: 150 radius: parent.radius gradient: Gradient { GradientStop { position: 0.0; color: "#B2CFE7" } GradientStop { position: 1.0; color: "white" } } Image { anchors.top: parent.top anchors.right: parent.right source: "file:///usr/share/software-center/images/clouds.png" asynchronous: true } } Text { id: screenshottitle anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: 15 height: 25 font.pointSize: 14 font.bold: true text: list.currentItem != null ? "Screenshot for " + list.currentItem.appname : "" } Image { id: screenshotfullimg anchors.top: screenshottitle.bottom anchors.right: parent.right anchors.margins: 20 width: parent.width - 50 sourceSize.width: width asynchronous: true MouseArea { anchors.fill: parent onClicked: { screenshotview.fadeOut() } } } Button { id: screenshotbackbtn text: qsTr("Done") anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 15 onClicked: { screenshotview.fadeOut() } } } } } software-center-13.10/softwarecenter/ui/qml/star-full.svg0000664000202700020270000000560612151440100023740 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/softwarecenter/ui/qml/CategoriesView.qml0000664000202700020270000000522512151440100024736 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Michael Vogt * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { id: catview signal categoryChanged(string catname) clip: true SystemPalette { id: activePalette colorGroup: SystemPalette.Active } Rectangle { color: activePalette.base anchors.fill: parent } CloudsHeader { anchors.top: parent.top anchors.left: parent. left anchors.right: parent.right } ScrollBar { id: scrollbar width: 6 height: parent.height orientation: Qt.Vertical anchors.right: parent.right position: catgrid.visibleArea.yPosition pageSize: catgrid.visibleArea.heightRatio } Text { id: depheader anchors.top: parent.top anchors.left: parent.left anchors.margins: 24 text: qsTr("Departments") font.pointSize: 18 font.bold: true } GridView { id: catgrid anchors.top: depheader.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: scrollbar.left anchors.margins: 10 focus: true cellWidth: 160 cellHeight: 100 model: categoriesmodel delegate: Item { property string catname: _name property string caticon: _iconname width: catgrid.cellWidth height: catgrid.cellHeight Image { id: caticonimg source: caticon anchors.horizontalCenter: parent.horizontalCenter } Text { text: catname anchors.top: caticonimg.bottom anchors.horizontalCenter: parent.horizontalCenter } MouseArea { anchors.fill: parent onClicked: { catgrid.currentIndex = index catview.categoryChanged(catname) } } } } } software-center-13.10/softwarecenter/ui/qml/BreadCrumbs.qml0000664000202700020270000000637712151440100024220 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 Item { id: breadcrumbs property int animationDuration: 150 property alias model: crumbslist.model property alias count: crumbslist.count function addCrumb(text, view, key) { crumbslist.model.append({ label: text, view: view, key: key }) } function removeCrumb() { if (crumbslist.model.count > 1) { crumbslist.model.remove(crumbslist.model.count - 1) } } signal crumbClicked(int index) SystemPalette { id: activePalette } ListView { id: crumbslist interactive: false anchors.fill: parent anchors.margins: 10 model: ListModel {} orientation: ListView.Horizontal delegate: Rectangle { id: delegate property real margins: 10 property real fullWidth: text.paintedWidth + 2 * margins width: fullWidth height: crumbslist.height border.width: 1 border.color: activePalette.shadow color: mousearea.containsMouse && !mousearea.pressed ? activePalette.light : activePalette.button clip: true Text { id: text anchors.verticalCenter: parent.verticalCenter x: parent.margins color: activePalette.buttonText text: label } MouseArea { id: mousearea anchors.fill: parent hoverEnabled: true onClicked: { while ((crumbslist.model.count - 1) > index) { breadcrumbs.removeCrumb() } breadcrumbs.crumbClicked(index) } } ListView.onAdd: SequentialAnimation { PropertyAction { target: delegate; property: "ListView.delayRemove"; value: true } PropertyAction { target: delegate; property: "width"; value: 0 } NumberAnimation { target: delegate; property: "width"; to: fullWidth; duration: breadcrumbs.animationDuration } PropertyAction { target: delegate; property: "ListView.delayRemove"; value: false } } ListView.onRemove: SequentialAnimation { PropertyAction { target: delegate; property: "ListView.delayRemove"; value: true } NumberAnimation { target: delegate; property: "width"; to: 0; duration: breadcrumbs.animationDuration } PropertyAction { target: delegate; property: "ListView.delayRemove"; value: false } } } } } software-center-13.10/softwarecenter/ui/qml/star-large-empty.png0000664000202700020270000000215512151440100025205 0ustar dobeydobey00000000000000‰PNG  IHDR szzôsBIT|dˆ pHYsããæß§tEXtSoftwarewww.inkscape.org›î<êIDATX…½•ÏoEÇŸwwvlï¬×ö®)”Ö”µ‚K*QµIý#qH8©¨åÀ8  •$$@pÄ&!ÅI\'Hi*PQ•4¡M{wýcÄøÇ®9¥ Ž7Mãw›÷f¾ß4ïÍØêõ:XŸ®$Çê†Áž9úȪ†Í*ÀLü‡£=< °r÷N°·ÿé[Vt(KîàäÈ»‚à>$îC<Ï¿mUÇÀD,ê$ m­9Â÷MĢΖ¸\¨Ç+vn­ÝnO§àöŒ¶ €#d˜¢î¥( 8Žn @2?ïõJ'ó^x2™ˆŸ?pBø–e™Æ«V«r­Z½Y*mþR¯×—žyö\©)Àøwߺx—ðÆøCÓ"EÓ"EQ"M3ÆXÄØŽfÇ•ÿ¯Ðk5(W*•rùo¥V«*†a¨†®«5]ÏVÊåß4­ð•íÊlâg¿ÿè©ícÕŠ0 n/ÞºJÕªÕ·Ö׋k-u€bQ[Õuýu*ŽÌËræ M+¬¶Ê¼PÈßQäÌ«pdá^Î%§}¾öOÁí?Hó|.û§¢È/÷{ S0›˜ê‘|mŸ{<ÞNS…}„ª*7UE „#ó[¹c˜LÄOIRÛ^¯øØƒ4W”̪ª¼ ÷_Ûžoú$gâOŠ’ïKQ”Ž=sYÎüžUå ÁÞë5Ó‡èòô]¢(}#ùÚŽïÇ<“IßȪÊó¡¾Íê¦Ãìíÿ5N½V*mV­š—J›•Lzí3ó]bý˜ÅÈ*Æv–Åì® ½+¶ÛŸ hÚª?PvlÜ2˲Xv¿§X`hfß Ãø-LÄ¢˲‡÷ €{d"åö àtrA'GD³ºaN§®¦Ó©Ã0Lœ×îp8O›ÕM?zŒñS5€|>·”˪_çó¹‹`ÛÜØøÀã_p»=;î!v»ý Ä÷À²Øß˜+•Jš"g¾/rïE‡ïn+½?uiü³¢VøDòµ9ò/-ŒMGÑ€A÷›G×õz&³6WÔ´áÈt³ý}CËp!™ˆºxaÌ×Ö~š¦ibУf>¦=@Q´ËeÿZ¾=¶–Z ™™o@(2™J­–—?ÌfÕEe#fûMÿ‚¹ËÓó B ëÅ‹}Céÿ2nñÉñÂóïèzíD÷ÙpO³=ÿSk¤6¥øéIEND®B`‚software-center-13.10/softwarecenter/ui/qml/sc.qmlproject0000664000202700020270000000074212151440100024011 0ustar dobeydobey00000000000000/* File generated by QtCreator */ import QmlProject 1.1 Project { /* Include .qml, .js, and image files from current directory and subdirectories */ QmlFiles { directory: "." } JavaScriptFiles { directory: "." } ImageFiles { directory: "." } /* this requires qtcreator 2.2 */ Files { filter: "*.py" } /* List of plugin directories passed to QML runtime */ // importPaths: [ "../exampleplugin" ] } software-center-13.10/softwarecenter/ui/qml/dummydata/0000755000202700020270000000000012224614355023302 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/ui/qml/dummydata/pkglistmodel.qml0000664000202700020270000000524512151440100026503 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 ListModel { ListElement { _appname: "Gestionnaire de Mises à Jour" _pkgname: "update-manager" _icon: "/usr/share/icons/Humanity/categories/32/applications-other.svg" _summary: "Voir et installer les mises à jour disponibles" _installed: true _description: "This is the GNOME apt update manager. It checks for updates and lets the user choose which to install." _ratings_total: 4.12 _ratings_average: 17 _installremoveprogress: -1 } ListElement { _appname: "Unity 2D" _pkgname: "unity-2d" _icon: "/usr/share/icons/Humanity/categories/32/applications-other.svg" _summary: "Unity interface for non-accelerated graphics cards" _installed: true _description: "The Unity 2D interface installs a fully usable 2D session and provides the common configuration files and defaults. Installing this package will offer a session called Unity 2D in your login manager. Unity 2D is designed to run smoothly without any graphics acceleration." _ratings_total: 4.68 _ratings_average: 25 _installremoveprogress: -1 } ListElement { _appname: "Neverball" _pkgname: "neverball" _icon: "file:///usr/share/app-install/icons/neverball.xpm" _summary: "Un jeu d'arcade 3D avec une balle" _installed: false _description: "Dans la grande tradition de Marble Madness et Super Monkey Ball, Neverball vous fait guider une boule roulant à travers des territoires dangereux. Balancez-vous sur des ponts étroits, naviguez dans des labyrinthes, roulez sur des plate-formes en mouvement et évitez des pousseurs et des glisseurs pour arriver au but. Courrez contre la montre pour collecter les pièces pour obtenir des boules supplémentaires." _ratings_total: 4.53 _ratings_average: 15 _installremoveprogress: -1 } function setCategory(category) { console.log("setting category to", category) } } software-center-13.10/softwarecenter/ui/qml/dummydata/reviewslistmodel.qml0000664000202700020270000000171112151440100027400 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 ListModel { // ListElement { // _summary: // _review_text: // _rating: // _date_created: // _reviewer_displayname: // } function refreshReviewStats() { console.log("reviewslistmodel.refreshReviewStats()") } } software-center-13.10/softwarecenter/ui/qml/dummydata/categoriesmodel.qml0000664000202700020270000000167012151440100027151 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 ListModel { ListElement { _name: "Accessories"; _iconname: "/usr/share/icons/Humanity/categories/48/applications-accessories.svg" } ListElement { _name: "Games"; _iconname: "/usr/share/icons/Humanity/categories/48/applications-games.svg" } } software-center-13.10/softwarecenter/ui/qml/star-small-half.png0000664000202700020270000000110112151440100024765 0ustar dobeydobey00000000000000‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYsqq?€wtIMEÛ:ø·"ÁIDAT8Ëcøÿÿ?.ü`Š]ðé“Ç"ñ©abÀÄxòxyù²¶nZLjK NîO² åå±ãáᵑ #Ù1Þ$FFFFFF^^¾x’ ¸ÑanÇ'Âíãsqs¹ž<~Ä ›ZÆ}ÉŠ¼Ê"ü¢<*lœlòÌ,L²¬ì,<‚\Z Ï”»¾ÿvýÏï?—ÿýÿ÷ôïŸ?¿~ýróýû÷ÇXõ¥ç+èI3œœ\š œ š0¾°++Û¦÷ÞF¿yòa‰àÓ§[ß¿ÍdÑíç“ë/¢Þ=û¸‡XÍŸ?}ÜõúÕË(g7Ï_L ->Þ»ð$øíÓ;‰±ùÅ‹çÁ¶ΟPbÁ¤óò§Ç×_üýý§æ¿ÿ2¼~õ²ØÞÉõ Öh’ä×ffeÆi33377ÎtÀÉË¡IÈ lìì8 `acV±ÿüóýÕƒ·3Þ¿7ãÏŸ?ßáj˜YqÀ̤ðïï¿ÿïž}Üóðò3W±øÝ™Úºú™/_>wùüùÓžÿþýgbf’AqrÖ|8Õn㽉6¸²îÙS'‚Ï9¹Y ù–ðINŠ™ñIEND®B`‚software-center-13.10/softwarecenter/ui/qml/NavigationBar.qml0000664000202700020270000000342712151440100024544 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { property alias searchQuery: searchbox.text property alias breadcrumbs: breadcrumbs property bool searchBoxVisible: true signal crumbClicked(int index) signal searchActivated height: searchbox.height + 2 * 10 // 10px margins SystemPalette { id: activePalette colorGroup: SystemPalette.Active } Rectangle { anchors.fill: parent color: activePalette.window } Rectangle { height: 1 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom color: activePalette.mid } // TODO: back/forward buttons BreadCrumbs { id: breadcrumbs anchors.fill: parent onCrumbClicked: parent.crumbClicked(index) } SearchBox { id: searchbox width: 160 anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.margins: 10 onActivated: parent.searchActivated() opacity: parent.searchBoxVisible ? 1.0 : 0.0 focus: parent.searchBoxVisible } } software-center-13.10/softwarecenter/ui/qml/CloudsHeader.qml0000664000202700020270000000201612151440100024353 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 Rectangle { height: 150 gradient: Gradient { GradientStop { position: 0.0; color: "#B2CFE7" } GradientStop { position: 1.0; color: "white" } } Image { anchors.top: parent.top anchors.right: parent.right source: "../../../data/images/clouds.png" asynchronous: true } } software-center-13.10/softwarecenter/ui/qml/Button.qml0000664000202700020270000000254012151440100023266 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Michael Vogt * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 Rectangle { property string text: "ButtonText" signal clicked SystemPalette { id: activePalette } Text { id: buttontxt anchors.centerIn: parent text: parent.text color: activePalette.buttonText } width: buttontxt.width + 10 height: buttontxt.height + 10 radius: 4 border.width: 1 border.color: activePalette.shadow color: mousearea.containsMouse && !mousearea.pressed ? activePalette.light : activePalette.button MouseArea { id: mousearea anchors.fill: parent hoverEnabled: true onClicked: parent.clicked() } } software-center-13.10/softwarecenter/ui/qml/ScrollBar.qml0000664000202700020270000000654212151440100023704 0ustar dobeydobey00000000000000/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor ** the names of its contributors may be used to endorse or promote ** products derived from this software without specific prior written ** permission. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** $QT_END_LICENSE$ ** ****************************************************************************/ import QtQuick 1.0 Item { id: scrollBar // The properties that define the scrollbar's state. // position and pageSize are in the range 0.0 - 1.0. They are relative to the // height of the page, i.e. a pageSize of 0.5 means that you can see 50% // of the height of the view. // orientation can be either Qt.Vertical or Qt.Horizontal property real position property real pageSize property variant orientation : Qt.Vertical // A light, semi-transparent background Rectangle { id: background anchors.fill: parent radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1) color: "white" opacity: 0.3 } // Size the bar to the required size, depending upon the orientation. Rectangle { x: orientation == Qt.Vertical ? 1 : (scrollBar.position * (scrollBar.width-2) + 1) y: orientation == Qt.Vertical ? (scrollBar.position * (scrollBar.height-2) + 1) : 1 width: orientation == Qt.Vertical ? (parent.width-2) : (scrollBar.pageSize * (scrollBar.width-2)) height: orientation == Qt.Vertical ? (scrollBar.pageSize * (scrollBar.height-2)) : (parent.height-2) radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1) color: "black" opacity: 0.7 } } software-center-13.10/softwarecenter/ui/qml/FrameSwitcher.qml0000664000202700020270000000534512151440100024564 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { id: frameSwitch property int duration: 200 property alias currentIndex: frames.currentIndex property alias count: frames.count clip: true ListModel { id: frames property int currentIndex: -1 } onWidthChanged: { for (var i = 0; i < frames.count; i++) { frames.get(i).frame.width = frameSwitch.width } if (frames.count > 0) { frames.get(0).frame.x = -frameSwitch.width * frames.currentIndex } } function pushFrame(frame) { frame.duration = frameSwitch.duration frames.append({ frame: frame }) frame.parent = frameSwitch frame.width = frame.parent.width frame.anchors.top = frame.parent.top frame.anchors.bottom = frame.parent.bottom if (frames.count == 1) { frame.x = 0 frame.focus = true frames.currentIndex = 0 } else { frame.anchors.left = frames.get(frames.count - 2).frame.right } } function currentFrame() { if (frames.count > 0) { return frames.get(frames.currentIndex).frame } else { return null } } function changeIndex(newIndex) { if (frames.count <= 1) return if (frames.currentIndex == newIndex) return if (newIndex < 0 || newIndex >= frames.count) return frameConnection.target = frames.get(0).frame frames.get(0).frame.x = -frameSwitch.width * newIndex frames.currentIndex = newIndex } function goToFrame(frame) { for (var i = 0; i < frames.count; i++) { if (frames.get(i).frame == frame) { changeIndex(i) break } } } Connections { id: frameConnection target: null onRunningChanged: { if (!frameConnection.target.running) { frameConnection.target = null currentFrame().shown() currentFrame().focus = true } } } } software-center-13.10/softwarecenter/ui/qml/sc.qml0000664000202700020270000001125712151440100022425 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * 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, see . */ import QtQuick 1.0 FocusScope { width: 800 height: 600 focus: true SystemPalette { id: activePalette colorGroup: SystemPalette.Active } NavigationBar { id: navigation anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top focus: true KeyNavigation.down: (switcher.currentFrame() == listview) ? switcher : null property string searchResults: qsTr("Search Results") property string categoryKey: "category" property string searchresultsKey: "searchresults" Binding { target: pkglistmodel property: "searchQuery" value: navigation.searchQuery } Component.onCompleted: breadcrumbs.addCrumb(qsTr("Get Software"), catview, "") onCrumbClicked: { if (index == 0 || (index == 1 && navigation.breadcrumbs.model.get(1).key == categoryKey)) { searchQuery = "" pkglistmodel.setCategory("") } switcher.goToFrame(navigation.breadcrumbs.model.get(index).view) } searchBoxVisible: switcher.currentFrame() != detailsview function doSearch() { if (searchQuery.length > 0) { var bc = navigation.breadcrumbs if (bc.count == 1 || (bc.count == 2 && bc.model.get(1).key == categoryKey)) { bc.addCrumb(searchResults, listview, searchresultsKey) } switcher.goToFrame(listview) } } onSearchQueryChanged: doSearch() onSearchActivated: doSearch() } FrameSwitcher { id: switcher anchors.left: parent.left anchors.right: parent.right anchors.top: navigation.bottom anchors.bottom: parent.bottom duration: 180 } Frame { id: catview CategoriesView { anchors.fill: parent focus: true onCategoryChanged: { pkglistmodel.setCategory(catname) navigation.breadcrumbs.addCrumb(catname, listview, navigation.categoryKey) switcher.goToFrame(listview) } } } Frame { id: listview AppListView { id: list focus: true model: pkglistmodel anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.bottom: statusframe.top KeyNavigation.up: navigation onMoreInfoClicked: { navigation.breadcrumbs.addCrumb(currentItem.appname, detailsview, "") switcher.goToFrame(detailsview) } } Rectangle { id: statusframe height: 20 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom color: activePalette.window Rectangle { height: 1 anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top color: activePalette.mid } Text { anchors.fill: parent anchors.margins: 5 verticalAlignment: Text.AlignVCenter text: qsTr("%1 items available").arg(list.count) } } } Frame { id: detailsview DetailsView { id: details anchors.fill: parent focus: true } onShown: { details.loadThumbnail() details.loadReviews() } onHidden: { details.hideScreenshot() details.unloadThumbnail() } } Component.onCompleted: { switcher.pushFrame(catview) switcher.pushFrame(listview) switcher.pushFrame(detailsview) reviewslistmodel.refreshReviewStats() } } software-center-13.10/softwarecenter/ui/qml/star-empty.svg0000664000202700020270000000560712151440100024135 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/softwarecenter/ui/qml/SearchBox.qml0000664000202700020270000000512712151440100023675 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { id: searchbox property alias text: textinput.text signal activated height: textinput.height + 7 SystemPalette { id: activePalette colorGroup: SystemPalette.Active } Rectangle { anchors.fill: parent color: activePalette.base radius: 4 border.color: parent.activeFocus ? activePalette.highlight : activePalette.mid } Image { id: searchicon source: "file:///usr/share/icons/Humanity/actions/16/edit-find.svg" asynchronous: true anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: 4 fillMode: Image.PreserveAspectFit sourceSize.height: height opacity: searchmousearea.containsMouse ? 0.8 : 1.0 MouseArea { id: searchmousearea anchors.fill: parent hoverEnabled: true onClicked: textinput.accepted() } } TextInput { id: textinput anchors.verticalCenter: parent.verticalCenter anchors.left: searchicon.right anchors.right: clearicon.left anchors.margins: 4 focus: true selectByMouse: true onAccepted: if (textinput.text.length > 0) searchbox.activated() } Image { id: clearicon source: "file:///usr/share/icons/Humanity/actions/16/edit-clear.svg" asynchronous: true anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: 4 fillMode: Image.PreserveAspectFit sourceSize.height: height visible: textinput.text.length > 0 opacity: clearmousearea.containsMouse ? 0.8 : 1.0 MouseArea { id: clearmousearea anchors.fill: parent hoverEnabled: true onClicked: textinput.text = "" } } } software-center-13.10/softwarecenter/ui/qml/Frame.qml0000664000202700020270000000224012151440100023042 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 FocusScope { signal shown signal hidden property alias duration: xAnimation.duration property alias running: xAnimation.running Behavior on x { NumberAnimation { id: xAnimation } } // Changing the 'visible' property badly messes with the active focus. // Changing the opacity preserves the focus, which is the desired behaviour. opacity: (x > -width) && (x < parent.width) ? 1.0 : 0.0 onOpacityChanged: if (opacity == 0.0) hidden() } software-center-13.10/softwarecenter/ui/qml/Stars.qml0000664000202700020270000000310712151440100023107 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * 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, see . */ import QtQuick 1.0 Row { property double ratings_average Repeater { height: parent.height model: Math.floor(ratings_average) Image { source: (height <= 16) ? "star-small-full.png" : "star-large-full.png" smooth: true height: parent.height width: height } } Image { visible: Math.floor(ratings_average) != Math.ceil(ratings_average) source: (height <= 16) ? "star-small-half.png" : "star-large-half.png" smooth: true height: parent.height width: height } Repeater { height: parent.height model: 5 - Math.ceil(ratings_average) Image { source: (height <= 16) ? "star-small-empty.png" : "star-large-empty.png" smooth: true height: parent.height width: height } } } software-center-13.10/softwarecenter/ui/qml/star-small-empty.png0000664000202700020270000000110112151440100025211 0ustar dobeydobey00000000000000‰PNG  IHDRóÿasBIT|dˆ pHYsqq?€wtEXtSoftwarewww.inkscape.org›î<¾IDAT8•RÏKAýff“ ´Å¦Ri•Ò†`[š’øÈb/oöÐÿÉ“—´4b -!¨I¶Isè!xÛƒù!’ug«9¸»ÓSÊF\5|0ßã½÷½a !À¯š ]s'8¿ðz˃}Õ Pö‘1õCqû3Û ñ£º"SšVö6|?¶¢°u„ „€1um,ƒê~9-Ë47œe*/Õ*{onâ¢Â'öàá£EJ•X xF0ž"’ô*–_z‰ƒÁe˾²¹Â=qlûÏÅÅßCÃ0PC¯æ£O&5¿ˆ·U·ÓÎã~ÿlÕ4Ï¿Ž+æÜ,F ! ¼[ŠL<ŽæU5’»[ `qóûéio%•Ér ~—3;ísóÛ}6w»-•Éräý‰åÝR|f&Ö„Ü(v~ÅS™ìáyFU$üÄ„ T™ób#ÁPèÅ]W†Bqï,]ÛžmÛXß`L]“$) iÚ7Æø¹ëº‚s³Ôë¶—ÉÙDrv£×ëä,‹—\טà§#‘„ÿ»Y× u½²ìżÝЫÚÏzí‹û•ÄŸFOaIEND®B`‚software-center-13.10/softwarecenter/ui/qml/star-large-half.png0000664000202700020270000000215712151440100024763 0ustar dobeydobey00000000000000‰PNG  IHDR szzôsRGB®ÎébKGDÿÿÿ ½§“ pHYsããæß§tIMEÛ& *âÏ©ïIDATXý–[lÛTÇOìã»'µÓ Òm]Z!c)“˜ºŒ¶iKH ±‰‚X%.&qQⲩâ !ñÀ#Ïh»ÐQº®ÚĶ®¨camÚ¨ IÓÜ'iÒªY.¶ybt#ÙˆÓäH~ðù>ŸÿOßù.š¦½Ï­¯Z/O\ø¢’3 sMÛa³ÚÄ·Ñrtì—Ÿ[ôž£Àha?MïÉdÖ§5Mó=ÿ‹™bþ†ñ£Ûu üG< q(@ˆ Dˆ£"m$Š% Œ,É BÍß”´)…Èær¹lö¶\(äeUUãª¢Ä Š’Èe³³étêG(n1Ÿß±¯yïÆ²Ú¬…Bhqš¦ mªª‚¿¼ ‡‘üíü)i5j¼VWÓ!EQÞEìîÉå¹è{‰P*T+ñTj% Ǥ·Ú=S<ñåôOÙH¿Húª-¾’L,ƤèëŽvç™»Êp×ñ#K³‘×$b¡Zâñ¸ì‘¤è‘ý]ãEû€}èæ¥å¹H_Ä+{6[\–¥yY–Ž´;{&ïÛˆìC®«ÁyéÕðBln³Äc1É—c/w8ŸýýuÂÖ“®ëAt8è‘Ü•ŠKRt&.Ç^êèê½YV+~êë?o,Ý ¿³–\ÏëÏdÖsR4òfgwY@ÐXŘ^‚ qœÀ[t#ÊHîD1Twø$A>®€dˆí•æŽÛt`¬Bؤ àb¿'|k¥†o;wúS6'0œÈ¥G­ –fÂ×£Ñ𔪪%h†±RÝV6ÅO^¼±,%}îßO,N¡à²Ãïó­¬$ýÅ#€’$÷•ýGD²Äîn-¹žy¤3±@ò“¶ïæ—7˜>ýõüÙVÓ©“¢Åz€¢(ö®D$ˆ–²p »PÈ)ZÀ¹”§N´»Æ)âßÝ{Àxeb|ô9#ÇZê­m(ŠƒXsÙW€¢’/î»âô¹‚­Ã®±%]{gÏH8l÷û¼Ÿ'q/[Êß iZQß=9‰“ØTJZ;¾÷[wTOŒŽœm`9î#E)ØÏ8÷óù€sÀŠI¼þÕIEND®B`‚software-center-13.10/softwarecenter/ui/qml/reviewslist.py0000664000202700020270000000721512151440100024236 0ustar dobeydobey00000000000000# # Copyright (C) 2011 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 GLib from datetime import datetime from PyQt4.QtCore import QAbstractListModel, QModelIndex, pyqtSignal, pyqtSlot from softwarecenter.db.database import Application from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.backend.reviews import get_review_loader class ReviewsListModel(QAbstractListModel): # should match the softwarecenter.backend.reviews.Review attributes COLUMNS = ('_summary', '_review_text', '_rating', '_date_created', '_reviewer_displayname', ) def __init__(self, parent=None): super(ReviewsListModel, self).__init__() self._reviews = [] roles = dict(enumerate(ReviewsListModel.COLUMNS)) self.setRoleNames(roles) # FIXME: make this async self.cache = get_pkg_info() self.reviews = get_review_loader(self.cache) self.reviews.connect( "refresh-review-stats-finished", self._on_refresh_review_stats_finished) self.reviews.connect( "get-reviews-finished", self._on_reviews_ready_callback) # QAbstractListModel code def rowCount(self, parent=QModelIndex()): return len(self._reviews) def data(self, index, role): if not index.isValid(): return None review = self._reviews[index.row()] role = self.COLUMNS[role] if role == '_date_created': when = datetime.strptime(getattr(review, role[1:]), '%Y-%m-%d %H:%M:%S') return when.strftime('%Y-%m-%d') return unicode(getattr(review, role[1:])) # helper def clear(self): self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1) self._reviews = [] self.endRemoveRows() def _on_reviews_ready_callback(self, loader, app, reviews): self.beginInsertRows(QModelIndex(), self.rowCount(), # first self.rowCount() + len(reviews) - 1) # last self._reviews += reviews self.endInsertRows() # getReviews interface (for qml) @pyqtSlot(str) def getReviews(self, pkgname, page=1): # pkgname is a QString, so we need to convert it to a old-fashioned str pkgname = unicode(pkgname).encode("utf-8") app = Application("", pkgname) # support pagination by not cleaning _reviews for subsequent pages if page == 1: self.clear() # load in the eventloop to ensure that animations are not delayed GLib.timeout_add( 10, self.reviews.get_reviews, app, page) # refresh review-stats (for qml) def _on_refresh_review_stats_finished(self, loader, stats): self.reviewStatsChanged.emit() @pyqtSlot() def refreshReviewStats(self): self.reviews.refresh_review_stats() # FIXME: how is this signal actually used in the qml JS? # signals reviewStatsChanged = pyqtSignal() software-center-13.10/softwarecenter/ui/qml/ProgressBar.qml0000664000202700020270000000234212151440100024244 0ustar dobeydobey00000000000000/* * Copyright 2011 Canonical Ltd. * * Authors: * Olivier Tilloy * * 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, see . */ import QtQuick 1.0 Rectangle { property int progress: 0 // between 0 and 100 SystemPalette { id: activePalette colorGroup: SystemPalette.Active } color: activePalette.base Rectangle { color: activePalette.highlight anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: progress / 100 * parent.width } Rectangle { anchors.fill: parent color: "transparent" border.width: 1 border.color: activePalette.shadow } } software-center-13.10/softwarecenter/ui/qml/pkglist.py0000664000202700020270000001606512151440100023336 0ustar dobeydobey00000000000000# # Copyright (C) 2011 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 import os from PyQt4 import QtCore from PyQt4.QtCore import QAbstractListModel, QModelIndex, pyqtSlot from softwarecenter.db.database import StoreDatabase, Application from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.db.categories import CategoriesParser from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.backend.reviews import get_review_loader class PkgListModel(QAbstractListModel): COLUMNS = ('_appname', '_pkgname', '_icon', '_summary', '_installed', '_description', '_ratings_total', '_ratings_average', '_installremoveprogress') def __init__(self, parent=None): super(PkgListModel, self).__init__() self._docs = [] roles = dict(enumerate(PkgListModel.COLUMNS)) self.setRoleNames(roles) self._query = "" self._category = "" pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") self.cache = get_pkg_info() self.db = StoreDatabase(pathname, self.cache) self.db.open(use_axi=False) self.backend = get_install_backend() self.backend.connect("transaction-progress-changed", self._on_backend_transaction_progress_changed) self.reviews = get_review_loader(self.cache) # FIXME: get this from a parent self._catparser = CategoriesParser(self.db) self._categories = self._catparser.parse_applications_menu( '/usr/share/app-install') # QAbstractListModel code def rowCount(self, parent=QModelIndex()): return len(self._docs) def data(self, index, role): if not index.isValid(): return None doc = self._docs[index.row()] role = self.COLUMNS[role] pkgname = unicode(self.db.get_pkgname(doc), "utf8", "ignore") appname = unicode(self.db.get_appname(doc), "utf8", "ignore") if role == "_pkgname": return pkgname elif role == "_appname": return appname elif role == "_summary": return unicode(self.db.get_summary(doc)) elif role == "_installed": if not pkgname in self.cache: return False return self.cache[pkgname].is_installed elif role == "_description": if not pkgname in self.cache: return "" return self.cache[pkgname].description elif role == "_icon": iconname = self.db.get_iconname(doc) return self._findIcon(iconname) elif role == "_ratings_average": stats = self.reviews.get_review_stats(Application(appname, pkgname)) if stats: return stats.ratings_average return 0 elif role == "_ratings_total": stats = self.reviews.get_review_stats(Application(appname, pkgname)) if stats: return stats.ratings_total return 0 elif role == "_installremoveprogress": if pkgname in self.backend.pending_transactions: return self.backend.pending_transactions[pkgname].progress return -1 return None # helper def _on_backend_transaction_progress_changed(self, backend, pkgname, progress): column = self.COLUMNS.index("_installremoveprogress") # FIXME: instead of the entire model, just find the row that changed top = self.createIndex(0, column) bottom = self.createIndex(self.rowCount() - 1, column) self.dataChanged.emit(top, bottom) def _findIcon(self, iconname): path = "/usr/share/icons/Humanity/categories/32/applications-other.svg" for ext in ["svg", "png", ".xpm"]: p = "/usr/share/app-install/icons/%s" % iconname if os.path.exists(p + ext): path = "file://%s" % p + ext break return path def clear(self): if self._docs == []: return self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1) self._docs = [] self.endRemoveRows() def _runQuery(self, querystr): self.clear() docs = self.db.get_docs_from_query( str(querystr), start=0, end=500, category=self._category) self.beginInsertRows(QModelIndex(), 0, len(docs) - 1) self._docs = docs self.endInsertRows() # install/remove interface (for qml) @pyqtSlot(str) def installPackage(self, pkgname): appname = "" iconname = "" app = Application(appname, pkgname) self.backend.install(app, iconname) @pyqtSlot(str) def removePackage(self, pkgname): appname = "" iconname = "" app = Application(appname, pkgname) self.backend.remove(app, iconname) # searchQuery property (for qml ) def getSearchQuery(self): return self._query def setSearchQuery(self, query): self._query = query self._runQuery(query) searchQueryChanged = QtCore.pyqtSignal() searchQuery = QtCore.pyqtProperty(unicode, getSearchQuery, setSearchQuery, notify=searchQueryChanged) # allow to refine searches for specific categories @pyqtSlot(str) def setCategory(self, catname): # empty category resets it if not catname: self._category = None else: # search for the category for cat in self._categories: if cat.name == catname: self._category = cat break else: raise Exception("Can not find category '%s'" % catname) # and trigger a query self._runQuery(self._query) if __name__ == "__main__": from PyQt4.QtGui import QApplication from PyQt4.QtDeclarative import QDeclarativeView import sys app = QApplication(sys.argv) app.cache = get_pkg_info() app.cache.open() view = QDeclarativeView() model = PkgListModel() rc = view.rootContext() rc.setContextProperty('pkglistmodel', model) # load the main QML file into the view qmlpath = os.path.join(os.path.dirname(__file__), "AppListView.qml") view.setSource(qmlpath) # show it view.show() sys.exit(app.exec_()) software-center-13.10/softwarecenter/ui/qml/categoriesmodel.py0000664000202700020270000000627512151440100025031 0ustar dobeydobey00000000000000# # Copyright (C) 2011 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 import os # this is a bit silly, but *something* imports gtk2 symbols, so if we # force gtk3 here it crashes - the only reason we need this at all is to # get the icon path import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk from PyQt4.QtCore import QAbstractListModel, QModelIndex #from PyQt4.QtGui import QIcon from softwarecenter.db.categories import CategoriesParser from softwarecenter.db.database import StoreDatabase from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.paths import XAPIAN_BASE_PATH class CategoriesModel(QAbstractListModel): # should match the softwarecenter.backend.reviews.Review attributes COLUMNS = ('_name', '_iconname', ) def __init__(self, parent=None): super(CategoriesModel, self).__init__() self._categories = [] roles = dict(enumerate(CategoriesModel.COLUMNS)) self.setRoleNames(roles) pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") # FIXME: move this into app cache = get_pkg_info() db = StoreDatabase(pathname, cache) db.open() # /FIXME self.catparser = CategoriesParser(db) self._categories = self.catparser.parse_applications_menu() # QAbstractListModel code def rowCount(self, parent=QModelIndex()): return len(self._categories) def data(self, index, role): if not index.isValid(): return None cat = self._categories[index.row()] role = self.COLUMNS[role] if role == "_name": return unicode(cat.name, "utf8", "ignore") elif role == "_iconname": # funny, but it appears like Qt does not have something # to lookup the icon path in QIcon icons = Gtk.IconTheme.get_default() info = icons.lookup_icon(cat.iconname, 48, 0) if info: return info.get_filename() return "" if __name__ == "__main__": from PyQt4.QtGui import QApplication from PyQt4.QtDeclarative import QDeclarativeView import sys app = QApplication(sys.argv) app.cache = get_pkg_info() app.cache.open() view = QDeclarativeView() categoriesmodel = CategoriesModel() rc = view.rootContext() rc.setContextProperty('categoriesmodel', categoriesmodel) # load the main QML file into the view qmlpath = os.path.join(os.path.dirname(__file__), "CategoriesView.qml") view.setSource(qmlpath) # show it view.show() sys.exit(app.exec_()) software-center-13.10/softwarecenter/hw.py0000664000202700020270000001326312151440100021066 0ustar dobeydobey00000000000000# Copyright (C) 2012 Canonical # -*- coding: utf-8 -*- # # 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 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 = { # 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'), # "special" private tag extension 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 ' 'are currently connected'), 'hardware::webcam': _('This software requires a video camera, but none ' 'are currently connected'), 'hardware::input:mouse': _('This software requires a mouse, ' 'but none is currently set up.'), 'hardware::input:joystick': _('This software requires a joystick, ' 'but none are currently connected.'), 'hardware::input:touchscreen': _('This software requires a touchscreen, ' 'but the computer does not have one.'), 'hardware::gps': _('This software requires a GPS, ' 'but the computer does not have one.'), '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 ' 'currently set up.'), 'hardware::storage:cd': _('This software requires a CD drive, but none ' 'are currently connected.'), '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 ' 'are currently connected.'), 'hardware::storage:dvd-writer': _('This software requires a DVD burner, ' 'but none are currently connected.'), '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 ' '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": 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 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 software-center-13.10/softwarecenter/i18n.py0000664000202700020270000000536012151440100021226 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 import locale import logging import os LOG = logging.getLogger(__name__) # fallback if locale parsing fails FALLBACK = "en" # those languages need the full language-code, the other ones # can be abbreved FULL = ["pt_BR", "zh_CN", "zh_TW"] def init_locale(): try: locale.setlocale(locale.LC_ALL, "") # we need this for bug #846038, with en_NG setlocale() is fine # but the next getlocale() will crash (fun!) locale.getlocale() except: LOG.exception("setlocale failed, resetting to C") locale.setlocale(locale.LC_ALL, "C") def get_languages(): """Helper that returns the split up languages""" langs = [] if "LANGUAGE" in os.environ: langs = os.environ["LANGUAGE"].split(":") for lang in langs[:]: if "_" in lang and not lang in FULL: langs.remove(lang) # ensure the data from get_language() part of the result too # (see LP: #979013) from_get_language = get_language() if not from_get_language in langs: langs.insert(0, from_get_language) return langs def get_language(): """Helper that returns the current language """ try: language = locale.getdefaultlocale( ('LANGUAGE', 'LANG', 'LC_CTYPE', 'LC_ALL'))[0] except Exception as e: LOG.warn("Failed to get language: '%s'" % e) language = "C" # use fallback if we can't determine the language if language is None or language == "C": return FALLBACK if language in FULL: return language return language.split("_")[0] def langcode_to_name(langcode): from lxml import etree from gettext import dgettext for iso in ["iso_639_3", "iso_639"]: path = os.path.join("/usr/share/xml/iso-codes/", iso + ".xml") if os.path.exists(path): root = etree.parse(path) xpath = ".//%s_entry[@part1_code='%s']" % (iso, langcode) match = root.find(xpath) if match is not None: return dgettext(iso, match.attrib["name"]) return langcode software-center-13.10/softwarecenter/db/0000755000202700020270000000000012224614354020473 5ustar dobeydobey00000000000000software-center-13.10/softwarecenter/db/enquire.py0000664000202700020270000003417012151440100022505 0ustar dobeydobey00000000000000# Copyright (C) 2011 Canonical # # Authors: # Matthew McGowan # 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 import threading import xapian from gi.repository import GObject, GLib from softwarecenter.enums import (SortMethods, XapianValues, NonAppVisibility, DEFAULT_SEARCH_LIMIT) from softwarecenter.db.database import ( SearchQuery, LocaleSorter, TopRatedSorter) from softwarecenter.distro import get_distro from softwarecenter.utils import ExecutionTime LOG = logging.getLogger(__name__) class AppEnquire(GObject.GObject): """ A interface to enquire data from a xapian database. It can combined with any xapian query and with a generic filter function (that can filter on data not available in xapian) """ # signal emitted __gsignals__ = {"query-complete": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()), } def __init__(self, cache, db): """ Init a AppEnquire object :Parameters: - `cache`: apt cache (for stuff like the overlay icon) - `db`: a xapian.Database that contains the applications """ GObject.GObject.__init__(self) self.cache = cache self.db = db self.distro = get_distro() self.search_query = SearchQuery(None) self.nonblocking_load = True self.sortmode = SortMethods.UNSORTED self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE self.limit = DEFAULT_SEARCH_LIMIT self.filter = None self.exact = False self.nr_pkgs = 0 self.nr_apps = 0 self._matches = [] self.match_docids = set() def __len__(self): return len(self._matches) @property def matches(self): """ return the list of matches as xapian.MSetItem """ return self._matches def _threaded_perform_search(self): self._perform_search_complete = False # 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): thread_name = 'ThreadedQuery-%s' % i if not thread_name in names: break # create and start it t = threading.Thread( target=self._blocking_perform_search, name=thread_name) t.start() # don't block the UI while the thread is running context = GLib.main_context_default() while not self._perform_search_complete: time.sleep(0.02) # 50 fps while context.pending(): context.iteration() t.join() # call the query-complete callback self.emit("query-complete") 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, q, xapian.Query("ATapplication"))) try: tmp_matches = enquire.get_mset(0, len(self.db), None, xfilter) except Exception: LOG.exception("_get_estimate_nr_apps_and_nr_pkgs failed") return (0, 0) nr_apps = tmp_matches.get_matches_estimated() 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 it's *not* # allowed to touch gtk, otherwise hell breaks loose # 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 # so concurrent queries don't result in an inconsistent database # an alternative would be to serialise queries enquire = xapian.Enquire(self.db.xapiandb) if self.filter and self.filter.required: xfilter = self.filter else: xfilter = None # go over the queries self.nr_apps, self.nr_pkgs = 0, 0 _matches = self._matches match_docids = self.match_docids for q in self.search_query: LOG.debug("initial query: '%s'" % q) # for searches we may want to disable show/hide terms = [term for term in q] exact_pkgname_query = (len(terms) == 1 and terms[0].startswith("XP")) # see if we should do a app query and skip the pkg query # see bug #891613 and #1043159 if exact_pkgname_query: with ExecutionTime("de-duplication"): q_app = xapian.Query(terms[0].replace("XP", "AP")) nr_apps, nr_pkgs = self._get_estimate_nr_apps_and_nr_pkgs( enquire, q_app, xfilter) if nr_apps == 1: q = q_app # this is a app query now exact_pkgname_query = False with ExecutionTime("calculate nr_apps and nr_pkgs: "): 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, xapian.Query("ATapplication"), q) LOG.debug("nearly completely filtered query: '%s'" % q) # 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, q, xapian.Query("XD"))) # sort results # cataloged time - what's new category if self.sortmode == SortMethods.BY_CATALOGED_TIME: sorter = xapian.MultiValueKeyMaker() if (self.db._axi_values and "catalogedtime" in self.db._axi_values): sorter.add_value( self.db._axi_values["catalogedtime"]) sorter.add_value(XapianValues.DB_CATALOGED_TIME) enquire.set_sort_by_key(sorter, reverse=True) elif self.sortmode == SortMethods.BY_TOP_RATED: from softwarecenter.backend.reviews import get_review_loader review_loader = get_review_loader(self.cache, self.db) sorter = TopRatedSorter(self.db, review_loader) enquire.set_sort_by_key(sorter, reverse=True) # search ranking - when searching elif self.sortmode == SortMethods.BY_SEARCH_RANKING: #enquire.set_sort_by_value(XapianValues.POPCON) # use the default enquire.set_sort_by_relevance() pass # display name - all categories / channels 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? # fallback to pkgname - if needed? 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) else: matches = enquire.get_mset(0, self.limit, None, xfilter) LOG.debug("found ~%i matches" % matches.get_matches_estimated()) #~ except: #~ logging.exception("get_mset") #~ matches = [] # 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 self.nr_pkgs -= 2 # add matches, but don't duplicate docids with ExecutionTime("append new matches to existing ones:"): for match in matches: if not match.docid in match_docids: _matches.append(match) match_docids.add(match.docid) # if we have no results, try forcing pkgs to be displayed # if not NonAppVisibility.NEVER_VISIBLE is set if (not _matches and self.nonapps_visible not in (NonAppVisibility.ALWAYS_VISIBLE, NonAppVisibility.NEVER_VISIBLE)): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self._blocking_perform_search() # wake up the UI if run in a search thread self._perform_search_complete = True def get_estimated_matches_count(self, query): with ExecutionTime("estimate item count for query: '%s'" % query): enquire = xapian.Enquire(self.db.xapiandb) enquire.set_query(query) # no performance difference between the two #tmp_matches = enquire.get_mset(0, 1, None, None) #nr_pkgs = tmp_matches.get_matches_estimated() tmp_matches = enquire.get_mset(0, len(self.db), None, None) nr_pkgs = len(tmp_matches) return nr_pkgs def set_query(self, search_query, limit=DEFAULT_SEARCH_LIMIT, sortmode=SortMethods.UNSORTED, filter=None, exact=False, nonapps_visible=NonAppVisibility.MAYBE_VISIBLE, nonblocking_load=True, persistent_duplicate_filter=False): """ Set a new query :Parameters: - `search_query`: a single search as a xapian.Query or a list - `limit`: how many items the search should return (0 == unlimited) - `sortmode`: sort the result - `filter`: filter functions that can be used to filter the data further. A python function that gets a pkgname - `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 /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. - 'persistent_duplicate_filter': if True allows filtering of duplicate matches across multiple queries """ self.search_query = SearchQuery(search_query) self.limit = limit self.sortmode = sortmode # make a copy for good measure if filter: self.filter = filter.copy() else: self.filter = None self.exact = exact self.nonblocking_load = nonblocking_load self.nonapps_visible = nonapps_visible # no search query means "all" if not search_query: self.search_query = SearchQuery(xapian.Query("")) self.sortmode = SortMethods.BY_ALPHABET self.limit = 0 # flush old query matches self._matches = [] if not persistent_duplicate_filter: self.match_docids = set() # we support single and list search_queries, # if list, we append them one by one with ExecutionTime("populate model from query: '%s' (threaded: %s)" % ( " ; ".join([str(q) for q in self.search_query]), self.nonblocking_load), with_traceback=False): if self.nonblocking_load: self._threaded_perform_search() else: self._blocking_perform_search() return True # def get_pkgnames(self): # xdb = self.db.xapiandb # pkgnames = [] # for m in self.matches: # doc = xdb.get_document(m.docid) # pkgnames.append(doc.get_value(XapianValues.PKGNAME) or # doc.get_data()) # return pkgnames # def get_applications(self): # apps = [] # for pkgname in self.get_pkgnames(): # apps.append(Application(pkgname=pkgname)) # return apps def get_docids(self): """ get the docids of the current matches """ xdb = self.db.xapiandb return [xdb.get_document(m.docid).get_docid() for m in self._matches] def get_documents(self): """ get the xapian.Document objects of the current matches """ xdb = self.db.xapiandb return [xdb.get_document(m.docid) for m in self._matches] software-center-13.10/softwarecenter/db/dataprovider.py0000664000202700020270000001447312151440100023525 0ustar dobeydobey00000000000000# Copyright (C) 2012 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 import dbus import dbus.service import logging import time from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) from gi.repository import GLib from .categories import ( CategoriesParser, get_category_by_name, ) from .database import StoreDatabase from .application import Application from softwarecenter.backend.reviews import get_review_loader from softwarecenter.db.utils import run_software_center_agent # To test, run with e.g. """ dbus-send --session --type=method_call \ --dest=com.ubuntu.SoftwareCenterDataProvider --print-reply \ /com/ubuntu/SoftwareCenterDataProvider \ com.ubuntu.SoftwareCenterDataProvider.GetAppDetails string:"" string:"apt" """ LOG = logging.getLogger(__file__) DBUS_BUS_NAME = 'com.ubuntu.SoftwareCenterDataProvider' DBUS_DATA_PROVIDER_IFACE = 'com.ubuntu.SoftwareCenterDataProvider' DBUS_DATA_PROVIDER_PATH = '/com/ubuntu/SoftwareCenterDataProvider' def update_activity_timestamp(fn): def wrapped(*args, **kwargs): self = args[0] self._update_activity_timestamp() return fn(*args, **kwargs) return wrapped class SoftwareCenterDataProvider(dbus.service.Object): # 5 min by default IDLE_TIMEOUT = 60 * 5 IDLE_CHECK_INTERVAL = 60 def __init__(self, bus_name, object_path=DBUS_DATA_PROVIDER_PATH, main_loop=None): dbus.service.Object.__init__(self, bus_name, object_path) self.bus_name = bus_name if main_loop is None: main_loop = GLib.MainLoop(GLib.main_context_default()) self.main_loop = main_loop # the database self.db = StoreDatabase() self.db.open() self.db._aptcache.open(blocking=True) # categories self.categories = CategoriesParser(self.db).parse_applications_menu() # ensure reviews get refreshed self.review_loader = get_review_loader(self.db._aptcache, self.db) self.review_loader.refresh_review_stats() # ensure we query new applications run_software_center_agent(self.db) # setup inactivity timer self._update_activity_timestamp() self._idle_timeout = GLib.timeout_add_seconds( self.IDLE_CHECK_INTERVAL, self._check_inactivity) def stop(self): """ stop the dbus controller and remove from the bus """ LOG.debug("stop() called") self.main_loop.quit() LOG.debug("exited") # internal helper def _check_inactivity(self): """ Check for activity """ now = time.time() if (self._activity_timestamp + self.IDLE_TIMEOUT) < now: LOG.info("stopping after %s inactivity" % self.IDLE_TIMEOUT) self.stop() return True def _update_activity_timestamp(self): self._activity_timestamp = time.time() # public dbus methods with their implementations, the dbus decorator # does not like additional decorators so we use a separate function # for the actual implementation @dbus.service.method(DBUS_DATA_PROVIDER_IFACE, in_signature='ss', out_signature='a{sv}') def GetAppDetails(self, appname, pkgname): LOG.debug("GetAppDetails() called with ('%s', '%s')" % ( appname, pkgname)) return self._get_app_details(appname, pkgname) @update_activity_timestamp def _get_app_details(self, appname, pkgname): app = Application(appname, pkgname) appdetails = app.get_details(self.db) return appdetails.as_dbus_property_dict() @dbus.service.method('com.ubuntu.SoftwareCenterDataProvider', in_signature='', out_signature='as') def GetAvailableCategories(self): LOG.debug("GetAvailableCategories() called") return self._get_available_categories() @update_activity_timestamp def _get_available_categories(self): return [cat.name for cat in self.categories] @dbus.service.method(DBUS_DATA_PROVIDER_IFACE, in_signature='s', out_signature='as') def GetAvailableSubcategories(self, category_name): LOG.debug("GetAvailableSubcategories() called") return self._get_available_subcategories(category_name) @update_activity_timestamp def _get_available_subcategories(self, category_name): cat = get_category_by_name(self.categories, category_name) return [subcat.name for subcat in cat.subcategories] @dbus.service.method('com.ubuntu.SoftwareCenterDataProvider', in_signature='s', out_signature='a(ssss)') def GetItemsForCategory(self, category_name): LOG.debug("GetItemsForCategory() called with ('%s')" % category_name) return self._get_items_for_category(category_name) @update_activity_timestamp def _get_items_for_category(self, category_name): result = [] cat = get_category_by_name(self.categories, category_name) for doc in cat.get_documents(self.db): result.append( (self.db.get_appname(doc), self.db.get_pkgname(doc), self.db.get_iconname(doc), self.db.get_desktopfile(doc), )) return result def dbus_main(bus=None): if bus is None: bus = dbus.SessionBus() # apt needs the right locale for the translated package descriptions from softwarecenter.i18n import init_locale init_locale() main_context = GLib.main_context_default() main_loop = GLib.MainLoop(main_context) bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus) data_provider = SoftwareCenterDataProvider(bus_name, main_loop=main_loop) data_provider # pyflakes # run it main_loop.run() software-center-13.10/softwarecenter/db/__init__.py0000664000202700020270000000047212151440100022572 0ustar dobeydobey00000000000000import logging try: from debfile import DebFileApplication, DebFileOpenError DebFileApplication # pyflakes DebFileOpenError # pyflakes except: logging.exception("DebFileApplication import") class DebFileApplication(object): pass class DebFileOpenError(Exception): pass software-center-13.10/softwarecenter/db/appfilter.py0000664000202700020270000001035112151440100023016 0ustar dobeydobey00000000000000import xapian from softwarecenter.distro import get_distro from softwarecenter.enums import (XapianValues, 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 are based around the package details that are not listed in xapian (like installed_only) or archive section """ def __init__(self, db, cache): xapian.MatchDecider.__init__(self) self.distro = get_distro() self.db = db self.cache = cache self.available_only = False self.supported_only = global_filter.supported_only 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 """ return (self.available_only or global_filter.supported_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: return True 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) #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): return False if self.installed_only: if (not pkgname in self.cache or not self.cache[pkgname].is_installed): return False if self.not_installed_only: if (pkgname in self.cache and self.cache[pkgname].is_installed): return False if global_filter.supported_only: if not self.distro.is_supported(self.cache, doc, pkgname): return False if self.restricted_list is not 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.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 def reset(self): """ reset the values that are not global """ self.available_only = False self.installed_only = False self.not_installed_only = False self.restricted_list = False software-center-13.10/softwarecenter/db/categories.py0000664000202700020270000005606112216063163023201 0ustar dobeydobey00000000000000# 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 import gettext import glob import locale import logging import os import string import xapian # we use lxml.etree instead of xml.etree.ElementTree because it's quite a bit # faster, especially on slow machines like ARM import lxml.etree as ET from xml.sax.saxutils import escape as xml_escape from xml.sax.saxutils import unescape as xml_unescape import softwarecenter.paths from softwarecenter.enums import ( AppInfoFields, NonAppVisibility, SortMethods, ) from softwarecenter.backend.recagent import RecommenderAgent from softwarecenter.db.appfilter import AppFilter from softwarecenter.db.enquire import AppEnquire 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: return cat[0] return None def categories_sorted_by_name(categories): # sort categories by name sorted_catnames = [] # first pass, sort by translated names for cat in categories: sorted_catnames.append(cat.name) sorted_catnames = sorted(sorted_catnames, cmp=locale.strcoll) # second pass, assemble cats by sorted their sorted catnames sorted_cats = [] for name in sorted_catnames: for cat in categories: if cat.name == name: 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() for c in categories: if untranslated_category_name == c.untranslated_name: query = c.query return query return False class Category(GObject.GObject): """represents a menu category""" def __init__(self, untranslated_name, name, iconname, query, only_unallocated=True, dont_display=False, flags=[], subcategories=[], sortmode=SortMethods.BY_ALPHABET, item_limit=0): GObject.GObject.__init__(self) if type(name) == str: self.name = unicode(name, 'utf8').encode('utf8') else: self.name = name.encode('utf8') self.untranslated_name = untranslated_name self.iconname = iconname for subcategory in subcategories: query = xapian.Query(xapian.Query.OP_OR, query, subcategory.query) self.query = query self.only_unallocated = only_unallocated self.subcategories = subcategories self.dont_display = dont_display self.flags = flags self.sortmode = sortmode self.item_limit = item_limit @property def is_forced_sort_mode(self): return (self.sortmode != SortMethods.BY_ALPHABET) def get_documents(self, db): """ return the database docids for the given category """ enq = AppEnquire(db._aptcache, db) app_filter = AppFilter(db, db._aptcache) if "available-only" in self.flags: app_filter.set_available_only(True) if "not-installed-only" in self.flags: app_filter.set_not_installed_only(True) enq.set_query(self.query, limit=self.item_limit, filter=app_filter, sortmode=self.sortmode, nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE, nonblocking_load=False) return enq.get_documents() def __str__(self): return "" % ( self.name, self.sortmode, self.item_limit) 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,), ), } def __init__(self, db, subcategory=None): self.db = db 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__( 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) self.recommender_agent.connect( "error", self._recommender_agent_error) self.recommender_agent.query_recommend_me() def _recommend_me_result(self, recommender_agent, result_list): pkgs = [] for item in result_list['data']: pkgs.append(item['package_name']) if self.subcategory: self.query = xapian.Query(xapian.Query.OP_AND, self.db.get_query_for_pkgnames(pkgs), self.subcategory.query) else: self.query = self.db.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" % 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,), ), } def __init__(self, db, pkgname): super(AppRecommendationsCategory, self).__init__( u"People Also Installed", _(u"People Also Installed"), None, xapian.Query(), flags=['available-only', 'not-installed-only'], item_limit=4) self.db = db self.recommender_agent = RecommenderAgent() self.recommender_agent.connect( "recommend-app", self._recommend_app_result) self.recommender_agent.connect( "error", self._recommender_agent_error) self.recommender_agent.query_recommend_app(pkgname) def _recommend_app_result(self, recommender_agent, result_list): pkgs = [] for item in result_list['data']: pkgs.append(item['package_name']) self.query = self.db.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" % msg) self.emit("recommender-agent-error", msg) class CategoriesParser(object): """ Parser that is able to read the categories from a menu file """ # class wide cache to avoid multiple parsing CATEGORIES_PARSER_CACHE = {} def __init__(self, db): self.db = db # build the string substitution support self._build_string_template_dict() def parse_applications_menu(self, datadir=None, use_cache=True): """ parse a application menu and return a list of Category objects """ if datadir is None: datadir = softwarecenter.paths.desktopdir # the DB must be part of the cache key because different DBs may # yield different results (e.g. in the tests) cachekey = (self.db, datadir) if use_cache and cachekey in self.CATEGORIES_PARSER_CACHE: return self.CATEGORIES_PARSER_CACHE[cachekey] 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") for f in menu_files: if not os.path.exists(f): continue tree = ET.parse(f) root = tree.getroot() for child in root.getchildren(): category = None if child.tag == "Menu": category = self._parse_menu_tag(child) if category: categories.append(category) # 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 # unallocated entries in their sublevel for cat in categories: self._build_unallocated_queries(cat.subcategories) self._build_unallocated_queries(categories) # add to the cache self.CATEGORIES_PARSER_CACHE[cachekey] = categories # debug print for cat in categories: LOG.debug("%s %s %s" % (cat.name.decode('utf8'), cat.iconname, cat.query)) return categories def _build_string_template_dict(self): """ this build the dict used to substitute menu entries dynamically, currently used for the CURRENT_REGION """ region = "%s" % get_region_cached()["countrycode"] 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 if a.untranslated_name == "System": return 1 elif b.untranslated_name == "System": return -1 elif a.untranslated_name == "Developer Tools": return 1 elif b.untranslated_name == "Developer Tools": return -1 return locale.strcoll(a.name, b.name) def _parse_directory_tag(self, element): from softwarecenter.db.update import DesktopConfigParser cp = DesktopConfigParser() fname = "/usr/share/desktop-directories/%s" % element.text if not os.path.exists(fname): return None LOG.debug("reading '%s'" % fname) cp.read(fname) try: untranslated_name = name = cp.get("Desktop Entry", "Name") except Exception: LOG.warn("'%s' has no name" % fname) return None try: gettext_domain = cp.get("Desktop Entry", "X-Ubuntu-Gettext-Domain") except: gettext_domain = None try: icon = cp.get("Desktop Entry", "Icon") except Exception: icon = "applications-other" name = cp.get_value(AppInfoFields.NAME, translated=True) return (untranslated_name, name, gettext_domain, icon) def _parse_flags_tag(self, element): flags = [] for an_elem in element.getchildren(): flags.append(an_elem.text) return flags def _parse_and_or_not_tag(self, element, query, xapian_op): """parse a , , tag """ for operator_elem in element.getchildren(): # get the query-text if operator_elem.text: qtext = self._substitute_string_if_needed( operator_elem.text).lower() # parse the individual element if operator_elem.tag == "Not": query = self._parse_and_or_not_tag( operator_elem, query, xapian.Query.OP_AND_NOT) elif operator_elem.tag == "Or": or_elem = self._parse_and_or_not_tag( operator_elem, xapian.Query(), xapian.Query.OP_OR) 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) query = xapian.Query(xapian_op, query, q) elif operator_elem.tag == "SCSection": LOG.debug("adding section: %s" % operator_elem.text) # we have the section once in apt-xapian-index and once # in our own DB this is why we need two prefixes # 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)) 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) 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) 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) 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) 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) q = xapian.Query(xapian.Query.OP_OR, q1, 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 and ensure that the pkgname is # mangled to workaround xapian's query parser lack of # quoting :/ s = "pkg_wildcard:%s" % qtext.replace("-", "_") 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)) 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) if include.tag == "And": query = xapian.Query("") 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()) else: LOG.warn("UNHANDLED: _parse_include_tag: %s" % include.tag) # empty query matches all return xapian.Query("") def _parse_menu_tag(self, item): name = None untranslated_name = None query = None icon = None only_unallocated = False dont_display = False flags = [] subcategories = [] sortmode = SortMethods.BY_ALPHABET 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): continue if element.tag == "Name": untranslated_name = element.text # gettext/xml writes stuff from software-center.menu # out into the pot as escaped xml, so we need to escape # the name first, get the translation and unescape it again escaped_name = xml_escape(untranslated_name) name = xml_unescape(gettext.gettext(escaped_name)) elif element.tag == "SCIcon": icon = element.text elif element.tag == 'Flags': flags = self._parse_flags_tag(element) elif element.tag == "Directory": l = self._parse_directory_tag(element) if l: (untranslated_name, name, gettext_domain, icon) = l elif element.tag == "Include": query = self._parse_include_tag(element) elif element.tag == "OnlyUnallocated": only_unallocated = True elif element.tag == "SCDontDisplay": dont_display = True elif element.tag == "SCSortMode": sortmode = int(element.text) if not self._verify_supported_sort_mode(sortmode): return None elif element.tag == "SCItemLimit": item_limit = int(element.text) elif element.tag == "Menu": subcat = self._parse_menu_tag(element) if subcat: 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) else: 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, SortMethods.BY_SEARCH_RANKING, SortMethods.BY_CATALOGED_TIME): return True # we don't know this sortmode LOG.error("unknown sort mode '%i'" % sortmode) return False def _build_unallocated_queries(self, categories): for cat_unalloc in categories: if not cat_unalloc.only_unallocated: 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) #print cat_unalloc.name, cat_unalloc.query return # static category mapping for the tiles category_cat = { 'Utility': 'Accessories', 'System': 'Accessories', 'Education': 'Education', 'Game': 'Games', 'Sports': 'Games', 'Graphics': 'Graphics', 'Network': 'Internet', 'Office': 'Office', 'Science': 'Science & Engineering', 'Audio': 'Sound & Video', 'AudioVideo': 'Sound & Video', 'Video': 'Sound & Video', 'Settings': 'Themes & Tweaks', 'Accessibility': 'Universal Access', 'Development': 'Developer Tools', 'X-Publishing': 'Books & Magazines', } category_subcat = { 'BoardGame': 'Games;Board Games', 'CardGame': 'Games;Card Games', 'LogicGame': 'Games;Puzzles', 'RolePlaying': 'Games;Role Playing', 'SportsGame': 'Games;Sports', '3DGraphics': 'Graphics;3D Graphics', 'VectorGraphics': 'Graphics;Drawing', 'RasterGraphics': 'Graphics;Painting & Editing', 'Photography': 'Graphics;Photography', 'Publishing': 'Graphics;Publishing', 'Scanning': 'Graphics;Scanning & OCR', 'OCR': 'Graphics;Scanning & OCR', 'Viewer': 'Graphics;Viewers', 'InstantMessaging': 'Internet;Chat', 'IRCClient': 'Internet;Chat', 'FileTransfer': 'Internet;File Sharing', 'Email': 'Internet;Mail', 'WebBrowser': 'Internet;Web Browsers', 'Astronomy': 'Science & Engineering;Astronomy', 'Biology': 'Science & Engineering;Biology', 'Chemistry': 'Science & Engineering;Chemistry', 'ArtificialIntelligence': 'Science & Engineering;Computing & Robotics', 'ComputerScience': 'Science & Engineering;Computing & Robotics', 'Robotics': 'Science & Engineering;Computing & Robotics', 'Electronics': 'Science & Engineering;Electronics', 'Engineering': 'Science & Engineering;Engineering', 'Geography': 'Science & Engineering;Geography', 'Geology': 'Science & Engineering;Geology', 'Geoscience': 'Science & Engineering;Geology', 'DataVisualization': 'Science & Engineering;Mathematics', 'Math': 'Science & Engineering;Mathematics', 'NumericalAnalysis': 'Science & Engineering;Mathematics', 'MedicalSoftware': 'Science & Engineering;Medicine', 'Electricity': 'Science & Engineering;Physics', 'Physics': 'Science & Engineering;Physics', 'Debugger': 'Developer Tools;Debugging', 'GUIDesigner': 'Developer Tools;Graphic Interface Design', 'IDE': 'Developer Tools;IDEs', 'Translation': 'Developer Tools;Localization', 'Profiling': 'Developer Tools;Profiling', 'RevisionControl': 'Developer Tools;Version Control', 'WebDevelopment': 'Developer Tools;Web Development', } software-center-13.10/softwarecenter/db/update.py0000664000202700020270000013205712200237544022335 0ustar dobeydobey00000000000000#!/usr/bin/python # 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 import logging import json import re import os import string import shutil import time import xapian from aptsources.sourceslist import SourceEntry from gi.repository import GLib from piston_mini_client import PistonResponseObject from softwarecenter.backend.scagent import SoftwareCenterAgent from softwarecenter.backend.ubuntusso import UbuntuSSO from softwarecenter.distro import get_distro from softwarecenter.utils import utf8 from gettext import gettext as _ # py3 compat try: from configparser import RawConfigParser, NoOptionError RawConfigParser # pyflakes NoOptionError # pyflakes except ImportError: from ConfigParser import RawConfigParser, NoOptionError # py3 compat try: import cPickle as pickle pickle # pyflakes except ImportError: import pickle from glob import glob from urlparse import urlparse import softwarecenter.paths from softwarecenter.enums import ( AppInfoFields, AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, DB_SCHEMA_VERSION, XapianValues, ) from softwarecenter.db.database import parse_axi_values_file from locale import getdefaultlocale import gettext from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.distro import ( get_current_arch, get_foreign_architectures, ) from softwarecenter.region import ( get_region_cached, REGION_BLACKLIST_TAG, REGION_WHITELIST_TAG, ) # weights for the different fields WEIGHT_DESKTOP_NAME = 10 WEIGHT_DESKTOP_KEYWORD = 5 WEIGHT_DESKTOP_GENERICNAME = 3 WEIGHT_DESKTOP_COMMENT = 1 WEIGHT_APT_PKGNAME = 8 WEIGHT_APT_SUMMARY = 5 WEIGHT_APT_DESCRIPTION = 1 # some globals (FIXME: that really need to go into a new Update class) popcon_max = 0 seen = set() LOG = logging.getLogger(__name__) # init axi axi_values = parse_axi_values_file() # get cataloged_times cataloged_times = {} CF = "/var/lib/apt-xapian-index/cataloged_times.p" if os.path.exists(CF): try: cataloged_times = pickle.load(open(CF)) except Exception as e: LOG.warn("failed to load file %s: %s", CF, e) del CF # Enable Xapian's CJK tokenizer (see LP: #745243) os.environ['XAPIAN_CJK_NGRAM'] = '1' def process_date(date): result = None if re.match("\d+-\d+-\d+ \d+:\d+:\d+", date): # strip the subseconds from the end of the published date string result = str(date).split(".")[0] return result def process_popcon(popcon): xapian.sortable_serialise(float(popcon)) return popcon def get_pkgname_terms(pkgname): result = ["AP" + pkgname, # workaround xapian oddness by providing a "mangled" version # with a different prefix "APM" + pkgname.replace('-', '_')] return result def get_default_locale(): return getdefaultlocale(('LANGUAGE', 'LANG', 'LC_CTYPE', 'LC_ALL'))[0] class AppInfoParserBase(object): """Base class for reading AppInfo meta-data.""" # map Application Info Fields to xapian "values" FIELD_TO_XAPIAN = { AppInfoFields.ARCH: XapianValues.ARCHIVE_ARCH, AppInfoFields.CHANNEL: XapianValues.ARCHIVE_CHANNEL, AppInfoFields.DEB_LINE: XapianValues.ARCHIVE_DEB_LINE, AppInfoFields.DESCRIPTION: XapianValues.SC_DESCRIPTION, AppInfoFields.DOWNLOAD_SIZE: XapianValues.DOWNLOAD_SIZE, AppInfoFields.CATEGORIES: XapianValues.CATEGORIES, AppInfoFields.CURRENCY: XapianValues.CURRENCY, AppInfoFields.DATE_PUBLISHED: XapianValues.DATE_PUBLISHED, AppInfoFields.ICON: XapianValues.ICON, AppInfoFields.ICON_URL: XapianValues.ICON_URL, AppInfoFields.GETTEXT_DOMAIN: XapianValues.GETTEXT_DOMAIN, AppInfoFields.LICENSE: XapianValues.LICENSE, AppInfoFields.LICENSE_KEY: XapianValues.LICENSE_KEY, AppInfoFields.LICENSE_KEY_PATH: XapianValues.LICENSE_KEY_PATH, AppInfoFields.NAME: XapianValues.APPNAME, AppInfoFields.NAME_UNTRANSLATED: XapianValues.APPNAME_UNTRANSLATED, AppInfoFields.PACKAGE: XapianValues.PKGNAME, AppInfoFields.POPCON: XapianValues.POPCON, AppInfoFields.PPA: XapianValues.ARCHIVE_PPA, AppInfoFields.PRICE: XapianValues.PRICE, AppInfoFields.PURCHASED_DATE: XapianValues.PURCHASED_DATE, AppInfoFields.SECTION: XapianValues.ARCHIVE_SECTION, AppInfoFields.SIGNING_KEY_ID: XapianValues.ARCHIVE_SIGNING_KEY_ID, AppInfoFields.SCREENSHOT_URLS: XapianValues.SCREENSHOT_URLS, AppInfoFields.SUMMARY: XapianValues.SUMMARY, AppInfoFields.SUPPORT_URL: XapianValues.SUPPORT_SITE_URL, AppInfoFields.SUPPORTED_DISTROS: XapianValues.SC_SUPPORTED_DISTROS, AppInfoFields.THUMBNAIL_URL: XapianValues.THUMBNAIL_URL, AppInfoFields.VERSION: XapianValues.VERSION_INFO, AppInfoFields.VIDEO_URL: XapianValues.VIDEO_URL, AppInfoFields.WEBSITE: XapianValues.WEBSITE, } # map Application Info Fields to xapian "terms" FIELD_TO_TERMS = { AppInfoFields.NAME: lambda name: ('AA' + name,), AppInfoFields.CHANNEL: lambda channel: ('AH' + channel,), AppInfoFields.SECTION: lambda section: ('AS' + section,), AppInfoFields.PACKAGE: get_pkgname_terms, AppInfoFields.PPA: # add archive origin data here so that its available even if # the PPA is not (yet) enabled lambda ppa: ('XOOlp-ppa-' + ppa.replace('/', '-'),), } # map apt cache origins to terms ORIGINS_TO_TERMS = { "XOA": "archive", "XOC": "component", "XOL": "label", "XOO": "origin", "XOS": "site", } # data that needs a transformation during the processing FIELD_TRANSFORMERS = { AppInfoFields.DATE_PUBLISHED: process_date, AppInfoFields.PACKAGE: lambda pkgname, pkgname_extension: pkgname + pkgname_extension, AppInfoFields.POPCON: process_popcon, AppInfoFields.PURCHASED_DATE: process_date, AppInfoFields.SUMMARY: lambda s, name: s if s != name else None, AppInfoFields.SUPPORTED_DISTROS: json.dumps, } # a mapping that the subclasses override, it defines the mapping # from the Application Info Fields to the "native" keywords used # by the various subclasses, e.g. " # X-AppInstall-Channel for desktop files # or # "channel" for the json data MAPPING = {} NOT_DEFINED = object() SPLIT_STR_CHAR = ';' def get_value(self, key, translated=True): """Get the AppInfo entry for the given key.""" return getattr(self, self._apply_mapping(key), None) def _get_value_list(self, key, split_str=None): if split_str is None: split_str = self.SPLIT_STR_CHAR result = [] list_str = self.get_value(key) if list_str is not None: try: for item in filter(lambda s: s, list_str.split(split_str)): result.append(item) except (NoOptionError, KeyError): pass return result def _apply_mapping(self, key): return self.MAPPING.get(key, key) def get_categories(self): return self._get_value_list(AppInfoFields.CATEGORIES) def get_mimetypes(self): result = self._get_value_list(AppInfoFields.MIMETYPE) if not result: result = [] return result def _set_doc_from_key(self, doc, key, translated=True, dry_run=False, **kwargs): value = self.get_value(key, translated=translated) if value is not None: modifier = self.FIELD_TRANSFORMERS.get(key, lambda i, **kw: i) value = modifier(value, **kwargs) if value is not None and not dry_run: # add value to the xapian database if defined doc_key = self.FIELD_TO_XAPIAN[key] doc.add_value(doc_key, value) # add terms to the xapian database get_terms = self.FIELD_TO_TERMS.get(key, lambda i: []) for t in get_terms(value): doc.add_term(t) return value @property def desktopf(self): """Return the file that the AppInfo comes from.""" @property def is_ignored(self): ignored = self.get_value(AppInfoFields.IGNORE) if ignored: ignored = ignored.strip().lower() return (ignored == "true") def make_doc(self, cache): """Build a Xapian document from the desktop info.""" doc = xapian.Document() # app name is the data name = self._set_doc_from_key(doc, AppInfoFields.NAME) assert name is not None doc.set_data(name) self._set_doc_from_key(doc, AppInfoFields.NAME_UNTRANSLATED, translated=False) # check if we should ignore this file if self.is_ignored: LOG.debug("%r.make_doc: %r is ignored.", self.__class__.__name__, self.desktopf) return # architecture pkgname_extension = '' arches = self._set_doc_from_key(doc, AppInfoFields.ARCH) if arches: native_archs = get_current_arch() in arches.split(',') foreign_archs = list(set(arches.split(',')) & set(get_foreign_architectures())) if not (native_archs or foreign_archs): return if not native_archs and foreign_archs: pkgname_extension = ':' + foreign_archs[0] # package name pkgname = self._set_doc_from_key(doc, AppInfoFields.PACKAGE, pkgname_extension=pkgname_extension) doc.add_value(XapianValues.DESKTOP_FILE, self.desktopf) # display name display_name = axi_values.get("display_name") if display_name is not None: doc.add_value(display_name, name) # cataloged_times catalogedtime = axi_values.get("catalogedtime") if catalogedtime is not None and pkgname in cataloged_times: doc.add_value(catalogedtime, xapian.sortable_serialise(cataloged_times[pkgname])) # section (mail, base, ..) if pkgname in cache and cache[pkgname].candidate: section = cache[pkgname].section doc.add_term("AE" + section) fields = ( AppInfoFields.CHANNEL, # channel (third party stuff) AppInfoFields.DEB_LINE, # deb-line (third party) AppInfoFields.DESCRIPTION, # description software-center extension AppInfoFields.GETTEXT_DOMAIN, # check gettext domain AppInfoFields.ICON, # icon AppInfoFields.LICENSE, # license (third party) AppInfoFields.LICENSE_KEY, # license key (third party) AppInfoFields.LICENSE_KEY_PATH, # license keypath (third party) AppInfoFields.PPA, # PPA (third party stuff) AppInfoFields.PURCHASED_DATE, # purchased date AppInfoFields.SCREENSHOT_URLS, # screenshot (for third party) AppInfoFields.SECTION, # pocket (main, restricted, ...) AppInfoFields.SIGNING_KEY_ID, # signing key (third party) AppInfoFields.SUPPORT_URL, # support url (mainly pay stuff) AppInfoFields.SUPPORTED_DISTROS, # supported distros AppInfoFields.THUMBNAIL_URL, # thumbnail (for third party) AppInfoFields.VERSION, # version support (for e.g. the scagent) AppInfoFields.VIDEO_URL, # video support (for third party mostly) AppInfoFields.WEBSITE, # homepage url (developer website) ) for field in fields: self._set_doc_from_key(doc, field) # date published date_published_str = self._set_doc_from_key( doc, AppInfoFields.DATE_PUBLISHED) # we use the date published value for the cataloged time as well if date_published_str is not None: LOG.debug("pkgname: %s, date_published cataloged time is: %s", pkgname, date_published_str) date_published = time.mktime(time.strptime(date_published_str, "%Y-%m-%d %H:%M:%S")) # a value for our own DB doc.add_value(XapianValues.DB_CATALOGED_TIME, xapian.sortable_serialise(date_published)) if "catalogedtime" in axi_values: # compat with a-x-i doc.add_value(axi_values["catalogedtime"], xapian.sortable_serialise(date_published)) # icon (for third party) url = self._set_doc_from_key(doc, AppInfoFields.ICON_URL) if url and self.get_value(AppInfoFields.ICON) is None: # prefix pkgname to avoid name clashes doc.add_value(XapianValues.ICON, "%s-icon-%s" % (pkgname, os.path.basename(url))) # price (pay stuff) price = self._set_doc_from_key(doc, AppInfoFields.PRICE) if price: # this is a commercial app, indicate it in the component value doc.add_value(XapianValues.ARCHIVE_SECTION, "commercial") # this is hard-coded to US dollar for now, but if the server # ever changes we can update doc.add_value(XapianValues.CURRENCY, "US$") # add donwload size as string (its send as int) download_size = self.get_value(AppInfoFields.DOWNLOAD_SIZE) if download_size is not None: doc.add_value(XapianValues.DOWNLOAD_SIZE, xapian.sortable_serialise((download_size))) # write out categories for cat in self.get_categories(): doc.add_term("AC" + cat.lower()) categories_string = ";".join(self.get_categories()) doc.add_value(XapianValues.CATEGORIES, categories_string) # mimetypes for mime in self.get_mimetypes(): doc.add_term("AM" + mime.lower()) # get type (to distinguish between apps and packages) app_type = self.get_value(AppInfoFields.TYPE) if app_type: doc.add_term("AT" + app_type.lower()) # (deb)tags (in addition to the pkgname debtags) tags_string = self.get_value(AppInfoFields.TAGS) if tags_string: # convert to list and register tags = [tag.strip().lower() for tag in tags_string.split(",")] for tag in tags: doc.add_term("XT" + tag) # ENFORCE region blacklist/whitelist by not registering # the app at all region = get_region_cached() if region: countrycode = region["countrycode"].lower() blacklist = [t.replace(REGION_BLACKLIST_TAG, "") for t in tags if t.startswith(REGION_BLACKLIST_TAG)] whitelist = [t.replace(REGION_WHITELIST_TAG, "") for t in tags if t.startswith(REGION_WHITELIST_TAG)] if countrycode in blacklist: if countrycode in whitelist: LOG.debug("%r.make_doc: %r black AND whitelisted for " "region %r. Treating as blacklisted.", self.__class__.__name__, name, countrycode) LOG.debug("%r.make_doc: skipping region restricted app %r " "(blacklisted)", self.__class__.__name__, name) return if len(whitelist) > 0 and countrycode not in whitelist: LOG.debug("%r.make_doc: skipping region restricted " "app %r (region not whitelisted)", self.__class__.__name__, name) return # popcon # FIXME: popularity not only based on popcon but also # on archive section, third party app etc popcon = self._set_doc_from_key(doc, AppInfoFields.POPCON) if popcon is not None: global popcon_max popcon_max = max(popcon_max, popcon) # comment goes into the summary data if there is one, # otherwise we try GenericName and if nothing else, # the summary of the candidate package summary = self._set_doc_from_key(doc, AppInfoFields.SUMMARY, name=name) if summary is None and pkgname in cache and cache[pkgname].candidate: summary = cache[pkgname].candidate.summary doc.add_value(XapianValues.SUMMARY, summary) return doc def index_app_info(self, db, cache): term_generator = xapian.TermGenerator() term_generator.set_database(db) try: # this tests if we have spelling suggestions (there must be # a better way?!?) - this is needed as inmemory does not have # spelling corrections, but it allows setting the flag and will # raise a exception much later db.add_spelling("test") db.remove_spelling("test") # this enables the flag for it (we only reach this line if # the db supports spelling suggestions) term_generator.set_flags(xapian.TermGenerator.FLAG_SPELLING) except xapian.UnimplementedError: pass doc = self.make_doc(cache) if not doc: LOG.debug("%r.index_app_info: returned invalid doc %r, ignoring.", self.__class__.__name__, doc) return name = doc.get_data() if name in seen: LOG.debug("%r.index_app_info: duplicated name %r (%r)", self.__class__.__name__, name, self.desktopf) LOG.debug("%r.index_app_info: indexing %r", self.__class__.__name__, name) seen.add(name) term_generator.set_document(doc) term_generator.index_text_without_positions(name, WEIGHT_DESKTOP_NAME) pkgname = doc.get_value(XapianValues.PKGNAME) # add packagename as meta-data too term_generator.index_text_without_positions(pkgname, WEIGHT_APT_PKGNAME) # now add search data from the desktop file for weight, key in [('GENERICNAME', AppInfoFields.GENERIC_NAME), ('COMMENT', AppInfoFields.SUMMARY), ('DESCRIPTION', AppInfoFields.DESCRIPTION)]: s = self.get_value(key) if not s: continue k = "WEIGHT_DESKTOP_" + weight w = globals().get(k) if w is None: LOG.debug("%r.index_app_info: WEIGHT %r not found", self.__class__.__name__, k) w = 1 term_generator.index_text_without_positions(s, w) # add data from the apt cache if pkgname in cache and cache[pkgname].candidate: term_generator.index_text_without_positions( cache[pkgname].candidate.summary, WEIGHT_APT_SUMMARY) term_generator.index_text_without_positions( cache[pkgname].candidate.description, WEIGHT_APT_DESCRIPTION) for origin in cache[pkgname].candidate.origins: for (term, attr) in self.ORIGINS_TO_TERMS.items(): doc.add_term(term + getattr(origin, attr)) # add our keywords (with high priority) keywords = self.get_value(AppInfoFields.KEYWORDS) if keywords: for keyword in filter(lambda s: s, keywords.split(";")): term_generator.index_text_without_positions( keyword, WEIGHT_DESKTOP_KEYWORD) # now add it db.add_document(doc) class SCAApplicationParser(AppInfoParserBase): """Map the data we get from the software-center-agent.""" # map from requested key to sca_application attribute MAPPING = { AppInfoFields.KEYWORDS: 'keywords', AppInfoFields.TAGS: 'tags', AppInfoFields.NAME: 'name', AppInfoFields.NAME_UNTRANSLATED: 'name', AppInfoFields.CHANNEL: 'channel', AppInfoFields.PPA: 'archive_id', AppInfoFields.SIGNING_KEY_ID: 'signing_key_id', AppInfoFields.CATEGORIES: 'categories', AppInfoFields.DATE_PUBLISHED: 'date_published', AppInfoFields.ICON_URL: 'icon_url', AppInfoFields.LICENSE: 'license', AppInfoFields.PACKAGE: 'package_name', AppInfoFields.PRICE: 'price', AppInfoFields.DESCRIPTION: 'description', AppInfoFields.DOWNLOAD_SIZE: 'binary_filesize', AppInfoFields.SUPPORTED_DISTROS: 'series', AppInfoFields.SCREENSHOT_URLS: 'screenshot_url', AppInfoFields.SUMMARY: 'comment', AppInfoFields.SUPPORT_URL: 'support_url', AppInfoFields.THUMBNAIL_URL: 'thumbnail_url', AppInfoFields.VERSION: 'version', AppInfoFields.VIDEO_URL: 'video_embedded_html_url', AppInfoFields.WEBSITE: 'website', # tags are special, see _apply_exception } # map from requested key to a static data element STATIC_DATA = { AppInfoFields.TYPE: 'Application', } def __init__(self, sca_application): super(SCAApplicationParser, self).__init__() # the piston object we got from software-center-agent self.sca_application = sca_application self.origin = "software-center-agent" self._apply_exceptions() def _apply_exceptions(self): # for items from the agent, we use the full-size screenshot for # the thumbnail and scale it for display, this is done because # we no longer keep thumbnail versions of screenshots on the server if (hasattr(self.sca_application, "screenshot_url") and not hasattr(self.sca_application, "thumbnail_url")): self.sca_application.thumbnail_url = \ self.sca_application.screenshot_url if hasattr(self.sca_application, "description"): comment, desc = self.sca_application.description.split("\n", 1) self.sca_application.comment = comment.strip() self.sca_application.description = desc.strip() # debtags is send as a list, but we need it as a comma separated string debtags = getattr(self.sca_application, "debtags", []) self.sca_application.tags = ",".join(debtags) # we only support a single video currently :/ urls = getattr(self.sca_application, "video_embedded_html_urls", None) if urls: self.sca_application.video_embedded_html_url = urls[0] else: self.sca_application.video_embedded_html_url = None # XXX 2012-01-16 bug=917109 # We can remove these work-arounds once the above bug is fixed on # the server. Until then, we fake a channel here and empty category # to make the parser happy. Note: available_apps api call includes # these already, it's just the apps with subscriptions_for_me which # don't currently. self.sca_application.channel = \ AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME if not hasattr(self.sca_application, 'categories'): self.sca_application.categories = "" # detect if its for the partner channel and set the channel # attribute appropriately so that the channel-adding magic works if hasattr(self.sca_application, "archive_root"): u = urlparse(self.sca_application.archive_root) if u.scheme == "http" and u.netloc == "archive.canonical.com": distroseries = get_distro().get_codename() self.sca_application.channel = "%s-partner" % distroseries if u.scheme == "http" and u.netloc == "extras.ubuntu.com": self.sca_application.channel = "ubuntu-extras" # support multiple screenshots if hasattr(self.sca_application, "screenshot_urls"): # ensure to html-quote "," as this is also our separator s = ",".join([url.replace(",", "%2C") for url in self.sca_application.screenshot_urls]) self.sca_application.screenshot_url = s keywords = getattr(self.sca_application, 'keywords', self.NOT_DEFINED) if keywords is self.NOT_DEFINED: self.sca_application.keywords = '' def get_value(self, key, translated=True): if key in self.STATIC_DATA: return self.STATIC_DATA[key] return getattr(self.sca_application, self._apply_mapping(key), None) def get_categories(self): try: dept = ['DEPARTMENT:' + self.sca_application.department[-1]] return (dept + self._get_value_list(AppInfoFields.CATEGORIES)) except: return self._get_value_list(AppInfoFields.CATEGORIES) @property def desktopf(self): return self.origin class SCAPurchasedApplicationParser(SCAApplicationParser): """A purchased application has some additional subscription attributes.""" SUBSCRIPTION_MAPPING = { # this key can be used to get the original deb_line that the # server returns, it will be at the distroseries that was current # at purchase time AppInfoFields.DEB_LINE_ORIG: 'deb_line', # this is what s-c will always use, the deb_line updated to the # current distroseries, note that you should ensure that the app # is not in state: PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES AppInfoFields.DEB_LINE: 'deb_line', AppInfoFields.PURCHASED_DATE: 'purchase_date', AppInfoFields.LICENSE_KEY: 'license_key', AppInfoFields.LICENSE_KEY_PATH: 'license_key_path', } def __init__(self, sca_subscription): # The sca_subscription is a PistonResponseObject, whereas any child # objects are normal Python dicts. self.sca_subscription = sca_subscription self.MAPPING.update(self.SUBSCRIPTION_MAPPING) super(SCAPurchasedApplicationParser, self).__init__( PistonResponseObject.from_dict(sca_subscription.application)) @classmethod def update_debline(cls, debline): # Be careful to handle deblines with pockets. source_entry = SourceEntry(debline) distro_pocket = source_entry.dist.split('-') distro_pocket[0] = get_distro().get_codename() source_entry.dist = "-".join(distro_pocket) return unicode(source_entry) def get_value(self, key, translated=True): result = getattr(self.sca_subscription, self._apply_mapping(key), self.NOT_DEFINED) if result is not self.NOT_DEFINED and key == AppInfoFields.DEB_LINE: result = self.update_debline(result) elif result is self.NOT_DEFINED: result = super( SCAPurchasedApplicationParser, self).get_value(key) return result def _apply_exceptions(self): super(SCAPurchasedApplicationParser, self)._apply_exceptions() # WARNING: item.name needs to be different than # the item.name in the DB otherwise the DB # gets confused about (appname, pkgname) duplication self.sca_application.name = utf8(_("%s (already purchased)")) % utf8( self.sca_application.name) for attr_name in ('license_key', 'license_key_path'): attr = getattr(self.sca_subscription, attr_name, self.NOT_DEFINED) if attr is self.NOT_DEFINED: setattr(self.sca_subscription, attr_name, None) class JsonTagSectionParser(AppInfoParserBase): MAPPING = { AppInfoFields.CATEGORIES: 'categories', AppInfoFields.NAME: 'application_name', AppInfoFields.PACKAGE: 'package_name', AppInfoFields.PRICE: 'price', AppInfoFields.SUMMARY: 'description', } STATIC_DATA = { AppInfoFields.TYPE: 'Application', } def __init__(self, tag_section, url): super(JsonTagSectionParser, self).__init__() self.tag_section = tag_section self.url = url def get_value(self, key, translated=True): if key in self.STATIC_DATA: return self.STATIC_DATA[key] return self.tag_section.get(self._apply_mapping(key)) @property def desktopf(self): return self.url class AppStreamXMLParser(AppInfoParserBase): MAPPING = { AppInfoFields.CATEGORIES: 'appcategories', AppInfoFields.ICON: 'icon', AppInfoFields.KEYWORDS: 'keywords', AppInfoFields.MIMETYPE: 'mimetypes', AppInfoFields.NAME: 'name', AppInfoFields.PACKAGE: 'pkgname', AppInfoFields.SUMMARY: 'summary', } LISTS = { "appcategories": "appcategory", "keywords": "keyword", "mimetypes": "mimetype", } # map from requested key to a static data element STATIC_DATA = { AppInfoFields.TYPE: 'Application', } SPLIT_STR_CHAR = ',' def __init__(self, appinfo_xml, xmlfile): super(AppStreamXMLParser, self).__init__() self.appinfo_xml = appinfo_xml self.xmlfile = xmlfile def get_value(self, key, translated=True): if key in self.STATIC_DATA: return self.STATIC_DATA[key] key = self._apply_mapping(key) if key in self.LISTS: return self._parse_with_lists(key) else: return self._parse_value(key, translated) def _parse_value(self, key, translated): locale = get_default_locale() for child in self.appinfo_xml.iter(key): if translated: if child.get("lang") == locale: return child.text if child.get("lang") == locale.split('_')[0]: return child.text continue elif not child.get("lang"): return child.text if translated: return self._parse_value(key, False) def _parse_with_lists(self, key): l = [] for listroot in self.appinfo_xml.iter(key): for child in listroot.iter(self.LISTS[key]): l.append(child.text) return ",".join(l) @property def desktopf(self): subelm = self.appinfo_xml.find("id") return subelm.text class DesktopTagSectionParser(AppInfoParserBase): MAPPING = { AppInfoFields.ARCH: 'X-AppInstall-Architectures', AppInfoFields.CHANNEL: 'X-AppInstall-Channel', AppInfoFields.DATE_PUBLISHED: 'X-AppInstall-Date-Published', AppInfoFields.DEB_LINE: 'X-AppInstall-Deb-Line', AppInfoFields.DESCRIPTION: 'X-AppInstall-Description', AppInfoFields.DOWNLOAD_SIZE: 'X-AppInstall-DownloadSize', AppInfoFields.GENERIC_NAME: 'GenericName', AppInfoFields.GETTEXT_DOMAIN: 'X-Ubuntu-Gettext-Domain', AppInfoFields.ICON: 'Icon', AppInfoFields.ICON_URL: 'X-AppInstall-Icon-Url', AppInfoFields.IGNORE: 'X-AppInstall-Ignore', AppInfoFields.KEYWORDS: 'X-AppInstall-Keywords', AppInfoFields.LICENSE: 'X-AppInstall-License', AppInfoFields.LICENSE_KEY: 'X-AppInstall-License-Key', AppInfoFields.LICENSE_KEY_PATH: 'X-AppInstall-License-Key-Path', AppInfoFields.NAME: ('X-Ubuntu-Software-Center-Name', 'X-GNOME-FullName', 'Name'), AppInfoFields.NAME_UNTRANSLATED: ('X-Ubuntu-Software-Center-Name', 'X-GNOME-FullName', 'Name'), AppInfoFields.PACKAGE: 'X-AppInstall-Package', AppInfoFields.POPCON: 'X-AppInstall-Popcon', AppInfoFields.PPA: 'X-AppInstall-PPA', AppInfoFields.PRICE: 'X-AppInstall-Price', AppInfoFields.PURCHASED_DATE: 'X-AppInstall-Purchased-Date', AppInfoFields.SCREENSHOT_URLS: 'X-AppInstall-Screenshot-Url', AppInfoFields.SECTION: 'X-AppInstall-Section', AppInfoFields.SIGNING_KEY_ID: 'X-AppInstall-Signing-Key-Id', AppInfoFields.SUMMARY: ('Comment', 'GenericName'), AppInfoFields.SUPPORTED_DISTROS: 'Supported-Distros', AppInfoFields.SUPPORT_URL: 'X-AppInstall-Support-Url', AppInfoFields.TAGS: 'X-AppInstall-Tags', AppInfoFields.THUMBNAIL_URL: 'X-AppInstall-Thumbnail-Url', AppInfoFields.TYPE: 'Type', AppInfoFields.VERSION: 'X-AppInstall-Version', AppInfoFields.VIDEO_URL: 'X-AppInstall-Video-Url', AppInfoFields.WEBSITE: 'Homepage', } LOCALE_EXPR = '%s-%s' def __init__(self, tag_section, tagfile): super(DesktopTagSectionParser, self).__init__() self.tag_section = tag_section self.tagfile = tagfile def get_value(self, key, translated=True): keys = self.MAPPING.get(key, key) if isinstance(keys, basestring): keys = (keys,) for key in keys: result = self._get_desktop(key, translated) if result: return result def _get_desktop(self, key, translated=True): untranslated_value = self._get_option_desktop(key) # shortcut if not translated: return untranslated_value # first try dgettext domain = self._get_option_desktop('X-Ubuntu-Gettext-Domain') if domain and untranslated_value: translated_value = gettext.dgettext(domain, untranslated_value) if untranslated_value != translated_value: return translated_value # then try app-install-data if untranslated_value: translated_value = gettext.dgettext('app-install-data', untranslated_value) if untranslated_value != translated_value: return translated_value # then try the i18n version of the key (in [de_DE] or # [de]) but ignore errors and return the untranslated one then try: locale = get_default_locale() if locale: new_key = self.LOCALE_EXPR % (key, locale) result = self._get_option_desktop(new_key) if not result and "_" in locale: locale_short = locale.split("_")[0] new_key = self.LOCALE_EXPR % (key, locale_short) result = self._get_option_desktop(new_key) if result: return result except ValueError: pass # and then the untranslated field return untranslated_value def _get_option_desktop(self, key): if key in self.tag_section: return self.tag_section.get(key) @property def desktopf(self): return self.tagfile class DesktopConfigParser(RawConfigParser, DesktopTagSectionParser): """Thin wrapper that is tailored for xdg Desktop files.""" DE = "Desktop Entry" LOCALE_EXPR = '%s[%s]' def _get_desktop(self, key, translated=True): """Get the generic option 'key' under 'Desktop Entry'.""" # never translate the pkgname if key == self.MAPPING[AppInfoFields.PACKAGE]: return self._get_option_desktop(key) return super(DesktopConfigParser, self)._get_desktop(key, translated) def _get_option_desktop(self, key): if self.has_option(self.DE, key): return self.get(self.DE, key) def read(self, filename): self._filename = filename RawConfigParser.read(self, filename) @property def desktopf(self): return self._filename class ScopeConfigParser(DesktopConfigParser): """Thin wrapper to handle Scope files.""" SCOPE = "Scope" def _get_option_desktop(self, key): # Mark scopes as Type=Scope. if key.lower() == 'type': return 'Scope' value = super(ScopeConfigParser, self)._get_option_desktop(key) if value is not None: return value # Fall back to values from the SCOPE section. if self.has_option(self.SCOPE, key): return self.get(self.SCOPE, key) def ascii_upper(key): """Translate an ASCII string to uppercase in a locale-independent manner.""" ascii_trans_table = string.maketrans(string.ascii_lowercase, string.ascii_uppercase) return key.translate(ascii_trans_table) def update(db, cache, datadir=None): if not datadir: datadir = softwarecenter.paths.APP_INSTALL_DESKTOP_PATH update_from_app_install_data(db, cache, datadir) update_from_var_lib_apt_lists(db, cache) # add db global meta-data LOG.debug("adding popcon_max_desktop %r", popcon_max) db.set_metadata("popcon_max_desktop", xapian.sortable_serialise(float(popcon_max))) def update_from_json_string(db, cache, json_string, origin): """Index from json string, must include origin url (free form string).""" for sec in json.loads(json_string): parser = JsonTagSectionParser(sec, origin) parser.index_app_info(db, cache) return True def update_from_var_lib_apt_lists(db, cache, listsdir=None): """ index the files in /var/lib/apt/lists/*AppInfo """ try: import apt_pkg except ImportError: return False if not listsdir: listsdir = apt_pkg.config.find_dir("Dir::State::lists") context = GLib.main_context_default() for appinfo in glob("%s/*AppInfo" % listsdir): LOG.debug("processing %r", appinfo) # process events while context.pending(): context.iteration() tagf = apt_pkg.TagFile(open(appinfo)) for section in tagf: parser = DesktopTagSectionParser(section, appinfo) parser.index_app_info(db, cache) return True def update_from_single_appstream_file(db, cache, filename): from lxml import etree tree = etree.parse(open(filename)) root = tree.getroot() if not root.tag == "applications": LOG.error("failed to read %r expected Applications root tag", filename) return for appinfo in root.iter("application"): parser = AppStreamXMLParser(appinfo, filename) parser.index_app_info(db, cache) def update_from_appstream_xml(db, cache, xmldir=None): if not xmldir: xmldir = softwarecenter.paths.APPSTREAM_XML_PATH context = GLib.main_context_default() if os.path.isfile(xmldir): update_from_single_appstream_file(db, cache, xmldir) return True for appstream_xml in glob(os.path.join(xmldir, "*.xml")): LOG.debug("processing %r", appstream_xml) # process events while context.pending(): context.iteration() update_from_single_appstream_file(db, cache, appstream_xml) return True def update_from_app_install_data(db, cache, datadir=None): """ index the desktop files in $datadir/desktop/*.desktop """ if not datadir: datadir = softwarecenter.paths.APP_INSTALL_DESKTOP_PATH context = GLib.main_context_default() for desktopf in glob(datadir + "/*.desktop") + glob(datadir + "/*.scope"): LOG.debug("processing %r", desktopf) # process events while context.pending(): context.iteration() try: if desktopf.endswith('.scope'): parser = ScopeConfigParser() else: parser = DesktopConfigParser() parser.read(desktopf) parser.index_app_info(db, cache) except Exception as e: # Print a warning, no error (Debian Bug #568941) LOG.debug("error processing: %r %r", desktopf, e) warning_text = _( "The file: '%s' could not be read correctly. The application " "associated with this file will not be included in the " "software catalog. Please consider raising a bug report " "for this issue with the maintainer of that application") LOG.warning(warning_text, desktopf) return True def update_from_software_center_agent(db, cache, ignore_cache=False, include_sca_qa=False): """Update the index based on the software-center-agent data.""" def _available_cb(sca, available): LOG.debug("update_from_software_center_agent: available: %r", available) sca.available = available sca.good_data = True loop.quit() def _available_for_me_cb(sca, available_for_me): LOG.debug("update_from_software_center_agent: available_for_me: %r", available_for_me) sca.available_for_me = available_for_me loop.quit() def _error_cb(sca, error): LOG.warn("update_from_software_center_agent: error: %r", error) sca.good_data = False loop.quit() context = GLib.main_context_default() loop = GLib.MainLoop(context) sca = SoftwareCenterAgent(ignore_cache) sca.connect("available", _available_cb) sca.connect("available-for-me", _available_for_me_cb) sca.connect("error", _error_cb) sca.available = [] sca.available_for_me = [] # query what is available for me first available_for_me_pkgnames = set() # this will ensure we do not trigger a login dialog helper = UbuntuSSO() token = helper.find_oauth_token_sync() if token: sca.query_available_for_me(no_relogin=True) loop.run() for item in sca.available_for_me: try: parser = SCAPurchasedApplicationParser(item) parser.index_app_info(db, cache) available_for_me_pkgnames.add(item.application["package_name"]) except: LOG.exception("error processing: %r", item) # ... now query all that is available if include_sca_qa: sca.query_available_qa() else: sca.query_available() # create event loop and run it until data is available # (the _available_cb and _error_cb will quit it) loop.run() # process data for entry in sca.available: # do not add stuff here that's already purchased to avoid duplication if entry.package_name in available_for_me_pkgnames: continue # process events while context.pending(): context.iteration() try: # now the normal parser parser = SCAApplicationParser(entry) parser.index_app_info(db, cache) except: LOG.exception("update_from_software_center_agent: " "error processing %r:", entry.name) # return true if we have updated entries (this can also be an empty list) # but only if we did not got a error from the agent return sca.good_data def rebuild_database(pathname, debian_sources=True, appstream_sources=False, appinfo_dir=None): #cache = apt.Cache(memonly=True) cache = get_pkg_info() cache.open() old_path = pathname + "_old" rebuild_path = pathname + "_rb" if not os.path.exists(rebuild_path): try: os.makedirs(rebuild_path) except: LOG.warn("Problem creating rebuild path %r.", rebuild_path) LOG.warn("Please check you have the relevant permissions.") return False # check permission if not os.access(pathname, os.W_OK): LOG.warn("Cannot write to %r.", pathname) LOG.warn("Please check you have the relevant permissions.") return False #check if old unrequired version of db still exists on filesystem if os.path.exists(old_path): LOG.warn("Existing xapian old db was not previously cleaned: %r.", old_path) if os.access(old_path, os.W_OK): #remove old unrequired db before beginning shutil.rmtree(old_path) else: LOG.warn("Cannot write to %r.", old_path) LOG.warn("Please check you have the relevant permissions.") return False # write it db = xapian.WritableDatabase(rebuild_path, xapian.DB_CREATE_OR_OVERWRITE) if debian_sources: update(db, cache, appinfo_dir) if appstream_sources: if os.path.exists('./data/app-stream/appdata.xml'): update_from_appstream_xml(db, cache, './data/app-stream/appdata.xml') else: update_from_appstream_xml(db, cache) # write the database version into the file db.set_metadata("db-schema-version", DB_SCHEMA_VERSION) # update the mo file stamp for the langpack checks mofile = gettext.find("app-install-data") if mofile: mo_time = os.path.getctime(mofile) db.set_metadata("app-install-mo-time", str(mo_time)) db.flush() # use shutil.move() instead of os.rename() as this will automatically # figure out if it can use os.rename or needs to do the move "manually" try: shutil.move(pathname, old_path) shutil.move(rebuild_path, pathname) shutil.rmtree(old_path) return True except: LOG.warn("Cannot copy refreshed database to correct location: %r.", pathname) return False software-center-13.10/softwarecenter/db/database.py0000664000202700020270000006341512216112732022616 0ustar dobeydobey00000000000000# 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 import locale import logging import os import re import string import threading import xapian from softwarecenter.db.application import Application from softwarecenter.db.pkginfo import get_pkg_info import softwarecenter.paths from gi.repository import GObject, Gio, GLib from softwarecenter.utils import ExecutionTime from softwarecenter.enums import ( AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, PkgStates, XapianValues ) from softwarecenter.paths import XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT from gettext import gettext as _ LOG = logging.getLogger(__name__) def get_reinstall_previous_purchases_query(): """Return a query to get applications purchased :return: a xapian query to get all the apps that are purchased """ # this query will give us all documents that have a purchase date != "" query = xapian.Query(xapian.Query.OP_VALUE_GE, XapianValues.PURCHASED_DATE, "1") return query def parse_axi_values_file(filename="/var/lib/apt-xapian-index/values"): """ parse the apt-xapian-index "values" file and provide the information in the self._axi_values dict """ axi_values = {} if not os.path.exists(filename): return axi_values for raw_line in open(filename): line = string.split(raw_line, "#", 1)[0] if line.strip() == "": continue (key, value) = line.split() 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 It provides __eq__ to easily compare two search query lists """ def __init__(self, query_string_or_list): if query_string_or_list is None: pass # turn single queries into a single item list elif isinstance(query_string_or_list, xapian.Query): self.append(query_string_or_list) else: self.extend(query_string_or_list) def __eq__(self, other): # turn single queries into a single item list if isinstance(other, xapian.Query): other = [other] 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)) stats = self.review_loader.get_review_stats(app) import xapian if stats: 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""" # TRANSLATORS: List of "grey-listed" words separated with ";" # Do not translate this list directly. Instead, # provide a list of words in your language that people are likely # to include in a search but that should normally be ignored in # the search. SEARCH_GREYLIST_STR = _("app;application;package;program;programme;" "suite;tool") # signal emitted __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) # initialize at creation time to avoid spurious AttributeError self._use_agent = False self._use_axi = False if pathname is None: pathname = softwarecenter.paths.XAPIAN_PATH self._db_pathname = pathname if cache is None: cache = get_pkg_info() self._aptcache = cache self._additional_databases = [] # the xapian values as read from /var/lib/apt-xapian-index/values self._axi_values = {} # we open one db per thread, thread names are reused eventually # so no memory leak self._db_per_thread = {} self._parser_per_thread = {} self._axi_stamp_monitor = None @property def xapiandb(self): """ returns a per thread db """ thread_name = threading.current_thread().name if not thread_name in self._db_per_thread: self._db_per_thread[thread_name] = self._get_new_xapiandb() return self._db_per_thread[thread_name] @property def xapian_parser(self): """ returns a per thread query parser """ thread_name = threading.current_thread().name if not thread_name in self._parser_per_thread: 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): xapiandb = xapian.Database(self._db_pathname) if self._use_axi: try: axi = xapian.Database( softwarecenter.paths.APT_XAPIAN_INDEX_DB_PATH) xapiandb.add_database(axi) except: 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) xapiandb.add_database(sca) except Exception as e: logging.warn("failed to add sca db %s" % e) for db in self._additional_databases: xapiandb.add_database(db) return xapiandb def _get_new_xapian_parser(self): xapian_parser = xapian.QueryParser() xapian_parser.set_database(self.xapiandb) xapian_parser.add_boolean_prefix("pkg", "XP") xapian_parser.add_boolean_prefix("pkg", "AP") xapian_parser.add_boolean_prefix("mime", "AM") xapian_parser.add_boolean_prefix("section", "XS") xapian_parser.add_boolean_prefix("origin", "XOC") xapian_parser.add_prefix("pkg_wildcard", "XP") xapian_parser.add_prefix("pkg_wildcard", "XPM") xapian_parser.add_prefix("pkg_wildcard", "AP") xapian_parser.add_prefix("pkg_wildcard", "APM") 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.debug("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 = {} # 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) self.nr_databases = 0 self._use_axi = use_axi self._axi_values = {} self._use_agent = use_agent if use_axi: if self._axi_stamp_monitor: self._axi_stamp_monitor.disconnect_by_func( self._on_axi_stamp_changed) self._axi_values = parse_axi_values_file() self.nr_databases += 1 # mvo: we could monitor changes in # softwarecenter.paths.APT_XAPIAN_INDEX_DB_PATH here too # as its a text file that points to the current DB # *if* we do that, we need to change the event == ATTRIBUTE # change in _on_axi_stamp_changed too self._axi_stamp = Gio.File.new_for_path( softwarecenter.paths.APT_XAPIAN_INDEX_UPDATE_STAMP_PATH) self._timeout_id = None self._axi_stamp_monitor = self._axi_stamp.monitor_file(0, None) self._axi_stamp_monitor.connect( "changed", self._on_axi_stamp_changed) if use_agent: self.nr_databases += 1 # additional dbs for db in self._additional_databases: self.nr_databases += 1 self.emit("open", self._db_pathname) def _on_axi_stamp_changed(self, monitor, afile, otherfile, event): # we only care about the utime() update from update-a-x-i if not event == Gio.FileMonitorEvent.ATTRIBUTE_CHANGED: return LOG.debug("afile '%s' changed" % afile) if self._timeout_id: GLib.source_remove(self._timeout_id) self._timeout_id = None self._timeout_id = GLib.timeout_add(500, self.reopen) def add_database(self, database): self._additional_databases.append(database) self.xapiandb.add_database(database) self.reopen() def del_database(self, database): self._additional_databases.remove(database) self.reopen() def schema_version(self): """Return the version of the database layout This is useful to ensure we force a rebuild if it's older than what we expect """ return self.xapiandb.get_metadata("db-schema-version") def reopen(self): """ reopen the database """ LOG.debug("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")) assert popcon_max > 0 return popcon_max def get_query_for_pkgnames(self, pkgnames): """ return a xapian query that matches exactly the list of pkgnames """ enquire = xapian.Enquire(self.xapiandb) query = xapian.Query() for pkgname in pkgnames: # even on the raspberry pi this query is super quick (~0.003s) with ExecutionTime("de-dup query_for_pkgnames"): tmp_query = xapian.Query("AP" + pkgname) enquire.set_query(tmp_query) result = enquire.get_mset(0, 1) # see bug #1043159, we need to ensure that we de-duplicate # when there is a pkg and a app (e.g. from the s-c-agent) in the db if len(result) == 1: query = xapian.Query(xapian.Query.OP_OR, query, xapian.Query("AP" + pkgname)) else: query = xapian.Query(xapian.Query.OP_OR, query, xapian.Query("XP" + pkgname)) return query 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 """ def _add_category_to_query(query): """ helper that adds the current category to the query""" if not category_query: return query return xapian.Query(xapian.Query.OP_AND, category_query, query) # empty query returns a query that matches nothing (for performance # reasons) if search_term == "" and category_query is None: return SearchQuery(xapian.Query()) # we cheat and return a match-all query for single letter searches if len(search_term) < 2: return SearchQuery(_add_category_to_query(xapian.Query(""))) # check if there is a ":" in the search, if so, it means the user # is using a xapian prefix like "pkg:" or "mime:" and in this case # we do not want to alter the search term (as application is in the # greylist but a common mime-type prefix) if not ":" in search_term: # filter query by greylist (to avoid overly generic search terms) 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: LOG.debug("greylist changed search term: '%s'" % search_term) # restore query if it was just greylist words if search_term == '': 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) search_term = search_term.strip() # get a pkg query if "," in search_term: pkg_query = self.get_query_for_pkgnames(search_term.split(",")) else: pkg_query = xapian.Query() for term in search_term.split(): pkg_query = xapian.Query(xapian.Query.OP_OR, 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 # 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) # 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, xapian.QueryParser.FLAG_BOOLEAN) # now add categories fuzzy_query = _add_category_to_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) if isinstance(query, str): if query == "": query = xapian.Query("") else: query = self.xapian_parser.parse_query(query) if category: query = xapian.Query(xapian.Query.OP_AND, category.query, query) enquire.set_query(query) if end == -1: end = len(self) return enquire.get_mset(start, end) def get_docs_from_query(self, query, start=0, end=-1, category=None): matches = self.get_matches_from_query(query, start, end, category) return [m.document for m in matches] def get_spelling_correction(self, search_term): # get a search query if not ':' in search_term: # ie, not a mimetype query # we need this to work around xapian oddness 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): """ return a list of the most popular applications for the given 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) 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), popcon=self.get_popcon(doc)) if only_uninstalled: if app.get_details(self).pkg_state == PkgStates.UNINSTALLED: apps.append(app) else: apps.append(app) if len(apps) == num: break return apps def get_summary(self, doc): """ get human readable summary of the given document """ summary = doc.get_value(XapianValues.SUMMARY) 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: pkgname = self.get_pkgname(doc) if (pkgname in self._aptcache and self._aptcache[pkgname].candidate): return self._aptcache[pkgname].candidate.summary elif channel: # FIXME: print something if available for our arch pass return summary def get_application(self, doc): """ Return a application from a xapian document """ appname = self.get_appname(doc) pkgname = self.get_pkgname(doc) iconname = self.get_iconname(doc) return Application(appname, pkgname, iconname) 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 # 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() # so we use that if we have a updated DB, otherwise # fallback to the old way (the xapian DB may not yet be rebuilt) if self._axi_values and "pkgname" in self._axi_values: pkgname = doc.get_value(self._axi_values["pkgname"]) else: pkgname = doc.get_data() return pkgname def get_appname(self, doc): """ Return a appname from a xapian document, or None if a value for appname cannot be found in the document """ return doc.get_value(XapianValues.APPNAME) def get_iconname(self, doc): """ Return the iconname from the xapian document """ iconname = doc.get_value(XapianValues.ICON) return iconname def get_desktopfile(self, doc): """ Return the desktopfile from the xapian document """ desktopf = doc.get_value(XapianValues.DESKTOP_FILE) return desktopf 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_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) enquire = xapian.Enquire(self.xapiandb) enquire.set_query(pkg_and_cat_query) matches = enquire.get_mset(0, len(self)) if matches: return True return False def get_apps_for_pkgname(self, pkgname): """ Return set of docids with the matching applications for the given pkgname """ result = set() 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) return url def get_popcon(self, doc): """ Return a popcon value from a xapian document """ popcon_raw = doc.get_value(XapianValues.POPCON) if popcon_raw: popcon = xapian.sortable_unserialise(popcon_raw) else: popcon = 0 return popcon def get_xapian_document(self, appname, pkgname): """Get the matching xapian document for appname, pkgname. If no document is found, raise a IndexError. """ #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): 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): 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): doc = self.xapiandb.get_document(m.docid) return doc # no matching document found raise IndexError("No app '%s' for '%s' in database" % (appname, pkgname)) def is_pkgname_known(self, pkgname): """Check if 'pkgname' is known to this database. Note that even if this function returns True, it may mean that the package needs to be purchased first or is available in a not-yet-enabled source. """ # check cache first, then our own database return (pkgname in self._aptcache or any(self.xapiandb.postlist("AP" + 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)): 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 """ for_purchase_query = xapian.Query( "AH" + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME) enquire = xapian.Enquire(self.xapiandb) enquire.set_query(for_purchase_query) matches = enquire.get_mset(0, self.xapiandb.get_doccount()) installed_purchased_packages = set() for m in matches: pkgname = self.get_pkgname(m.document) if (pkgname in self._aptcache and self._aptcache[pkgname].is_installed): installed_purchased_packages.add(pkgname) return installed_purchased_packages def get_origins_from_db(self): """ return all origins available in the current database """ origins = set() for term in self.xapiandb.allterms("XOO"): if term.term[3:]: origins.add(term.term[3:]) 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. """ 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): match = self.xapiandb.get_document(m.docid) for m in self.xapiandb.postlist("AP" + app.pkgname): match = self.xapiandb.get_document(m.docid) matches.append(FakeMSetItem(match)) return matches def __len__(self): """return the doc count of the database""" return self.xapiandb.get_doccount() def __iter__(self): """ support iterating over the documents """ for it in self.xapiandb.postlist(""): doc = self.xapiandb.get_document(it.docid) yield doc class FakeMSetItem(object): def __init__(self, doc): self.document = doc if __name__ == "__main__": import apt import sys db = StoreDatabase("/var/cache/software-center/xapian", apt.Cache()) db.open() if len(sys.argv) < 2: search = "apt,apport" else: search = sys.argv[1] query = db.get_query_list_from_search_entry(search) print(query) enquire = xapian.Enquire(db.xapiandb) enquire.set_query(query) matches = enquire.get_mset(0, len(db)) for m in matches: doc = m.document print(doc.get_data()) # test origin 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)) software-center-13.10/softwarecenter/db/utils.py0000664000202700020270000000512412151440100022172 0ustar dobeydobey00000000000000# Copyright (C) 2011-2013 Canonical Ltd. # # 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 os from gi import version_info as gi_version from gi.repository import GObject, GLib import softwarecenter.paths LOG = logging.getLogger(__name__) def run_software_center_agent(db): """ Helper that triggers the update-software-center-agent helper and will also reopen the database on success """ def _on_update_software_center_agent_finished(pid, condition): LOG.info("software-center-agent finished with status %i" % os.WEXITSTATUS(condition)) if os.WEXITSTATUS(condition) == 0: db.reopen() # run the update sc_agent_update = os.path.join( softwarecenter.paths.datadir, "update-software-center-agent") (pid, stdin, stdout, stderr) = GLib.spawn_async( [sc_agent_update, "--datadir", softwarecenter.paths.datadir], flags=GObject.SPAWN_DO_NOT_REAP_CHILD) # python-gobject >= 3.7.3 has changed some API in incompatible # ways, so we need to check the version for which one to use. if gi_version < (3, 7, 3): GLib.child_watch_add( pid, _on_update_software_center_agent_finished) else: GLib.child_watch_add( GLib.PRIORITY_DEFAULT, pid, _on_update_software_center_agent_finished) def get_installed_apps_list(db): """ return a list of installed applications """ apps = set() for doc in db: if db.get_appname(doc): pkgname = db.get_pkgname(doc) if (pkgname in db._aptcache and db._aptcache[pkgname].is_installed): apps.add(db.get_application(doc)) return apps def get_installed_package_list(): """ return a set of all of the currently installed packages """ from softwarecenter.db.pkginfo import get_pkg_info installed_pkgs = set() cache = get_pkg_info() for pkg in cache: if pkg.is_installed: installed_pkgs.add(pkg.name) return installed_pkgs software-center-13.10/softwarecenter/db/application.py0000664000202700020270000011707212151440100023343 0ustar dobeydobey00000000000000# Copyright (C) 2010 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, Gio import json import locale import logging import os import re import xapian import softwarecenter.distro from gettext import gettext as _ from softwarecenter.backend.channel import is_channel_available from softwarecenter.enums import PkgStates, XapianValues, Icons from softwarecenter.paths import (APP_INSTALL_CHANNELS_PATH, SOFTWARE_CENTER_ICON_CACHE_DIR, ) from softwarecenter.utils import utf8, split_icon_ext, capitalize_first_word from softwarecenter.region import get_region_cached, REGIONTAG 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): """ The central software item abstraction. it contains a pkgname that is always available and a optional appname for packages with multiple applications There is also a __cmp__ method and a name property """ def __init__(self, appname="", pkgname="", request="", popcon=0): if not (appname or pkgname): raise ValueError("Need either appname or pkgname or request") # defaults self.pkgname = pkgname.replace("$kernel", os.uname()[2]) if appname: self.appname = utf8(appname) else: self.appname = '' # the request can take additional "request" data like apturl # strings or the path of a local deb package self.request = request # a archive_suite can be used to force a specific version that # would not be installed automatically (like ubuntu-backports) self.archive_suite = "" # popcon self._popcon = popcon # a "?" in the name means its a apturl request if "?" in pkgname: # the bit before the "?" is the pkgname, everything else the req (self.pkgname, sep, self.request) = pkgname.partition("?") @property def name(self): """Show user visible name""" 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 """ return AppDetails(db, application=self) def get_untranslated_app(self, db): """ return a Application object with the untranslated application name """ try: doc = db.get_xapian_document(self.appname, self.pkgname) except IndexError: return self untranslated_application = doc.get_value( XapianValues.APPNAME_UNTRANSLATED) uapp = Application(untranslated_application, self.pkgname) return uapp @staticmethod def get_display_name(db, doc): """ Return the application name as it should be displayed in the UI If the appname is defined, just return it, else return the summary (per the spec) """ if doc: appname = db.get_appname(doc) if appname: if db.is_appname_duplicated(appname): appname = "%s (%s)" % (appname, db.get_pkgname(doc)) return appname else: return capitalize_first_word(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 doc: if db.get_appname(doc): return capitalize_first_word(db.get_summary(doc)) else: return db.get_pkgname(doc) # special methods def __hash__(self): return utf8("%s:%s" % ( utf8(self.appname), utf8(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 __unicode__(self): return "%s,%s" % (unicode(self.appname, "utf-8", "replace"), unicode(self.pkgname, "utf-8", "replace")) def __repr__(self): return "[Application: appname=%s pkgname=%s]" % (self.appname, self.pkgname) @staticmethod def apps_cmp(x, y): """ sort method for the applications """ # sort(key=locale.strxfrm) would be more efficient, but its # currently broken, see http://bugs.python.org/issue2481 if x.appname and y.appname: return locale.strcoll(x.appname, y.appname) elif x.appname: return locale.strcoll(x.appname, y.pkgname) elif y.appname: return locale.strcoll(x.pkgname, y.appname) 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,), ), } def __init__(self, db, doc=None, application=None): """ Create a new AppDetails object. It can be created from a xapian.Document or from a db.application.Application object """ GObject.GObject.__init__(self) if not doc and not application: raise ValueError("Need either document or application") self._db = db self._db.connect("reopen", self._on_db_reopen) self._cache = self._db._aptcache self._distro = softwarecenter.distro.get_distro() self._history = None # import here (instead of global) to avoid dbus dependency # in update-software-center (that imports application, but # never uses AppDetails) LP: #620011 from softwarecenter.backend.installbackend import get_install_backend self._backend = get_install_backend() # FIXME: why two error states ? self._error = None self._error_not_found = None self._screenshot_list = [] # load application self._app = application if doc: self._app = Application(self._db.get_appname(doc), self._db.get_pkgname(doc), "") # substitute for apturl if self._app.request: self._app.request = self._app.request.replace( "$distro", self._distro.get_codename()) # load pkg cache self._pkg = None if (self._app.pkgname in self._cache and self._cache[self._app.pkgname].candidate): self._pkg = self._cache[self._app.pkgname] # load xapian document self._doc = doc if not self._doc: try: self._doc = self._db.get_xapian_document( self._app.appname, self._app.pkgname) except IndexError: # if there is no document and no apturl request, # set error state debfile_matches = re.findall(r'/', self._app.request) channel_matches = re.findall(r'channel=[a-z,-]*', self._app.request) section_matches = re.findall(r'section=[a-z]*', self._app.request) if (not self._pkg and not debfile_matches and not channel_matches and not section_matches): 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) def same_app(self, other): return self.pkgname == other.pkgname def _on_db_reopen(self, db): if self._doc: try: LOG.debug("db-reopen, refreshing docid for %s" % self._app) self._doc = self._db.get_xapian_document( self._app.appname, self._app.pkgname) except IndexError: LOG.warn("document no longer valid after db reopen") self._doc = None def _get_version_for_archive_suite(self, pkg, archive_suite): """ helper for the multiple versions support """ if not archive_suite: return pkg.candidate else: for ver in pkg.versions: archive_suites = [origin.archive for origin in ver.origins] if archive_suite in archive_suites: return ver raise ValueError("pkg '%s' has not archive_suite '%s'" % ( pkg, archive_suite)) def as_dbus_property_dict(self): """ return all properties as a dict with name/value """ import inspect properties = {} # FIXME: I wish there was a more generic way for this NOT_EXPOSE = set(["props", "pkg"]) for name, value in inspect.getmembers(self): if name.startswith("_") or callable(value) or name in NOT_EXPOSE: continue # dbus does not like sets if isinstance(value, set): value = list(value) # make sure that the type can be encoded if not type(value) in [ list, dict, tuple, basestring, int, float, bool, None]: value = "%s" % value # dbus does not like empty dicts/lists if isinstance(value, dict) or isinstance(value, list): if len(value) == 0: value = "" # and set it properties[name] = value return properties @property def channelname(self): if self._doc: channel = self._doc.get_value(XapianValues.ARCHIVE_CHANNEL) path = APP_INSTALL_CHANNELS_PATH + channel + ".list" if os.path.exists(path): 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) if channel_matches: channel = channel_matches[0] channelfile = APP_INSTALL_CHANNELS_PATH + channel + ".list" if os.path.exists(channelfile): return channel @property def channelfile(self): channel = self.channelname if channel: return APP_INSTALL_CHANNELS_PATH + channel + ".list" @property def eulafile(self): channel = self.channelname if channel: eulafile = APP_INSTALL_CHANNELS_PATH + channel + ".eula" if os.path.exists(eulafile): return eulafile @property def component(self): """Get the component (main, universe, ..). This uses the data from apt, if there is none it uses the data from the app-install-data files. """ # try apt first if self._pkg: for origin in self._pkg.candidate.origins: if (origin.origin == self._distro.get_distro_channel_name() and origin.trusted and origin.component): return origin.component # then xapian elif self._doc: comp = self._doc.get_value(XapianValues.ARCHIVE_SECTION) return comp # then apturl requests else: 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): valid_section_matches.append(section_match) if valid_section_matches: return ('&').join(valid_section_matches) @property def desktop_file(self): if self._doc: return self._doc.get_value(XapianValues.DESKTOP_FILE) @property def description(self): if self._pkg: ver = self._get_version_for_archive_suite( self._pkg, self._app.archive_suite) return ver.description elif self._doc: if self._doc.get_value(XapianValues.SC_DESCRIPTION): return self._doc.get_value(XapianValues.SC_DESCRIPTION) # if its in need-source state and we have a eula, display it # as the description if self.pkg_state == PkgStates.NEEDS_SOURCE and self.eulafile: return open(self.eulafile).read() return "" @property def error(self): if self._error_not_found: return self._error_not_found elif self._error: 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 " u"package called \u201c%s\u201D in your current software " u"sources.")) % utf8(self.pkgname) return self._error_not_found @property def icon(self): if self.pkg_state == PkgStates.NOT_FOUND: return Icons.MISSING_PKG if self._doc: return split_icon_ext(self._db.get_iconname(self._doc)) if not self.summary: return Icons.MISSING_PKG @property def icon_file_name(self): if self._doc: return self._db.get_iconname(self._doc) @property def icon_url(self): if self._doc: return self._db.get_icon_download_url(self._doc) @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)) @property def installation_date(self): from softwarecenter.db.history import get_pkg_history self._history = get_pkg_history() return self._history.get_installed_date(self.pkgname) @property def is_desktop_dependency(self): if self._pkg: depends = self._cache.get_packages_removed_on_remove( self._pkg) if "ubuntu-desktop" in depends: return True return False @property def purchase_date(self): if self._doc: return self._doc.get_value(XapianValues.PURCHASED_DATE) @property def license(self): xapian_license = None if self._doc: xapian_license = self._doc.get_value(XapianValues.LICENSE) if xapian_license: # try to i18n this, the server side does not yet support # translations, but fortunately for the most common ones # like "Proprietary" we have translations in s-c return _(xapian_license) elif self._pkg and self._pkg.license: return self._pkg.license else: return self._distro.get_license_text(self.component) @property def date_published(self): if self._doc: return self._doc.get_value(XapianValues.DATE_PUBLISHED) @property def maintenance_status(self): return self._distro.get_maintenance_status( self._cache, self.display_name, self.pkgname, self.component, self.channelname) @property def name(self): """ Return the name of the application, this will always return Application.name. Most UI will want to use the property display_name instead """ return self._app.name @property def display_name(self): """ Return the application name as it should be displayed in the UI If the appname is defined, just return it, else return the summary (per the spec) """ if self._error_not_found: return self._error if self._doc: return Application.get_display_name(self._db, self._doc) return self.name @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 self._doc: return Application.get_display_summary(self._db, self._doc) return "" @property def pkg(self): if self._pkg: return self._pkg @property def pkgname(self): return self._app.pkgname @property def pkg_state(self): # purchase state if self.pkgname in self._backend.pending_purchases: return PkgStates.INSTALLING_PURCHASED # via the pending transactions dict if self.pkgname in self._backend.pending_transactions: # FIXME: we don't handle upgrades yet # if there is no self._pkg yet, that means this is a INSTALL # from a previously not-enabled source (like a purchase) if self._pkg and self._pkg.installed: return PkgStates.REMOVING else: return PkgStates.INSTALLING # if we have _pkg that means its either: # - available for download (via sources.list) # - locally installed # - installed and available for download # - installed but the user wants to switch versions between # not-automatic channels (like experimental/backports) if self._pkg: if self._pkg.installed and self._app.archive_suite: archive_suites = [origin.archive for origin in self._pkg.installed.origins] if not self._app.archive_suite in archive_suites: return PkgStates.FORCE_VERSION # Don't handle upgrades yet, see bug LP #976525 we need more UI # for this #if self._pkg.installed and self._pkg.is_upgradable: # return PkgStates.UPGRADABLE if self._pkg.is_installed: return PkgStates.INSTALLED else: return PkgStates.UNINSTALLED # if we don't have a _pkg, then its either: # - its in a unavailable repo # - the repository information is outdated # - the repository information is missing (/var/lib/apt/lists empty) # - its a failure in our meta-data (e.g. typo in the pkgname in # the metadata) if not self._pkg: if self.channelname: if self._unavailable_channel(): return PkgStates.NEEDS_SOURCE else: 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.raw_price: return PkgStates.NEEDS_PURCHASE if (self.purchase_date and self._doc.get_value(XapianValues.ARCHIVE_DEB_LINE)): supported_distros = self.supported_distros # Until bug 917109 is fixed on the server we won't have # any supported_distros for a for-purchase app, so we # follow the current behaviour in this case. if not supported_distros: return PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED current_distro = self._distro.get_codename() current_arch = self._distro.get_architecture() if current_distro in supported_distros and ( current_arch in supported_distros[current_distro] or 'any' in supported_distros[current_distro]): return PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED else: return PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES if self.component: components = self.component.split('&') for component in components: 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 " u"package called \u201c%s\u201D in your current " u"software sources.")) % utf8(self.pkgname) return PkgStates.NOT_FOUND return PkgStates.UNKNOWN @property def currency(self): """ Get the currency for the price """ if self._doc: return self._doc.get_value(XapianValues.CURRENCY) @property def raw_price(self): """ The raw price, useful for e.g. sort-by """ if self._doc: return self._doc.get_value(XapianValues.PRICE) # the unity-lens expects a "" string here for "no-price" return "" @property def price(self): """ The price and the currency ready to display to the user or "Free" is its a libre or gratis app """ raw_price = self.raw_price if raw_price == '0.00': # TRANSLATORS: Free here means Gratis return _("Free") currency = self.currency if raw_price and currency: # FIXME: need to determine the currency dynamically once we can # get that info from the software-center-agent/payments # service. # NOTE: the currency string for this label is purposely not # translatable when hard-coded, since it (currently) # won't vary based on locale and as such we don't want # it translated # FIXME: if we support different currencies eventually we need # to format them here differently too, e.g. # "US$ 1" but "1EUR" return "%s %s" % (currency, raw_price) # TRANSLATORS: Free here means Libre return _("Free") @property def supported_distros(self): if self._doc: supported_series = self._doc.get_value( XapianValues.SC_SUPPORTED_DISTROS) if not supported_series: return {} return json.loads(supported_series) @property def ppaname(self): if self._doc: return self._doc.get_value(XapianValues.ARCHIVE_PPA) @property def deb_line(self): if self._doc: return self._doc.get_value(XapianValues.ARCHIVE_DEB_LINE) @property def signing_key_id(self): if self._doc: return self._doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID) @property def screenshot(self): """Return the screenshot url.""" # if there is a custom screenshot url provided, use that if self._doc: # we do support multiple screenshots in the database but # return only one here screenshot_url = self._doc.get_value(XapianValues.SCREENSHOT_URLS) 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, } @property def screenshots(self): """Return a list of screenshots. It's required that "query_multiple_screenshots" was run before and emitted the signal. """ if not self._screenshot_list: return [{ 'small_image_url': self.thumbnail, 'large_image_url': self.screenshot, 'version': self.version, }] return self._screenshot_list # used for the unity data provider only for now @property def size(self): """Return the size of the application without dependencies Note that this will return the download size if the app is not installed and the installed size if it is installed. """ if self._pkg: if not self._pkg.installed: if self._app.archive_suite: ver = self._get_version_for_archive_suite( self._pkg, self._app.archive_suite) if ver: return ver.size return self._pkg.candidate.size else: return self._pkg.installed.size elif self._doc: size = self._doc.get_value(XapianValues.DOWNLOAD_SIZE) if size: return xapian.sortable_unserialise( self._doc.get_value(XapianValues.DOWNLOAD_SIZE)) @property def tags(self): """Return a set() of tags.""" terms = set() if self._doc: for term_iter in self._doc.termlist(): if term_iter.term.startswith("XT"): terms.add(term_iter.term[2:]) return terms def _get_multiple_screenshots_from_db(self): screenshot_list = [] if self._doc: 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, }) 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 that's fine, # and we will query the screenshot server if not self._screenshot_list: self._screenshot_list = self._get_multiple_screenshots_from_db() # check if we have it cached if self._screenshot_list: self.emit("screenshots-available", self._screenshot_list) return # download it distro = self._distro url = distro.SCREENSHOT_JSON_URL % self._app.pkgname try: f = Gio.File.new_for_uri(url) f.load_contents_async( None, self._gio_screenshots_json_download_complete_cb, None) except: LOG.exception("failed to load content") def _sort_screenshots_by_best_version(self, screenshot_list): """ take a screenshot result dict from screenshots.debian.org and sort it """ from softwarecenter.utils import version_compare my_version = self.version # discard screenshots which are more recent than the available version for item in screenshot_list[:]: v = item['version'] if v and version_compare(my_version, v) < 0: 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 ''), reverse=True) def _gio_screenshots_json_download_complete_cb(self, source, result, path): try: res, content, etag = source.load_contents_finish(result) except GObject.GError: # ignore read errors, most likely transient return if content is not None: try: content = json.loads(content) except ValueError as e: LOG.error("can not decode: '%s' (%s)" % (content, e)) content = None if isinstance(content, dict): # a list of screenshots as listed online screenshot_list = content['screenshots'] else: # fallback to a list of screenshots as supplied by the axi screenshot_list = [] # save for later and emit self._screenshot_list = self._sort_screenshots_by_best_version( screenshot_list) self.emit("screenshots-available", self._screenshot_list) @property def summary(self): # not-automatic if self._pkg and self._app.archive_suite: ver = self._get_version_for_archive_suite( self._pkg, self._app.archive_suite) return ver.summary # normal case if self._doc: return capitalize_first_word(self._db.get_summary(self._doc)) elif self._pkg: return self._pkg.candidate.summary @property def thumbnail(self): # if there is a custom thumbnail url provided, use that if self._doc: 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, } @property def video_url(self): # if there is a custom video url provided, use that if self._doc: 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, #} @property def version(self): if self._pkg: if self._pkg.installed and not self._app.archive_suite: return self._pkg.installed.version else: ver = self._get_version_for_archive_suite( self._pkg, self._app.archive_suite) 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) with additional versions of the given package that can be forced with force_not_automatic_archive_suite """ archive_suites = [] if self._pkg: for v in self._pkg.versions: if v.not_automatic: archive_suites.append((v.version, v.origins[0].archive)) # if we have a not automatic version, ensure that the user can # always pick the default too if archive_suites: # get candidate ver = self._pkg.candidate # if the candidate is the not-automatic version, find the first # non-not-automatic one if ver.not_automatic: for ver in sorted(self._pkg.versions, reverse=True): if ver.downloadable and not ver.not_automatic: break archive_suites.insert(0, (ver.version, ver.origins[0].archive)) return archive_suites def force_not_automatic_archive_suite(self, archive_suite): """ this will force to use the given "archive_suite" version of the app (or clears it if archive_suite is empty) """ # set or reset value if archive_suite: # add not-automatic suite to app for ver in self._pkg.versions: if archive_suite in [origin.archive for origin in ver.origins]: self._app.archive_suite = archive_suite return True # no suitable archive found raise ValueError("Pkg '%s' has no archive_suite '%s'" % ( self._pkg, archive_suite)) else: # clear version self._app.archive_suite = "" return True @property def warning(self): # 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) if minver_matches and self.version: minver = minver_matches[0][7:] from softwarecenter.utils import version_compare if version_compare(minver, self.version) > 0: return _("Version %s or later not available.") % minver # can we enable a source if not self._pkg: source_to_enable = None if self.channelname and self._unavailable_channel(): source_to_enable = self.channelname elif (self.component and self.component not in ("independent", "commercial")): source_to_enable = self.component if source_to_enable: sources = source_to_enable.split('&') sources_length = len(sources) if sources_length == 1: 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: # Available from the following sources: %s, ... %s, %s. warning = _("Available from the following sources: ") # Cycle through all, but the last for source in sources[:-1]: warning += _("\"%s\", ") % source warning += _("\"%s\".") % sources[sources_length - 1] return warning @property def website(self): """This application's website (homepage).""" result = None if self._pkg: result = self._pkg.website elif self._doc: result = self._doc.get_value(XapianValues.WEBSITE) return result @property def supportsite(self): if self._doc: return self._doc.get_value(XapianValues.SUPPORT_SITE_URL) @property def license_key(self): if self._doc: return self._doc.get_value(XapianValues.LICENSE_KEY) return "" @property def license_key_path(self): if self._doc: return self._doc.get_value(XapianValues.LICENSE_KEY_PATH) @property def region_requirements_satisfied(self): my_region = get_region_cached()["countrycode"] # if there are no region tag we are good res = True for tag in self.tags: if tag.startswith(REGIONTAG): # we found a region tag, now the region must match res = False if tag == REGIONTAG + my_region: # we have the right region return True return res @property def hardware_requirements_satisfied(self): for key, value in self.hardware_requirements.iteritems(): if value == "no": return False return True @property def hardware_requirements(self): result = {} try: 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 """ 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 """ if component_to_check: component = component_to_check elif self.component: component = self.component else: component = self._doc.get_value(XapianValues.ARCHIVE_SECTION) if not component: return False distro_codename = self._distro.get_codename() available = self._cache.component_available(distro_codename, component) return (not available) def __str__(self): details = [] details.append("* AppDetails") details.append(" name: %s" % self.name) details.append(" display_name: %s" % self.display_name) details.append(" pkg: %s" % self.pkg) details.append(" pkgname: %s" % self.pkgname) details.append(" channelname: %s" % self.channelname) details.append(" ppa: %s" % self.ppaname) details.append(" channelfile: %s" % self.channelfile) details.append(" component: %s" % self.component) details.append(" desktop_file: %s" % self.desktop_file) details.append(" description: %s" % self.description) details.append(" error: %s" % self.error) details.append(" icon: %s" % self.icon) details.append(" icon_file_name: %s" % self.icon_file_name) details.append(" icon_url: %s" % self.icon_url) 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_path: %s" % self.license_key_path) details.append(" date_published: %s" % self.date_published) details.append(" maintenance_status: %s" % self.maintenance_status) details.append(" pkg_state: %s" % self.pkg_state) details.append(" price: %s" % self.price) details.append(" screenshot: %s" % self.screenshot) details.append(" summary: %s" % self.summary) details.append(" display_summary: %s" % self.display_summary) details.append(" thumbnail: %s" % self.thumbnail) details.append(" version: %s" % self.version) details.append(" website: %s" % self.website) return '\n'.join(details) software-center-13.10/softwarecenter/db/history.py0000664000202700020270000000547312151440100022542 0ustar dobeydobey00000000000000# 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 import logging LOG = logging.getLogger(__name__) class Transaction(object): """ Represents an pkg transaction o Attributes: - 'start_date': the start date/time of the transaction as datetime - 'install', 'upgrade', 'downgrade', 'remove', 'purge': contain the list of packagenames affected by this action """ PKGACTIONS = ["Install", "Upgrade", "Downgrade", "Remove", "Purge"] def __init__(self): pass def __len__(self): count = 0 for k in self.PKGACTIONS: count += len(getattr(self, k.lower())) return count def __repr__(self): return (' 0 and trans.start_date <= self._transactions[0].start_date): continue # add it # FIXME: this is a list, so potentially slow, but its sorted # 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 self.update_callback: self.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: for pkg in trans.install: if pkg.split(" ")[0] == pkg_name: installed_date = trans.start_date return installed_date return installed_date def _find_in_terminal_log(self, date, term_file): found = False term_lines = [] for line in term_file: if line.startswith("Log started: %s" % date): found = True elif line.endswith("Log ended") or line.startswith("Log started"): found = False if found: term_lines.append(line) return term_lines def find_terminal_log(self, date): """Find the terminal log part for the given transaction (this can be rather slow) """ # FIXME: try to be more clever here with date/file timestamps term = apt_pkg.config.find_file("Dir::Log::Terminal") 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"): term_lines = self._find_in_terminal_log(date, gzip.open(f)) if term_lines: return term_lines return term_lines software-center-13.10/softwarecenter/db/history_impl/__init__.py0000664000202700020270000000000012151440100025277 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/db/pkginfo.py0000664000202700020270000001471212151440100022472 0ustar dobeydobey00000000000000# Copyright (C) 2010 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 class _Version: @property def description(self): pass @property def downloadable(self): pass @property def summary(self): pass @property def size(self): return self.pkginfo.get_size(self.name) @property def installed_size(self): return 0 @property def version(self): pass @property def origins(self): return [] @property def record(self): return {} @property def not_automatic(self): """ should not be installed/upgraded automatically, the user needs to opt-in once (used for e.g. ubuntu-backports) """ return False class _Package: def __init__(self, name, pkginfo): self.name = name self.pkginfo = pkginfo def __str__(self): return repr(self).replace('<', ' 0: self.emit("cache-broken") # implementation specific code # temporarily return a full apt.Package so that the tests and the # code keeps working for now, this needs to go away eventually # and get replaced with the abstract _Package class #def __getitem__(self, key): # return self._cache[key] def __iter__(self): return self._cache.__iter__() def __contains__(self, k): return self._cache.__contains__(k) def _on_apt_finished_stamp_changed(self, monitor, afile, other_file, event): if not event == Gio.FileMonitorEvent.CHANGES_DONE_HINT: return if self._timeout_id: GLib.source_remove(self._timeout_id) self._timeout_id = None self._timeout_id = GLib.timeout_add_seconds(10, self.open) def _get_rdepends_by_type(self, pkg, type, onlyInstalled): rdeps = set() # make sure this is a apt.Package object try: pkg = self._cache[pkg.name] except KeyError: LOG.error("package %s not found in AptCache" % str(pkg)) return rdeps for rdep in pkg._pkg.rev_depends_list: dep_type = rdep.dep_type_untranslated if dep_type in type: rdep_name = rdep.parent_pkg.name if (rdep_name in self._cache and (not onlyInstalled or ( onlyInstalled and self._cache[rdep_name].is_installed))): rdeps.add(rdep.parent_pkg.name) return rdeps def _installed_dependencies(self, pkg_name, all_deps=None): """ recursively return all installed dependencies of a given pkg """ #print "_installed_dependencies", pkg_name, all_deps if not all_deps: all_deps = set() if pkg_name not in self._cache: return all_deps cur = self._cache[pkg_name]._pkg.current_ver if not cur: return all_deps for t in self.DEPENDENCY_TYPES + self.RECOMMENDS_TYPES: try: for dep in cur.depends_list[t]: dep_name = dep[0].target_pkg.name if not dep_name in all_deps: all_deps.add(dep_name) all_deps |= self._installed_dependencies(dep_name, all_deps) except KeyError: pass return all_deps def get_installed_automatic_depends_for_pkg(self, pkg): """ Get the installed automatic dependencies for this given package only. Note that the package must be marked for removal already for this to work Not: unused """ installed_auto_deps = set() deps = self._installed_dependencies(pkg.name) for dep_name in deps: try: pkg = self._cache[dep_name] except KeyError: continue else: if (pkg.is_installed and pkg.is_auto_removable): installed_auto_deps.add(dep_name) return installed_auto_deps def get_all_origins(self): """ return a set of the current channel origins from the apt.Cache itself """ origins = set() for pkg in self._cache: if not pkg.candidate: continue for item in pkg.candidate.origins: context = GLib.main_context_default() while context.pending(): context.iteration() if item.origin: origins.add(item.origin) return origins def get_origins(self, pkgname): """ return package origins from apt.Cache """ if not pkgname in self._cache or not self._cache[pkgname].candidate: return origins = set() for origin in self._cache[pkgname].candidate.origins: if origin.origin: origins.add(origin) return origins def get_origin(self, pkgname): """ return a unique origin for the given package name. currently this will use """ if not pkgname in self._cache or not self._cache[pkgname].candidate: return None origins = set([origin.origin for origin in self.get_origins(pkgname)]) if len(origins) > 1: LOG.warn("more than one origin '%s'" % origins) return None if not origins: return None # we support only a single origin (but its fine if that is available # on multiple mirrors). lowercase as the server excepts it this way origin_str = origins.pop() return origin_str.lower() def component_available(self, distro_codename, component): """ check if the given component is enabled """ # FIXME: test for more properties here? for it in self._cache._cache.file_list: if (it.component != "" and it.component == component and it.archive != "" and it.archive == distro_codename): return True return False @convert_package_argument def _get_depends_by_type(self, pkg, types): version = pkg.installed if version is None: version = pkg.candidate return version.get_dependencies(*types) def _get_depends_by_type_str(self, pkg, *types): def not_in_list(list, item): for i in list: if i == item: return False return True deps = self._get_depends_by_type(pkg, *types) deps_str = [] for dep in deps: for dep_ in dep.or_dependencies: if not_in_list(deps_str, dep_.name): deps_str.append(dep_.name) return deps_str # FIXME: there are cleaner ways to do this than below # pkg relations def _get_depends(self, pkg): return self._get_depends_by_type_str(pkg, self.DEPENDENCY_TYPES) def _get_recommends(self, pkg): return self._get_depends_by_type_str(pkg, self.RECOMMENDS_TYPES) def _get_suggests(self, pkg): return self._get_depends_by_type_str(pkg, self.SUGGESTS_TYPES) def _get_enhances(self, pkg): return self._get_depends_by_type_str(pkg, self.ENHANCES_TYPES) @convert_package_argument def _get_provides(self, pkg): # note: can use ._cand, because pkg has been converted to apt.Package provides_list = pkg.candidate._cand.provides_list provides = [] for provided in provides_list: provides.append(provided[0]) # the package name return provides # reverse pkg relations def _get_rdepends(self, pkg): return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, False) def _get_rrecommends(self, pkg): return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, False) def _get_rsuggests(self, pkg): return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, False) def _get_renhances(self, pkg): return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, False) @convert_package_argument def _get_renhances_lowlevel_apt_pkg(self, pkg): """ takes a apt_pkg.Package and returns a list of pkgnames that enhance this package - this is needed to support enhances for virtual packages """ renhances = [] for dep in pkg.rev_depends_list: if dep.dep_type_untranslated == "Enhances": renhances.append(dep.parent_pkg.name) return renhances def _get_rprovides(self, pkg): return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, False) # installed reverse pkg relations def get_packages_removed_on_remove(self, pkg): return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, True) def get_packages_removed_on_install(self, pkg): depends = set() deps_remove = self._try_install_and_get_all_deps_removed(pkg) for depname in deps_remove: if self._cache[depname].is_installed: depends.add(depname) return depends def _get_installed_rrecommends(self, pkg): return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, True) def _get_installed_rsuggests(self, pkg): return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, True) def _get_installed_renhances(self, pkg): return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, True) def _get_installed_rprovides(self, pkg): return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, True) # language pack stuff def _is_language_pkg(self, addon): # a simple "addon in self._language_packages" is not enough for template in self._language_packages: if addon.startswith(template): return True return False def _read_language_pkgs(self): language_packages = set() if not os.path.exists(self.LANGPACK_PKGDEPENDS): return language_packages for line in open(self.LANGPACK_PKGDEPENDS): line = line.strip() if line.startswith('#'): continue try: (cat, code, dep_pkg, language_pkg) = line.split(':') except ValueError: continue language_packages.add(language_pkg) return language_packages # these are used for calculating the total size @convert_package_argument def _get_changes_without_applying(self, pkg): try: if pkg.installed is None: pkg.mark_install() else: pkg.mark_delete() except SystemError: # TODO: ideally we now want to display an error message # and block the install button LOG.warning("broken packages encountered while getting deps for %s" % pkg.name) return {} changes_tmp = self._cache.get_changes() changes = {} for change in changes_tmp: if change.marked_install or change.marked_reinstall: changes[change.name] = PkgStates.INSTALLING elif change.marked_delete: changes[change.name] = PkgStates.REMOVING elif change.marked_upgrade: changes[change.name] = PkgStates.UPGRADING else: changes[change.name] = PkgStates.UNKNOWN self._cache.clear() return changes def _try_install_and_get_all_deps_installed(self, pkg): """ Return all dependencies of pkg that will be marked for install """ changes = self._get_changes_without_applying(pkg) installing_deps = [] for change in changes.keys(): if change != pkg.name and changes[change] == PkgStates.INSTALLING: installing_deps.append(change) return installing_deps def _try_install_and_get_all_deps_removed(self, pkg): """ Return all dependencies of pkg that will be marked for remove""" changes = self._get_changes_without_applying(pkg) removing_deps = [] for change in changes.keys(): if change != pkg.name and changes[change] == PkgStates.REMOVING: removing_deps.append(change) return removing_deps def _set_candidate_release(self, pkg, archive_suite): # Check if the package is provided in the release for version in pkg.versions: if [origin for origin in version.origins if origin.archive == archive_suite]: break else: return False res = pkg._pcache._depcache.set_candidate_release( pkg._pkg, version._cand, archive_suite) return res # space calculation stuff def _on_total_size_calculation_done(self, trans, space): pkgname = trans.packages[0][0] self.emit( "query-total-size-on-install-done", pkgname, trans.download, trans.space) def _on_trans_simulate_error(self, error): LOG.exception("simulate failed") def _on_trans_commit_packages_ready(self, trans): trans.connect("space-changed", self._on_total_size_calculation_done) try: trans.simulate(reply_handler=lambda: True, error_handler=self._on_trans_simulate_error) except: LOG.exception("simulate failed") def query_total_size_on_install(self, pkgname, addons_install=[], addons_remove=[], archive_suite=""): if not pkgname in self._cache: self.emit("query-total-size-on-install-done", pkgname, 0, 0) # ensure the syntax is right if archive_suite: pkgname = pkgname + "/" + archive_suite # and simulate the install/remove via aptdaemon install = [pkgname] + addons_install remove = addons_remove reinstall = purge = upgrade = downgrade = [] if self._aptd_trans: self._aptd_trans.cancel() self._aptd_trans = None # do this async try: self.aptd_client.commit_packages( install, reinstall, remove, purge, upgrade, downgrade, # wait False, # reply and error handlers self._on_trans_commit_packages_ready, self._on_trans_simulate_error) except: LOG.exception( "getting commit_packages trans failed for '%s'" % pkgname) def get_all_deps_upgrading(self, pkg): # note: this seems not to be used anywhere changes = self._get_changes_without_applying(pkg) upgrading_deps = [] for change in changes.keys(): if change != pkg.name and changes[change] == PkgStates.UPGRADING: upgrading_deps.append(change) return upgrading_deps # determine the addons for a given package def get_addons(self, pkgname, ignore_installed=True): """ get the list of addons for the given pkgname The optional parameter "ignore_installed" controls if the output should be filtered and pkgs already installed should be ignored in the output (e.g. useful for testing). :return: a tuple of pkgnames (recommends, suggests) """ logging.debug("get_addons for '%s'" % pkgname) def _addons_filter(addon): """ helper for get_addons that filters out unneeded ones """ # we don't know about this one (perfectly legal for suggests) if not addon in self._cache: LOG.debug("not in cache %s" % addon) return False # can happen via "lonely" depends if addon == pkg.name: LOG.debug("circular %s" % addon) return False # child pkg is addon of parent pkg, not the other way around. if addon == '-'.join(pkgname.split('-')[:-1]): LOG.debug("child > parent %s" % addon) return False # get the pkg addon_pkg = self._cache[addon] # we don't care for essential or important (or references # to our self) if addon_pkg.essential or addon_pkg._pkg.important: LOG.debug("essential or important %s" % addon) return False # we have it in our dependencies already if addon in deps: LOG.debug("already a dependency %s" % addon) return False # its a language-pack, language-selector should deal with it if self._is_language_pkg(addon): LOG.debug("part of language pkg rdepends %s" % addon) return False # something on the system depends on it rdeps = self.get_packages_removed_on_remove(addon_pkg) if rdeps and ignore_installed: LOG.debug("already has a installed rdepends %s" % addon) return False # looks good return True #---------------------------------------------------------------- def _addons_filter_slow(addon): """ helper for get_addons that filters out unneeded ones """ # this addon would get installed anyway (e.g. via indirect # dependency) so it would be misleading to show it if addon in all_deps_if_installed: LOG.debug("would get installed automatically %s" % addon) return False return True #---------------------------------------------------------------- # deb file, or pkg needing source, etc if not pkgname in self._cache or not self._cache[pkgname].candidate: return ([], []) # initial setup pkg = self._cache[pkgname] # recommended addons addons_rec = self._get_recommends(pkg) LOG.debug("recommends: %s" % addons_rec) # suggested addons and renhances addons_sug = self._get_suggests(pkg) LOG.debug("suggests: %s" % addons_sug) renhances = self._get_renhances(pkg) LOG.debug("renhances: %s" % renhances) addons_sug += renhances provides = self._get_provides(pkg) LOG.debug("provides: %s" % provides) for provide in provides: virtual_aptpkg_pkg = self._cache._cache[provide] renhances = self._get_renhances_lowlevel_apt_pkg( virtual_aptpkg_pkg) LOG.debug("renhances of %s: %s" % (provide, renhances)) addons_sug += renhances context = GLib.main_context_default() while context.pending(): context.iteration() # get more addons, the idea is that if a package foo-data # just depends on foo we want to get the info about # "recommends, suggests, enhances" for foo-data as well # # FIXME: find a good package where this is actually the case and # replace the existing test # (arduino-core -> avrdude -> avrdude-doc) with that # FIXME2: if it turns out we don't have good/better examples, # kill it deps = self._get_depends(pkg) for dep in deps: if dep in self._cache: pkgdep = self._cache[dep] if len(self._get_rdepends(pkgdep)) == 1: # pkg is the only known package that depends on pkgdep pkgdep_rec = self._get_recommends(pkgdep) LOG.debug("recommends from lonely dependency %s: %s" % ( pkgdep, pkgdep_rec)) addons_rec += pkgdep_rec pkgdep_sug = self._get_suggests(pkgdep) LOG.debug("suggests from lonely dependency %s: %s" % ( pkgdep, pkgdep_sug)) addons_sug += pkgdep_sug pkgdep_enh = self._get_renhances(pkgdep) LOG.debug("renhances from lonely dependency %s: %s" % ( pkgdep, pkgdep_enh)) addons_sug += pkgdep_enh context = GLib.main_context_default() while context.pending(): context.iteration() # remove duplicates from suggests (sets are great!) addons_sug = list(set(addons_sug) - set(addons_rec)) # filter out stuff we don't want addons_rec = filter(_addons_filter, addons_rec) addons_sug = filter(_addons_filter, addons_sug) # this is not integrated into the filter above, as it is quite # expensive to run this call, so we only run it if we actually have # addons if addons_rec or addons_sug: # now get all_deps if the package would be installed try: all_deps_if_installed = \ self._try_install_and_get_all_deps_installed(pkg) except: # if we have broken packages, then we return no addons LOG.warn( "broken packages encountered while getting deps for %s" % pkgname) return ([], []) # filter out stuff we don't want addons_rec = filter(_addons_filter_slow, addons_rec) addons_sug = filter(_addons_filter_slow, addons_sug) return (addons_rec, addons_sug) if __name__ == "__main__": c = AptCache() c.open() print("deps of unrar") print(c._installed_dependencies(c["unrar"].name)) print("unused deps of 4g8") pkg = c._cache["4g8"] pkg.mark_delete() print(c.get_installed_automatic_depends_for_pkg(pkg)) pkg = c["unace"] print(c.get_installed_automatic_depends_for_pkg(pkg)) print(c.get_packages_removed_on_remove(pkg)) print(c._get_installed_rrecommends(pkg)) print(c._get_installed_rsuggests(pkg)) print("deps of gimp") pkg = c["gimp"] print(c._get_depends(pkg)) print(c._get_recommends(pkg)) print(c._get_suggests(pkg)) print(c._get_enhances(pkg)) print(c._get_provides(pkg)) print("rdeps of gimp") print(c._get_rdepends(pkg)) print(c._get_rrecommends(pkg)) print(c._get_rsuggests(pkg)) print(c._get_renhances(pkg)) print(c._get_rprovides(pkg)) software-center-13.10/softwarecenter/db/pkginfo_impl/__init__.py0000664000202700020270000000000012151440100025233 0ustar dobeydobey00000000000000software-center-13.10/softwarecenter/region.py0000664000202700020270000001066412151440100021735 0ustar dobeydobey00000000000000# Copyright (C) 2012 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 import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import locale import logging import os from gettext import dgettext from gettext import gettext as _ LOG = logging.getLogger(__name__) # warning displayed if region does not match REGION_WARNING_STRING = _("Sorry, this software is not available in your " "region.") # the prefix tag for regions that the software is most useful in REGIONTAG = "iso3166::" # blacklist this region REGION_BLACKLIST_TAG = "blacklist-iso3166::" # or whitelist it REGION_WHITELIST_TAG = "whitelist-iso3166::" def get_region_name(countrycode): """ return translated region name from countrycode using iso3166 """ from lxml import etree # find translated name if countrycode: for iso in ["iso_3166", "iso_3166_2"]: path = os.path.join("/usr/share/xml/iso-codes/", iso + ".xml") if os.path.exists(path): root = etree.parse(path) xpath = ".//%s_entry[@alpha_2_code='%s']" % (iso, countrycode) match = root.find(xpath) if match is not None: name = match.attrib.get("common_name") if not name: name = match.attrib["name"] 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 class AllowedResources: NONE = 0 NETWORK = 1 << 0 CELL = 1 << 1 GPS = 1 << 2 ALL = (1 << 10) - 1 class RegionDiscover(object): def get_region(self): """ return a dict with at least "county" and "countrycode" as keys - they may be empty if no region is found """ res = {} # try geoclue first try: res = self._get_region_geoclue() if res: return res except Exception as e: LOG.warn("failed to use geoclue: '%s'" % e) # fallback res = self._get_region_dumb() return res def _get_region_dumb(self): """ return dict estimate about the current countrycode/country """ res = {'countrycode': '', 'country': '', } try: # use LC_MONETARY as the best guess loc = locale.getlocale(locale.LC_MONETARY)[0] except Exception as e: LOG.warn("Failed to get locale: '%s'" % e) return res if not loc: return res countrycode = loc.split("_")[1] res["countrycode"] = countrycode res["country"] = get_region_name(countrycode) return res def _get_region_geoclue(self): """ return the dict with at least countrycode,country from a geoclue provider """ bus = dbus.SessionBus() master = bus.get_object( '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 0, # (i) time False, # (b) require_updates AllowedResources.ALL) # (i) allowed_resources # this is crucial client.AddressStart() # now get the data time, address_res, accuracy = client.GetAddress() return address_res my_region = None def get_region_cached(): global my_region if my_region is None: rd = RegionDiscover() my_region = rd.get_region() return my_region software-center-13.10/data/0000755000202700020270000000000012224614354015764 5ustar dobeydobey00000000000000software-center-13.10/data/channels/0000755000202700020270000000000012224614354017557 5ustar dobeydobey00000000000000software-center-13.10/data/channels/Ubuntu/0000755000202700020270000000000012224614354021041 5ustar dobeydobey00000000000000software-center-13.10/data/channels/Ubuntu/ubuntu-extras.list.in0000664000202700020270000000036312151440100025156 0ustar dobeydobey00000000000000 ## This software is not part of Ubuntu, but is offered by third-party ## developers who want to ship their latest software. deb http://extras.ubuntu.com/ubuntu/ #DISTROSERIES# main deb-src http://extras.ubuntu.com/ubuntu/ #DISTROSERIES# main software-center-13.10/data/channels/Ubuntu/ubuntu-extras.eula0000664000202700020270000000023712151440100024524 0ustar dobeydobey00000000000000The Ubuntu independent channel contains applications that are available for Ubuntu from third-party software developers who want to ship their latest software.software-center-13.10/data/delete_review_gtk3.py0000777000202700020270000000000012151440077027604 2../utils/delete_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/data/dbus/0000755000202700020270000000000012224614354016721 5ustar dobeydobey00000000000000software-center-13.10/data/dbus/com.ubuntu.SoftwareCenterDataProvider.service0000664000202700020270000000016012151440100027621 0ustar dobeydobey00000000000000[D-BUS Service] Name=com.ubuntu.SoftwareCenterDataProvider Exec=/usr/share/software-center/software-center-dbus software-center-13.10/data/dbus/com.ubuntu.SoftwareCenter.conf0000664000202700020270000000074612151440100024613 0ustar dobeydobey00000000000000 software-center-13.10/data/piston_generic_helper.py0000777000202700020270000000000012151440077034150 2../utils/piston-helpers/piston_generic_helper.pyustar dobeydobey00000000000000software-center-13.10/data/icons/0000755000202700020270000000000012224614354017077 5ustar dobeydobey00000000000000software-center-13.10/data/icons/128x128/0000755000202700020270000000000012224614354020034 5ustar dobeydobey00000000000000software-center-13.10/data/icons/128x128/apps/0000755000202700020270000000000012224614354020777 5ustar dobeydobey00000000000000software-center-13.10/data/icons/128x128/apps/softwarecenter.svg0000664000202700020270000033204712151440100024547 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/16x16/0000755000202700020270000000000012224614354017664 5ustar dobeydobey00000000000000software-center-13.10/data/icons/16x16/apps/0000755000202700020270000000000012224614354020627 5ustar dobeydobey00000000000000software-center-13.10/data/icons/16x16/apps/softwarecenter.svg0000664000202700020270000004660012151440100024374 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/24x24/0000755000202700020270000000000012224614354017662 5ustar dobeydobey00000000000000software-center-13.10/data/icons/24x24/apps/0000755000202700020270000000000012224614354020625 5ustar dobeydobey00000000000000software-center-13.10/data/icons/24x24/apps/ppa.svg0000664000202700020270000005507212151440100022122 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/24x24/apps/unknown-channel.svg0000664000202700020270000003426512151440100024450 0ustar dobeydobey00000000000000 software-center-13.10/data/icons/24x24/apps/softwarecenter.svg0000664000202700020270000005660012151440100024373 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/48x48/0000755000202700020270000000000012224614354017676 5ustar dobeydobey00000000000000software-center-13.10/data/icons/48x48/apps/0000755000202700020270000000000012224614354020641 5ustar dobeydobey00000000000000software-center-13.10/data/icons/48x48/apps/softwarecenter.svg0000664000202700020270000005532612151440100024413 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/scalable/0000755000202700020270000000000012224614354020645 5ustar dobeydobey00000000000000software-center-13.10/data/icons/scalable/apps/0000755000202700020270000000000012224614354021610 5ustar dobeydobey00000000000000software-center-13.10/data/icons/scalable/apps/category-show-all.svg0000664000202700020270000007520412151440100025665 0ustar dobeydobey00000000000000 software-center-13.10/data/icons/scalable/apps/softwarecenter.svg0000664000202700020270000033204712151440100025360 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/icons/scalable/apps/partner.svg0000664000202700020270000000571212151440100023774 0ustar dobeydobey00000000000000 image/svg+xml software-center-13.10/data/update-software-center-channels0000777000202700020270000000000012151440077033542 2../utils/update-software-center-channelsustar dobeydobey00000000000000software-center-13.10/data/update-software-center-agent0000777000202700020270000000000012151440077032350 2../utils/update-software-center-agentustar dobeydobey00000000000000software-center-13.10/data/submit_usefulness_gtk3.py0000777000202700020270000000000012151440077031474 2../utils/submit_usefulness_gtk3.pyustar dobeydobey00000000000000software-center-13.10/data/extra-unity-categories.menu.in0000664000202700020270000000152212151440100023656 0ustar dobeydobey00000000000000 unity-whats-new available-only carousel-only not-installed-only 3 20 Application unity-top-rated available-only carousel-only 4 100 Application software-center-13.10/data/emblems/0000755000202700020270000000000012224614354017410 5ustar dobeydobey00000000000000software-center-13.10/data/emblems/software-center-installed.png0000664000202700020270000000236012151440100025167 0ustar dobeydobey00000000000000‰PNG  IHDRàw=øsBIT|dˆ pHYs a aüÌJ%tEXtSoftwarewww.inkscape.org›î<mIDATH‰¥–klUÇwfv§»[hw»-R©i¬DC‰¶Dc€¢‰–‡4ñbPHŒ&FP£‰4H“‚‰1á‹!Ä(!&Š‘¨M,P„ Äøà]1u)Êk+´¥»îÎÎÜ{ý°Û¦°Å%z’“™Ü{æÿ?{Ï¡µ¦”!DáÕ,<%€¾Åõl  A`"÷?2³໯O$Ò@îzd!L ¶ð‰æç/ùJÍ”xCMõMáHÐÈ8žßw)9ôw_b÷¶cïïúìð`@k-ÿ• àµ Ô¿¶iñ»,œóPŸ×m z½ zçq¼ËDq¢iTë©¶ÕÞ®ï¬è|8¸ã£¹– ìîŵ ž]Óúq|êäX÷І¼ '· Z+Ûè¿ؼ¾ë™Ÿ;/ti­³EB ˜¾ñÀcìX&špö£Š#žP aÒP~^hð¥¹Ûç=ZkÀ—šª•›îÚŽûÑÄpið["÷°®ù"íM稲o%‘þžp\FŸßÔ´¨=yFÁÞž½,ÞvGKEk¯sEiÏ®{›YÁñ+\Êö œv2c^¬µeY¼|-Ç&ÏZx£?wZ¸r¸$øÑG©/ŸMN9ì:ß>¶ž•iús Ñüdh 0À*„1£Cµ#> &SXÍ¥L‚ª²z~¸¸•³©H jœ"l´6É +ü,tïÐí@j´î/[²Ÿ§’æßñ"„VŽ;ÄGçlú(§‡~䫞7Ç@¥¥ „Èœ$—– þ¥~=ü©÷àÂÍî©m‘ÃvÈ´‘ h‰F¡¤‡F£µBÓ "0† ÂÀ÷²äR.žKö“åî,®mv……Þƒ:˲Ã^&—rÑ~–(Ç ˜¢« †AIA6åäÁ³:sh‹»è¿*‚Bc§m}ÙæšÛ­"´ Úa ÃÄóGp=íK¤ç!„Ò}¿ëý;×zÏQjàŒ#2ØôE,˜1ßZ®L³#)}¤’ÚËêL6¥Îý¶×oïù†.ndd^CR4ôëïå6L8s?ø?Cÿ:dð~[þò—HAíÿ|IEND®B`‚software-center-13.10/data/software-center.menu.in0000664000202700020270000003445712151440100022367 0ustar dobeydobey00000000000000 <_Name>Accessories Utility.directory Utility Accessibility <_Name>Universal Access Utility-Accessibility.directory Accessibility Settings <_Name>Developer Tools applications-development Development devel restricted/devel universe/devel multiverse/devel emacs.desktop <_Name>Debugging applications-debugging Debugger <_Name>Graphic Interface Design applications-interfacedesign GUIDesigner <_Name>Haskell applications-haskell haskell restricted/haskell universe/haskell multiverse/haskell <_Name>IDEs applications-ide IDE <_Name>Java applications-java java restricted/java universe/java multiverse/java <_Name>Libraries applications-libraries libdevel restricted/libdevel universe/libdevel multiverse/libdevel <_Name>Lisp applications-lisp lisp restricted/lisp universe/lisp multiverse/lisp <_Name>Localization preferences-desktop-locale Translation <_Name>Mono/CLI applications-monodevelopment cli-mono restricted/cli-mono universe/cli-mono multiverse/cli-mono <_Name>OCaml applications-ocaml ocaml restricted/ocaml universe/ocaml multiverse/ocaml <_Name>Perl applications-perl perl restricted/perl universe/perl multiverse/perl <_Name>Profiling applications-profiling Profiling <_Name>Python applications-python python restricted/python universe/python multiverse/python <_Name>Ruby applications-ruby ruby restricted/ruby universe/ruby multiverse/ruby <_Name>Version Control applications-versioncontrol vcs restricted/vcs universe/vcs multiverse/vcs RevisionControl <_Name>Web Development WebDevelopment applications-internet <_Name>Education Education.directory Education Science X-Publication <_Name>Science & Engineering applications-science Science Engineering <_Name>Astronomy applications-astronomy Astronomy <_Name>Biology applications-biology Biology <_Name>Chemistry applications-science Chemistry <_Name>Computer Science & Robotics computer ArtificialIntelligence ComputerScience Robotics <_Name>Electronics applications-electronics Electronics <_Name>Engineering applications-engineering Engineering <_Name>Geography applications-geography Geography <_Name>Geology applications-geology GeologyGeoscience <_Name>Mathematics applications-mathematics DataVisualization Math NumericalAnalysis math restricted/math universe/math multiverse/math gnu-r restricted/gnu-r universe/gnu-r multiverse/gnu-r <_Name>Physics applications-physics Physics <_Name>Fonts applications-fonts ttf-* otf-* fonts restricted/fonts universe/fonts multiverse/fonts <_Name>Games Game.directory Game <_Name>Arcade applications-arcade ArcadeGame <_Name>Board Games applications-boardgames BoardGame <_Name>Card Games applications-cardgames CardGame <_Name>Puzzles applications-puzzles LogicGame <_Name>Role Playing applications-roleplaying RolePlaying <_Name>Simulation SimulationGames.directory applications-simulation Simulation <_Name>Sports SportsGames.directory applications-sports SportsGame <_Name>Graphics Graphics.directory Graphics <_Name>3D Graphics applications-3D 3DGraphics <_Name>Drawing applications-drawing VectorGraphics Viewer <_Name>Painting & Editing applications-painting RasterGraphics Viewer Scanning <_Name>Photography applications-photography Photography <_Name>Publishing applications-publishing Publishing <_Name>Scanning & OCR scanner Scanning OCR <_Name>Viewers applications-viewers Viewer <_Name>Internet Network.directory Network <_Name>Chat applications-chat InstantMessaging <_Name>File Sharing applications-filesharing FileTransfer <_Name>Mail applications-mail Email <_Name>Web Browsers applications-webbrowsers WebBrowser <_Name>Multimedia AudioVideo.directory AudioVideo <_Name>Office Office.directory Office <_Name>Themes & Tweaks preferences-other Settings <_Name>Dash Search Plugins nonapps-visible preferences-other unity-lens-* unity-scope-* <_Name>System applications-system <_Name>Books & Magazines application-pdf X-Publication software-center-13.10/data/report_review_gtk3.py0000777000202700020270000000000012151440077027726 2../utils/report_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/data/x2go_helper.py0000777000202700020270000000000012151440077027666 2../utils/piston-helpers/x2go_helper.pyustar dobeydobey00000000000000software-center-13.10/data/modify_review_gtk3.py0000777000202700020270000000000012151440077027656 2../utils/modify_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/data/default_banner/0000755000202700020270000000000012224614354020735 5ustar dobeydobey00000000000000software-center-13.10/data/default_banner/fallback.png0000664000202700020270000003257212151440100023176 0ustar dobeydobey00000000000000‰PNG  IHDRCÈ¥=­>tEXtSoftwareAdobe ImageReadyqÉe<fiTXtXML:com.adobe.xmp °1ªIDATxÚìkpç¹ß¼ ¢aF–h)¢ìÈvn–˜s’žÎ‘5sfÒ‰fjM;ef:sÔ‘&ê'³ŒòA>R>ÔãɸúPKµÚFõ9¥Ï9i”J±rgGJ"ÛrbKô=6Mù.Ê&u£hŠI ®} ì¾ b± îâþûóxý`ñî» îÏûß÷õ¦ÓiÔ!^”JPr€’h"%9ëë ´ßóê¦Â!_ Xð‹¾™Š„åä"¸•¼´=Ui†Ãäèèp:•òú|›¶ÐÏôs…šñÑ[Ù仿b/y!“ÜgÝÏï¼’íç/ÿËìq-ÌúVÜRxÏÞNG¼+ÚïÚ¬÷ƒI~ll4]ðu¬h۸ɲ±‹ç³ÉŸ»7¿Ÿæ|+ºŒÉï¤3Éw~yé®òòc¿›Ýó_²nÆ'ï¥#Þöζ;¾h<þ’ÜÑÙÖûy;¨öóÒ\Jüò…ÔbÔ×ÞÑúÙ»´éhØÛ(|e,[ô¶u´®ûœõž¯~œIno]{§uòµOD²¯­ÝûKÿ¯ÚQÚ&&'ÒqeÏþ5½–{NL]NÇcJòmë,““3×Ó‰¸×ßÚÒ½ÆFò”hšÇëm¹uµe²ø §“Io‹ßÐ/àt2!¶º®æ=©¤Ç×â[±ÒzÏ‘p6¹3`É)O| %gö#] é´r!yÛWXî9‹ª{—‡ur<æŠÂçõúÛ¬“ñŒñì«%‡RNŠÇ+Z"ïCÙR`×9Uãõ6‰’ó-Ý4ûÔ?kqèWƒZ–kóBø¹Ÿiý|óÉÃZÎÍÿ#/VûyþÙŸæ}V6ç§ÿKŠ¿/î"ê½DÄzþoê9?ÿßZ¬œ»d"•yKèk²/ô‹¿×Ïi.iážJ†¦³ÛùÙ[þôչߗ®ÿ£$/Š}zçŸù©rïQ· æŽ}nîô“zþ¯ÿ1µ·ñ:ûÔ?IÇøÏãðó¿Êh”hæ¸~¨çœ<*Å?ÊÝ&# ¯œ‚&w¼Ç¤c×Û<÷‡çúÙ»ðÊ3úöÓ'´xþÙŸHñO…Œw1qžîgzÛ†Oé9g~¡Å¢ Ù{¶ØþÇ_é9RþÓ¯sm^ŒŽœUÏ£²ý…§r²lA‹•ígŸV“E×EÞøc:‘Èn?wR“q o<¯·aøT*¾¨¾eá¥ßçµ-¿ª{ôüËâ&®ž¾ÈŸÿ¨#ÞzAþ-§‰†ØÇïimX|ÿ ù箕/¼ÓŠ`•Õ¹®M'>ÔãK §DRÉøå1Y’ÊÂW׈Ÿ^Éê*%g\Ï¿>!Å—4ù•¼9%®ÿüíɤP±’¢½’:žäëâ*ÉnW?+³ŸÄÌu½ Ó×Ò™q9%oLÊbרesñÜŒ¾ÿÙi]Y©ÛÕxþ¦&¡M™Râ‘$Lçe)))ÅÅÂqî‹™9}1õ¤( ê¿¬bNJ\Šs°$NŽ=u?2Y &'ÔtÇ—ÿª6W~-iqì“wÕŸømw|IÞ^0ŽŒ¼(>K-˜åh±øj‰«JüxR+ rÎÒö¨e ¹ÍEö_R›£ï¼*. ûm·j_×­KÛ,Çj F­‰+¯ó/ÿ†~¦Ÿ+ÔÏï¾–í绿bÝæFÄ_gßÊ[Õšœi?gjijMNÜ0:û¶ïg‘¯i2Õ>ÿí´:\:ó¶µ-­Ï Åàñ·ejr÷ÊÛåš™Ç.ž_¿Ôºf½Z²’s”âMk›œûøT4âIÄôz­Y~dNÜDE²h†Íg}]Úv)ž÷uf«,‹üY)tt¶mø‚¼½`Ÿø@Ü)[º>ÓÚ{O~N4¬ÕWÔX©É‰?×ÉTû=}š@ñú[ ÄÑpBܰ3e6O¯¶=¯"¢m u’LzÛÛ[×~ÎtŸ¹XH“TxÖ¸E­ÉÏWNwFŸiEG!§¼~¡ö$’7®ªWKËgÖj9r¾\ëJ\O§“ÞV¥&'o7ÔÃÄݽ¥%[“{nïÈ–ÙrÛóëU™XÑOÊ]1­ð2E©¥±ø,¥¾¥ÔäZ”çó.&å¶‹~S„š,†×§«%±ò;*÷´´fkrEó³59G¯%Ë9Æz•øý w½mzÕ*.+?3”2[¦&g’£ÅŠäR xjM®h¾è=UN¼>óklJæÒšœy~i59“Ú^ý*9¨,äm*ª©æÖZ{õ¸ègúÙÑq-Ì–5¿ôöÌ•–™kÈ󒎆k«=¹aåFëçÜhÍ|!“ *`¨C™+9Ù‹SÄWTj¬ [úŠŠä»ØžšŠégúÙÖçÚðÃcÙgâ“bƒÎÔ÷?õø'r¬ûáfŸþ¡¾]òÃÝüÙ=–üpšoÌ#ùá2ñßë>¶_þ½&Ë4œ'ç{ÓbM´i¾·äôµ¹?Ⱦ·'¤øµýk8!ãæOëž3ƒîéHñÿÕãßJq!?œ`îÔÿÓãßóÃecÉ7÷Œì3øáôXòÃÍŸù¹ë~¸°ä?3÷à éñ ¿–bÉ÷âo4»’ˆs²l!üâÓzÎY)>wR÷Éå¼zù>¹—~§Ç/[ûä"¯ŸÑã7ÿ¤Ço½¨ÅÑÑ—ôøÝWµØà“ûè--Ž×cÉÿ@‹ã—>Òã+º7NöÃ%®K>6K?\æé “øŠî{›’|oš.c5ĹA6}{*ið½™zã¤øæ§zÒ½qÉÙº84øäBÒ¯ÓYéѼ‡¥_ Õ|QŸœ›øädµš*Õ'W÷]-îãY^,?£ç$ß­öT+.÷qÑÏôsEûYò®™Waœá½›¾]ÜEZ‚«4·Ô— ÑÖ²êö¥Ûó|oš¿-ºÑüÌÒíÆ| ¯Ûrâ輯ÃÔ§Õϼc+[‘|Ãy±³³xqA{ìÑ<_ò·IϨñÉöÆ™øäìÄ?œY¼Ä'§«BÞ¸<ŸœþYrŽ¡&m·òÆåÇr{Ló%ÿ™!?]Ø%fåu«D\ª/ÍìX ˆ’€ºŸÇE?Ó·«Ö|r‘R}ró y^äQ­šh>¹ }!Õ'æJÿVåcú™~vÏ'gæ“â']òÆçŠÓb³ùáB?—=pz¹!½ßBCOȱ&ÚB¿þG5ÈøätÏ™aN¸§þY÷ÉåüpBÆæÆûmýpæóÃé± ?Üüó¿ÔcÙ÷raŸœ6Wœ§¨7NŽ%ŸÜÓ9Y¶`ôÆýV‡ ûääyàŒsÈýA%oÜÂkÏj±™O.úö9=>/ûä^ÓâÌL1¹8;‡œBìbaŸœa¹ËôøÊE=¾ú‰'e›ì3óÃÉù†øªî““|oùÞ8ís§¯iö/%Îå|oŸÜTáXÎ7›C.7A¦Ç¦O.jâ“ËÌb˜ãf>¹xA•lPÌøäòOà+*[Œ‹~n:Ÿœ‰ÎÜ''ùÞœøäL}o’OnöFË-šON÷±•Å'gâ3õÃ9òÉ)«h,Ýn8/õâ“«¨7® >¹R½q¦>9þ³FõÉÕÿ|oUPrÐJ®È:•U¡ÖÚMÎ[[ÚïÚT¼ÙôssWÃö³ùº«&ùù뢺Ü©g/_¯¥5r-­6ÚcXwµqúÙdÝÕê}! ë®BãOÎV<=ø_"#gÅ–éÁ‡ÆÿãW/}ûëc÷¯ÿDpõ»ý–ÿ>þ÷w‹dñ*Þ;ùÈn±Ÿ¹Ó'¦ÛC?{ðÉY~Væ+‡7Nž+®ÈüpzüK=–ýd†ùᆥuQŸÐdŸæóH~8OƧùä4?\rúÚüiyMXÃüpºO.çS|rÏý\:;~8)6õÃ=)ÅfsŹã‡3ÄÒüp /ës³ýpv¼qO˱î“;û[M&j±ÇÜ'·`擳1‡œ¹ONZkÕà“{Y‹ß{]?Ð×Z5úäÞÑcSŸœÉr×$Ÿœìc³å+ì{“ç‡3øÞ´XÞ®xà$ŸÜ ŸœaÞ8oœ­9ä$Ÿœì³³Öªa9Ù'gg9ÉÇ|rEjrøŠÔD^>6þ¾ø»°xa´|϶mÜÔ~×&ñÚÙ·U¼zðoá“«WŸœ>oœá¸L¹ØÄjU©­÷ó–͈_ú0µõµw´®¿Ç:ùò…t,êmëhýì]vP-l,=À{¾úqvÏkïÔ+&>¹øµO²É·ßa½g‘_ôµ¶ûm$'&ÇS±E_[»¿gCJ‹Ñ'—¸>‘©†¶û׬·Þó§W²É·­µLNÞœRkŠ-·®¶NM«ý¬=7]e0Q©ÆµøVHÆÊdÂSÈ'§ÌÄ‘)Ýi•Úbe¨èB:òz}ZyµXòâ‚ú,ìƒ4úäD·g÷l䍨B2%Â# ùɉx6¹P8?97NjËS(v«æ¼>É9UÓ4+Cè”Ù§tIºŒšxµã+Rö™ˆ«o±ô‰´ùÓ'´éjŠûŠÔ}Êm.Òž¼6 7uxÏÅ·~ò‘ÝsâkišÖØØ¨êÉ»þß¿ø›ø•‹õÛÏÖmÎíß2?ÛæÜ~ÅóÓ·ÄÜï\×óOJìg}}Ï"Þ8uŠ&¥Í6¼qs™µPÕý[zã„$ Ÿù…6TqoœÚÏógtÏ–q~8Ù÷©hDü}¯²Nö“ÉóÃÍ?û3qSLe6šNȲ¼9á´6/ˆc9[æ‡KÞ˜Tö£ågÖK2Nù ðü¯tŸ\Χøä$/Úì©©¶žôbÔè‡+¹tL9B&ÚñÉ-žÅ“sS™Ï'÷²¦0âãhm0ŸOî­ŸåõR >¹¼µV{VZ™ãM^_ÕÄ'—³-ó}1›N‹…Øs:gÿ2õÉeüpêw0ysZòÉMêù²ïmæz:cK§Óoœ‰O.{[̼Åz­U±SñϵÁÎZ«ÙWÄ'§î2mÏ'—.Õ'W÷#“jr^[Ç—ÿª 6—½2±OÞU‹mw|ÉÒOyQÑÒ™Jƒ¥ÿfñÃ7ÅÕÖÒ½F-K÷©5 ¹ÍEö¯¶Y„‘?¿0æg‰É‰::U+þú_¿ùŸ:û¶ÖK?Û¼6¢ï¼*¾´öÛ,$‘¯ëÖ¥m^êKSkrâÛÞù—Ó ×ó{jM®míûùÝ×ÄDµ&gÝæ o‹¿Ô¾•Aµ&gÌÉ_GU­Éy’©ŽÍ]ฌ8ñ¥S+gþž^ËuQ•Ð[Û|ímw~Ȩ́Ţ7Ä]Ó¿ú³jɪ¸ï-6þ¾rËIÄÛsý\$_)ÿdÊl-·­³ôÃe”7S“»ÛÒ÷¿<–šW®çÖu=Å|rŠï-.ÚœRDCÛÆ{µ;œ™-qãºzÖD‡Xúäâ—.Å ’[o¿ÃÒ'—¸6ž9ï]jM®¸ON­É)ƒ ½÷,Í1¶'‘¼qU-³µ|ævKŸœ¸”6·¶ùo[gé“Sjr™ÞÈÖäŠúä²59§åÖÛäíKcñ¹Š(Qkr+,}rÊÝ'«É™yãr±R“Kƽ-­Ùš\Qÿ™Z“Sj3Z-ÙÜ''T~¶&×ÖaéiS~ˆÿôùÄy±ôÀå×äŠúäJ©É¥•·S“³¯ä›ØØhhhPþ¡\wˆû_÷ÎýûîoÈÙ+ $ È[yÑ,v+_V°¹ŸeÇ‘‘³Wþîß\úö×ëZÆ©¿D§ïßýUe@0st5ÕÏ5'®_jìëÙq<[8^(-–gR0æÌYƦ3)H±Œ!GšA^}HžõÀ°}v¦àvc,ÍÎ0c'6[éÈ,þÔ:™Ì!ÅÆQªÂ±!Ñ:ßPÎp’ŸVR’gš(5Ž™ÅQëØ°â“Iœ°1Û…aæ “Õ¢¤ï»qÔÏdÐð‡#i›åËõ;û)9NYÇi;qÚýØÃ3šö”œìÝ1‹Íæè*’¯]ôÚvqk‘ý7yïÕn¶^_5qåc=¾&ûá¤_†9áôí‰× þòIH¿ f¿äuTµ_©drfÒôׂæ«Ó~!äùäLæŠKÊ¿v$?\Ò0oœükPþ…&­¯Ñ•}ªä9äl¨gY3Ÿ\ŽeŽ®šy}Ì•Gæ²OLNL>>Ùàb<\5p kÛOã®Pnêâzn„~–ügv×Eµu^tm†²Ÿ“9çLókl­ˆúéè”-›—¬NX›ʧäj¾dš9v(44Ø<'ÒßÓ»zïõyhÐ'7æç㻿ÖT2Γ)@^ýnÿä#»µ®À'××s­yãŒ>¹«Òö¹’â’}r6|o¥ûäêÃWªïM5k,Ÿœ ÏœŸ\Ü-Ÿ\´àv|rÚ‰O®ÂJ®~}rªš¹~ð?×ÔÌp•$<|r|÷WU‹O®Ž¯ç’½qv|rRlæûµ7N^{Tö«™ùá kþA_{TöÃ…ÏJkzJkwŠvòÉÍÏÿIöÉéö‰ùç~¦ûäró–)>¹¥µD%\øeÝ'§­û©øä^1óÆžÛÌÔgðÃ=/Å’?ìÏ…ýp†ø­ ·Çëm‹¾%ùäL¼qFŸÜÓÅÛñÉIý&­‹º ûäÞx^Ë7û Rlæ““æŠ3øä^ÑâÅw_Óc3ŸÜÅózlðÉ} ÇÒúªö|r’NžÎÌ'wÃ-Ÿœñ‚µOÎćO®žiŸœ/3Ç5­†Ë£³oëê½GXò«~¯çÆìg|rµy^ðÉU¨£ñÉA-)¹šBÜV§ûNÃ?ÙP*Bj¬~ðû-Ûé €†½ÝF–q¹óíÇ‘‘³ã»¿†Œ+ØÏ“ìž:¼GÍ૟\ÝÌ!·P%Ÿ\ÄÄ'g2Ç›Œù¹ÒýpåöÆM/;®iŸœoœ[>9[ž¹:÷É•=Æ'WÿJ®’¾¢"ïµã+ ^ýn?#ªE˜;}âÊßýÛ"ç Ÿ\­\Ï¥zãœÌ!÷”ì3™CÎÄ7wJŸ+.,ùÕL½q§Ÿ”¶ëórË~8ƒONšnöéè>¹Ü¯5!×ä_nšΓY·T÷Éåæ2NžCÎ8WÜïuŸ\Î㕊Λ{ãlÌgËg2šä‡‹Ê±a~¸ÂÞ8Ù÷•æ]3÷É=W°ÿ Þ¸×KœOÎà³ã““ûÁÄ'g♋¾=¬Ç²ONžOî}i}ÕF´866ªÇ²On\öÉ™Í'÷IÁ_2¦>99vâ“›‘|rE~9”æ“3ñÌÍׂO.aã“+@½úäħª÷*'ØÁž‡2GIÍ^ÏÍÒÏ%úä„,óÝÒ]B>>¹å|rêh|rPKJ®ºˆûè•ýýòï*°Ãê½GÔ „ 1hùÞ÷¾'ÿwdälbr¼µgƒ¦™¼mKcÁâ‡oÆ/_HÍÞðutÌ‘c-Ù¿êv³-ɱ #©…9‘¼4'~é£KßþW‰ëœ¼RY>%ú­³o«~΋Õ3hçÚqì“÷ì_‘‘“®\Zœ¸~ɸ%::¿6ÿ õ³közvÔÏãïÇ/¥ænøÚÍÚ<«Õ#£Ã‰ë—E²ÿ3=©…Yokv{ÁXüLŠ]|'™oé^³$gN‹ÓW}+ºß#ñéåø•‹z›¥œTÄǯŒÅÅé‡<©¤V?KÞ˜,ÇÆßKL^É-ÁÛÌr´Xé‰SÑHKpUfûu_g ›3“Ç'>HL]N\ýØ¿f}¡œ)9NN_M|z%žUÚÜ‘Û~sJŠ?õudkT¢7Rs3âH[ººåíÉO«qüêEÑæt"ÞÒu«¼½`,Ú,v’øôª8ƒzm,WK“ãäì q8¢”"ek[Á9Ž_ûDä‹ä–•·¦#Åóׯ3×ÓѰoe0³]ÎÏÅ•,’•g3'¥x~òÆ5Ñcéè‚·µÕÛ’Û‹ŒE×¥æC¢Uâ LÇ¢Þ.GŠã‹j¬ôFhÊ«§U© ·´äê‘1¯Ï‹3+Ú^Ðë»JÍRËIJq,*š­üKÄ=¾¯/WoK&<…âT$,š”N&¼þ6¥Þ¦Õç ÅJb‹%¹Õ,G‹E?({NH5oó|±[¥Áɤ×ç5ÉIy¼^í¼(‡N)=)mÏÔóce· ‘œÎvQ:-åäÇéÌn•=k}%ç(~8)_ü§úªlóZÕ>sõ)¯·Inî*½ó§¢ÅE|Bâ¢T/M;¾"ů“ˆ«o±ô‰´ð ¿ÑLÇrþü×+û«lŒó÷ôvmÛѽs_ÏÃG»¾±cÃߨxjjí£C"¯"ÿÄÿUñOݾjà€Ø(Þ[Ý™AÛÜþ~Ë~^z^ÔsgçÚPó® ³Xm³šì±òÉ¥3n‰ðŸ~]×óÓÖýœñÃeûù™ŸæmÏņ9äô6ÿÚÄ'yæÂÏýÜ“s±XÎ!—Š.,¼ô{Íæ"{ãä5UÃÊüÙTú9"­jæçZ(-uÿfÞ8y~¸ðóCâ.’μEóÃe|rº7N‹Ó‹ÑÈ›/hý¬ûäf®çyã”,*NèÈYÉ'w:wìaÚª¯=›ÊØzÄ«oœº¾s:³ËyãÄ=5öшf’瓽qªïM?‘çK3óÀ‰|ÕÇ)^ͼqr¬z×Ôg^7ñÉåâT|qqô%Í¥åôÉ¥2ÍX|ç>¹?¥c1µvæ–S=pªË*z^÷ÉEåùäÞC“bñ‰ š Ë8·Ü»ZœõÉeúY~jJüüÐâÄUƒO.L*驤ø™$m/ì™Ë>…“ù»”˜ÖŸÈ)虾R³ÓiÍ÷&ûä–9ÙS& £O.Uw¶|r¥>QÿOQä®FFΊ_-í÷ü……ä€>yWÜBì$«uñY6“?|³àž… ™:¼§*Ò­³okÇæ­íwmjÛ¸©¤6®½úLüò…ØØyñƒU^ºbˆ£Xwp(~å¢ý6—t€5’àľŽM[jðzv!ù£·²ÉwÅ:ùˆ¸ûú:W´ßÕg™…}+Ì.xÞß#ùaÝþù¯Zôs<&nŠ©Åˆ¯½³íŽ/ZïùW=þVoGgÛ†/X·yüýÔÜŒ¯ë3m>o™¬Tï2Íhí½Ç:ùÒGBÆùÚ:Z×ßm|eLÈ8o{GëºÖÉW/f<š­kï´ÑæÄŸk¯×g«Í×>QÝŸ­·ßa|y,JøZÛý6’Å_­TxÖ·âÿíl%Ç}míþÉ×'T'¥õÏÝħ—Óñ¸’|Û:Ëd¡æU/ Zc¶HÚH©y[n½Í29µ0'´…·¥Å·¢ËFò¼"|>­š[ìË]Pê-âú·¶$*5¹Ìß:;þE¥ˆ˜¹ÄUj,´¸CbÏ­ÖNbEŠeêjvl—Ús Z µhv*[¤³c4l¾š\Ýøä*/ãÄ=¬kÛŽÀ–í客…‡O. Ÿ Ÿ;UÉZ£*æ°í äJÆULÀU]Ò!æPr#ã„€ öØN*+BÆ 17sü`b²ìOu æPre$66zéÛ_/ëGì øÍZ4‘‘³7*·‘.°e{ÏÃGù& äÜ—qe}Rµf5\…õ\×¶«÷áË€’s !àÆw­|2.Ø?нk_½ , %7uxOùÆ[»wîëÞµŸïJÎW¾U:û¶®Þ{¤ºóº-ÐÐà̱CeR·¬€’s‡ÉGvË‹d»…/\5p ®õJbrbzð¡2uκƒCUàê[É…†…Xq}·}[{>ÚÏiÎ>!ºÈõ✿§wýãgx”%·L"#g¯~·ßõÝ®8ìh¤3—˜œ¸öÈ·\€zwí£C|1Pr%Sާü=½·?ü£F4œ|(44èî>yú%·\·Ç5ÃÌ·å˜9yýãg0ÌÔ>¾ÚiJhhÐ]×µmG3¸¾Êq˜×ùV%€úVr‰É‰™c‡\Üa° Â³Ý é3sìàÕïöOÞSaäzéÑõÓå VFW…rqƒªL&‚Vë?Sá¸> ßÚG‡:û¶ò%¨Yj¢&7wúD½Ë¸Ää„|BN•iZãbçÒí áÊ1 4”’K…C.*†j-T°Ôá'äiN§«bNˆÑ™cù’ äLqqù©`ÿ@µÖoXªÛʱ ƒM1·æÁ#nyæBCO”o±W¨o%'T‚[Ó¡ ·jà@UŽB(Ñ¥c©âÐ*?ÀªââÊcÇ)Ë ä áÖ¸ªÐ.~RU&|îTÁíU`Õ;äÁﻲ+qÕ’¤P»J.2rÖ•!HOﺃÕ\`ja¸°’«Ö«J`Ëv·Š”<ú€’Ëçæqwf,»ýáUqúßT8d¦Ø“Õ5™û„žsEs»øp1Ô½’sK¬8PÝu¥Ì†V³ÿ·ªe9Áê¿ïïé­Ù  ä\Q}[ƒýÕíA³¡U•*Zå²'8tÅAHY%—%66ê\ÒóðÑêv_‘¡UíH«>‹‡Ð»Ý;÷9ßÏìÐ|aPrWfY5p Šö8•âC«Ùœj° ºwíw>Æ*„¹åš]É¥Â!çcŽ}[«5 °Lñ¡U•ª°ª¸2ÆêÖäP¯J.ô+Ô@g“±So«…V·´¯P¥n-Èu©äæžqZ£ ö¸òÄÊ+@3*9‡#ŒBÔŠCnY²¬vžút¾Ä¬Í¥ä"#g­ÖJAnyC¥µ3Àêïé lÙ^ù€zUrïým7‰5ÒkË[4¶vX+·ýmuu9Ô“’[žúѨ…µ¹Tœ¸Äjg€5°e»Ã¡j‡'êFÉ¥Â!!€*é2'OnÖÔ dà¾û¼=:rŽï@S(9‡õ›Î¾­µ°<—гMÕ±Vl¹¿ŠçêFÉÅ.8*È9Ônˆ³ 8”uKkæ©O‡¬‹¬P7J.ú¶£‘¸ÆZUi¤VÊrÕÅ_™YtP“ó÷ô–4´š ‡B¿t¨Ëq *ás§“ýåh[ÛÆMÝ»ö•TfëØ¼Õ‰6vömå[ÐÈJ.19ádÆŠRµÂôàCµ¼œ”èŠ2•²Än…´ZûèPùúv©’ã+PE*1ºŸwòöŽÍ¥©f^´ÔiÞJ­w.Õè|…\ÉE• Úï*mBàYÑ«jg´ÄÃo߸ىpä+ÐàJ.žuòöR—vö?д§s šµm¼×ÙÉe¥€†VrNÜTË0ruïÚ¿jà@Væºwî^ê»:œYåœ?5­ä’óË/Û,ÏÅìXwp¨vÖi-7¢—Ö?~FHØe¼·µgƒ“¦&ÐàJÎIMοf™~|!ㄸYÆhcÝز]é²e«ÃÅ3bÔä[É9ÁáØßª=mÔ‘Vq\«÷q~€ÍS¼@É•FÕ§ lÙ¾áèë7­_ëumÛá|W-+¬Ùužo@Ã*9'&9OéS>È@pí£CÝ;÷5Ìi ö8QÍÃÉ+>9€FVrÎE˜[»êÞµ_¨‡¶°Zè¡J—ñŒj1%·¦—oJ®ÖQƒlÙ^§íïìÛÚ#Å€’³wÀ`ÏÃGWï=RwA¬8°öÑ¡&_ÁšZÉ©tmÛá¢Ï¬Ü¨ÓÅ5ÃŒ*Ð8J®¬J«^äQ}‰N@Ée©À &µLM Á•œ¿gƒ“·;ñã/Ê­f?·µ@'JŽU(šAÉ9ºßG+;æX­¡Õj‰H'Cº­Î4:Ô’s¸hA…ç­­ÖÐjU>Ý¡3¯có}|…\Éyœ¹©*ü@µ†V³Ÿ^ÙŠ ÃzgÛ]˜äš@É9\é¡b³æVwhµòR2ì¬ؾq3_!€ÆWrŸp¬Ø˜cu‡V+܆Ää„“Ç| O<4…’sh•«Øku‡V³m¨T]Ða¥Óá9€ºQrm79[³k¢¬±±Q‡ÊßÓ»zïçS¬UFP:\¬£ÇšCÉy—p*0æèPÙ¶l_ÿø™®m;Ö¯5~°‡V=ÔäšJÉ­Ør¿“·‡Ï*÷˜ã²Ë~¾@pÕÀž‡ªuGñºzïí?—×’rìÜé'¼ÝßÓËêM¤ä–p„²)ë˜ã²°‚fÝÁ¡`ÿ@ÞvµD·l¹SnkàÜ3'ªx6 Î”œó*ÎÌñƒeT6ËZNÈ8³ã‡,Ä\÷Î}ËØsYXÅÁ.O¶j8¬°@)9R¦rtûâ£|•ªR‡V}`ÏÃGW °BíÞµí£C¥Ž´–µ9ï¬ 'Ž%°e;_€æRr]ÛþƒÃ=Ü<~¨ +uhµ³oëúÇÏØW3"ÃÑ×KR?Êhry×jØ¡ ÜGA ù”œóVç*¤ % ­vïÜ·öÑ¡R'ÅÕjxößR¦Vçj˜¡U€fTr¥,·ÃáÊQ–³YýjLh¸î]û—ýAÁþûA”c€Õ¹fh y•œsàzYÎæÐªhù†£¯;fS}ÖÕŽ¢-Ç«sÌÐ*@ó*9O¯s17ux‹M²3´*OçB§ÛžpÎÝVq¤ÎEðÒùV Y”œà–þî!19t«=ÅÅMÛÆMë?SùbgÂ9XSáói\:û¶2!0@S+9¡J}\`)3Ç9œM…E­R—Þ*Ÿv±œpÎÅVWzlå7vðhj%çQÿÜïpBâL>ä¼%f:I}ÔtõÞ#n¨ë¢ι2À*Ôªó*¦ÐÎX€ºWrûîw®„s¸æ½™NRGT+ù„¦:á\ÁÇ)œ;Û„ê½þ˜ ÎÂ. r(9O¦âtì–L>äpÄp© ìÞ¹OÈ8çã¿ËhÉÚG‡–N8ç\òÎ;Td¹„SöMžu@Ée²À¹FI…C×ù–“=È_8Ÿ.Î…nÉL8'ëÈî]ûœì0<|Ò•§C„ò®À@3”„7NWë³gŽœqcš_¡~JZ;!ÄäÄÜé'}+ƒ]ÛvÔˆXQt8w*19¸o»“ç-Ä¡]úö×ÅÞœêý@pÃÑ×Qr(9ƒ^ßý5ç:C°zïÌøK»÷Êþ~ç㪞̈suK•P_5?;tRK“™:¼ÇÉÒHL=öWúÄßÓ‹Œ@É kÛ·ž-p«þÔ 2îð·&¢s>e 4¦’ódF]Ùúôƒ+cµõNhhÐùü,*}[¶@ÉÓ nÍÜ–˜œ¸²¿¿ÉÅœÐp®Ì™¬âÖð74¦’Så‚[ÏEÆÆF›YÌ 7ux[{ ö°Ê*J΂Œ§~Ÿ[{SÅœ+«²Ö¡¡Aeœ»'ÊA5g!Éãêwû¯L¥KÔ@pÝÁ¡æ)) ç–7Neí£CW€ÚÁW;Mqw¹zu65·žß¬eÄ‘N>²Û]ì@ÆÔ>5T“ód––¢ÄÝ}®8 tI£ž¿ÄäĵG¾åîô+m7­ü ß €ÚÇWS­ lÙîúœÓƒ uØÏ@á{éÛ_wWÆùÁ5á‹PÔVMÎãêS2þžÞÕ{4̈¡è¥™c‡BCƒ®ï™uÏPrŽ(ßL"±~¨èŸë•eu²`ÿÈ äœRÜJ]çÊWŠó`@ɹˆÐ+.®UG×¶Ý;÷»µäke˜;}bæøÁ2Í“'dܺƒC.>; M­äÔÀÓ”øÁ`ÿÝ»ös) ä¡än?Ô`zNÕpÁo`‰@É5>BÉÍ=>YïâïéíúÆ44‘’SILN„†çNŸ¨Çç!:û¶®üÆüpФJNEȸð¹SBÒÅÆFk¿µ¾@0pßýÁþžK”œNbr"<|RHºÚœ‚®kÛŽ[îlÙΕ (9 I·0|ªêFø{z;û¶"à%W2©pHˆ¹èÈ9ñZ±±W_ (Ô[Gß}â•!T@ɹƒªçÄ¿Ää„‹å:OokφŽÍ÷µÝµ©}ãfñŸt5 äÊK*Z¼0*^c”r]lì¼g`Û6nònñ­ Š %óJOJ%(9@ÉJ%(9@É ä%(9@É ä%(9” ä%(9” ä%ÐŒü®—ÀãÌ~IEND®B`‚software-center-13.10/data/ui/0000755000202700020270000000000012224614354016401 5ustar dobeydobey00000000000000software-center-13.10/data/ui/gtk3/0000755000202700020270000000000012224614354017251 5ustar dobeydobey00000000000000software-center-13.10/data/ui/gtk3/submit_review.ui0000664000202700020270000007633112151440100022471 0ustar dobeydobey00000000000000 400 500 False 12 400 500 True softwarecenter True False 2 True False False False True False True True False 0 0 True False True False 0 6 label True True True 8 1 True True 1 True False True False 6 3 True False 9 True False gtk-missing-image 6 False False 0 True False 0 label True end True True 1 False False 0 False 0 label True True 1 True False 3 True False 0 label False True 0 True True True in True True 2 word-char 2 2 False True True 1 True True 2 True False True False 0 1 <small>Be brief and informative. Do not post bug reports.</small> True True False True 0 True True True True False none https://wiki.ubuntu.com/SoftwareCenter/RatingsAndReviewsGuidelines True False <small>More…</small> True False False 1 True False 1 True True 2 False True 3 True False 3 True False 0 label False False 0 True True 256 • False False False 1 True False 1 True False True 2 False True 4 True False True False 0 label False False 0 True False 12 True False False 1 False False 5 True False 0 fill True word-char False False 5 6 True False 6 True True False False 0 True False False 0 False False 7 1 True False 12 True 4 True False 0 0 0 True False 6 start Yes True True True False False True 0 No True True True False False True 1 1 2 1 1 True False 1 0 True False You have started a review, are you really sure you want to cancel now? center True 1 1 1 1 2 True True 0 True False 12 True False 6 True False 0 5 end True True True 1 True False 6 end gtk-cancel True True True False True False False 5 0 Publish True False True True False False False 5 1 False False 2 False True 1 True True True False True 2 False word False True True False 0 Error Details False False 2 software-center-13.10/data/ui/gtk3/art/0000755000202700020270000000000012224614354020037 5ustar dobeydobey00000000000000software-center-13.10/data/ui/gtk3/art/stipple.png0000664000202700020270000007505412151440100022223 0ustar dobeydobey00000000000000‰PNG  IHDRXXq•04sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ9=V½tEXtCommentCreated with GIMPW IDATxXy§†îéêÿéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçêéçê×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿÖìÅ[ IDAT×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ.¤$ IDAT×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ0Ú!¸cIDAT×ÐÔÿ×ÐÔÿ×ÐÔÿ×ÐÔÿ´ÞƒÑ½:ÝIEND®B`‚software-center-13.10/data/ui/gtk3/art/exhibit-dropshadow-s.png0000664000202700020270000001075312151440100024602 0ustar dobeydobey00000000000000‰PNG  IHDRd ‰@(ÛsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ žû‘ètEXtCommentCreated with GIMPWFIDATX ;Äî ÝU)¸IEND®B`‚software-center-13.10/data/ui/gtk3/art/itemview-background.png0000664000202700020270000000175612151440100024507 0ustar dobeydobey00000000000000‰PNG  IHDR©ñž~tEXtSoftwareAdobe ImageReadyqÉe<fiTXtXML:com.adobe.xmp –CøIDATxÚbüÿÿÿF À$@X À±¦¹~ý·•IEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/0000755000202700020270000000000012224614354021152 5ustar dobeydobey00000000000000software-center-13.10/data/ui/gtk3/art/icons/pending-dropshadow.png0000664000202700020270000001133512151440100025442 0ustar dobeydobey00000000000000‰PNG  IHDR#!SRsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ2ZB5tEXtCommentCreated with GIMPW8IDATX -Òíÿÿÿÿþþýþþþþÿÿýúúúúÿûùüÿ  ÿüøöôõýþôñöü  þû÷óôûûéýûýÿ  þüþ ýúþóõöûü  úöõøý  îùìï÷ýÿþöóñðó÷þ  þï óûÿÿþùñçîøñóöúÿ ÷ ÿû îùþÿüöì÷ùöøüþþ ùüýþþ  öüÿÿþøýöýüýÿÿýÿýøñøü þÿþúÿÿÿþþùñçÿï øùÿÿÿûôéùê ÿòòûÿÿÿÿüöüóûóôûÿþÿÿÿÿÿûùûýÿýýüýþÿÿÿÿÿýÿÿ  ýøøøûúüýþÿÿÿÿ  ÿ÷ôôôôõõúýÿÿÿùõñïìêëòþÿÿÿÿþ øóíäø÷ûþÿÿÿÿýüúü üõ ÿüûüÿÿÿùõôùÿý þõò÷ýÿýûöôôøýÿ øý  øëýüýÿÿÿü÷ñïïÿúü þ ÷ý  ðøïÿý÷ñì ñþþ öûýõþüüþÿÿþùðäûû  ÿüñíçíöüÿýúððû  ÿûùôðíìïöüÿ þü ýûøôôôôùûÿÿÿþþüûúúûüýÿÿÿÿÿýþþÿÿÿÿÿÿ¬·‹ƒ2n#°IEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/history-dropshadow.png0000664000202700020270000001072512151440100025521 0ustar dobeydobey00000000000000‰PNG  IHDR!!WäÂosRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛŒnò"tEXtCommentCreated with GIMPW0IDATX %Úîÿÿÿþÿþÿÿÿÿþüûûûüþÿÿ þýúöôõöùüþÿ  üúöòîïñõúýþ ÿÿýøñëéìòøýþ  ûùüÿ  ïøÿÿ úñðö ýøþ ÿýþùþÿõèäæïúøíëð÷ÿ ÿùùûÿÿôçäìõøðûôøý õõúöÿÿ  úëäèóüþÿúöøü ðôùþÿ  ñéìòùÿýüñ îõû úïîóúýÿüôïðü  öñóùüÿÿ üÿøôù ýÿøõùüÿÿöüüüúú þûûüÿÿÿþýûÿ øüøöúýÿþþÿÿþøóñüüýÿÿÿÿÿüöíòÿýù÷ü ÿþÿÿþýþÿý÷îøþýþ üûþÿþüúú üÿüöú ÿøøüÿÿÿÿûö÷þ  ôüüÿÿüý ýö÷úþþûõòùÿÿÿÿþÿÿ÷òõûþ ûüýþ þêæí÷þÿ ÿþÿ îåèñúÿÿ  òæäî÷þÿÿüôì  ðöúþ  òæåîöüÿÿþúòÿü  øûÿ üìéåêóúÿÿÿý÷ðìéíòúýÿþøþúõð ÿüøöòðïòöûþÿ þûø÷õö÷úüÿÿþþüûúüýÿÿÿÿÿþþÿÿÿÿü‡—ÒæÖÆIEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/available-dropshadow.png0000664000202700020270000001176212151440100025742 0ustar dobeydobey00000000000000‰PNG  IHDR$"7Y{…sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛþ0#tEXtCommentCreated with GIMPWMIDATX B½ìÿÿÿÿþÿýþýÿÿÿüûùýþûùúýÿÿÿ ûöôú ý÷óô÷ûüÿ  üöõø ü÷ñïñôøûýÿÿ  ýûýüüøóðððõùûþÿ ýÿ ÿùóóúÿùòïïðôúýÿ  ùõù ýøõÿ ëóþ óùùþýòðüýùù ýöøûý þùõöôö ûþøÿþùöõ  þþùñìíðö øõöùþÿÿÿüöï ýùîøòîñùùþ ÿ÷õîïöüÿÿþýûûûýüýýüÿýûýûûûýþ ùóøþÿ÷ ùêþ ñöû øìïú  úýì÷ùïóýýùôô  üøðñï÷ ýÿýøóð þ÷÷  ôéîöø  ÿôðóøýÿÿþýÿ   úöúøíçéð÷ýÿÿüõÿûøü÷ø üý û ýííçìñöüÿÿþúþüý ü  ýþìæíéðøüÿÿÿüøõôø üþþôîøôõøüÿÿÿþûùøùÿþýùÿù÷ùûþÿÿþüøóïñöüüöñïóøüþÿþùöðéâàåí÷þÿÿýüúúýýþü÷òêåÞÝáéóúÿþüúöõöúüþÿýúôìæàÜÝâêñùþÿÿýøýøóðíì ôêçæéíó÷ûþÿ òìéìòöûüÿÿ  öñòöøýÿÿÿûùúüþÿÿþþþÿÿÿÿ«`Ào)hIEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/installed-dropshadow.png0000664000202700020270000001176712151440100026006 0ustar dobeydobey00000000000000‰PNG  IHDR##Ù³YsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ'¶y7tEXtCommentCreated with GIMPWRIDATX G¸ìÿÿÿÿýÿþþúøûþ ÿÿÿüôðñøüþþðøûþ  ûüüþÿ ñõûñïöýÿ ÿüûïéôüÿ÷ ÿÿþüöôóüÿ÷ûÿýýüüüÿýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùýÿÿÿÿüÿ øþÿÿýýüÿþ õþþþüûü üÿÿ þúõ÷ùýÿùùüþÿÿúóíÿÿ ûòêëûûÿÿüùúîòúÿÿýùðÿò  øýóêõýþÿûôðùýÿûóùïýÿýõìïùþÿÿýûúúÿÿÿýþûûõúþÿÿþþÿÿÿýý  ýÿ õ÷øûÿÿÿÿÿ öÿý÷  òÿÿÿþùðñÿûõòúþÿÿüöïô ÿýùõñïøüÿþýúù÷ùûþÿþþýþýÿÿÿÿÿÿòŸíªf9IEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/history.png0000664000202700020270000000521512151440100023347 0ustar dobeydobey00000000000000‰PNG  IHDRÄé…csRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛös¯ºtEXtCommentCreated with GIMPW èIDATH Ý "ö Ïÿÿÿÿÿ¿p ¯ÿÿÿÿÿÿÿÿÿÿÿŸ`ð°Áà0?PðQÀ`ÿÿ0ÿÿÿ``ÿÿÏ0@ÿ€@ïÿÿ0 ïÿÏ@ÿ€0ïÿßðaðÐQ p ÿÿ€@ÿ€ŸÿïoðP@°ðàÐ?0Á±0à?€ðà0ÿÿ@ ÿÿÿÿÿÿÿÿ0@ÿÿà! àà00ÀààÀÀ?Pàpÿÿ@ÿÿP ÿÿ¿ÿïŸÿÿ@pÿÿ€ïÿï0PÿÿÏ@ÿÿï@pÿÿï0`ÿÿÿŸ @¿ÿÿï00¯ бàà0P ÐaЀïÿÿÿÿÿÿÿÿÿï€`ŸÏÿÿÿ¿ŸPÎÝ Øþñ$ÂIEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/installed.png0000664000202700020270000000605712151440100023632 0ustar dobeydobey00000000000000‰PNG  IHDRÔôUsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ(5_͉tEXtCommentCreated with GIMPW ŠIDATH  €ôÿðÀÁ_ ÿ€¿ÿÿ  0ÐðЀÿÿÿÿÿ`??ïÿÿÿÿÿßPÿÿÿÿÿÿÿ0ïÿÿÿÿÿÿÿÿÿï€ @À-§ÆH½öIEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/available.png0000664000202700020270000000605212151440100023566 0ustar dobeydobey00000000000000‰PNG  IHDR¤T<‰sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ;)+nùtEXtCommentCreated with GIMPW …IDATH z …ô00€ïÿ¿@@¿ÿï€PßÿïÏÿÿ¿@@¿ÿÿÏïÿßP0¿ÿÿŸ@¿ÿÿÿÿ¿@Ÿÿÿ¿0Ÿÿÿ¿0ŸÿÿÿÿŸ0¿ÿÿŸ€ïÿßP€ïÿß@@ßÿï€Pßÿï€ÿÿï@PßÿïppïÿßP@ïÿÿ@¿ÿÿ¿`¿ÿÿÿÿ¿`¿ÿÿ¿@@¿ÿÿÿÏ 0Ïÿÿÿ¿@¿ÿÿÿÿÿÿÿÿ¿@ïÿ¿`ßÿïp`ïÿß`¿ÿï@€ÿÿ€€ïÿÏ@@Ïÿÿÿ€ÿÿÏ Ÿÿÿ¿¿ÿÿŸ Ïÿÿ@Ïÿï€@ßÿÿß@€ïÿÏ@`ïÿß`ŸÿÿÿÿŸ`ïÿï`ÿÿ¿@0ÏÿÿÿÿÿÿÏ0@¿ÿÿ@ÿÿÿÿïÿÿÿÿÿÿÿÿïÿÿÿÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¿ÿÿÿÿÿÿÿÿÿÿÿÿ¿ @ßÿÿÿÿÿÿÿÿß@pïÿÿÿÿïpÿÿ éŽ(V²ÒIEND®B`‚software-center-13.10/data/ui/gtk3/art/icons/pending.png0000664000202700020270000000552512151440100023276 0ustar dobeydobey00000000000000‰PNG  IHDRÀU^sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ¶4býtEXtCommentCreated with GIMPW °IDATH ¥ Zõ@À@°@¯ÿÿÿÿÿÿÿ¯`Ÿï0¿ÿÿÿÿÿÿÿÿÿÿÿÏ0ÿÿPPïÿÿÿÏ€@@@p¯ÿÿÿÿÿÿÿŸÿÿ¿0 ¿ÿÿÿÿÿÿŸ ÿÿÿÿÿÿ`Ïÿÿÿÿÿÿ¿¯ÿÿÿÿÿÿÿÿ !ðøá00 ? xŸ0ðð@ÿÿÿÿÏPÿÿÿÿÿÿÿÿ¿0°àÐpÿÿÿÿÿÿŸ0ÿÿÿÿÿïŸÏïÿÿÿÿÿß``ßÿÿ¿Ÿ`°0 ðÀÑPðAÐ`ÿŸÿÿÿÿÿÿÿÿÿÿÿŸ €ÏÿÿÿÿÿÏ 0ðК 7yG&IEND®B`‚software-center-13.10/data/ui/gtk3/art/frame-border-image.png0000664000202700020270000006506612151440100024172 0ustar dobeydobey00000000000000‰PNG  IHDRRRÇ,ƒ›sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ7,âóÒHtEXtCommentCreated with GIMPW IDATxbi–ÿÿÿÿÿýüûüÿÿ üöøþ öøüþèþúòüþ äÿÿóûþåå üýýü ðð òò üüÏ^%Ë IDATKîÔ IDATÿþþÿÿÿüýÿýù øýÿüó!n¨ò9 mIDAT óüÿûñðHHðñûÿÿ'ÜýQ ÛëùþÿüûBú÷ý9K÷Øæõý  þûõìæãéóûþ  þýøóñóøüþÿÿýüûýþÿÿÿÿ–wqÒVzêIEND®B`‚software-center-13.10/data/ui/gtk3/art/exhibit-dropshadow-n.png0000664000202700020270000000711712151440100024575 0ustar dobeydobey00000000000000‰PNG  IHDRO >ýÐüsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ´–:'tEXtCommentCreated with GIMPW ªIDATH Ÿ `òZèìðôø+,ÇIEND®B`‚software-center-13.10/data/ui/gtk3/art/frame-border-image-2px-border-radius.png0000664000202700020270000006506612151440100027441 0ustar dobeydobey00000000000000‰PNG  IHDRRRÇ,ƒ›sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ £$ tEXtCommentCreated with GIMPW IDATxbi–ÿÿÿþüûûýÿ ñúùöýôúúô÷÷ A(V7 IDATKîÔ IDATô›Ä mIDATÿÿÿÿÿüüþüõ55õýÿûî[þúíÜîûÿûìë ÿüõîìúÿþýûúýÿÿÿÿ¿Ñ>@™ZdIEND®B`‚software-center-13.10/data/ui/gtk3/art/circle-dropshadow.png0000664000202700020270000005552412151440100024154 0ustar dobeydobey00000000000000‰PNG  IHDRLLÇ—Q+sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÛ6#“"3tEXtCommentCreated with GIMPW IDATxŒZs¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿþÿÿÿÿÿÿÿÿÿþÿÿÿþÿþÿþþÿþÿÿÿÿÿþÿýÿýþþþþÿþÿþÿÿÿÿÿþÿþþýþýýýþýþýþþÿÿþÿÿÿþþþýýüýüýüýýþþýÿÿÿÿÿÿÿþþþüüüüüüûüüüýþÿþÿÿÿþþýýýüûûûûúûûúûýýýþÿÿ þöøúÿþüøõ÷ü úô ôø ùùùùøùúúûûüýýþþÿÿÿ ûó  í ÿ ð í ÿÿÿ ò ÿóÿÿÿ í íÿÿÿÿÿ í íÿÿÿÿÿÿÿÿ ð ðÿÿÿÿþÿÿ í íÿþÿþþÿÿÿÿÿ í ìÿþþþþÿþÿÿÿ ò ï þþýÿýÿÿÿÿÿ ýþýþþþþþÿññ û öýÉ*3‡ IDATýýýýþÿþÿ÷÷úúôôø øö÷þýÿÿÿÿýýÿÿÿÿÿÿÿÿúúþþÿÿÿÿþþþþùùþþþþÿÿÿþÿÿþþýýýýþþÿÿþÿÿÿÿÿÿþþþþýþþþþþÿÿÿÿÿÿþÿÿþýüý üüýþÿÿþÿÿÿÿÿÿÿýýýüüôôüüýýýÿÿÿÿÿþÿþüüûûøøûûüüþÿþÿÿþÿýýýüûû ûûüýýýÿþÿÿÿÿþþýýüûùììùûüýýþþÿÿÿÿþ÷÷ùùûüþþÿÿÿÿÿþþþýûúùù ìì ùùúûýþþþÿÿ ÿüúö÷øúúüýýþÿÿÿÿþýüûùùøÿòòÿøùùûüýþÿÿÿí É-³È—IDATìöööøùûûýþÿþÿí íöö÷÷ùûûýýÿþÿï ïöõ÷÷ùúúýýýþÿÿí íööö÷øúúüüýÿÿÿì ìööö÷ùùûûýýÿÿÿÿÿÿÿÿïüðõõö÷øùúûýýþþÿÿÿÿÿÿÿÿ ï ìöö÷÷ùùùüüýýÿÿÿÿÿÿÿÿÿþþ öò ìÿúöö÷÷øùúüûýýÿþÿÿÿÿÿÿþþþýý ùðþôò þöö÷ö÷ùúûûüýýþþÿÿÿøöþÿýúùý ûûùûùúùúúûûüýýýþþÿÿÿþþýþüüûûûûúûûûûüüýýþþþÿÿÿÿÿÿþÿÿþÿþþýüûûÿÿþþþüüüüüüûüüüüýüþþÿþÿÿÿÿþþþýýüýýüýüýüþþýÿþÿÿÿÿÿþÿþþýþýýýþýþýþþÿÿþÿÿÿÿÿþÿýÿýÿýþþÿþÿþÿÿÿÿÿþÿÿÿþÿþÿþÿÿÿþÿÿÿÿÿÿþÿþÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹· ºy*ÚîIEND®B`‚software-center-13.10/data/ui/gtk3/css/0000755000202700020270000000000012224614354020041 5ustar dobeydobey00000000000000software-center-13.10/data/ui/gtk3/css/softwarecenter.css0000664000202700020270000000360212151440100023572 0ustar dobeydobey00000000000000@define-color light-aubergine #DED7DB; @define-color super-light-aubergine #F4F1F3; /* do not use text-shadow until bugzilla.gnome.org bug #686209 is fixed - its leaking memory */ #featured-star { border-color: #F89516; /* yellow */ color: #FFC51D; /* orange */ } .grid-lines { border-color: shade (@light-aubergine, 1.025); } #screenshot-preview { border-color: #000; color: #000; border-width: 2px; border-radius: 3px; } .backforward-left-button { border-radius: 3px 0 0 3px; } .backforward-right-button { border-radius: 0 3px 3px 0; } .subtle, #subtle-label { -GtkWidget-link-color: shade (@super-light-aubergine, 0.5); -GtkWidget-visited-link-color: shade (@super-light-aubergine, 0.6); color: shade (@super-light-aubergine, 0.5); /* non-link text color */ } .cellrenderer-button { border-radius: 3px; } .cellrenderer-avgrating-label { color: #8E8E8E; /* dark gray */ } .cellrenderer-avgrating-label:selected { color: white; } .more-link { -GtkButton-inner-border: 0; -GtkButton-default-border: 0; -GtkButton-default-outside-border: 0; color: darker (@light-aubergine); background-color: shade (@super-light-aubergine, 0.925); border-color: shade (@super-light-aubergine, 0.875); } .frame-header-title { /* intentionally left blank */ } .item-view-separator { border-color: shade (@light-aubergine, 0.9); border-width: 1px; } .light-aubergine-bg { background-color: @light-aubergine; } .super-light-aubergine-bg { background-color: @super-light-aubergine; border-color: shade (@super-light-aubergine, 0.975) } GtkViewport { background-color: @super-light-aubergine; border-width: 0; padding: 0; } GtkTreeView { background-color: @super-light-aubergine; } GtkTreeView:selected { background-color: @selected_bg_color; } #toolbar-popup { padding: 0; } software-center-13.10/data/ui/gtk3/css/softwarecenter.highcontrast.css0000664000202700020270000000117212151440100026266 0ustar dobeydobey00000000000000#featured-star { border-color: #ED0000; /* darkish red */ color: #ED0000; /* darkish red */ } .section-sel-bg { background-color: pink; } GtkArrow:hover, .section-sel:hover, .symbolic-icon:hover, .label-tile:hover, .category-tile:hover { color: blue; } .cellrenderer-button { border-radius: 0; } .more-link { -GtkButton-inner-border: 0; -GtkButton-default-border: 0; -GtkButton-default-outside-border: 0; background-color: #FF8888; /* pinkish */ } .subtle, #subtle-label { background-color: pink; /* link hover bg color */ } GtkViewport { border-width: 0; padding: 0; } software-center-13.10/data/ui/gtk3/css/softwarecenter.highcontrastinverse.css0000664000202700020270000000143412151440100027663 0ustar dobeydobey00000000000000#featured-star { border-color: #ED0000; /* darkish red */ color: #ED0000 /* darkish red */ } .section-sel-bg { background-color: #666699; } GtkArrow:hover, .section-sel:hover, .symbolic-icon:hover, .label-tile:hover, .category-tile:hover { color: pink; } .cellrenderer-button { border-radius: 0; } .grid-lines { border-color: white; } .subtle, #subtle-label { color: pink; /* non-link text color */ background-color: #666699; /* link hover bg color */ } .item-view-separator { border-color: white; border-width: 2px; } .more-link { -GtkButton-inner-border: 0; -GtkButton-default-border: 0; -GtkButton-default-outside-border: 0; background-color: #FF8888; /* pinkish */ } GtkViewport { border-width: 0; padding: 0; } software-center-13.10/data/ui/gtk3/report_abuse.ui0000664000202700020270000003426312151440100022275 0ustar dobeydobey00000000000000 400 500 12 center-on-parent 400 True softwarecenter True 2 True False False True True True 0 0 True True 0 6 label True 8 1 1 True 6 6 3 True 9 True gtk-missing-image 6 False False 0 True 0 label True end 1 False False 0 True 3 True 0 label 0 False 3 True 3 True 0 label False 0 True True automatic automatic in True True 2 word-char 2 2 False 1 4 True 0 Thank you. True True False 5 1 0 True True 0 5 end True 1 True 6 end gtk-cancel True True True True False False 5 0 Report True False True True False False 5 1 False False 2 False False 1 True True 60 True False True 2 False word False True True 0 Error Details False False 3 software-center-13.10/data/ui/gtk3/dialogs.ui0000664000202700020270000007211712151440100021225 0ustar dobeydobey00000000000000 False 5 True center normal True True False vertical 2 True False end Cancel False True True False False False False 0 Repair False True True True True True False False False 1 False True end 0 True False 24 12 True False 0 <span weight="bold" font_size="large">New software can’t be installed, because there is a problem with the software currently installed. Do you want to repair this problem now?</span> True True 1 0 1 1 True False 48 softwarecenter 0 0 1 1 True False 0 True True 1 1 1 1 True False 0 1 1 1 True True 1 button1 button2 False 5 True center dialog True True False vertical 2 True False end Cancel False True True False False False False 0 Remove False True True True True True False False False 1 False True end 0 True False 6 6 6 6 True False 2 2 True False 48 applications-other GTK_FILL 6 6 True False 0 True True 1 2 GTK_FILL GTK_FILL 6 6 True False queue 150 True True never 1 2 1 2 6 6 False True 1 button_deauthorize_cancel button_deauthorize_do False 5 False True center-on-parent dialog True False True False vertical 2 True False end Cancel False True True False False False False 0 Remove False True True True True True False False False 1 False True end 0 True False 6 6 True False 48 applications-other 0 0 1 1 True False 0 <big><b>Place holder text giving an impression of the kind of label the are will contain</b></big> True True 1 0 1 1 150 True True True True never etched-in 1 1 1 1 True True 1 button_dependency_cancel button_dependency_do False 5 True center-on-parent True normal True error There was a problem posting this review to %s %s had not responded within 30 seconds. True False vertical 2 True False end gtk-cancel False True True True False True False False 0 Retry False True True True False False False 1 False True end 0 gwib_err_cancel_button gwib_err_retry_button False 5 normal True False vertical 2 True False end OK False True True True True True False False False 0 False True end 0 True False 6 6 6 6 True False 2 2 True False 48 applications-other GTK_FILL 6 6 True False 0 <span weight="bold" font_size="large">Sorry, %s can't be installed at the moment. Try again in a day or two.</span> True True 40 1 2 GTK_FILL 6 6 True True True True True True True False Details 1 2 1 2 False True 1 button_install_error_ok software-center-13.10/data/ui/gtk3/SoftwareCenter.ui0000664000202700020270000007524112151440100022537 0ustar dobeydobey00000000000000 _Go Back gtk-go-back _Go Forward gtk-go-forward False normal 4.1.15 ©2009–2013 Canonical http://launchpad.net/software-center launchpad.net/software-center translator-credits softwarecenter True False vertical 2 True False end False True end 0 True False gtk-help 1 1 True False gtk-find 1 True False gtk-add 1 True False gtk-network 1 True False gtk-add 1 False center softwarecenter True False True False True False False _File True True False _Install True False False False True image3 False gtk-remove True False False False True True True False False Reinstall Previous _Purchases… True True False False _Deauthorize Computer… True True False False Sync Between Computers… True True False gtk-close True False False True True True False False _Edit True True False gtk-undo True False False True True gtk-redo True False False True True True False gtk-copy True False False True True True False False Copy _Web Link True gtk-paste True False False True True gtk-delete True False False True True True False gtk-select-all True False False True True True False Search… True False False True image2 False True False True False False _Software Sources… True True False False _View True True False True False False _All Software True True True menuitem_view_supported_only True False False _Canonical-Maintained Software True True True False _Go Back True False navhistory_back_action False True False _Go Forward True False navhistory_forward_action False True False True False True False False _New Applications in Launcher True True False True False False Turn On Recommendations… True True False False _Help True True False True False False True image1 False True False For Software _Developers True False False False True True False Terms of Use True False False False gtk-about True False False True True False True 0 True False False False True True 1 False True center-on-parent 440 250 dialog True False True False 10 10 10 10 True False Updating software catalog… software-center-13.10/data/ui/gtk3/submit_usefulness.ui0000664000202700020270000002606312151440100023361 0ustar dobeydobey00000000000000 400 False 12 center-on-parent True softwarecenter True False 2 True False False False True False True True False 0 0 True False True False 0 6 label True True True 8 1 True True 1 True False 6 False 6 3 True False 9 True False gtk-missing-image 6 False False 0 True False 0 label True end True True 1 False False 0 True False 0 Thank you. True True False True 1 1 True True 1 True False True False 0 5 end True True True 1 True False 6 end gtk-cancel True True True False True False False 5 0 Submit True False True True False False False 5 1 False False 2 False False 2 software-center-13.10/data/ui/sso/0000755000202700020270000000000012224614354017205 5ustar dobeydobey00000000000000software-center-13.10/data/ui/sso/sso.ui0000664000202700020270000011276512151440100020347 0ustar dobeydobey00000000000000 True False 5 True False 5 True False True 0 True False 5 True False True 1 True False password help True False True 2 True False 0 0 True False 300 60 True False 300 60 True False False False 0 300 True False gtk-missing-image True True 1 False False 0 True False False True True True False none False True False reload False True 0 False True 1 False True 3 True False False True 4 yes to updates False True True False False True True False True 5 True False 5 yes to tc False True True False False True False True 0 True False start show tc False True True True False False False 1 False True 1 True False 0 tc warning True True True 2 False True 6 True False 5 True False start login button False True True True False none False False 0 False True 0 True False 5 end gtk-cancel False True True True False True False False 0 gtk-go-forward False True True True False True False False 1 False True end 1 False True end 7 True False 10 True False True True True 0 True False end gtk-close False True True True False True False False 0 False True 1 True False 10 True False 0 0 True False 5 True True 0 True False 5 True False start forgot password button False True True True True False none False False 10 0 False True 0 True False 5 end gtk-cancel False True True True False True False False 0 gtk-go-back False True True True False True False False 1 gtk-connect False True True True False True False False 2 False True end 1 False True 1 True False 10 True False 10 True False 0 0 True False 5 True True 0 True False 5 end gtk-cancel False True True True False True False False 0 gtk-go-back False True True True False True False False 1 gtk-ok False True True True False True False False 2 False True 1 True False 10 True False True False label True False True 0 True False 0 0 True False 5 True True 1 True True 0 True False 5 end gtk-cancel False True True True False True False False 0 gtk-ok False True True True False True False False 1 False True 1 True False True True 10 never in True True 0 True False end gtk-go-back False True True True False True False False 0 False True 1 True False 10 True False 0 0 True False True True 0 True False 5 end gtk-ok False True True True False True False False 0 False True 1 False 10 center True False 5 True False 0 Header Label True False True 5 0 True False 0 help label True False True 1 True False 0 warning label True False True 2 True True False False True True 3 software-center-13.10/data/featured.menu.in0000664000202700020270000000205712151440100021045 0ustar dobeydobey00000000000000 <_Name>Featured gtk-about carousel-only armagetronad calibre cheese homebank stellarium gimp inkscape blender audacity gufw frozen-bubble fretsonfire moovida liferea arista gtg freeciv-client-gtk supertuxkart tumiki-fighters tuxpaint webservice-office-zoho software-center-13.10/data/piston_get_reviews_helper.py0000777000202700020270000000000012151440077035766 2../utils/piston-helpers/piston_get_reviews_helper.pyustar dobeydobey00000000000000software-center-13.10/data/top-rated.menu.in0000664000202700020270000000062412151440100021143 0ustar dobeydobey00000000000000 <_Name>Top Rated gtk-about available-only carousel-only 4 100 Application software-center-13.10/data/images/0000755000202700020270000000000012224614354017231 5ustar dobeydobey00000000000000software-center-13.10/data/images/spinner.gif0000664000202700020270000002672412151440100021374 0ustar dobeydobey00000000000000GIF89a ÷ !!!+++...111333444666777888999:::===>>>OOOhhhiiikkknnnuuuwwwyyy{{{~~~€€€„„„†††‡‡‡ˆˆˆ———œœœžžžŸŸŸ£££¥¥¥ªªª«««²²²¸¸¸ººº»»»¾¾¾ÃÃÃÆÆÆÊÊÊÏÏÏÐÐÐÑÑÑÒÒÒÔÔÔÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááâââãããäääåååæææçççèèèéééêêêëëëíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüýýýþþþÿÿÿ«¬­&&&///555???@@@BBBDDDEEEFFFHHHIIIKKKNNNRRRXXX___vvvŒŒŒ‘‘‘’’’“““•••ššš   ¡¡¡¦¦¦­­­±±±µµµ···¹¹¹½½½¿¿¿ÌÌÌÍÍÍÎÎÎììì """(((AAAGGGLLLTTTUUUVVVYYYZZZ[[[aaabbbpppŽŽŽ˜˜˜™™™¤¤¤©©©¬¬¬®®®ÀÀÀÄÄÄÅÅÅËËËÓÓÓ'''))),,,JJJSSSWWW```dddeeegggmmmooo‹‹‹¨¨¨´´´¶¶¶ÂÂÂÇÇÇÈÈÈÉÉÉ]]]fff|||–––ÁÁÁ$$$***PPPqqqsssttt¢¢¢¯¯¯³³³###%%%---ccc‚‚‚222;;;CCC\\\ƒƒƒ………ŠŠŠ°°°<<ô9ò¥ ¹.9¬ù Ê¥^¦0C¬)y|( ‘%Ž)kÞ̹³gÍ!ù, ÿÃH° Áƒ*\Ȱ¡Cƒ`Žó°¡Š4h U\hE‚™2u¸l˜„ /§°ù¨ÆÊÈ0W4´‰#¥”•dÔ\ÈåËÃ%vâ°qƒÂ‹9fÆÐé‰-·à™g ›Daüœ)(L`=DrgÎ!Âtat2 °4¢T‚aN™Ô !eE&+A-øe ”.¯P±‚x!˜-‚‘ñ§ŸUr rc‡Ü„QJôÙ£'Ï"…Kàöð‰Ë @°áPÈD5k„@j¡(‹æ!;~HaØ%ËŠŽ¿ ÚˆÊò`¢D¹ÕN *Ù$rC#p‹„Ps‹@!p‡;üÒl'a¶ß˜ânöŸ5l(ãeØ `|‘>hQQaUPÔE`ù€~½$Ð[44!!B[$áuvèᇠ†(b!ù, ÿÃH° Áƒ*\Ȱ¡CƒJ@…Šò!˜’"}¨¸ÊIâ\á8É¡_B©$é¥*$Ã` ‘I“©”S,µ|3²Ë””|ât S©/W.IrT‡K(€lqÈE”§Mtè  ÊÀ¤QaDx’A£ÑÃ%:Õ‘À'Œ–©¼€ñsРЉ€è´i…Á 5hñ±‘‹SS ~™¥ËÃ.W°€±¸ÅñB%-H‘J42!— 7vàEèÕ ¤ñP¸Ä.@zQ墶‹! ™¸†­0Ê 6‚pQÈeÈŽR~áÒeòB0_œ;ÜÒˆŠô`¢D‰MÕN ZbÙa×E€‰,PˆëäÁü°[ãI%5nL ÓÄuø‡N¬bƒ`x±ƒ]A@—>hÁÑWLÖEvá„Å4v5¡!B[$áw–hâ‰(¦¨b!ù, ÿÃH° Áƒ*\Ȱ¡CƒKü”ò!˜h^᩸°Š&4®è\á8pK_F‰…f,+$ÃxIëV¯”Tdµ”€%L—#Snñi‚-^_®ÌBÓj—*\IâL) ¸jÑ ¦ƒ»Âð€U¢‡NBa¸åIW˜-Á€yC @¤\O*&…Á‚ ƒxÐüâ…c”TÀ¸ÌJ—‡_¶hcqËã…XlÀ85C‹B.An숲PH°_©|%Q¸„†ë)™A{F#…L\Ó€­0‹9ˆ\FÈeÈŽ‚áÒ…²ò/ÎniD%ú@0Q¢Ä~ÈÅN *Z©A#Èð†Jt æŠç0Bt'wHÄu¼ãqm¢;üÃ(7¬¢^ìàZ`|‘>¼÷YPÖE®á…Å4P}44¡!B[$áÄv–hâ‰(¦¨¢@!ù, ÿÃH° Áƒ*\Ȱ¡CƒM šò!>jô©¸ÐŠ15 ŠaáHðJ•S:E`€É IFDåIö%L•ZhÙI£Šˆ@l8öâ –[Pèb¥X=*ÒóAa½Y3( 2be„…zHE(H…Ù²H‘0ÊVª¢“RgQ1l§(©Ð¨ÑÅàC»˜<Ó¥°Å-Žr!’Çȹ¹±°B'4BÓ€¢p‰è5‚ýD!“Ó©r‚#G/’‡ìø!Å"—.`,~ þpK#*Ä ‚‰%¶C.>;1¨¤ ˜š]$ ˜+Z HÝûa‘Ð5ZWÇA±‰èé«è¸¡Œ—¡ƒ€ù’ðq±Ep]äÄq‘ÜKaZ "´ÅN8á…f¨á††!ù, ÿÃH° Áƒ*\Ȱ¡CƒHñ²ò!Îä ¨¸ðÊgp(dáXÌ@*ÏœUòtEàFvQÄ‹†I+Ñœ½©52 ®fk9$"m``²sÖZ+Ÿ"=ZÊaR&úäÑfZ& Ô„b6UÙ4‡XNíÒ¥§W.5h|ÑbÒ2V³¢<”âk FñceG G,?~x1e &»tiˆEʆ_”èÐQdrÂ-Á‚防0 Ó4 (\‚ºÇ…RPÓx¢Ië× ¿Á‘£Èâ„\†ìBÅ"—.&‚ù’Üá–FTš% î‡\|ШáÄ ’¸A<;Qd}ºH0W´‚ZJÅ#§kÐþŽƒtÔÝ^ÙqC /;œÄrIà„z‘maR9œDr\HGRDœÙ„m±„×aèᇠ†(∠!ù, ÿÃH° Áƒ*\Ȱ¡Cƒ\V­úò°!/ ´ZT\ˆå­M¸lLh%6MŸ°ÄrÈ””‡Kh¦#¶:д„Y‚«\­$#¤b-x°ÉêÐÅJ†k“˜iÊÒJŒ¦P sˆÂ,%D•Êa¨XJ†KŽ_¶PpÅ,’­—±è@Õ‹‡Á>Ö"[²qË’$ J)5ÊIÅ.]^Bx!%:t‰œH¦j -¬Rƒ†i( ÷8²f€–J’¥M?Q¸ƒ"8#“$GŽ"^r²ˆ•†iZþ¢Üá–FTš%Šn‡\|ШḠ’ÒA8;UŒiºH0Wt†bš^‡GLטýÇ”0MÚwwxeŽ%`x±ƒiA€ñE8¡ÞC]ˆF9˜„r\H7RD˜ÖÄ… æÄu†(âˆ$–h"B!ù, ÿÃH° Áƒ*\Ȱ¡ÃƒW´0 !B}!rñ±6*A0”›Õâ’­E€R¹¢E%§d”ñ‚x$iÓJjà˜»‡À€1º ^Ùc ˜•I†ǸâIx~F9$„H]QÞF‘”N´ÅN¼”ᇠ†(âˆ$!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡‚‘a A´TL& GRŒÈe‹@/;>öøfËS:F6œ’G“0(?þÓE• ]% 8#ä£)a˜¬Z%Œ%âùiô°ÈGIf¹ *ñðˆëò 5šdä'\©*·8‘"s –«¬@ìB–á–&FŽPYE‡Ž"} W–' ;vÑqU¨Ä±è”s&ÁCâ‚“¯>QØŽiÓÙ>Œ²#G/ xëäÉ“‡ ½t©‹°K•ߪniD…w˜,… †ÈÅZ'Á„“dmÂè‡K®ézE£—YfUI3ñÈÇ£•ÔÀ4ž™ð ^Ùc ˜œ4‚€±Òa¬uÁ…@]äðuDÄG75xÐK8Á’„f¨á†vHP@!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡‚‰ØÐJ!Z(&#„# rÙ"ÐË=¾„ñ¢¤GˆSràh²ò$`¾Á"†J‡D<Ú˜†ÉªU/—¤:4WF‡F<ÒH"0Ë=ZLCc¢C-=hÔxYPˆ¸ˆ®BÜâDŠW‚^†üÀ±K—†\ž(Q’ea:ŠÜMˆDÔ wöIØE‡T(OØ2F ¶=^6–úDa‰M›8Ö“a”9Š”F¨ϧOÏD Yè¥Ë[ÆV¬\ÌpK#*·FÙUŠ(D.>Ä:)&W³uϘ@\"µH0W €Ñ"KR«WЪQ ³§€¤3hÒ=¼²Ç0&i`¸ÄÅÊ'yïISu‘ ðÆæ¬Ç !…”  ÄEJDÆK$¡–† †(âˆ$†!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$¨….‚BƒF’Œ¹tèeGÇ_Â|‘âdäC*;r4 S²ã0_ŠÔ˜1ÌC"mL ÃdÕª(ašÐ˜!£FJ‡H:z˜åŠ@"L“¸¥H 2‘‘hF®N¤ø,ø¥I’-»¸\ØEJ#*pFÑ¡£È܃LRôÉ#bÔL„]tH…’ ¡wÇÚe0W(ab©OªÀÅ™ó ½1‹xQØDToÞJ4Zè¥ËZ…^°ÈÝpËÝ×¢F%"_#‚A„cÂ.‘ZD¥¢`¬ÈB“fÛ»¡t¬‘HšI•‘½IB£`[ ‡WvàXÆK4q¹bΚ´<ìr¶‹ \‚Q¼‚À Õp} 5¼ô–Qxø!!Hf¨á†v8P@!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$¸EÉ’.‚BƒF’Œ½` ãeGÇ_Â|¡B%åC+Qxêƒå")Œ²#G± £ìÚ£GÔ¢³ E¢NÅŠ¬¨¬ø„P‹*¹øØê¤ ˜P•$„˜üp‰Ò"QƒùsÅS7ràQyx¤c'`<,[Ç"Œ®ÚÈG!õðÊKÀDIC€Õ,.X:8ˆÀí–‡]¸\rÀ0Nú5‚Ák˜‘´´7Â@R°pn‘K(Ódá…f¨á†!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$ÈEÉ’.‚!BƒF’Œ½` ãeGÇ_€¹r%¢:š4Iã˜/Gj¬‚ÑHGSÂ0Y…CJ˜&i ˜$éÇ0Z´äØQæÃ-=hÔˆbIG!^"nq"ŒÁ/Q ŒtØe­Â/Y®laE‡Ž"n f±á *& »èHÊ3!q¦J©H¡à¤O.2AÙ‹¼£ôÈQ¤1B*†¹[¡„á—.f~ѲÅ3Ã-¨¤&寖¹øÐê¤ iš°ùáúpIÒ"a²¨ . ²Ø!³òðHÇOÀ„ô ]˜Qr$dHb·âá•8–€‰²MY¤ \°„ƒ%Ó·¹l¹d’@™ì¢v!˜-[P.Œ¢CG‘¶·¹± H…]t…¢Ðˆ*À~ ,˜ã…?ZHnácnEÀ"ÄÌEºEX~éRvá.\ä6ÜÒˆJi‚S`¨ ‘‹‹R,eÁܨpÄC/L–T3¼dy‡ ]´QW¡± 40y Œ3fP1lóè½Rxx¥†'pY¤pcvíΗ,~ºa£J3Ã,I0YWí6¨Q€b-+d„ÄÕðŸ@U,CtIG5ÍdóÑA*Œ#Ç/†Kt(âˆ$2!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$Èe‰/¡AcIÆ^ºô²ƒc/aÀhÙÑ MÂäøÌ—#«t\¸‘†)a˜¬Ê!%LŽ4@L‚4‰@-\Aóá–4jD1Ȥ !!nq"ŒÁ/S ˆ|ØeíB0[º˜]E‡Ž"nn r#Ǽ»è@ E!S¤[ FúÄðŒÇ4Š*ŒÒ#G‘°·ø°qcæ„_ä2ãÅË܆[ýÈb Z"réo݃‡>è)d"“Ü £˜/ZòdÈ–‹k‡(”`…LŸ°…1ô,ƒ7xÁþNbÐ ^–)äŒó†\Ú3s¨A\b£Jàb9Ðo‹d´8€KÄáðJ9Q  \ØG^˜Sà.„ »ô–ᇠ†èP@!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/aÀl Ñ MÂäøÌ—#7xd¸‘†)a˜¬Ê!%LŽ4˜@L‚4‰@-\Aâ–4jT-Ȥ !­:‘Æà—)PX:ì¢V¡Ê¶ £èÐQnÁ-Anä@‚2aH¡(dʱP¿€9>QÈi * £ôÈQ$,B.CvðhRvá—.Ý~éë0ȹ; jñ1$*D"m,;g0F R¿R;ô#@Ø0za´(Êñ…ˆs£‚ÙMxAX=y¤Àü©´ U˜d!Dˆ:aäa°ÚòdG™¢i ¹<*Åe"$™ÌY#çÝ–0W 0t%£ü5°dpŸLTX&Ñ—¬‡7~$S6Šã AJœ0Mf¨á† !ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/¹`„h…‡Ž&aHrüæ >´@ÜHÃÆ”0LVå¦ GL &9šD .‰qK5¨dRƒ†•·8‘Æà—)PD>ì¢v!˜-mFÑ¡£H\ƒ[‚Üȱ¤lÂ.:ŽBQ¸”ã*s|¢ÉшFé‘£؃\†ìèÁ˜á—.~ÖDùPˆ®t‘A*qr¹aXõ$™0ˆ£*Pâ¶L^†0[jøø&]Š^§h>4·Ì^3S`¤ÕA§* #@)x%»òPÕ›q}´TF1FgÖ90\Já Ä&I0:éF‡¨Yj¼òÑI1:ÝÀ“[¤e…'tp¢Gk}€,|t¥âž„f¨¡C!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/¹ „h…‡Ž&aHrüæ @¶@ÜHÃÆ”0LVå¦ GQ &9šD .‰%úpK5’dRƒ†Œ·8‘Æà—)PD>ì¢v!˜-mFÑ¡£H\ƒ\‚ÜÈSaG¡(\r‡Î„]VùBDÈcB&…' Ö Î* ¹ ÙñãIC*M ºýRöáQ¦®˜mt1ârö\é2hd ¢*é&ÏÚ«a¸èÌŒŒhy¨+’µf H…)U¡Ãª0>\ ×–af®„LÓb傱 )Àx¡‘.ÆI‡‹$¬éƒù ºZÐt‰Ü$ˆˆNØ%ÐÔòÌw}$=µ|bBw ôE ú¡AQ´€Jvèᇠ!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/¹ „h…‡Ž&aHrüæ @¶@ÜHÃÆ”0LVå¦ GQ &9šD .‰%úpK5’dRƒ†Œ·8‘Æà—)PD>ì¢v!˜-m2¹‘£H\ƒ\‚Ðm´ ¦88àP¸ä¨Ž»• `À ¹Gw€M¨‚’6tZr²ãÕ…Tœ@uû¥ìÃ"&V\1ÛèbÄ › ,eP Ç £2¡õJÛƒpa¸ARS‡`ò\ÓæÀ¸0½: û¦°É• ¯ÈÒÖ`®-W>hKÈã¢f‘®Fz× ¨ÕQ8h'ã)'”rÈERx£A¦Lö‘@XÀ£…˜v @_²A<l¬B0[Ô¾M‘à€1% ¹¹‘ÊB& Þ¥pÉÑn.aP¦Ì0 ™=¹ÐœÏ´"ä2dǪ «4‚º°fÙ‡FPÂbL”(+¡åàM)ƒJº!ݰ‰…mläø ÓEI£²A˜:ãg›8²X„ñÊ Gœ^x8åqIÉ"*ž Ea¾©afÃ/ àHÅŠˆ>£P¡<[e;Ã)‰$c…@SÄÓ‡4¾Äö‘S'ôÈ 4D)ˆøaA\(xá†vHP@!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/» „hŇŽ&aHrüæ‹@¶@ÜHÃÆ”0LVå¦ GQ &9šD .‰%úpK5’dRƒ†Œ·8‘Æà—)PD>tÒ¬B0[Ô¾ä@Á< ¹¹±CkÂ&Ṵ̂b¥Bá’£'2Y ’¼]НL¸¢7sTr²ãGf…Už@]X³ìÃ$¥^d1&J”É‹<ƒ%‹—A%]ƒÈeبœXìÅ|RE`£L‚)A.V·yÉÂ0ZŒ(“£Eby‹S-B\´˜C2åKH˜/Fl¬ò»ð‹(N´Ü­ž"ÈT"e¿X¹‘J :…qE)ƒ ‚ßGÊ é  AUà@„[Vhá…6!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆ^ºô²ƒc/» „hŇŽ&aHrüæ‹@¶@l!ƒ†)a˜¬Ê!%LŽ4¢@ôp«gZ¸$‚´èà op)RZI B0B¢ ƒ_¦@ÙpK£%M´0³…mÂ%Ÿ 0x0‰B.Anìš0Pµj’ D*¡p Ò“ Q¸šìª™4…LþXyPI´JoÈipòwÈ Vra¢¤‘Ý„5Ï>\Â+•Ü‚`¢DáìɆb^Tò5Èë…P@°³mZ˜/U° ‚Ô¯Ã]³l=ã *ÌU7€fLæˆÄa–¶pµK×… ª`3‚Ô4²JGê†_PàÊÀKn_ðõpQ±´ˆ R…ÁÅ/ð!ÛG„*6p!A_HQÅ…vèá‡!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆT¸ô²ƒc/» „ø«R¨&aHrüæ‹@¶@¤¡)a˜¬Ê!%LŽ4¢@ì0À,#µˆ CiчBÚ ˆ¤È “4„`„8$µ©¿LÒÅá–'LœhafK[…¨lÁaÓ—Ç„\‚ÜØA%á•YëÖ¡9pmÂ%Hi"Ä2eË%2A LB.ÅÚî/B.Cv¹²ðJ&lé~ñüÐ "E: ‚‰eåÃ% èy fP Ø wJAí]¸aÀ\Á"PÒ'©]xbŽ0ÆoC=Ê1‰Ã.Þ}àÌ‹ƒÔL2˜ºÃdáúøÉG´mVC]aÄX2Ñ0ÄG1qƒU0x€Vhá…F!ù, ÿÃH° Áƒ*\Ȱ¡Ã‡#J$È¥I#/¡AcIÆR˜€ ãeÇ_vI 1X%}¼”äøÌ%:€lˆ¡ 3†Â0Y•CJ˜&iDŽ™já"HR£‡Èa娔A&5hÁ‘‰^T ~™¥‹C.Pš<ٹ̷ Ñ»4Ç™9& ¹¹±ƒJB,èÖ¨‰a›(…K’ÖD˜%8•.£PÈ$)‘Ý¡…mžˆ&‡ìre!–FN¤àUh´C(¿p¤%&J–™€ ‡A%aƒÌfˆeW.d£f„s‹@!I¡<œ‘Y BG D¿1åhRi 3¡âÆ—™4‚ØLBغÃ*.¨åpËG m±›C]hš@`°NA‹0" ¬›¦ «Q™aÁaºì0& ‘«4¤@äA‚™Ô !Ä#D%)Vh1øe ”.»T‘2… C0[+lÈ$Ô&ääÆ+ Á˜ÒÐΆ /.A ¤ìA0„>¨V C!Ò¦:¡V¢Ä Tr²È•…_¬TÁBS¡ÐØ µ8¡‚üc”(Åné1£†ƒJæQÜ ‘W‹„Ns‹@!h%7ô²ãj'a²ß˜RmV¤AhØP†ýÕ B%ÃÜ5´…U”ÕEWù`ÍI4ÄUJd„PF,a—…vèᇠ†8P@;software-center-13.10/data/submit_review_gtk3.py0000777000202700020270000000000012151440077027706 2../utils/submit_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/data/whats_new.menu.in0000664000202700020270000000067412151440100021250 0ustar dobeydobey00000000000000 <_Name>What’s New gtk-about available-only carousel-only not-installed-only 3 20 Application software-center-13.10/data/ubuntu-software-center.desktop.in0000664000202700020270000000102612151440100024376 0ustar dobeydobey00000000000000[Desktop Entry] _Name=Ubuntu Software Center _GenericName=Software Center _Comment=Lets you choose from thousands of applications available for Ubuntu Exec=/usr/bin/software-center %u Icon=softwarecenter Terminal=false Type=Application Categories=PackageManager;GTK;System;Settings; MimeType=application/x-deb;application/x-debian-package;x-scheme-handler/apt; StartupNotify=true X-Ubuntu-Gettext-Domain=software-center X-Unity-IconBackgroundColor=#ffbf87 _Keywords=Sources;PPA;Install;Uninstall;Remove;Purchase;Catalogue;Store;Apps; software-center-13.10/data/expunge-cache.py0000777000202700020270000000000012151440077025506 2../utils/expunge-cache.pyustar dobeydobey00000000000000software-center-13.10/run-tests.sh0000775000202700020270000000502312200237544017354 0ustar dobeydobey00000000000000#!/bin/bash set -e TESTS_DIR="tests" dpkg-checkbuilddeps -d 'xvfb, python-mock, python-unittest2, python3-aptdaemon.test, python-lxml, python-qt4' if [ ! -e /var/lib/apt-xapian-index/index ]; then echo "please run sudo update-apt-xapian-index" exit 1 fi # check if basic http access works HTTP_URL=http://software-center.ubuntu.com if ! curl -s $HTTP_URL >/dev/null; then echo "NEED curl and http access to $HTTP_URL" exit 1 fi ./setup.py build # run with xvfb XVFB_CMDLINE="" # mvo 2012-11-05: disabled as this causes hangs in raring #XVFB=$(which xvfb-run) XVFB="" if [ $XVFB ]; then XVFB_CMDLINE="$XVFB -a" fi PYTHON="$XVFB_CMDLINE python -m unittest" # and record failures here OUTPUT=$TESTS_DIR"/output" FAILED="" run_tests_for_dir() { for i in $(find $1 -maxdepth 1 -name 'test_*.py'); do TEST_NAME=$(basename $i | cut -d '.' -f 1) TEST_PREFIX=$(echo `dirname $i` | sed -e s'/\//./g') printf '%-50s' "Testing $TEST_NAME..." if ! $PYTHON -v -c -b $TEST_PREFIX.$TEST_NAME > $OUTPUT/$TEST_NAME.out 2>&1; then FAILED="$FAILED $TEST_NAME" echo "[ FAIL ]" # add .FAIL symlink to make finding the broken ones trivial (cd $OUTPUT ; ln -s $TEST_NAME.out $TEST_NAME.out.FAIL) else echo "[ OK ]" rm -f ${OUTPUT}/$file.out; fi done } if [ "$1" = "--sso-gtk" ]; then # Run the SSO GTK+ suite $PYTHON discover -s softwarecenter/sso/ elif [ $# -gt 0 ]; then # run the requested tests if arguments were given, # otherwise run the whole suite # example of custom params (discover all the tests under the tests/gtk3 dir): # ./run-tests.sh discover -v -s tests/gtk3/ # See http://docs.python.org/library/unittest.html#test-discovery # for more info. RUN_TESTS="$PYTHON $@" echo "Running the command: $RUN_TESTS" $RUN_TESTS else # 2012-05-30, nessita: Ideally, we should be able to run the whole suite # using discovery, but there is too much interference between tests in # order to do so, so we need a new python process per test file. ##RUN_TESTS="$PYTHON discover -v -c -b" rm -rf $OUTPUT mkdir $OUTPUT run_tests_for_dir "$TESTS_DIR/gtk3" run_tests_for_dir $TESTS_DIR # gather the coverage data ##./gen-coverage-report.sh if [ -n "$FAILED" ]; then echo "FAILED: $FAILED" echo "Check ${OUTPUT}/ directory for the details" exit 1 else echo "All OK!" fi fi ./setup.py clean software-center-13.10/PKG-INFO0000644000202700020270000000210212224614355016144 0ustar dobeydobey00000000000000Metadata-Version: 1.1 Name: software-center Version: 13.10 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Requires: PyQt4 Requires: apt Requires: apt_pkg Requires: aptdaemon Requires: aptsources.sourceslist Requires: cairo Requires: dbus Requires: debtagshw Requires: defer Requires: gi Requires: gi.repository.Atk Requires: gi.repository.GLib Requires: gi.repository.GMenu Requires: gi.repository.GObject Requires: gi.repository.Gdk Requires: gi.repository.GdkPixbuf Requires: gi.repository.Gio Requires: gi.repository.Gst Requires: gi.repository.Gtk Requires: gi.repository.LaunchpadIntegration Requires: gi.repository.PackageKitGlib Requires: gi.repository.Pango Requires: gi.repository.PangoCairo Requires: gi.repository.Soup Requires: gi.repository.UnityWebapps Requires: gi.repository.WebKit Requires: httplib2 Requires: lxml Requires: mock Requires: oneconf.dbusconnect Requires: oneconf.enums Requires: piston_mini_client Requires: ubuntu_sso Requires: xapian Requires: xdg Provides: softwarecenter software-center-13.10/README0000664000202700020270000001023712151440100015721 0ustar dobeydobey00000000000000= User notes = The software-center application aims to make the software handling on the computer easy and consistent. = Developer notes = All non UI code must come with tests in the test/ subdirectoy. To setup your development environment, you'll need to ensure the following extra packages are installed: sudo apt-get install xvfb python-coverage python-mock python-aptdaemon.test \ python-qt4 python-unittest2 python-lxml pep8 sudo apt-get build-dep software-center You can then run tests with: cd test;make You can run a developer instance with: python setup.py build ./software-center The initial launch of this will take a bit as it will build a private search database but this is only needed once. == query parser == The query parser understands : "pkg:2vcard", "mime:text/html", "section:web", "origin:main" prefixes. == aptdaemon == * the dbus limits for the system bus are rather low, this means that adding 512 and using something bigger than 512 is a good idea == environment == The following environment variables are supported: SOFTWARE_CENTER_AGENT_HOST - an alternative host to query for pay software SOFTWARE_CENTER_REVIEWS_HOST - an alternative host for the ratings&reviews SOFTWARE_CENTER_DEBUG_HTTP - enable httplib2 debuging SOFTWARE_CENTER_DEBUG_WEBKIT - enable webkit debuging SOFTWARE_CENTER_IPSUM_REVIEWS - generate random reviews SOFTWARE_CENTER_FAKE_REVIEW_API - use a fake server for all review network operations SOFTWARE_CENTER_GWIBBER_MOCK_USERS=2 - use mock gwibber service SOFTWARE_CENTER_AGENT_INCLUDE_QA - show not yet QA apps available from the agent SOFTWARE_CENTER_NET_DISCONNECTED - make software-center's netstatus module believe network manager is in a disconnected state SOFTWARE_CENTER_WEBLIVE_HOST - overwrite default weblive server SOFTWARE_CENTER_DISTRO_CODENAME - overwrite "lsb_release -c -s" output SOFTWARE_CENTER_ARCHITECTURE - overwrite the current architecture SOFTWARE_CENTER_NO_SC_AGENT - disable the software-center-agent SOFTWARE_CENTER_DISABLE_SPAWN_HELPER - disable everything that is run via the "SpawnHelper", i.e. recommender-agent, software-center-agent, reviews SOFTWARE_CENTER_DEBUG_TABS - show notebook tabs for debugging SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK - disables certificates checking in webkit views (for use in test environments) SOFTWARE_CENTER_FORCE_NON_SSL - disable SSL (for use in test environments) == applications.menu == The menu file parser understands: Category, And, Or, Not The following additional XML filters are definied: SCType - e.g. "Applicatin" SCChannel - e.g. "lucid-partner" SCSection - e.g. "net" SCPkgname - e.g. "gimp" Additional .menu files can be added in: /usr/share/app-install/menu.d that software-center will read and parse. == XAPIAN == The following special prefixes are used: AA - application name (Abiword) AP - package name (abiword) AS - archive pocket (main) AE - archive section (mail, base, ...) AC - category (AudioVideo) AM - MimeType (application/x-ogg) AT - type (Application) AH - channel The following values are used: XAPIAN_VALUE_PKGNAME - pkgname XAPIAN_VALUE_ICON - icon name XAPIAN_VALUE_GETTEXT_DOMAIN - gettext domain XAPIAN_VALUE_ARCHIVE_SECTION - archive section (main, restricted, universe, multiverse) XAPIAN_VALUE_ARCHIVE_ARCH - architectures (seperated with ",", e.g. i386,amd64) - may be empty XAPIAN_VALUE_POPCON - popcon data XAPIAN_VALUE_SUMMARY - summary text XAPIAN_VALUE_DESKTOP_FILE - the desktop file that the information comes from XAPIAN_VALUE_PRICE - the price (if its a for-pay app) XAPIAN_VALUE_ARCHIVE_CHANNEL - channel (third party) XAPIAN_VALUE_ARCHIVE_PPA - the PPA name that the application is in XAPIAN_VALUE_ARCHIVE_DEBLINE - a deb line for the sources.list to access the given app XAPIAN_VALUE_ARCHIVE_SIGNING_KEYID - signing key id for the repository XAPIAN_VALUE_PURCHASED_DATE - the data a for-pay app was purchased (only available after the software-center-agent server was queried) XAPIAN_VALUE_SCREENSHOT_URLS - a (optional) list of "," seperated screenshot urls that overrides the default XAPIAN_VALUE_ICON_NEEDS_DOWNLOAD - icon needs to be fetched XAPIAN_VALUE_THUMBNAIL_URL - thumbnail url software-center-13.10/contrib/0000755000202700020270000000000012224614354016513 5ustar dobeydobey00000000000000software-center-13.10/contrib/USC-start-stop-times.py0000664000202700020270000000270412151440100022742 0ustar dobeydobey00000000000000#!/usr/bin/env python import ldtp import time import unittest start_time = time.time() class TestCaseUSCStartStop(unittest.TestCase): def setUp(self): ldtp.launchapp('./software-center') assert ldtp.waittillguiexist('frmUbuntuSoftwareCent*') self.msgs = [] a = "Time taken for the frame to open is: " + str( time.time() - start_time) + " Cpu percentage: " + str(ldtp.getcpustat('software-center')) + " Memory usage in MB: " + str(ldtp.getmemorystat('software-center')) self.msgs.append(a) def tearDown(self): ldtp.selectmenuitem('frmUbuntuSoftwareCent*', 'mnuClose') assert ldtp.waittillguinotexist('frmUbuntuSoftwareCent*') c = "This test took a total of " + str(time.time() - start_time) + " Cpu percentage: " + str(ldtp.getcpustat('software-center')) + " Memory usage in MB: " + str(ldtp.getmemorystat('software-center')) self.msgs.append(c) print '\n'.join(self.msgs) def test_1(self): ldtp.waittillguiexist('frmUbuntuSoftwareCent*', 'btnAccessories') assert ldtp.objectexist('frmUbuntuSoftwareCent*', 'btnAccessories') b = "Time taken from start to find the Accessories button " + str( time.time() - start_time) + " Cpu percentage: " + str(ldtp.getcpustat('software-center')) + " Memory usage in MB: " + str(ldtp.getmemorystat('software-center')) self.msgs.append(b) if __name__ == "__main__": unittest.main() software-center-13.10/contrib/appstream-xml/0000755000202700020270000000000012224614354021305 5ustar dobeydobey00000000000000software-center-13.10/contrib/appstream-xml/appdata.xml0000664000202700020270000000277512151440100023437 0ustar dobeydobey00000000000000 firefox.desktop firefox Firefox Firefoux Web browser Navigateur web internet web browser navigateur web-browser network web text/html text/xml application/xhtml+xml application/vnd.mozilla.xul+xml text/mml application/x-xpinstall x-scheme-handler/http x-scheme-handler/https http://www.mozilla.com cheese.desktop cheese Cheese Take photos and videos with your webcam, with fun graphical effects cheese GNOME AudioVideo software-center-13.10/contrib/simulate-slow-network.sh0000775000202700020270000000111412151440100023326 0ustar dobeydobey00000000000000#!/bin/sh if [ -z "$1" ]; then NETDEV=$(route -n|grep ^0.0.0.0|awk '{print $8}') else NETDEV="$1" fi if [ "$(id -u)" != "0" ]; then echo "You need to be root to run this script" exit 1 fi if [ -z "$NETDEV" ]; then echo "Can not find a default netdev, please specifcy one" exit 1 fi echo "Simulating slow network for default gateway device $NETDEV" # reset tc qdisc del dev $NETDEV root 2> /dev/null # make it slow tc qdisc add dev $NETDEV root handle 1: tbf rate 64kbit buffer 1600 limit 3000 tc qdisc add dev $NETDEV parent 1: handle 10: netem delay 1000ms software-center-13.10/run_local.sh0000775000202700020270000000106212151440100017352 0ustar dobeydobey00000000000000#!/bin/sh export SOFTWARE_CENTER_REVIEWS_HOST="http://127.0.0.1:8000/reviews/api/1.0" export SOFTWARE_CENTER_FORCE_NON_SSL=1 export SOFTWARE_CENTER_FORCE_DISABLE_CERTS_CHECK=1 # 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 if [ -n "$PYTHONPATH" ]; then export PYTHONPATH=$(pwd):$PYTHONPATH else export PYTHONPATH=$(pwd) fi if [ ! -d "./build" ]; then echo "Please run: 'python setup.py build' before $0" fi ./bin/software-center $@ software-center-13.10/run_against_testing_env.sh0000775000202700020270000000060112151440100022311 0ustar dobeydobey00000000000000#!/bin/sh export SOFTWARE_CENTER_REVIEWS_HOST="https://reviews.staging.ubuntu.com/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) ./bin/software-center $@ software-center-13.10/utils/0000755000202700020270000000000012224614355016214 5ustar dobeydobey00000000000000software-center-13.10/utils/delete_review_gtk3.py0000777000202700020270000000000012151440077026521 2submit_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/utils/update-software-center-channels0000775000202700020270000000451312151440100024310 0ustar dobeydobey00000000000000#!/usr/bin/python import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) import logging import sys import xapian from gi.repository import GObject, GLib from softwarecenter.db.database import StoreDatabase from softwarecenter.backend.installbackend import get_install_backend import softwarecenter.paths LOG = logging.getLogger("update-software-center-channels") def compare_channels_in_db_to_cache(db): # the operation get_origins can take some time (~60s?) cache_origins = set(db._aptcache.get_all_origins()) db_origins = set(db.get_origins_from_db()) # origins LOG.debug("cache_origins: %s" % cache_origins) LOG.debug("db_origins: %s" % db_origins) # the db_origins will contain origins from the s-c-agent, so # we don't need to rebuild if the db has more origins then # the cache, but we need to rebuild if the cache has origins # that a-x-i does not have if not cache_origins.issubset(db_origins): return True return False def trigger_axi_update_and_wait(): def _axi_finished(res): main.quit() context = GLib.main_context_default() main = GLib.MainLoop(context) system_bus = dbus.SystemBus() try: axi = dbus.Interface( system_bus.get_object("org.debian.AptXapianIndex","/"), "org.debian.AptXapianIndex") axi.connect_to_signal("UpdateFinished", _axi_finished) # first arg is force, second update_only axi.update_async(True, False) main.run() except Exception as e: print e LOG.warning("could not update axi") def check_for_channel_updates_and_trigger_axi(): db = StoreDatabase() db._aptcache.open() db.open() if compare_channels_in_db_to_cache(db): trigger_axi_update_and_wait() return True return False if __name__ == "__main__": # poor mans argparse if "--debug" in sys.argv: logging.basicConfig(level=logging.DEBUG) # do it try: res = check_for_channel_updates_and_trigger_axi() # return "1" means xapian got updated if res: sys.exit(1) except xapian.DatabaseOpeningError as e: # this can happen if there is no a-x-i DB yet and we can ignore it LOG.info("failed to open xapian db: '%s'" % e) # return "0" nothing was done sys.exit(0) software-center-13.10/utils/update-software-center-agent0000775000202700020270000001022212151440100023605 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2010 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 import apt import gettext import locale import logging import os import os.path import shutil import string import sys import xapian from optparse import OptionParser if os.path.exists("./softwarecenter/enums.py"): sys.path.insert(0, ".") import softwarecenter.log import softwarecenter.paths from softwarecenter.paths import XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT from softwarecenter.db.update import update_from_software_center_agent if __name__ == "__main__": try: locale.setlocale(locale.LC_ALL, "") except Exception, e: logging.warn("setlocale failed with '%s'" % e) # init gettext gettext.bindtextdomain("software-center", "/usr/share/locale") gettext.textdomain("software-center") # parser parser = OptionParser() parser.add_option("--debug", "", action="store_true", default=False, help="show debug output") parser.add_option("--ignore-cache", "", action="store_true", default=False, help="ignore the local cache when updating") parser.add_option("--datadir", "", default=None) parser.add_option("--pretend-distro", "", default=None) parser.add_option("--target-db-path", "", default=XAPIAN_BASE_PATH_SOFTWARE_CENTER_AGENT) (options, args) = parser.parse_args() logging.basicConfig(level=logging.INFO) if options.debug: logging.basicConfig(level=logging.DEBUG) softwarecenter.log.root.setLevel(level=logging.DEBUG) if options.datadir: softwarecenter.paths.datadir = options.datadir if options.pretend_distro: os.environ["SOFTWARE_CENTER_DISTRO_CODENAME"] = options.pretend_distro # support global disable if "SOFTWARE_CENTER_NO_SC_AGENT" in os.environ: logging.warn("SOFTWARE_CENTER_NO_SC_AGENT in environ disabled the agent") sys.exit(1) # get a cache cache = apt.Cache(memonly=True) # setup path pathname = options.target_db_path + ".tmp" if not os.path.exists(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): logging.warn("Cannot write to '%s'." % pathname) logging.warn("Please check you have the relevant permissions.") sys.exit(1) # get a writable DB try: db = xapian.WritableDatabase(pathname, xapian.DB_CREATE_OR_OVERWRITE) except xapian.DatabaseLockError: # Ref: http://launchpad.net/bugs/625189 logging.warn("Another instance of the update agent already holds " "a write lock on %s" % pathname) sys.exit(1) # ensure permissions are 0700 as this may contain repo passwords from # the reinstall-previous-purchase repos os.chmod(pathname, 0o700) # the following requires a http connection, so we do it in a # seperate database include_sca_qa = "SOFTWARE_CENTER_AGENT_INCLUDE_QA" in os.environ if not update_from_software_center_agent(db, cache, options.ignore_cache, include_sca_qa): logging.debug("no updates from update-software-center-agent") sys.exit(1) # flush ... db.flush() del db # and move into place final_pathname = string.rsplit(pathname, ".tmp", 1)[0] if os.path.exists(final_pathname): shutil.rmtree(final_pathname) os.rename(pathname, final_pathname) software-center-13.10/utils/stats.py0000664000202700020270000000434612151440100017715 0ustar dobeydobey00000000000000#!/usr/bin/python # 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 import os import sys import xapian sys.path.insert(0, "../") from softwarecenter.enums import XAPIAN_VALUE_PKGNAME from softwarecenter.paths import XAPIAN_BASE_PATH if __name__ == "__main__": # mapping from a package to the apps it has pkg_to_app = {} # mapping from applications names to packages (a generic name # like Terminal may be provided by multiple packages) app_to_pkg = {} # gather data pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) for m in db.postlist(""): doc = db.get_document(m.docid) appname = doc.get_data() pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME) # add data if not pkgname in pkg_to_app: pkg_to_app[pkgname] = set() pkg_to_app[pkgname].add(appname) if not appname in app_to_pkg: app_to_pkg[appname] = set() app_to_pkg[appname].add(pkgname) # analyize print "Applications with the same name from multiple packages:" for app in app_to_pkg: if len(app_to_pkg[app]) > 1: print "app: %s (%s): %s" % (app, len(app_to_pkg[app]), sorted(app_to_pkg[app])) print print "Packages with multiple Applications:" for pkg in pkg_to_app: if len(pkg_to_app[pkg]) > 1: print "pkg: %s (%s): %s" % (pkg, len(pkg_to_app[pkg]), sorted(pkg_to_app[pkg])) software-center-13.10/utils/submit_usefulness_gtk3.py0000777000202700020270000000000012151440077027455 2submit_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/utils/show_top_rated_for_various_powers.py0000775000202700020270000000247512151440100025621 0ustar dobeydobey00000000000000#!/usr/bin/python import sys sys.path.insert(0,"../") from softwarecenter.backend.reviews import get_review_loader from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.utils import calc_dr sys.path.insert(0, "../utils") def show_top_rated_apps(): # get the ratings cache = get_pkg_info() loader = get_review_loader(cache) review_stats = loader.REVIEW_STATS_CACHE # recalculate using different default power results = {} for i in [0.5, 0.4, 0.3, 0.2, 0.1, 0.05]: for (key, value) in review_stats.iteritems(): value.dampened_rating = calc_dr(value.rating_spread, power=i) top_rated = loader.get_top_rated_apps(quantity=25) print "For power: %s" % i for (i, key) in enumerate(top_rated): item = review_stats[key] print "%(rang)2i: %(pkgname)-25s avg=%(avg)1.2f total=%(total)03i dampened=%(dampened)1.5f spread=%(spread)s" % { 'rang' : i+1, 'pkgname' : item.app.pkgname, 'avg' : item.ratings_average, 'total' : item.ratings_total, 'spread' : item.rating_spread, 'dampened' : item.dampened_rating, } print results[i] = top_rated[:] if __name__ == "__main__": show_top_rated_apps() software-center-13.10/utils/bench.py0000664000202700020270000000547012151440100017635 0ustar dobeydobey00000000000000#!/usr/bin/python import os import sys import xapian sys.path.insert(0, "../") from softwarecenter.enums import XAPIAN_VALUE_PKGNAME, XAPIAN_VALUE_APPNAME from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.utils import ExecutionTime def run_benchmark(db): # test postlist with ExecutionTime('postlist("")'): for m in db.postlist(""): pass # test postlist + get_document with ExecutionTime('postlist+get_document'): for m in db.postlist(""): doc = db.get_document(m.docid) # test postlist + get_document with ExecutionTime('postlist+get_document+get_value'): for m in db.postlist(""): doc = db.get_document(m.docid) doc.get_value(XAPIAN_VALUE_PKGNAME) def run_query(parser, search_term): search_query = parser.parse_query(search_term, xapian.QueryParser.FLAG_PARTIAL | xapian.QueryParser.FLAG_BOOLEAN) enquire = xapian.Enquire(db) enquire.set_query(search_query) with ExecutionTime("enquire"): mset = enquire.get_mset(0, db.get_doccount()) print "len mset: ", len(mset) if __name__ == "__main__": pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) print "app db only" run_benchmark(db) print "with apt-xapian-index" axi = xapian.Database("/var/lib/apt-xapian-index/index") db.add_database(axi) run_benchmark(db) print "simple query: a" parser = xapian.QueryParser() run_query(parser, "a") run_query(parser, "ab") run_query(parser, "abc") print "with db query: a" parser.set_database(db) parser.add_boolean_prefix("pkg", "AP") parser.set_default_op(xapian.Query.OP_AND) run_query(parser, "a") run_query(parser, "ab") run_query(parser, "abc") print "query for all !ATapplication" search_query = xapian.Query(xapian.Query.OP_AND_NOT, xapian.Query(""), xapian.Query("ATapplication")) enquire = xapian.Enquire(db) enquire.set_query(search_query) with ExecutionTime("enquire"): mset = enquire.get_mset(0, db.get_doccount()) print "len mset: ", len(mset) print "look at all !ATapplication" search_query = xapian.Query(xapian.Query.OP_AND_NOT, xapian.Query(""), xapian.Query("ATapplication")) enquire = xapian.Enquire(db) enquire.set_query(search_query) with ExecutionTime("enquire"): mset = enquire.get_mset(0, db.get_doccount()) print "len mset: ", len(mset) for m in mset: doc = m.document appname = doc.get_value(XAPIAN_VALUE_APPNAME) pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME) software-center-13.10/utils/report_review_gtk3.py0000777000202700020270000000000012151440077026572 2submit_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/utils/piston-helpers/0000755000202700020270000000000012224614355021170 5ustar dobeydobey00000000000000software-center-13.10/utils/piston-helpers/piston_generic_helper.py0000775000202700020270000001457512151440100026112 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2011 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 import httplib2 import argparse import logging import os import json import pickle import sys # useful for debugging if "SOFTWARE_CENTER_DEBUG_HTTP" in os.environ: httplib2.debuglevel = 1 import piston_mini_client.auth import piston_mini_client.failhandlers from piston_mini_client.failhandlers import APIError try: import softwarecenter except ImportError: if os.path.exists("../softwarecenter"): sys.path.insert(0, "../") else: sys.path.insert(0, "/usr/share/software-center") import softwarecenter.paths from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR from softwarecenter.backend.ubuntusso import UbuntuSSO # the piston import 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.enums import RECOMMENDER_HOST SoftwareCenterRecommenderAPI.default_service_root = \ RECOMMENDER_HOST + "/api/1.0" # patch default_service_root to the one we use 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 RatingsAndReviewsAPI # pyflakes UbuntuSsoAPI # pyflakes SoftwareCenterAgentAPI # pyflakes SoftwareCenterRecommenderAPI # pyflakes LOG = logging.getLogger(__name__) if __name__ == "__main__": logging.basicConfig() # command line parser parser = argparse.ArgumentParser( description="Backend helper for piston-mini-client based APIs") parser.add_argument("--debug", action="store_true", default=False, help="enable debug output") parser.add_argument("--datadir", default="/usr/share/software-center", help="setup alternative datadir") parser.add_argument("--ignore-cache", action="store_true", default=False, help="force ignore cache") parser.add_argument("--disable-offline-mode", action="store_true", default=False, help="force disable offline mode") parser.add_argument("--needs-auth", default=False, action="store_true", help="need oauth credentials") parser.add_argument("--no-relogin", default=False, action="store_true", help="do not attempt relogin if token is invalid") parser.add_argument("--output", default="pickle", help="output result as [pickle|json|text]") parser.add_argument("--parent-xid", default=0, help="xid of the parent window") parser.add_argument('klass', help='class to use') parser.add_argument('function', help='function to call') parser.add_argument('kwargs', nargs="?", help='kwargs for the function call as json') args = parser.parse_args() if args.debug: logging.basicConfig(level=logging.DEBUG) LOG.setLevel(logging.DEBUG) if args.ignore_cache: cachedir = None else: cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "piston-helper") # check what we need to call klass = globals()[args.klass] func = args.function kwargs = json.loads(args.kwargs or '{}') softwarecenter.paths.datadir = args.datadir if args.needs_auth: helper = UbuntuSSO(args.parent_xid) token = helper.get_oauth_token_and_verify_sync( no_relogin=args.no_relogin) # if we don't have a token, error here if not token: # it may happen that the parent is closed already so the pipe # is gone, that is ok as we exit anyway try: sys.stderr.write("ERROR: can not obtain a oauth token\n") except IOError: pass sys.exit(1) 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) piston_reply = None # handle the args f = getattr(api, func) try: piston_reply = f(**kwargs) except httplib2.ServerNotFoundError as e: if not args.disable_offline_mode: # switch to offline mode and try again from the cache try: api._offline_mode = True piston_reply = f(**kwargs) except Exception as e: LOG.warn(e) sys.exit(1) except APIError as e: LOG.warn(e) sys.exit(1) except: LOG.exception("urclient_apps") sys.exit(1) # no data is a error, the server does always return something, # this can happen if e.g. we try cached data but have nothing # in the cache if piston_reply is None: LOG.warn("no data") sys.exit(1) if args.debug: for itm in piston_reply: s = "** itm: %s\n" % itm for var in vars(itm): s += "%s: '%s'\n" % (var, getattr(itm, var)) LOG.debug(s) # print to stdout where its consumed by the parent # check what format to use if args.output == "pickle": res = pickle.dumps(piston_reply) elif args.output == "json": res = json.dumps(piston_reply) elif args.output == "text": res = piston_reply # and output it try: print res except IOError: # this can happen if the parent gets killed, no need to trigger # apport for this pass software-center-13.10/utils/piston-helpers/x2go_helper.py0000775000202700020270000000566612151440100023762 0ustar dobeydobey00000000000000#!/usr/bin/python -u import fcntl import gevent import os import shlex import sys import x2go def connect(server, port, login, password, session): print "PROGRESS: creating" cli = x2go.X2goClient(start_pulseaudio=True) uuid = cli.register_session( server=server, port=int(port), username=login, add_to_known_hosts=True, link="adsl", pack="adaptive-7", cmd="weblive-session %s" % session, geometry="1024x600", session_type="desktop" ) print "PROGRESS: connecting" try: if cli.connect_session(uuid, password=password) not in (None, True): # According to documentation, connect_session may return False exception("unable to connect") except: # Any paramiko exception will get here exception("unable to connect") print "PROGRESS: starting" try: if cli.start_session(uuid) not in (None, True): # According to documentation, start_session may return False exception("unable to start") except: # Just in case exception("unable to start") print "CONNECTED" return (cli,uuid) def exception(string): print "EXCEPTION: %s" % string sys.exit(1) def warning(string): print "WARNING: %s" % string def disconnect(connection, uuid): try: if connection.terminate_session(uuid) not in (None, True): # According to documentation, connect_session may return False exception("unable to disconnect") except: # Any paramiko exception will get here exception("unable to disconnect") print "DISCONNECTED" sys.exit(0) if __name__ == "__main__": # make stdin nonblocking fd = sys.stdin.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) # main loop connection = None uuid = None while True: # Get anything that appeared on stdin try: buf = sys.stdin.readline().strip() except IOError: buf = None if buf: params = shlex.split(buf) # Parse command from stdin if params[0] == "CONNECT:": if not len(params) == 6: exception("invalid connect string") if not uuid and not connection: connection, uuid = connect(*params[1:]) else: warning("already connected") elif params[0] == "DISCONNECT": if connection and uuid: disconnect(connection, uuid) else: exception("no existing connection") else: warning("invalid command: '%s'" % params) # Check if the session ended if connection and uuid and not connection.session_ok(uuid): disconnect(connection, uuid) if not buf: gevent.sleep(0.5) software-center-13.10/utils/piston-helpers/piston_get_reviews_helper.py0000775000202700020270000000707212151440100027013 0ustar dobeydobey00000000000000#!/usr/bin/python import httplib2 import logging import os import pickle import sys from optparse import OptionParser from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.distro import get_distro from piston_mini_client import APIError LOG = logging.getLogger(__name__) def try_get_reviews(kwargs): """ this tries to fetcher reviews and apply some heuristics if none are found (like fallback to the previous distro series) """ piston_reviews = rnrclient.get_reviews(**kwargs) # test if we don't have reviews for the current distroseries # and fallback to the previous oneif that is the case if (piston_reviews == [] and kwargs["distroseries"] == distro.DISTROSERIES[0]): kwargs["distroseries"] = distro.DISTROSERIES[1] piston_reviews = rnrclient.get_reviews(**kwargs) # the backend sometimes returns None so we fix this here if piston_reviews is None: piston_reviews = [] return piston_reviews if __name__ == "__main__": logging.basicConfig() distro = get_distro() # common options for optparse go here parser = OptionParser() # check options parser.add_option("--language", default="any") parser.add_option("--origin", default="any") parser.add_option("--distroseries", default="any") parser.add_option("--pkgname") parser.add_option("--version", default="any") parser.add_option("--page", default="1") parser.add_option("", "--debug", action="store_true", default=False) parser.add_option("--no-pickle", action="store_true", default=False) parser.add_option("--sort", default="helpful") (options, args) = parser.parse_args() if options.debug: LOG.setLevel(logging.DEBUG) cachedir = os.path.join(SOFTWARE_CENTER_CACHE_DIR, "rnrclient") rnrclient = RatingsAndReviewsAPI(cachedir=cachedir) kwargs = {"language": options.language, "origin": options.origin, "distroseries": options.distroseries, "packagename": options.pkgname.split(':')[0], #multiarch.. "version": options.version, "page": int(options.page), "sort" : options.sort, } piston_reviews = [] try: piston_reviews = try_get_reviews(kwargs) except ValueError as e: LOG.error("failed to parse '%s'" % e) #bug lp:709408 - don't print 404 errors as traceback when api request # returns 404 error except APIError as e: LOG.warn("_get_reviews_threaded: no reviews able to be retrieved for package: %s (%s, origin: %s)" % (options.pkgname, options.distroseries, options.origin)) LOG.debug("_get_reviews_threaded: no reviews able to be retrieved: %s" % e) except httplib2.ServerNotFoundError: # switch to offline mode and try again rnrclient._offline_mode = True piston_reviews = try_get_reviews(kwargs) except: LOG.exception("get_reviews") sys.exit(1) # useful for debugging if options.no_pickle: print "\n".join(["%s: %s" % (r.reviewer_username, r.summary) for r in piston_reviews]) else: # print to stdout where its consumed by the parent try: print pickle.dumps(piston_reviews) except IOError: # this can happen if the parent gets killed, no need to trigger # apport for this pass software-center-13.10/utils/modify_review_gtk3.py0000777000202700020270000000000012151440077026546 2submit_review_gtk3.pyustar dobeydobey00000000000000software-center-13.10/utils/gen-coverage-report.sh0000775000202700020270000000103312151440100022405 0ustar dobeydobey00000000000000#! /bin/sh set -e COVERAGE_DIR="./tests/coverage_html" # run for a single test if the argument is given if [ $1 ]; then python-coverage run --parallel $1 fi # combine the reports python-coverage combine if [ -d $COVERAGE_DIR ]; then rm -rf $COVERAGE_DIR fi # generate the coverage data OMIT="/usr/share/pyshared/*,*piston*,*test_" python-coverage report --omit=$OMIT | tee $COVERAGE_DIR/coverage_summary | tail python-coverage html --omit=$OMIT -d $COVERAGE_DIR echo "see $COVERAGE_DIR/index.html for the coverage details" software-center-13.10/utils/query.py0000664000202700020270000000464012151440100017721 0ustar dobeydobey00000000000000#!/usr/bin/python import os import sys import xapian from optparse import OptionParser sys.path.insert(0, "../") from softwarecenter.enums import XAPIAN_VALUE_PKGNAME from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.utils import ExecutionTime def parse_query(parser, search_strings, verbose=True): str_to_prefix = { 'section' : 'AE', 'type' : 'AT', 'category' : 'AC' } for st in search_strings: (search_prefix, search_term) = st.split(":") if search_prefix == "section": t = str_to_prefix[search_prefix] s = search_term.lower() query = xapian.Query(t+s) for pre in ["universe","multiverse","restricted"]: query = xapian.Query(xapian.Query.OP_OR, query, xapian.Query("%s%s/%s" % (t,pre,s))) query = xapian.Query(xapian.Query.OP_OR, query, xapian.Query("XS%s/%s" % (pre,s))) else: query = xapian.Query(str_to_prefix[search_prefix]+search_term.lower()) enquire = xapian.Enquire(db) enquire.set_query(query) with ExecutionTime("Search took"): mset = enquire.get_mset(0, db.get_doccount()) print "Found %i documents for search '%s'" % (len(mset), st) if verbose: for m in mset: doc = m.document appname = doc.get_data() pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME) print "%s ; %s" % (appname, pkgname) print if __name__ == "__main__": parser = OptionParser() parser.add_option("-v", "--verbose", action="store_true", default=False, help="print found apps/pkgs too") (options, args) = parser.parse_args() pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) axi = xapian.Database("/var/lib/apt-xapian-index/index") db.add_database(axi) parser = xapian.QueryParser() parser.set_database(db) if not args: print "example usage: " print " section:net" print " category:AudioVideo" print " type:Application" sys.exit(1) parse_query(parser, args, options.verbose) software-center-13.10/utils/search_query.py0000664000202700020270000000325112151440100021243 0ustar dobeydobey00000000000000#!/usr/bin/python import os import sys import xapian from optparse import OptionParser sys.path.insert(0, "../") from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.utils import ExecutionTime def run_query(parser, search_terms, verbose): for search_term in search_terms: search_query = parser.parse_query(search_term, xapian.QueryParser.FLAG_WILDCARD| xapian.QueryParser.FLAG_PARTIAL) print search_query enquire = xapian.Enquire(db) enquire.set_query(search_query) with ExecutionTime("enquire"): mset = enquire.get_mset(0, db.get_doccount()) for m in mset: doc = m.document print doc, doc.get_data() if verbose: for t in doc.termlist(): print "'%s': %s (%s); " % (t.term, t.wdf, t.termfreq), print "\n" if __name__ == "__main__": parser = OptionParser() parser.add_option("-v", "--verbose", action="store_true", default=False, help="print found apps/pkgs too") (options, args) = parser.parse_args() pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) axi = xapian.Database("/var/lib/apt-xapian-index/index") db.add_database(axi) parser = xapian.QueryParser() parser.set_database(db) parser.add_boolean_prefix("pkg","XP") parser.add_boolean_prefix("pkg","AP") parser.add_prefix("pkg_wildcard","XP") parser.add_prefix("pkg_wildcard","AP") run_query(parser, args, options.verbose) software-center-13.10/utils/update-software-center0000775000202700020270000001551512151440100022523 0ustar dobeydobey00000000000000#!/usr/bin/python # 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 # We have to import Gio before everything, for dynamic PK to work. try: from gi.repository import Gio except: pass import locale import gettext import logging import os import os.path import sys import time import xapian from optparse import OptionParser from softwarecenter.enums import * from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.db.update import rebuild_database import softwarecenter.paths # dbus may not be available during a upgrade so we # only set it up if its there try: import dbus import dbus.service from gi.repository import GLib, GObject from dbus.mainloop.glib import DBusGMainLoop except ImportError as e: logging.warn("failure to import: '%s'" % e) # add a bit of extra time between sending the "we-rebuild-the-db-now" # signal and the actual rebuilding to help the applications to shutdown # the DB access APP_CATCHUP_DELAY = 2 # the language pack directory that we need for the triggers checking LANGPACKDIR = "/usr/share/locale-langpack" logging.basicConfig(level=logging.INFO) LOG = logging.getLogger("softwarecenter.db.update") class UpdateSoftwarecenterDbus(dbus.service.Object): """ This is a helper to provide the UpdateSoftwarecenterIFace """ def __init__(self, bus_name, object_path='/com/ubuntu/Softwarecenter'): dbus.service.Object.__init__(self, bus_name, object_path) @dbus.service.method('com.ubuntu.Softwarecenter') def IsRebuilding(self): return True @dbus.service.signal(dbus_interface='com.ubuntu.Softwarecenter', signature='b') def DatabaseRebuilding(self, isRebuilding): LOG.debug("Sending DatabaseRebuilding signal '%s'" % isRebuilding) if __name__ == "__main__": try: locale.setlocale(locale.LC_ALL, "") except Exception, e: LOG.warn("setlocale failed with '%s'" % e) # parser parser = OptionParser() parser.add_option("--triggered", "", default="", help="triggered from dpkg") parser.add_option("--local", "", action="store_true", default=False, help="update local database") parser.add_option("--appstream-only", "", action="store_true", default=False, help="appstream app-info only") parser.add_option("--appstream-xml-path", "", default=None, help="set different appstream xml rootdir") parser.add_option("--debug", "", action="store_true", default=False, help="show debug output") parser.add_option("--use-packagekit", action="store_true", help="use PackageKit backend (experimental)", default=False) parser.add_option("--app-install-desktop-dir", "", default=None) parser.add_option("--target-db-path", "", default=None) (options, args) = parser.parse_args() #logging.basicConfig(level=logging.INFO) if options.debug: LOG.setLevel(logging.DEBUG) # setup directories if options.target_db_path: xapian_base_path = options.target_db_path elif options.local: if not os.path.exists('./data'): LOG.error("trying to update local database, ./data does not exist") sys.exit(-1) LOG.info("updating local database") xapian_base_path = './data' else: xapian_base_path = XAPIAN_BASE_PATH if options.appstream_only: LOG.info("updating only information from app-info") if options.appstream_xml_path: softwarecenter.paths.APPSTREAM_XML_PATH = options.appstream_xml_path if options.use_packagekit: LOG.info("using the PackageKit backend") softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True # check if we are dpkg triggered because of langpack change # and avoid unneeded database rebuilds by checking the timestamp # of the app-install-data mo file if options.triggered and options.triggered == LANGPACKDIR: LOG.debug("triggered with '%s'" % options.triggered) mofile = gettext.find("app-install-data") if not mofile: LOG.info("no translation information in database needed") sys.exit(0) mo_time = os.path.getctime(mofile) pathname = os.path.join(xapian_base_path, "xapian") if os.path.exists(pathname): db = xapian.Database(pathname) mo_db_time = db.get_metadata("app-install-mo-time") LOG.debug("mo_time: %s db_mo_time: %s" % (mo_time, mo_db_time)) if str(mo_time) == mo_db_time: LOG.info("translation information in database is up-to-date") sys.exit(0) # setup dbus dbus_controller = None try: DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter', bus) dbus_controller = UpdateSoftwarecenterDbus(bus_name) dbus_controller.DatabaseRebuilding(True) time.sleep(APP_CATCHUP_DELAY) except: LOG.warn("Failed to setup dbus (ignoring)") # rebuild and send signal when done try: # setup path pathname = os.path.join(xapian_base_path, "xapian") if not os.path.exists(pathname): os.makedirs(pathname) # rebuild the database, the default context is run to ensure # dbus queries are processed print "Updating software catalog...this may take a moment." if options.appstream_only: result = rebuild_database(pathname, debian_sources=False, appstream_sources=True) else: result = rebuild_database( pathname, appinfo_dir=options.app_install_desktop_dir) if result: print "Software catalog update was successful." else: print "There was a problem updating the software catalog. Please try again or check the log." finally: # signal that the xapian db is valid again if dbus_controller: time.sleep(APP_CATCHUP_DELAY) dbus_controller.DatabaseRebuilding(False) context = GLib.main_context_default() while context.pending(): context.iteration() software-center-13.10/utils/submit_review_gtk3.py0000775000202700020270000001755112151440100022400 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # 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 Gtk, GObject GObject.threads_init() import gettext import locale import logging import logging.handlers import os import sys from gettext import gettext as _ from optparse import OptionParser import softwarecenter.paths from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR from softwarecenter.db.database import Application from softwarecenter.ui.gtk3.review_gui_helper import ( DeleteReviewApp, ReportReviewApp, SubmitReviewsApp, SubmitUsefulnessApp, ) #import httplib2 #httplib2.debuglevel = 1 if __name__ == "__main__": try: locale.setlocale(locale.LC_ALL, "") except: logging.exception("setlocale failed, resetting to C") locale.setlocale(locale.LC_ALL, "C") gettext.bindtextdomain("software-center", "/usr/share/locale") gettext.textdomain("software-center") if os.path.exists("./data/ui/gtk3/submit_review.ui"): default_datadir = "./data" else: default_datadir = "/usr/share/software-center/" # common options for optparse go here 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_handler.setLevel(logging.INFO) logging.getLogger().addHandler(logfile_handler) logging.getLogger().addHandler(logging.StreamHandler()) # run review personality if "submit_review" in sys.argv[0]: # check options parser.add_option("-a", "--appname") parser.add_option("-p", "--pkgname") parser.add_option("-i", "--iconname") parser.add_option("-V", "--version") parser.add_option("-O", "--origin") parser.add_option("", "--parent-xid") parser.add_option("", "--test", action="store_true", default=False) parser.add_option("", "--debug", action="store_true", default=False) (options, args) = parser.parse_args() softwarecenter.paths.datadir = options.datadir if options.test: options.pkgname = options.pkgname or 'apt' options.appname = options.appname or 'Apt' options.iconname = options.iconname or 'folder' options.version = options.version or '1.0' options.origin = options.origin or 'Ubuntu' options.parent_xid = options.parent_xid or '1' if not (options.pkgname and options.version): parser.error(_("Missing arguments")) if options.debug: logging.basicConfig(level=logging.DEBUG) # personality logging.debug("submit_review mode") # initialize and run theapp = Application(options.appname, options.pkgname) review_app = SubmitReviewsApp(datadir=options.datadir, 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 parser.add_option("", "--review-id") parser.add_option("", "--parent-xid") parser.add_option("", "--debug", action="store_true", default=False) (options, args) = parser.parse_args() softwarecenter.paths.datadir = options.datadir if not (options.review_id): parser.error(_("Missing review-id arguments")) if options.debug: logging.basicConfig(level=logging.DEBUG) # personality logging.debug("report_abuse mode") # initialize and run report_app = ReportReviewApp(datadir=options.datadir, review_id=options.review_id, parent_xid=options.parent_xid) report_app.run() if "submit_usefulness" in sys.argv[0]: # check options parser.add_option("", "--review-id") parser.add_option("", "--parent-xid") parser.add_option("", "--is-useful") parser.add_option("", "--debug", action="store_true", default=False) (options, args) = parser.parse_args() softwarecenter.paths.datadir = options.datadir if not (options.review_id): parser.error(_("Missing review-id arguments")) if options.debug: logging.basicConfig(level=logging.DEBUG) # personality logging.debug("report_abuse mode") # initialize and run usefulness_app = SubmitUsefulnessApp(datadir=options.datadir, review_id=options.review_id, parent_xid=options.parent_xid, is_useful=int(options.is_useful)) usefulness_app.run() if "delete_review" in sys.argv[0]: #check options parser.add_option("", "--review-id") parser.add_option("", "--parent-xid") parser.add_option("", "--debug", action="store_true", default=False) (options, args) = parser.parse_args() softwarecenter.paths.datadir = options.datadir if not (options.review_id): parser.error(_("Missing review-id argument")) if options.debug: logging.basicConfig(level=logging.DEBUG) logging.debug("delete review mode") delete_app = DeleteReviewApp(datadir=options.datadir, review_id=options.review_id, parent_xid=options.parent_xid) delete_app.run() if "modify_review" in sys.argv[0]: # check options parser.add_option("", "--review-id") parser.add_option("-i", "--iconname") parser.add_option("", "--parent-xid") parser.add_option("", "--debug", action="store_true", default=False) (options, args) = parser.parse_args() softwarecenter.paths.datadir = options.datadir if not (options.review_id): parser.error(_("Missing review-id argument")) if options.debug: logging.basicConfig(level=logging.DEBUG) # personality logging.debug("modify_review mode") # initialize and run modify_app = SubmitReviewsApp(datadir=options.datadir, app=None, parent_xid=options.parent_xid, iconname=options.iconname, origin=None, version=None, action="modify", review_id=options.review_id ) modify_app.run() # main Gtk.main() software-center-13.10/utils/topapps.py0000664000202700020270000000163412151440100020242 0ustar dobeydobey00000000000000#!/usr/bin/python import heapq import os import sys import xapian sys.path.insert(0, "../") from softwarecenter.enums import XapianValues from softwarecenter.paths import XAPIAN_BASE_PATH if __name__ == "__main__": topn = 20 if len(sys.argv) > 1: topn = int(sys.argv[1]) pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) heap = [] for m in db.postlist(""): doc = db.get_document(m.docid) pkgname = doc.get_value(XapianValues.PKGNAME) appname = doc.get_value(XapianValues.APPNAME) summary = doc.get_value(XapianValues.SUMMARY) popcon = xapian.sortable_unserialise(doc.get_value(XapianValues.POPCON)) heapq.heappush(heap, (popcon, appname, pkgname, summary)) for (popcon, appname, pkgname, summary) in heapq.nlargest(topn, heap): print "[%i] %s - %s [%s]" % (popcon, appname, summary, pkgname) software-center-13.10/utils/wildcard_query_parser.py0000664000202700020270000000216712151440100023150 0ustar dobeydobey00000000000000#!/usr/bin/python import os import sys import xapian from optparse import OptionParser sys.path.insert(0, "../") from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.utils import ExecutionTime if __name__ == "__main__": parser = OptionParser() parser.add_option("-v", "--verbose", action="store_true", default=False, help="print found apps/pkgs too") (options, args) = parser.parse_args() pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) axi = xapian.Database("/var/lib/apt-xapian-index/index") db.add_database(axi) query_set = set() with ExecutionTime("allterms XP/AP"): for search_term in args: for m in db.allterms("XP"): term = m.term if search_term in term: query_set.add(term) for m in db.allterms("AP"): term = m.term if search_term in term: query_set.add(term) print "found terms: ", len(query_set) if options.verbose: print sorted(query_set) software-center-13.10/utils/installedapps.py0000664000202700020270000000170012151440100021411 0ustar dobeydobey00000000000000#!/usr/bin/python import apt import os import sys import xapian sys.path.insert(0, "../") from softwarecenter.enums import XAPIAN_VALUE_PKGNAME, XAPIAN_VALUE_APPNAME, XAPIAN_VALUE_SUMMARY from softwarecenter.paths import XAPIAN_BASE_PATH if __name__ == "__main__": cache = apt.Cache() pathname = os.path.join(XAPIAN_BASE_PATH, "xapian") db = xapian.Database(pathname) installed = [] for m in db.postlist(""): doc = db.get_document(m.docid) pkgname = doc.get_value(XAPIAN_VALUE_PKGNAME) appname = doc.get_value(XAPIAN_VALUE_APPNAME) summary = doc.get_value(XAPIAN_VALUE_SUMMARY) if pkgname in cache and cache[pkgname].is_installed: installed.append("%s: %s [%s]" % (appname, summary, pkgname)) print "\n".join(sorted(installed, cmp=lambda x, y: cmp(x.split(":")[0].lower(), y.split(":")[0].lower()))) software-center-13.10/utils/expunge-cache.py0000775000202700020270000000422512151440100021272 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2012 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 """ Expunge httplib2 caches """ import argparse import logging import os import sys from softwarecenter.expunge import ExpungeCache if __name__ == "__main__": parser = argparse.ArgumentParser( description='clean software-center httplib2 cache') parser.add_argument( '--debug', action="store_true", help='show debug output') parser.add_argument( '--dry-run', action="store_true", help='do not act, just show what would be done') parser.add_argument( 'directories', metavar='directory', nargs='+', type=str, help='directories to be checked') parser.add_argument( '--by-days', type=int, default=0, help='expire everything older than N days') parser.add_argument( '--by-unsuccessful-http-states', action="store_true", help='expire any non 200 status responses') args = parser.parse_args() if args.debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) # sanity checking if args.by_days == 0 and not args.by_unsuccessful_http_states: print "Need either --by-days or --by-unsuccessful-http-states argument" sys.exit(1) # be nice os.nice(19) # do it cleaner = ExpungeCache(args.directories, args.by_days, args.by_unsuccessful_http_states, args.dry_run) cleaner.clean() software-center-13.10/man/0000755000202700020270000000000012224614354015626 5ustar dobeydobey00000000000000software-center-13.10/man/software-center.10000664000202700020270000000315712151440100021011 0ustar dobeydobey00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH SOFTWARE-CENTER 1 "2.1.8" "August 2010" .SH NAME software-center \- manage software .SH SYNOPSIS .B software-center [\fIoptions\fR] [ \fIpackage-name\fR | \fIapt-url\fR | \fIdeb-file\fR ] .SH DESCRIPTION \fBsoftware-center\fR a graphical interface for package management in Ubuntu. .SH OPTIONS Listed below are the command line options for \fBsoftware-center\fR: .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-\-debug\fR enable debug mode .SH EXAMPLES .TP software-center Display the main screen of software-center. .TP software-center gedit Display the details view of the gedit package. .TP software-center banshee?section=universe Display the details view of the banshee package and offer to enable the universe component. .TP software-center adobe-flashplugin?channel=maverick-partner Display the details view of the adobe-flashplugin package and offer to enable the maverick-partner channel. .TP software-center cheese,gtg Display a list containing the cheese and gtg packages. .TP software-center /home/pgg/skype.deb Display the details view of the local debian file '/home/pgg/skype.deb'. .SH AUTHOR \fBsoftware-center\fR was written by Michael Vogt , and this manual page by Andrew Higginson . Both are released under the GNU General Public License, version 3 or later. software-center-13.10/man/update-software-center.80000664000202700020270000000123212151440100022270 0ustar dobeydobey00000000000000.\" Copyright (C) 2010 Julian Andres Klode. Released under the terms of the .\" GNU General Public License, version 3 or later .TH update-software-center 8 "2010-02-05" "1.1.10" .SH NAME update-software-center \- Update the database for software-center .SH SYNOPSIS .B update-software-center .RI [ options ] .SH DESCRIPTION .B update-software-center updates the database used by software-center. .SH OPTIONS .TP .B \-h or \-\-help print quick usage details to the screen. .TP .B \-\-triggered=TRIGGERED passed when the command is triggered by dpkg. .TP .B \-\-debug show debug output .SH AUTHOR This manpage has been written by Julian Andres Klode . software-center-13.10/setup.py0000775000202700020270000001152612224614346016600 0ustar dobeydobey00000000000000#!/usr/bin/python # # Copyright 2009-2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . """Setup.py: build, distribute, clean.""" import platform import glob import sys from DistUtilsExtra.auto import setup # The VERSION of software-center VERSION = '13.10' # Get the distribution information for various functions. (distro, release, codename) = platform.dist() def merge_extras_ubuntu_com_channel_file(): # TODO: Only do this during setup.py install. # update ubuntu-extras.list.in (this will not be part of debian as # its killed of in debian/rules on a non-ubuntu build) channelfile = "data/channels/Ubuntu/ubuntu-extras.list" s = open(channelfile + ".in").read() open(channelfile, "w").write(s.replace("#DISTROSERIES#", codename)) # update version.py def update_version(): # TODO: Move this to a build command. # this comes from the build host open("softwarecenter/version.py", "w").write(""" VERSION = '%s' CODENAME = '%s' DISTRO = '%s' RELEASE = '%s' """ % (VERSION, codename, distro, release)) # double check that we have the latest distro series and fail if not if distro == "Ubuntu": import softwarecenter.distro.ubuntu if not codename in softwarecenter.distro.ubuntu.Ubuntu.DISTROSERIES: raise ValueError("Could not find '%s' in ubuntu distro class " "please add it to DISTROSERIES", codename) # update po4a if sys.argv[1] == "build": update_version() merge_extras_ubuntu_com_channel_file() # real setup setup( name="software-center", version=VERSION, scripts=[ "bin/software-center", "bin/software-center-dbus", # gtk3 "utils/submit_review_gtk3.py", "utils/report_review_gtk3.py", "utils/submit_usefulness_gtk3.py", "utils/delete_review_gtk3.py", "utils/modify_review_gtk3.py", # db helpers "utils/update-software-center", "utils/update-software-center-channels", "utils/update-software-center-agent", # generic helpers "utils/expunge-cache.py", ] + glob.glob("utils/piston-helpers/*.py"), packages=[ 'softwarecenter', 'softwarecenter.backend', 'softwarecenter.backend.installbackend_impl', 'softwarecenter.backend.channel_impl', 'softwarecenter.backend.oneconfhandler', 'softwarecenter.backend.login_impl', 'softwarecenter.backend.piston', 'softwarecenter.backend.reviews', 'softwarecenter.db', 'softwarecenter.db.pkginfo_impl', 'softwarecenter.db.history_impl', 'softwarecenter.distro', 'softwarecenter.plugins', 'softwarecenter.ui', 'softwarecenter.ui.gtk3', 'softwarecenter.ui.gtk3.dialogs', 'softwarecenter.ui.gtk3.models', 'softwarecenter.ui.gtk3.panes', 'softwarecenter.ui.gtk3.session', 'softwarecenter.ui.gtk3.views', 'softwarecenter.ui.gtk3.widgets', 'softwarecenter.ui.qml', ], data_files=[ # gtk3 ('share/software-center/ui/gtk3/', glob.glob("data/ui/gtk3/*.ui")), ('share/software-center/ui/gtk3/css/', glob.glob("data/ui/gtk3/css/*.css")), ('share/software-center/ui/gtk3/art/', glob.glob("data/ui/gtk3/art/*.png")), ('share/software-center/ui/gtk3/art/icons', glob.glob("data/ui/gtk3/art/icons/*.png")), ('share/software-center/default_banner', glob.glob("data/default_banner/*")), # dbus ('../etc/dbus-1/system.d/', ["data/dbus/com.ubuntu.SoftwareCenter.conf"]), ('share/dbus-1/services', ["data/dbus/com.ubuntu.SoftwareCenterDataProvider.service"]), # images ('share/software-center/images/', glob.glob("data/images/*.png") + glob.glob("data/images/*.gif")), ('share/software-center/icons/', glob.glob("data/emblems/*.png")), # xapian ('share/apt-xapian-index/plugins', glob.glob("apt_xapian_index_plugin/*.py")), # apport # TODO: Move this over from the packaging # ('share/apport/package-hooks/', ['debian/source_software-center.py']), # extra software channels (can be distro specific) ('share/app-install/channels/', glob.glob("data/channels/%s/*.{eula,list}" % distro)), ], ) software-center-13.10/po/0000755000202700020270000000000012224614354015471 5ustar dobeydobey00000000000000software-center-13.10/po/gl.po0000664000202700020270000002644012151440100016424 0ustar dobeydobey00000000000000# Galician translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-02 23:34+0000\n" "Last-Translator: Francisco Diéguez \n" "Language-Team: Galician \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-10-03 06:48+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERRO" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical xa non fornece actualizacións de %s en Ubuntu %s. Pode atopar " "actualizacións na seguinte versión de Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical fornece actualizacións críticas de %(appname)s ata " "%(support_end_month_str)s de %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical fornece actualizacións críticas proporcionadas polos " "desenvolvedores de %(appname)s ata %(support_end_month_str)s de " "%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical non fornece actualizacións de %s. Poden aparecer actualizacións " "fornecidas por terceiros vendedores." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical fornece actualizacións críticas de %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical fornece actualizacións críticas proporcionadas polos " "desenvolvedores de %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical non fornece actualizacións de %s. Poden aparecer algunhas " "actualizacións proporcionadas pola comunidade de Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "O aplicativo %s ten un estado de mantemento descoñecido." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Descrición" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Non está dispoñíbel cos datos actuais" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Non está dispoñíbel para a súa arquitectura de hardware" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Captura de pantalla do aplicativo" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versión: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s está instalado neste computador." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Iste é usado por %s parte de software instalado." msgstr[1] "Iste é usado por %s partes de software instalados." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Sitio web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Descoñecido" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Código aberto" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Privativo" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licenza: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratuíto" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Prezo: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Captura de pantalla" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Para eliminar %s, tamén ten que eliminar os seguintes elementos:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Eliminar todos" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Se desinstala %s, as actualizacións futuras non incluirán novos elementos no " "conxunto %s . Está seguro de que quere continuar?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Eliminar en calquera caso" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s é un aplicativo básico en Ubuntu. A desinstalación pode causar que " "actualizacións futuras queden bloqueadas. Está seguro de que quere continuar?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Actualizar" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Eliminar" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Instalar" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Activar a canle" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Conservar" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "Substituí_r" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Cambiou a configuración do ficheiro '%s'" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Quere usar a nova versión?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Obter software libre" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s coincidencia" msgstr[1] "%s coincidencias" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s elemento dispoñíbel" msgstr[1] "%s elementos dispoñíbeis" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Departamentos" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detalles" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Cancelar" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependencia" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Software instalado" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s elemento instalado" msgstr[1] "%s elementos instalados" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "En progreso (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copiar ligazón _web" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Permítelle escoller entre milleiros de aplicativos libres para Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Reconstruír o catálogo ... do aplicativo" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Buscar..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "_Axuda do Centro de Software" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centro de software deUbuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Todos os aplicativos" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Aplicativos sostidos por _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Editar" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Ficheiro" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Axuda" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Instalar" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "Fontes de _Software ..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Ver" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "dispoñíbel" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "instalado" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "pendente" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Permítelle escoller entre milleiros de aplicativos libres dispoñíbeis para " "Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centro de software" #, python-format #~ msgid "%s items available" #~ msgstr "%s elementos dispoñíbeis" #, python-format #~ msgid "Search in %s" #~ msgstr "Procurar en %s" #~ msgid "All" #~ msgstr "Todo" #~ msgid "Install and remove software" #~ msgstr "Instalar e eliminar sóftware" #~ msgid "Software Store" #~ msgstr "Tenda de sóftware" #~ msgid "Installed software" #~ msgstr "Sóftware instalado" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Pendente (%i)" #~ msgid "Get Free software" #~ msgstr "Opter sóftware libre" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Software Store" software-center-13.10/po/POTFILES.in0000664000202700020270000000562312151440100017237 0ustar dobeydobey00000000000000[encoding: UTF-8] bin/software-center utils/update-software-center data/ubuntu-software-center.desktop.in [type: gettext/xml] data/featured.menu.in [type: gettext/xml] data/whats_new.menu.in [type: gettext/xml] data/software-center.menu.in softwarecenter/backend/channel.py softwarecenter/backend/login_impl/login_sso.py softwarecenter/backend/reviews/__init__.py softwarecenter/backend/reviews/rnr.py softwarecenter/backend/oneconfhandler/__init__.py softwarecenter/backend/oneconfhandler/core.py softwarecenter/backend/installbackend_impl/aptd.py softwarecenter/backend/installbackend_impl/packagekitd.py softwarecenter/db/application.py softwarecenter/db/categories.py softwarecenter/db/database.py softwarecenter/db/update.py softwarecenter/db/pkginfo_impl/aptcache.py softwarecenter/db/pkginfo_impl/packagekit.py softwarecenter/distro/debian.py softwarecenter/distro/fedora.py softwarecenter/distro/__init__.py softwarecenter/distro/ubuntu.py softwarecenter/enums.py softwarecenter/hw.py softwarecenter/region.py 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/report_abuse.ui [type: gettext/glade]data/ui/gtk3/submit_review.ui [type: gettext/glade]data/ui/gtk3/submit_usefulness.ui softwarecenter/db/debfile.py softwarecenter/distro/suselinux.py 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/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/viewswitcher.py softwarecenter/ui/gtk3/views/appdetailsview.py softwarecenter/ui/gtk3/views/appview.py softwarecenter/ui/gtk3/views/catview.py softwarecenter/ui/gtk3/views/lobbyview.py softwarecenter/ui/gtk3/views/purchaseview.py softwarecenter/ui/gtk3/widgets/apptreeview.py softwarecenter/ui/gtk3/widgets/backforward.py softwarecenter/ui/gtk3/widgets/buttons.py softwarecenter/ui/gtk3/widgets/exhibits.py softwarecenter/ui/gtk3/widgets/labels.py softwarecenter/ui/gtk3/widgets/reviews.py softwarecenter/ui/gtk3/widgets/searchaid.py softwarecenter/ui/gtk3/widgets/searchentry.py softwarecenter/ui/gtk3/widgets/stars.py softwarecenter/ui/gtk3/widgets/thumbnail.py softwarecenter/ui/gtk3/widgets/videoplayer.py softwarecenter/ui/gtk3/widgets/weblivedialog.py softwarecenter/ui/gtk3/widgets/webkit.py softwarecenter/ui/gtk3/widgets/recommendations.py utils/delete_review_gtk3.py utils/modify_review_gtk3.py utils/report_review_gtk3.py utils/submit_review_gtk3.py utils/submit_usefulness_gtk3.py software-center-13.10/po/sv.po0000664000202700020270000002565412151440100016460 0ustar dobeydobey00000000000000# Swedish translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-30 08:07+0000\n" "Last-Translator: Daniel Nylander \n" "Language-Team: Swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-10-01 06:44+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "FEL" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical tillhandahÃ¥ller inte längre uppdateringar för %s i Ubuntu %s. " "Uppdateringar kan finnas tillgängliga i en nyare version av Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tillhandahÃ¥ller kritiska uppdateringar för %(appname)s tills " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tillhandahÃ¥ller kritiska uppdateringar som levererats av " "utvecklarna av %(appname)s tills %(support_end_month_str)s " "%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical tillhandahÃ¥ller inte uppdateringar för %s. Vissa uppdateringar kan " "tillhandahÃ¥llas av en tredjepartsleverantör." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical tillhandahÃ¥ller kritiska uppdateringar för %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical tillhandahÃ¥ller uppdateringar som levereras av utvecklarna för %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical tillhandahÃ¥ller inte uppdateringar för %s. Vissa uppdateringar kan " "tillhandahÃ¥llas av Ubuntu-gemenskapen." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Programmet %s har en okänd underhÃ¥llsstatus." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Beskrivning" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Inte tillgänglig i aktuell data" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Inte tillgängligt för din hÃ¥rdvaruarkitektur." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Skärmbild av program" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Version: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s är installerat pÃ¥ denna dator" #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Används av %s installerad programvara." msgstr[1] "Används av %s installerade programvaror." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Webbplats" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Okänd" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Öppen källkod" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietär" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licens: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratis" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Pris: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Skärmbild" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "För att ta bort %s, mÃ¥ste dessa objekt ocksÃ¥ tas bort:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Ta bort alla" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Om du avinstallerar %s, framtida uppdateringar kommer ej inkluderas nya " "varor i %s set. Är du säker att du vill fortsätta?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Ta bort endÃ¥" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s är ett systemprogram i Ubuntu. Avinstallation av det kan göra att " "framtida uppgraderingar blir ofullständiga. Är du säker pÃ¥ att du vill " "fortsätta?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Uppgradera" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Ta bort" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Installera" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Aktivera kanal" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_BehÃ¥ll" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Ersätt" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Konfigurationsfilen \"%s\" har ändrats" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Vill du använda den nya versionen?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Hämta fri programvara" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s träffat objekt" msgstr[1] "%s träffade objekt" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s objekt tillgängligt" msgstr[1] "%s objekt tillgängliga" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Avdelningar" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detaljer" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Avbryt" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Beroende" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Installerad programvara" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s installerat objekt" msgstr[1] "%s installerade objekt" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "PÃ¥gÃ¥ende (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopiera _webblänk" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "LÃ¥ter dig välja frÃ¥n tusentals fria program som finns tillgängliga för " "Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Ã…terbygger programkatalog..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Sök..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Mjukvarucenter _Hjälp" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Programcentral för Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Alla program" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Canonical-underhÃ¥llna program" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "R_edigera" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Arkiv" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Hjälp" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Installera" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Programkällor..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Visa" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "tillgängliga" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "installerade" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "väntar" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "LÃ¥ter dig välja frÃ¥n tusentals fria program som finns tillgängliga för Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Programcentral" #, python-format #~ msgid "Search in %s" #~ msgstr "Sök i %s" #~ msgid "All" #~ msgstr "Alla" #~ msgid "Install and remove software" #~ msgstr "Installera och ta bort programvara" #~ msgid "Installed software" #~ msgstr "Installerad programvara" #~ msgid "Get Free software" #~ msgstr "Hämta fri programvara" software-center-13.10/po/de.po0000664000202700020270000003006612151440100016411 0ustar dobeydobey00000000000000# German translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-29 17:05+0000\n" "Last-Translator: Matthias Klumpp \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "Fehler" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical stellt keine Aktualisierungen für %s in Ubuntu %s mehr zur " "Verfügung. Aktualisierungen könnten in einer neueren Version von Ubuntu " "verfügbar sein." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical stellt wichtige Aktualisierungen für %(appname)s bis " "einschließlich %(support_end_month_str)s %(support_end_year)s bereit." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical stellt wichtige Aktualisierungen, die von den Entwicklern zur " "Verfügung gestellt werden, für %(appname)s bis einschließlich " "%(support_end_month_str)s %(support_end_year)s bereit." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical stellt keine Aktualisierungen für %s bereit. Einige " "Aktualisierungen sind möglicherweise über Drittanbieter verfügbar." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical stellt wichtige Aktualisierungen für %s bereit." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical stellt von den Entwicklern gelieferte wichtige Aktualisierungen " "für %s bereit." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical stellt keine Aktualisierungen für %s bereit. Einige " "Aktualisierungen sind möglicherweise über die Ubuntu-Gemeinschaft verfügbar." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Anwendung %s hat einen unbekannten Unterstützungsstatus." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Beschreibung" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Im aktuellen Datensatz nicht verfügbar" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Für Ihre Hardwarearchitektur nicht verfügbar." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Bildschirmfoto der Anwendung" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Version: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s ist auf diesem Rechner installiert." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" "Es wird von %s Bestandteil der bereits installierten Software verwendet." msgstr[1] "" "Es wird von %s Bestandteilen der bereits installierten Software verwendet." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Webseite" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Unbekannt" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Open Source" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietär" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Lizenz: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Kostenlos" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Preis: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Bildschirmfoto" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "" "Um %s zu entfernen, müssen folgende Software-Pakete ebenfalls entfernt " "werden:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Alle entfernen" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Wenn Sie %s deinstallieren, werden zukünftige Aktualisierungen keine neuen " "Bestandteile aus der Gruppe %s beinhalten. Sind Sie sicher, dass Sie " "fortfahren möchten?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Dennoch entfernen" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s ist eine Kernanwendung in Ubuntu. Eine Deinstallation verursacht unter " "Umständen in Zukunft lückenhafte Aktualisierungen. Sind Sie sicher, dass Sie " "fortfahren möchten?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Aktualisierung" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Entfernen" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Installieren" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Kanal aktivieren" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Beibehalten" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Ersetzen" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Konfigurationsdatei »%s« wurde geändert" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Möchten Sie die neue Version benutzen?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Freie Software erhalten" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s entsprechendes Element" msgstr[1] "%s entsprechende Elemente" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s Element verfügbar" msgstr[1] "%s Elemente verfügbar" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Bereiche" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Details" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Abbrechen" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Abhängigkeit" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Installierte Anwendungen" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s installiertes Element" msgstr[1] "%s installierte Elemente" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "In Bearbeitung (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "_Web-Verknüpfung kopieren" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "Lässt Sie aus tausenden freien Anwendungen für Ubuntu auswählen." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Anwendungskatalog wird erstellt..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Suche..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Software Center _Hilfe" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Software Center" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Alle Anwendungen" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Von _Canonical betreute Anwendungen" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Bearbeiten" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Datei" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Hilfe" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Installieren" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Software-Paketquellen..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Ansicht" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "verfügbar" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "installiert" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "ausstehend" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "Lässt Sie aus tausenden freien Anwendungen für Ubuntu auswählen" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Software Center" #, python-format #~ msgid "%s items available" #~ msgstr "%s Einträge verfügbar" #~ msgid "All" #~ msgstr "Alles" #~ msgid "Install and remove software" #~ msgstr "Software installieren und entfernen" #~ msgid "Installed software" #~ msgstr "Installierte Software" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Anstehend (%i)" #, python-format #~ msgid "Search in %s" #~ msgstr "Suchen in %s" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s ist von anderer Software dieses Systems abhängig. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s ist ein Kernkomponente." #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Eine Deinstallation bedeutet, dass folgende zusätzliche Software entfernt " #~ "wird." #~ msgid "Software Store" #~ msgstr "Software Store" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Software Store" #~ msgid "Get Free software" #~ msgstr "Freie Software installieren" #~ msgid "Canonical-Maintained Applications" #~ msgstr "Von Canonical Betreute Anwendungen" software-center-13.10/po/en_AU.po0000664000202700020270000001743512151440100017015 0ustar dobeydobey00000000000000# English (Australia) translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-09 09:54+0000\n" "Last-Translator: Stephen Brown \n" "Language-Team: English (Australia) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "ERROR" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "Description" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "Not available in the current data" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "Version: %s (%s)" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "%s is installed on this computer." #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "It is used by %s piece of installed software." msgstr[1] "It is used by %s pieces of installed software." #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Price: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "Free" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "%s depends on other software on the system. " #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" "Uninstalling it means that the following additional software needs to be " "removed." #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "%s is a core component" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "Upgrade" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Remove" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "Install" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "Enable channel" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "In Progress (%i)" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "Pending (%i)" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "Ubuntu Software Store" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "Software Store" #, python-format #~ msgid "%s items available" #~ msgstr "%s items available" #, python-format #~ msgid "Search in %s" #~ msgstr "Search in %s" #~ msgid "All" #~ msgstr "All" #~ msgid "Install and remove software" #~ msgstr "Install and remove software" #~ msgid "Installed software" #~ msgstr "Installed software" #~ msgid "Get Free software" #~ msgstr "Get Free software" software-center-13.10/po/ru.po0000664000202700020270000003214212151440100016444 0ustar dobeydobey00000000000000# Russian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-28 05:09+0000\n" "Last-Translator: Alexander Semyonov \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ОШИБКÐ" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Canonical больше не предоÑтавлÑет обновлений Ð´Ð»Ñ %s в Ubuntu %s. " "ÐžÐ±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ быть доÑтупны в новой верÑии Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical предоÑтавлÑет критичеÑкие Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð»Ñ %(appname)s до " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical предоÑтавлÑет критичеÑкие обновлениÑ, предоÑтавленные " "разработчиками %(appname)s, до %(support_end_month_str)s " "%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Canonical не предоÑтавлÑет обновлений Ð´Ð»Ñ %s. Ðекоторые Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ " "могут предоÑтавлÑтьÑÑ Ñ‚Ñ€ÐµÑ‚ÑŒÐ¸Ð¼Ð¸ лицами." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Canonical предоÑтавлÑет критичеÑкие Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð»Ñ %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Canonical предоÑтавлÑет критичеÑкие обновлениÑ, напиÑанные " "разработчиками %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "ÐšÐ¾Ð¼Ð¿Ð°Ð½Ð¸Ñ Canonical не предоÑтавлÑет обновлений Ð´Ð»Ñ %s. Ðекоторые Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ " "могут предоÑтавлÑтьÑÑ ÑообщеÑтвом Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¸ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ %s неизвеÑтен." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "ОпиÑание" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Ðет данных" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "ÐедоÑтупно Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ¹ архитектуры" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Снимок окна приложениÑ" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "ВерÑиÑ: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "Приложение %s уÑтановлено." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "ИÑпользуетÑÑ %s приложением." msgstr[1] "ИÑпользуетÑÑ %s приложениÑми." msgstr[2] "ИÑпользуетÑÑ %s приложениÑми." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Сайт" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "ÐеизвеÑтно" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Open Source" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "ПроприетарнаÑ" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "ЛицензиÑ: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "БеÑплатно" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "СтоимоÑть: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s — Снимок Ñкрана" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Удаление %s затронет также Ñледующие программы:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Удалить вÑÑ‘" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "ЕÑли вы удалите %s, то будущие Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ðµ будут включать новые Ñлементы в " "наборе %s. Продолжить?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Ð’ÑÑ‘ равно удалить" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s ÑвлÑетÑÑ Ð±Ð°Ð·Ð¾Ð²Ñ‹Ð¼ приложением Ubuntu. Ð’ Ñлучае его ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ " "возникнуть проблемы Ñ Ð¿Ð¾Ñледующим обновлением ÑиÑтемы. Удалить приложение?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Обновить" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Удалить" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "УÑтановить" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Включить канал" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_ОÑтавить" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Заменить" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Файл наÑтройки «%s» изменён" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Хотите иÑпользовать новую верÑию?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Получить Ñвободное ПО" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s подходÑщий Ñлемент" msgstr[1] "%s подходÑщих Ñлемента" msgstr[2] "%s подходÑщих Ñлементов" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s Ñлемент доÑтупен" msgstr[1] "%s Ñлемента доÑтупны" msgstr[2] "%s Ñлементов доÑтупны" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Разделы" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "ПодробноÑти" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Отменить" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "ЗавиÑимоÑть" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "УÑтановленные программы" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s Ñлемент уÑтановлен" msgstr[1] "%s Ñлемента уÑтановлены" msgstr[2] "%s Ñлементов уÑтановлены" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Ð’ процеÑÑе (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "© Canonical, 2009" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Копировать ÑÑ_ылку на Ñайт" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "ПредоÑтавлÑет вам выбор из тыÑÑч беÑплатных программ Ð´Ð»Ñ Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "ПереÑтройка каталога приложений..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Идет поиÑк..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "_Справка центра приложений" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Центр приложений Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Ð’Ñе приложениÑ" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "С _поддержкой Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Правка" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Файл" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Справка" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_УÑтановить" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_ИÑточники приложений..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Вид" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "доÑтупно" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "уÑтановлено" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "в очереди" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "ПредоÑтавлÑет вам выбор из тыÑÑч беÑплатных программ Ð´Ð»Ñ Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Центр приложений" #~ msgid "Search" #~ msgstr "ПоиÑк" #~ msgid "Homepage" #~ msgstr "ДомашнÑÑ Ñтраница" #~ msgid "Categories" #~ msgstr "Категории" #, python-format #~ msgid "%s items available" #~ msgstr "%s приложений доÑтупно" #~ msgid "Software Store" #~ msgstr "Software Store" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Ð’ очереди (%i)" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Software Store" #, python-format #~ msgid "Search in %s" #~ msgstr "ПоиÑк в %s" #~ msgid "All" #~ msgstr "Ð’Ñе" #~ msgid "Get Free software" #~ msgstr "Получить Ñвободное ПО" #~ msgid "Install and remove software" #~ msgstr "УÑтановка и удаление программ" #~ msgid "Installed software" #~ msgstr "УÑтановленные приложениÑ" software-center-13.10/po/eo.po0000664000202700020270000002506312151440100016425 0ustar dobeydobey00000000000000# Esperanto translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-07 22:09+0000\n" "Last-Translator: Patrick (Petriko) Oudejans \n" "Language-Team: Esperanto \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-10-08 06:49+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERARO" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ne plu provizas Äisdatigojn por %s en Ubuntu %s. Äœisdatigoj povas " "esti haveblaj en pli nova versio de Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical provizas kritikajn Äisdatigojn por %(appname)s Äis " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical provizas kritikajn Äisdatigojn havigatajn de la programistoj de " "%(appname)s Äis %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical ne provizas Äisdatigojn por %s. Kelkaj Äisdatigoj povas esti " "provizataj de ekstera liveranto." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical provizas kritikajn Äisdatigojn por %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical provizas kritikajn Äisdatigojn havigatajn de la programistoj de %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical ne provizas Äisdatigojn por %s. Kelkaj Äisdatigoj povas esti " "provizataj de la Ubuntu-komunumo." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Aplikaĵo %s havas nekonatan prizorgadan staton." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Priskribo" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Ne havebla en la nunaj datumoj." #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Ne havebla por via aparatar-arkitekturo." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Aplikaĵa ekranbildo" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versio: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s instaliÄis sur tiu ĉi komputilo." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Äœi estas uzata de %s instalita programo." msgstr[1] "Äœi estas uzata de %s instalitaj programoj." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Retejo" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Nekonata" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Libera Programaro" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Mallibera" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Permesilo: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Senkosta" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Prezo: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Ekranbildo" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Por forigi %s, ĉi tiuj elementoj devas ankaÅ­ esti forigataj:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Forigi Ĉiujn" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Se vi malinstalas %s-n, estontaj Äisdatigoj ne inkluzivos novajn elementojn " "en %s-aro. Ĉu vi certe volas daÅ­rigi?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Tamen forigi" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s estas kerna aplikaĵo en Ubuntu. Malinstalo de Äi kaÅ­zas ke estontaj " "promocioj estos nekompletaj. Ĉu vi certe volas daÅ­rigi?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Promocii" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Forigi" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Instali" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Åœalti kanalon" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Konservi" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_AnstataÅ­igi" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Agorda dosiero '%s' ÅanÄiÄis" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Ĉu vi volas uzi la novan version?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Akiri senpagan programaron" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s kongruanta elemento" msgstr[1] "%s kongruantaj elementoj" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s elemento havebla" msgstr[1] "%s elementoj haveblaj" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Fakoj" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detaloj" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Nuligi" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependeco" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Instalita programaro" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s instalita elemento" msgstr[1] "%s instalitaj elementoj" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Farata (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopii _ligilon" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "Lasas vin elekti milojn da senkostaj aplikaĵoj haveblaj por Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Rekonstruas aplikaĵan katalogon..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Serĉi..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Programareja _Helpo" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu Programarejo" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "Ĉ_iuj aplikaĵoj" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Aplikaĵoj prizorgataj de _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Redakti" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Dosiero" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Helpo" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Instali" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Programaraj fontoj..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Vido" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "havebla" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "instalita" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "pritraktota" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "Lasas vin elekti milojn da senkostaj aplikaĵoj haveblaj por Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Programarejo" #~ msgid "All" #~ msgstr "Ĉiuj" #, python-format #~ msgid "Pending (%i)" #~ msgstr "pritraktata (%i)" software-center-13.10/po/ro.po0000664000202700020270000002563412151440100016446 0ustar dobeydobey00000000000000# Romanian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-09 19:23+0000\n" "Last-Translator: Alex Eftimie \n" "Language-Team: Romanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n == 1 ? 0: (((n % 100 > 19) || ((n % 100 " "== 0) && (n != 0))) ? 2: 1));\n" "X-Launchpad-Export-Date: 2009-10-10 08:15+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "EROARE" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical nu mai asigură actualizări pentru %s în Ubuntu %s. Actualizări pot " "fi disponibile într-o versiune nouă de Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical asigură actualizări critice pentru %(appname)s până în " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical asigură actualizări critice furnizate de dezvoltatorii aplicaÈ›iei " "%(appname)s până în %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical nu oferă actualizări pentru %s. Unele actualizări pot fi oferite " "de terÈ›e părÈ›i." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical oferă actualizări critice pentru %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical oferă actualizări critice puse la dispoziÈ›ie de dezvoltatorii %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical nu oferă actualizări pentru %s. Unele actualizări pot fi oferite " "de către comunitatea Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "AplicaÈ›ia %s are o stare de întreÈ›inere necunoscută." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Descriere" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Indisponibil în datele curente" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Nu este disponibil pentru arhitectura dumneavoastră hardware." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Captură de ecran" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versiune: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s este instalat pe acest computer." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Este folosit de %s piesă software instalată." msgstr[1] "Este folosit de %s piese software instalate." msgstr[2] "Este folosit de %s de piese software instalate." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Sit web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Necunoscut" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Sursă deschisă" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietar" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licență: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratuit" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Cost: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - captură de ecran" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Eliminând %s, următoarele articole vor fi de asemenea eliminate:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "ÃŽnlătură tot" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Dacă dezinstalaÈ›i %s, viitoarele actualizări nu vor include articole noi din " "suita %s. Sigur doriÈ›i să continuaÈ›i?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "ÃŽnlătură oricum" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s este o aplicaÈ›ie de bază în Ubuntu. Dezinstalarea ei poate face ca " "viitoarele actualizări majore să fie incomplete. Sigur doriÈ›i să continuaÈ›i?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Actualizează" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Elimină" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Instalează" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Activează canal" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Păstrează" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "ÃŽn_locuieÈ™te" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "FiÈ™ierul de configuare '%s' a fost modificat" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "DoriÈ›i să folosiÈ›i versiunea nouă?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "ObÈ›ine software liber" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s articol care se potriveÈ™te" msgstr[1] "%s articole care se potrivesc" msgstr[2] "%s de articole care se potrivesc" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s articol disponibil" msgstr[1] "%s articole disponibile" msgstr[2] "%s de articole disponibile" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Departamente" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detalii" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Anulează" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependență" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Software instalat" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s articol instalat" msgstr[1] "%s articole instalate" msgstr[2] "%s de articole instalate" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "ÃŽn desfășurare (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copiază legătura web" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Oferă posibilitatea de a alege din mii de aplicaÈ›ii libere disponibile " "pentru Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Se reconstruieÈ™te catalogul..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Caută..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "A_jutor Centru Software" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centru Software Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "To_ate aplicaÈ›iile" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "AplicaÈ›iile întreÈ›inute de _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Editare" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_FiÈ™ier" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "A_jutor" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Instalează" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Surse software..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_AfiÈ™are" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "disponibil" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "instalat" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "în aÅŸteptare" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "PuteÈ›i alege din mii de aplicaÈ›ii libere disponibile pentru Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centru software" software-center-13.10/po/sr.po0000664000202700020270000001723312151440100016446 0ustar dobeydobey00000000000000# Serbian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-09 09:54+0000\n" "Last-Translator: Michael Vogt \n" "Language-Team: Serbian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "ГРЕШКÐ" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "ОпиÑ" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "Ðије доÑтупно у текућим подацима" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "Верзија: %s (%s)" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "" #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" msgstr[1] "" #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Цена: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "Слободно" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "" #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "Ðадоградња" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Уклони" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "ИнÑталација" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "У току (%i)" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "Чека (%i)" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "Убунту програмÑки магацин" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "ПрокрамÑки магацин" #, python-format #~ msgid "%s items available" #~ msgstr "%s Ñтавки доÑтупно" #, python-format #~ msgid "Search in %s" #~ msgstr "Претражи у %s" #~ msgid "All" #~ msgstr "Све" #~ msgid "Install and remove software" #~ msgstr "ИнÑталирај или уклони програме" #~ msgid "Installed software" #~ msgstr "ИнÑталирани програми" #~ msgid "Get Free software" #~ msgstr "Преузми Ñлободне програме" software-center-13.10/po/en_CA.po0000664000202700020270000002334312151440100016766 0ustar dobeydobey00000000000000# English (Canada) translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-28 15:02+0000\n" "Last-Translator: Dylan McCall \n" "Language-Team: English (Canada) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERROR" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Description" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Not available in the current data" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Version: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s is installed on this computer." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "It is used by %s piece of installed software." msgstr[1] "It is used by %s pieces of installed software." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Free" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Price: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Upgrade" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Remove" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Install" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Enable channel" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "In Progress (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Software Centre _Help" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu Software Centre" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Software Centre" #~ msgid "Software Store" #~ msgstr "Software Store" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Pending (%i)" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Software Store" #~ msgid "Install and remove software" #~ msgstr "Install and Remove Software" #, python-format #~ msgid "%s items available" #~ msgstr "%s items available" #~ msgid "Installed software" #~ msgstr "Installed Software" #~ msgid "Get Free software" #~ msgstr "Get Free software" #, python-format #~ msgid "%s is a core component" #~ msgstr "%s is a core component" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s depends on other software on the system. " #, python-format #~ msgid "Search in %s" #~ msgstr "Search in %s" #~ msgid "All" #~ msgstr "All" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Uninstallation means that the following additional software needs to be " #~ "removed." software-center-13.10/po/cs.po0000664000202700020270000002604212151440100016425 0ustar dobeydobey00000000000000# Czech translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-26 08:57+0000\n" "Last-Translator: Adrian GuniÅ¡ \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "CHYBA" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical již dále neposkytuje aktualizace pro %s v Ubuntu %s. Aktualizace " "mohou být dostupné v novÄ›jším vydání Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical poskytuje kritické aktualizace pro aplikaci %(appname)s do: " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical poskytuje kritické aktualizace dodané vývojáři aplikace " "%(appname)s do: %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical neposkytuje aktualizace pro %s. NÄ›které aktualizace mohou být " "dostupné od výrobce tÅ™etí strany." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical poskytuje kritické aktualizace pro %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical poskytuje kritické aktualizace dodané vývojáři aplikace %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical neposkytuje aktualizace pro %s. NÄ›které aktualizace mohou být " "poskytovány komunitou Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Aplikace %s má neznámý stav údržby." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Popis" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Není k dispozici v souÄasných datech" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Není k dispozici pro vaÅ¡i hardwarovou architekturu." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Snímek obrazovky aplikace" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Verze: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s je na tomto poÄítaÄi nainstalován." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Je použit v %s Äásti nainstalovaného softwaru." msgstr[1] "Je použit ve %s Äástech nainstalovaného softwaru." msgstr[2] "Je použit v %s Äástech nainstalovaného softwaru." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Webová stránka" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Neznámá" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Open Source" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietární" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licence: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Zdarma" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Cena: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Snímek obrazovky" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Pro odstranÄ›ní %s musí být rovněž odstranÄ›ny tyto položky:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Odstranit vÅ¡e" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Pokud odinstalujete %s, budoucí aktualizace nebudou zahrnovat nové položky v " "sadÄ› %s. Jste si jisti, že chcete pokraÄovat?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "StejnÄ› odstranit" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s je v Ubuntu klíÄovou aplikací. Její odinstalování může způsobit, že " "budoucí aktualizace nebudou kompletní. Jste si jisti, že chcete pokraÄovat?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "PÅ™ejít na vyšší verzi" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Odstranit" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Nainstalovat" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Povolit kanál" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Ponechat" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Nahradit" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "KonfiguraÄní soubor '%s' zmÄ›nÄ›n" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Chcete použít novou verzi?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Získat svobodný software" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s odpovídající položka" msgstr[1] "%s odpovídající položky" msgstr[2] "%s odpovídajících položek" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s dostupná položka" msgstr[1] "%s dostupné položky" msgstr[2] "%s dostupných položek" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Sekce" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Podrobnosti" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "ZruÅ¡it" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Závislost" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Nainstalovaný software" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s nainstalovaná položka" msgstr[1] "%s nainstalované položky" msgstr[2] "%s nainstalovaných položek" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Probíhá (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopírovat _webový odkaz" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "Vybírejte z tisíce svobodných aplikací dostupných pro Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Hledat..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "_NápovÄ›da pro Centrum softwaru" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centrum softwaru pro Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_VÅ¡echny aplikace" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Aplikace spravované firmou _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Upravit" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Soubor" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_NápovÄ›da" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "Na_instalovat" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "Z_droje softwaru..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Zobrazit" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "dostupné" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "nainstalováno" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "nevyřízeno" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "Vybírejte z tisíce svobodných aplikací dostupných pro Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centrum softwaru" #~ msgid "Install and remove software" #~ msgstr "Instalovat a smazat programy" #~ msgid "Installed software" #~ msgstr "Nainstalované programy" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Zbývá (%i)" software-center-13.10/po/ca.po0000664000202700020270000002675412151440100016415 0ustar dobeydobey00000000000000# Catalan translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-02 00:11+0000\n" "Last-Translator: David Planella \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-10-02 06:38+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERROR" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ja no proporcionarà més actualitzacions per a %s en l'Ubuntu %s. " "Pot ser que hi hagi actualitzacions en una versió més nova de l'Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical proporciona actualitzacions crítiques per a %(appname)s fins el/l' " "%(support_end_month_str)s de %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical proporciona actualitzacions crítiques creades pels desenvolupadors " "de %(appname)s fins el/l' %(support_end_month_str)s de %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical no proporciona actualitzacions per a %s, però pot ser que el " "fabricant del programa ho faci." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical proporciona actualitzacions crítiques per a %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical proporciona actualitzacions crítiques proveïdes pels " "desenvolupadors de %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical no proporciona actualitzacions per a %s, però pot ser que la " "comunitat de l'Ubuntu ho faci." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "L'estat de manteniment per a %s no és conegut." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Descripció" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "No està disponible en les dades actuals" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "No està disponible per a l'arquitectura del vostre ordinador." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Captura de pantalla" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versió: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s està instal·lat en aquest ordinador." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "És utilitzat per %s component de programari instaÅ€lat." msgstr[1] "És utilitzat per %s components de programari instaÅ€lats." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Lloc web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Desconegut" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Programari lliure" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "De propietat" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Llicència: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratuït" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Preu: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Captura de pantalla" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Per a eliminar %s, també cal eliminar aquests elements:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Elimina-ho tot" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Si elimineu %s, futures actualitzacions no inclouran elements nous del " "conjunt %s. Esteu segur que voleu continuar?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Elimina-ho de totes maneres" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s és un programa clau de l'Ubuntu. Eliminar-lo pot causar que " "actualitzacions futures resultin incompletes. Esteu segur que voleu " "continuar?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Actualitza" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Elimina" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Instal·la" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Activa el canal" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Manté" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Reemplaça" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Ha canviat el fitxer de configuració «%s»" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Voleu utilitzar la nova versió?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Obteniu programari lliure" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s element coincident" msgstr[1] "%s elements coincidents" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s element disponible" msgstr[1] "%s elements disponibles" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Departaments" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detalls" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Cancel·la" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependència" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Programari instal·lat" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s element instal·lat" msgstr[1] "%s elements instal·lats" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "En progrés (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "© 2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copia l'adreça _web" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Trieu d'entre milers d'aplicacions gratuïtes disponibles per a l'Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "S'està reconstruint el catàleg d'aplicacions..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Cerca..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "A_juda del centre de programari" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centre de programari de l'Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Totes les aplicacions" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Aplicacions mantingudes per _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Edita" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Fitxer" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "A_juda" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Instal·la" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Fonts de programari..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Visualitza" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "disponible" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "instal·lat" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "pendent" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Trieu d'entre milers d'aplicacions gratuïtes disponibles per a l'Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centre de programari" #, python-format #~ msgid "%s items available" #~ msgstr "%s elements disponibles" #~ msgid "Get Free software" #~ msgstr "Obteniu programari lliure" #~ msgid "Install and remove software" #~ msgstr "Instal·la i elimina programari" #~ msgid "Installed software" #~ msgstr "Programari instal·lat" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Pendent (%i)" #, python-format #~ msgid "Search in %s" #~ msgstr "Cerca en %s" #~ msgid "All" #~ msgstr "Tots" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Eliminar això significa que el següent programari addicional també s'ha " #~ "d'eliminar." #, python-format #~ msgid "%s is a core component" #~ msgstr "%s és un component clau" software-center-13.10/po/hu.po0000664000202700020270000002133712151440100016436 0ustar dobeydobey00000000000000# Hungarian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-20 07:00+0000\n" "Last-Translator: István Nyitrai \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "HIBA" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s elem érhetÅ‘ el" msgstr[1] "%s elem érhetÅ‘ el" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "A Canonical már nem biztosít frissítéseket a(z) %s csomaghoz az Ubuntu %s " "rendszeren. A frissítések elérhetÅ‘k lehetnek az Ubuntu újabb változatában." #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "A Canonical biztonsági frissítéseket biztosít a(z) %(appname)s alkalmazáshoz " "eddig: %(support_end_year)s. %(support_end_month_str)s." #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "A Canonical biztonsági frissítéseket szállít a(z) %(appname)s fejlesztÅ‘itÅ‘l " "eddig: %(support_end_year)s. %(support_end_month_str)s." #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "A Canonical nem biztosít frissítéseket ehhez: %s. Frissítések esetleg " "harmadik féltÅ‘l beszerezhetÅ‘k." #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "A Canonical biztonsági frissítéseket biztosít ehhez: %s." #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "A Canonical biztonsági frissítéseket szállít a(z) %s fejlesztÅ‘itÅ‘l." #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "A Canonical nem biztosít frissítéseket ehhez: %s. Frissítések esetleg " "beszerezhetÅ‘k az Ubuntu közösségtÅ‘l." #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "A(z) %s frissítése bizonytalan." #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "Leírás" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "Nem érhetÅ‘ el a jelenlegi adatok közt" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "Verzió: %s (%s)" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "A(z) %s telepítve van a számítógépre." #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Ezt %s telepített szoftver használja." msgstr[1] "Ezt %s telepített szoftver használja." #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "Weboldal" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Ãr: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "Szabad" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "%s - képernyÅ‘kép" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "%s a rendszer egyéb szoftvereitÅ‘l függ. " #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" "Eltávolítása azt jelenti, hogy a következÅ‘ szoftvereket is el kell " "távolítani." #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "%s alapvetÅ‘ összetevÅ‘" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "A csomag (%s) az Ubuntu alapvetÅ‘ összetevÅ‘je. Az eltávolításával problémát " "okozhat a késÅ‘bbi frissítéseknél. Biztos benne, hogy folytatja?" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "Frissítés" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Eltávolítás" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "Telepítés" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "Csatorna engedélyezése" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "Szabad szoftverek letöltése" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "Részletek" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "Mégse" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "FüggÅ‘ségek" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "Telepített szoftverek" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "Folyamatban (%i)" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "FüggÅ‘ben (%i)" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "Minden alkalmazás" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "A Canonical által karbantartott alkalmazások" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "Keresés..." #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "Ubuntu szoftveráruház" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "Nézet" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "S_zerkesztés" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "_Fájl" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "_Súgó" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "elérhetÅ‘" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "telepítve" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "Szoftveráruház" #~ msgid "All" #~ msgstr "Összes" #~ msgid "Install and remove software" #~ msgstr "Szoftverek telepítése és eltávolítása" #, python-format #~ msgid "Search in %s" #~ msgstr "Keresés ebben: %s" software-center-13.10/po/et.po0000664000202700020270000002617712151440100016441 0ustar dobeydobey00000000000000# Estonian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-27 22:24+0000\n" "Last-Translator: Madis \n" "Language-Team: Estonian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "VIGA" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ei paku Ubuntu %s-s enam pakile %s uuendusi. Uuemas Ubuntu " "versiooni võivad uuendused saadaval olla." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical pakub turvakriitilisi uuendusi rakendusele %(appname)s kuni " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical pakub rakenduse enda arendajate turvakriitilisi uuendusi " "rakendusele %(appname)s kuni %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical ei paku uuendusi rakendusele %s. Mõned uuendused võivad olla " "saadaval kolmanda osapoole pakkujatelt." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical pakub rakendusele %s turvakriitilisi uuendusi." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "Canonical pakub rakenduse %s arendajate turvakriitilisi uuendusi." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical ei paku uuendusi rakendusele %s. Mõningaid uuendusi pruugib " "pakkuda Ubuntu kogukond." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Rakenduse %s hooldatavus on teadmata." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Kirjeldus" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Pole saadaval praegustes andmetes." #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Pole saadaval sinu riistvara arhitektuuri jaoks." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Rakenduse kuvatõmmis" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versioon: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s on sellel arvutil paigaldatud." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "See on kasutusel paigaldatud tarkvara %s osa poolt." msgstr[1] "See on kasutusel paigaldatud tarkvara %s osa poolt." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Veebileht" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Teadmata" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Avatud lähtekoodiga" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Firmaomane" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Litsents: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Tasuta" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Hind: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Kuvatõmmis" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "%s eemaldamiseks peab eemaldama ka järgmised esemed:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Eemalda kõik" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "%s eemaldamisel ei kaasata tulevikus enam uuendamisel %s rühma " "kuuluvaid üksuseid. Oled kindel, et tahad jätkata?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Eemalda ikkagi" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s on Ubuntu tuumas olev programm. Selle eemaldamine võib põhjustada " "tulevikus mittetäielikke uuendamisi. Oled kindel, et tahad jätkata?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Uuenda" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Eemalda" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Paigalda" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Luba kanal" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Jäta alles" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Asenda" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Seadistustefail '%s' muudetud" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Kas sa tahad kasutada uut versiooni?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Hangi tasuta tarkvara" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s sobiv kirje" msgstr[1] "%s sobivat kirjet" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s toode saadaval" msgstr[1] "%s toodet saadaval" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Osakonnad" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Täpsemalt" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Katkesta" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Sõltuvus" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Paigaldatud tarkvara" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s paigaldatud toode" msgstr[1] "%s paigaldatud toodet" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Pooleli (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopeeri _veebilink" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "Laseb sul valida tuhandete tasuta rakenduste vahel Ubuntu jaoks." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Rakenduste kataloogi taasloomine..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Otsimine..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Tarkvarakeskuse _abi" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu Tarkvarakeskus" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Kõik rakendused" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Canonicali poolt hooldatavad rakendused" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Redigeerimine" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Fail" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Abi" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Paigalda" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Tarkvaraallikad..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Vaade" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "kättesaadav" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "paigaldatud" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "ootel" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "Laseb sul valida tuhandete tasuta rakenduste vahel Ubuntule." #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Tarkvarakeskus" #, python-format #~ msgid "%s items available" #~ msgstr "%s toodet saadaval" #~ msgid "All" #~ msgstr "Kõik" #~ msgid "Install and remove software" #~ msgstr "Paigalda ja eemalda tarkvara" #~ msgid "Installed software" #~ msgstr "Paigaldatud tarkvara" #~ msgid "Get Free software" #~ msgstr "Hangi tasuta tarkvara" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s sõltub süsteemil olevast muust tarkvarast. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s kuulub tuuma" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "Selle eemaldamine tähendab, et ka järgnev tarkvara tuleb eemaldada." software-center-13.10/po/POTFILES.skip0000664000202700020270000000036612151440100017576 0ustar dobeydobey00000000000000data/delete_review_gtk3.py data/modify_review_gtk3.py data/report_review_gtk3.py data/submit_review_gtk3.py data/submit_usefulness_gtk3.py doc/example_plugin.py test/gtk3/test_appdetailsview.py test/gtk3/test_reviews.py test/gtk3/test_widgets.py software-center-13.10/po/lt.po0000664000202700020270000002470212151440100016440 0ustar dobeydobey00000000000000# Lithuanian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-29 07:16+0000\n" "Last-Translator: Aurimas FiÅ¡eras \n" "Language-Team: Lithuanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "(n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "KLAIDA" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical daugiau netiekia %s atnaujinimų Ubuntu %s versijai. Atnaujinimai " "gali bÅ«ti prieinami naujesnÄ—je Ubuntu versijoje." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tiekia kritinius %(appname)s atnaujinimus iki %(support_end_year)s " "%(support_end_month_str)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tiekia kritinius %(appname)s atnaujinimus pateiktus programos " "kÅ«rÄ—jų iki %(support_end_year)s %(support_end_month_str)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical netiekia %s atnaujinimų. Kai kurie atnaujinimai gali bÅ«ti pateikti " "treÄiųjų Å¡alių." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical tiekia kritinius %s atnaujinimus." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical tiekia kritinius %s atnaujinimus pateiktus programos kÅ«rÄ—jų." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical netiekia %s atnaujinimų. Kai kurie atnaujinimai gali bÅ«ti pateikti " "Ubuntu bendruomenÄ—s." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Programa %s turi nežinomÄ… priežiÅ«ros bÅ«senÄ…." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "ApraÅ¡as" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versija: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "" #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "SvetainÄ—" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Nežinoma" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Atvirosios programinÄ—s įrangos" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "NuosavybinÄ—" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licencija: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Nemokama" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Kaina: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "PaÅ¡alinti viskÄ…" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Vis tiek paÅ¡alinti" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Atnaujinti" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "PaÅ¡alinti" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Ä®diegti" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Palikti" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "Pa_keisti" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "KonfigÅ«racijos failas '%s' pakeistas" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Ar norite naudoti naujÄ… versijÄ…?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s atitinkantis elementas" msgstr[1] "%s atitinkantys elementai" msgstr[2] "%s atitinkanÄių elementų" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Skyriai" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "IÅ¡samiau" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Atsisakyti" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "PriklausomybÄ—" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Ä®diegta programinÄ— įranga" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Progresas (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopijuoti tinklalapio saitÄ…" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Leidžia jums pasirinkti iÅ¡ tÅ«kstanÄių Ubuntu prieinamų nemokamų programų." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "IeÅ¡koti..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "PrograminÄ—s įrangos centro _žinynas" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu programinÄ—s įrangos centras" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "Visos programos" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Canonical prižiÅ«rimos programos" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Taisa" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Failas" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Žinynas" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "Ä®_diegti" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_PrograminÄ—s įrangos saugyklos..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Rodymas" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "prieinama" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "įdiegta" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Leidžia jums pasirinkti iÅ¡ tÅ«kstanÄių Ubuntu prieinamų nemokamų programų" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "PrograminÄ—s įrangos centras" #~ msgid "Install and remove software" #~ msgstr "Ä®diegti ar paÅ¡alinti programas" #~ msgid "Software Store" #~ msgstr "Programų įdiegimas ir priežiÅ«ra" #~ msgid "Installed software" #~ msgstr "Jau įdiegtos programos" #~ msgid "Get Free software" #~ msgstr "Ä®diegti laisvas programas" #~ msgid "Ubuntu Software Store" #~ msgstr "Programų įdiegimas ir priežiÅ«ra" #, python-format #~ msgid "%s items available" #~ msgstr "rasta %s programų (-os)" software-center-13.10/po/it.po0000664000202700020270000002631212151440100016434 0ustar dobeydobey00000000000000# Italian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-03 09:12+0000\n" "Last-Translator: Milo Casagrande \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-10-04 06:51+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "Errore" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical non fornisce più aggiornamenti per %s con Ubuntu %s. Alcuni " "aggiornamenti potrebbero essere disponibili in una nuova versione di Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical fornisce aggiornamenti critici per %(appname)s fino a " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical fornisce aggiornamenti critici da parte degli sviluppatori di " "%(appname)s fino a %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical non fornisce aggiornamenti per %s. Alcuni aggiornamenti potrebbero " "essere forniti da terze parti." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical fornisce aggiornamenti critici per %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical fornisce aggiornamenti critici rilasciati dagli sviluppatori di %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical non fornisce aggiornamenti per %s. Alcuni aggiornamenti potrebbero " "essere disponibili dalla comunità di Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "L'applicazione %s presenta uno stato di mantenimento sconosciuto." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Descrizione" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Non disponibile con i dati attuali" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Non disponibile per questo tipo di architettura" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Schermata applicazione" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versione: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s è installato su questo computer." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "È utilizzato da %s software installato." msgstr[1] "È utilizzato da %s software installati." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Sito web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Sconosciuto" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Open source" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietario" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licenza: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "gratuito" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Prezzo: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Schermata" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Per rimuovere %s, anche questi elementi deveno essere rimossi:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Rimuovi tutto" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Se viene disinstallato %s, i successivi aggiornamenti non includeranno gli " "elementi nell'insieme %s. Continuare?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Rimuovi comunque" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s è un'applicazione importante in Ubuntu. Se viene disinstallata i futuri " "aggiornamenti potrebbero risultare incompleti. Continuare?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Aggiorna" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Rimuovi" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Installa" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Abilita canale" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Mantieni" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Sostituisci" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Il file di configurazione «%s» è cambiato" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Usare la nuova versione?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Scarica software gratuito" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s elemento corrispondente" msgstr[1] "%s elementi corrispondenti" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s elemento disponibile" msgstr[1] "%s elementi disponibili" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Sezioni" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Dettagli" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Annulla" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dipendenza" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Software installato" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s elemento installato" msgstr[1] "%s elementi installati" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "In elaborazione (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "© 2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copia c_ollegamento" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Consente di scegliere tra migliaia di applicazioni liberamente disponibili " "per Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Ricostruzione catologo applicazioni..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Cerca..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "A_iuto di Software Center" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu Software Center" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Tutte le applicazioni" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Applicazioni mantenute da Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Modifica" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_File" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "A_iuto" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Installa" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Sorgenti software..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Visualizza" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "disponibile" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "installato" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "in sospeso" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Consente di scegliere tra migliaia di applicazioni liberamente disponibili " "per Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Software Center" #, python-format #~ msgid "Search in %s" #~ msgstr "Cercare in %s" #~ msgid "All" #~ msgstr "Tutti" #~ msgid "Installed software" #~ msgstr "Software installato" #, python-format #~ msgid "%s items available" #~ msgstr "%s applicazioni disponibili" #~ msgid "Install and remove software" #~ msgstr "Installa e rimuove software" #~ msgid "Software Store" #~ msgstr "Software Store" #~ msgid "Get Free software" #~ msgstr "Installa software libero" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Software Store" #, python-format #~ msgid "Pending (%i)" #~ msgstr "In attesa (%i)" software-center-13.10/po/sq.po0000664000202700020270000001663012151440100016445 0ustar dobeydobey00000000000000# Albanian translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-09 09:56+0000\n" "Last-Translator: Vilson Gjeci \n" "Language-Team: Albanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "GABIM" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "Përshkrimi" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "Nuk është i disponueshëm në të dhënat e momentit" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "Versioni: %s (%s)" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "" #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" msgstr[1] "" #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Çmimi: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "Falas" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "" #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "Përditëso" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Hiqe" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "Instalo" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "Në Përparim (%i)" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "Në Pritje (%i)" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "Dyqani i Programeve të Ubuntu" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "Dyqani i Programeve" #, python-format #~ msgid "%s items available" #~ msgstr "%s tema të disponueshme" #, python-format #~ msgid "Search in %s" #~ msgstr "Kërko në %s" #~ msgid "All" #~ msgstr "Të Gjitha" #~ msgid "Install and remove software" #~ msgstr "Instalo dhe hiq programet" #~ msgid "Installed software" #~ msgstr "Programet e instaluara" #~ msgid "Get Free software" #~ msgstr "Merr Programe Falas" software-center-13.10/po/nl.po0000664000202700020270000002406112151440100016430 0ustar dobeydobey00000000000000# Dutch translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-27 11:41+0000\n" "Last-Translator: cumulus007 \n" "Language-Team: Dutch \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "FOUT" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Omschrijving" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Niet beschilbaar in de huidige gegevens" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Niet beschikbaar voor uw type hardware." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versie: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s is geïnstalleerd op uw computer." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Het wordt gebruikt door %s geïnstalleerde applicatie." msgstr[1] "Het wordt gebruikt door %s geïnstalleerde applicaties" #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratis" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Prijs: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Schermafbeelding" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s behoort tot de kern van Ubuntu. Als u het verwijdert, slagen toekomstige " "opwaarderingen mogelijk niet geheel. Weet u zeker dat u wilt doorgaan?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Opwaarderen" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Verwijderen" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Installeren" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Kanaal inschakelen" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Gratis software verkrijgen" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s item beschikbaar" msgstr[1] "%s items beschikbaar" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Geïnstalleerde software" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Bezig (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Kies uit duizenden beschikbare gratis toepassingen beschikbaar voor Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Softwarebronnen..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Kies uit duizenden beschikbare gratis toepassingen beschikbaar voor Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "" #~ msgid "All" #~ msgstr "Alle" #~ msgid "Installed software" #~ msgstr "Geïnstalleerde software" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Wachtend (%i)" #, python-format #~ msgid "%s items available" #~ msgstr "%s items beschikbaar" #~ msgid "Install and remove software" #~ msgstr "Software verwijderen en installeren" #~ msgid "Software Store" #~ msgstr "Softwarewinkel" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu Softwarewinkel" #, python-format #~ msgid "Search in %s" #~ msgstr "Zoeken in %s" #~ msgid "Get Free software" #~ msgstr "Gratis software verkrijgen" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s is afhankelijk van andere software op de computer. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s is een kerncomponent" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Als u dit verwijdert, moet de volgende software ook verwijderd worden." software-center-13.10/po/pl.po0000664000202700020270000002747412151440100016445 0ustar dobeydobey00000000000000# Polish translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-26 10:51+0000\n" "Last-Translator: Tomasz Dominikowski \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "BÅÄ„D" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Firma Canonical nie dostarcza już aktualizacji dla %s w Ubuntu %s. " "Aktualizacje mogÄ… być dostÄ™pne w nowszej wersji Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Firma Canonical dostarcza krytyczne aktualizacje dla %(appname)s do " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Firma Canonical dostarcza krytyczne aktualizacje otrzymywane przez autorów " "%(appname)s do %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Firma Canonical nie dostarcza aktualizacji dla %s. Niektóre aktualizacje " "mogÄ… być dostarczane przez innych dostawców." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Firma Canonical dostarcza krytyczne aktualizacja dla %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Firma Canonical dostarcza krytyczne aktualizacje, uzyskiwane od deweloperów " "%s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Firma Canonical nie dostarcza aktualizacji dla %s. Niektóre aktualizacje " "mogÄ… być dostarczane przez spoÅ‚eczność Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Program %s ma nieznany stan obsÅ‚ugi." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Opis" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "NiedostÄ™pne w bieżących danych" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "NiedostÄ™pne dla tej architektury sprzÄ™towej." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Zrzut ekranu programu" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Wersja: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s jest już zainstalowany." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Używany przez %s zainstalowany program." msgstr[1] "Używany przez %s zainstalowane programy." msgstr[2] "Używany przez %s zainstalowanych programów." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Witryna" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Nieznany" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Wolne" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "WÅ‚asnoÅ›ciowe" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licencja: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Darmowy" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Cena: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - zrzut ekranu" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Aby usunąć %s, nastÄ™pujÄ…ce elementy także muszÄ… zostać usuniÄ™te:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "UsuÅ„ wszystko" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Deinstalacja %s oznacza, że przyszÅ‚e aktualizacje nie bÄ™dÄ… zawierać nowych " "elementów z %s. Na pewno kontynuować?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "UsuÅ„ mimo to" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s jest elementem systemowym Ubuntu. Jego deinstalacja może spowodować " "problemy z przyszÅ‚ymi aktualizacjami. Na pewno kontynuować?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Zaktualizuj" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "UsuÅ„" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Zainstaluj" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Włącz kanaÅ‚" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Zachowaj" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "Zas_tÄ…p" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Plik konfiguracji \"%s\" zostaÅ‚ zmieniony" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Użyć nowej wersji?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Pobierz wolne oprogramowanie" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s pasujÄ…cy element" msgstr[1] "%s pasujÄ…ce elementy" msgstr[2] "%s pasujÄ…cych elementów" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s dostÄ™pny element" msgstr[1] "%s dostÄ™pne elementy" msgstr[2] "%s dostÄ™pnych elementów" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "DziaÅ‚y" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Szczegóły" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Anuluj" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Zależność" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Zainstalowane oprogramowanie" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s zainstalowany element" msgstr[1] "%s zainstalowane elementy" msgstr[2] "%s zainstalowanych elementów" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "W toku (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "S_kopiuj adres strony WWW" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Pozwala wybierać spoÅ›ród tysiÄ™cy otwartych programów dostÄ™pnych dla Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Przetwarzanie katalogu programów..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Wyszukiwanie..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Pomo_c Centrum oprogramowania" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centrum oprogramowania Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Wszystkie programy" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Programy obsÅ‚ugiwane przez Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Edycja" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Plik" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "Pomo_c" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Zainstaluj" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "ŹródÅ‚a _oprogramowania..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Widok" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "dostÄ™pny" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "zainstalowany" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "oczekujÄ…cy" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Pozwala wybierać spoÅ›ród tysiÄ™cy bezpÅ‚atnych programów dostÄ™pnych dla Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centrum oprogramowania" #, python-format #~ msgid "%s items available" #~ msgstr "%s dostÄ™pnych elementów" #, python-format #~ msgid "Search in %s" #~ msgstr "Szukaj w %s" #~ msgid "All" #~ msgstr "Wszystkie" #~ msgid "Get Free software" #~ msgstr "Pobierz darmowe oprogramowanie" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Deinstalacja oznacza konieczność usuniÄ™cia dodatkowych, nastÄ™pujÄ…cych " #~ "programów." #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s zależy od innego oprogramowania w tym systemie. " #~ msgid "Install and remove software" #~ msgstr "Instalowanie i usuwanie oprogramowania" #~ msgid "Software Store" #~ msgstr "Centrum oprogramowania" #~ msgid "Installed software" #~ msgstr "Zainstalowane oprogramowanie" #~ msgid "Ubuntu Software Store" #~ msgstr "Centrum oprogramowania Ubuntu" #, python-format #~ msgid "Pending (%i)" #~ msgstr "OczekujÄ…ce (%i)" software-center-13.10/po/zh_CN.po0000664000202700020270000002573112151440100017025 0ustar dobeydobey00000000000000# Simplified Chinese translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-29 09:00+0000\n" "Last-Translator: Tao Wei \n" "Language-Team: Simplified Chinese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "错误" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "Canonical ä¸å†æä¾› %s 在 Ubuntu %s 中的å‡çº§ã€‚å‡çº§å¯èƒ½ä¼šåœ¨ Ubuntu 的新版本中å¯ç”¨ã€‚" #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical æä¾› %(appname)s 的关键更新至 %(support_end_year)s å¹´ " "%(support_end_month_str)s 月。" #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical æä¾›ç”±å¼€å‘者æä¾›çš„ %(appname)s 的关键更新至 %(support_end_year)s å¹´ " "%(support_end_month_str)s 月。" #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "Canonical ä¸å†ä¸º %s æä¾›å‡çº§ã€‚一些å‡çº§å¯èƒ½ä¼šç”±ç¬¬ä¸‰æ–¹æä¾›ã€‚" #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical 为 %s æä¾›é‡è¦å‡çº§ã€‚" #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "Canonical 为 %s æä¾›å¼€å‘者支æŒçš„é‡è¦å‡çº§ã€‚" #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "Canonical ä¸èƒ½æä¾› %s 的更新。一些更新å¯èƒ½é€šè¿‡ Ubuntu 社区æä¾›ã€‚" #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "ç¨‹åº %s 的维护处于未知状æ€ã€‚" #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "æè¿°" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "åœ¨å½“å‰æ•°æ®ä¸­ä¸å¯ç”¨" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "ä¸é€‚åˆåœ¨ä½ çš„硬件架构上使用。" #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "åº”ç”¨ç¨‹åºæˆªå›¾" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "版本:%s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s å·²ç»å®‰è£…在本计算机中。" #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "已安装的软件å ç”¨ %s." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "网å€" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "未知" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "开放æºä»£ç " #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "专有软件" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "许å¯è¯ï¼š%s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "自由" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "价格: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - 截图" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "è¦ç§»é™¤ %s,以下这些也必须移除:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "全部移除" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "如果移除 %s ,未æ¥çš„å‡çº§å°†ä¸ä¼šåŒ…括%s 。你确认è¦ç»§ç»­å—?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "确定移除" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "%s 是 Ubuntu 的核心组件。删除它å¯èƒ½å¯¼è‡´æœªæ¥çš„å‡çº§ä¸å®Œå…¨ã€‚是å¦ç¡®å®šç»§ç»­ï¼Ÿ" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "å‡çº§" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "删除" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "安装" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "å¯ç”¨é¢‘é“" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "ä¿æŒ(_K)" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "替æ¢(_R)" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "设置文件 \"%s\" å·²ç»å˜åЍ" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "ä½ è¦ä½¿ç”¨æ–°ç‰ˆæœ¬å—?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "获å–自由软件" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s é¡¹ç¬¦åˆæ¡ä»¶" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s 项å¯ç”¨" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "区划" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "详细信æ¯" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "å–æ¶ˆ" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "ä¾èµ–" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "已安装的软件" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s 已安装" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "正在执行 (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "å¤åˆ¶ç½‘å€é“¾æŽ¥(_W)" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "让你从数åƒç§é€‚åˆ Ubuntu 使用的ã€è‡ªç”±çš„程åºä¸­ï¼Œè¿›è¡Œé€‰æ‹©ã€‚" #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "é‡å»ºåº”用程åºåˆ—表..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "æœç´¢..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "软件中心帮助(_H)" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu 软件中心" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "全部应用程åº{_A)" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Canonical维护的应用程åº(_C)" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "编辑(_E)" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "文件(_F)" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "帮助(_H)" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "安装(_I)" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "软件æº(_S)..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "视图(_V)" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "å¯ç”¨" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "已安装" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "待处ç†" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "让你从数åƒç§é€‚åˆ Ubuntu 使用的ã€è‡ªç”±çš„程åºä¸­ï¼Œè¿›è¡Œé€‰æ‹©" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "软件中心" #, python-format #~ msgid "%s items available" #~ msgstr "共有 %s 项å¯ç”¨" #, python-format #~ msgid "Search in %s" #~ msgstr "在 %s 中查找" #~ msgid "All" #~ msgstr "全部" #~ msgid "Get Free software" #~ msgstr "获å–自由软件" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s ä¾èµ–于系统上安装的其它软件。 " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s 是核心组件" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "åˆ é™¤å®ƒå°†ä¼šåŒæ—¶åˆ é™¤å¦‚下软件。" #~ msgid "Install and remove software" #~ msgstr "安装和删除软件" #~ msgid "Software Store" #~ msgstr "软件商店" #~ msgid "Installed software" #~ msgstr "已安装软件" #, python-format #~ msgid "Pending (%i)" #~ msgstr "挂起 (%i)" #~ msgid "Ubuntu Software Store" #~ msgstr "Ubuntu 软件商店" software-center-13.10/po/sco.po0000664000202700020270000001560012151440100016602 0ustar dobeydobey00000000000000# Scots translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-09 09:56+0000\n" "Last-Translator: Stephen Brown \n" "Language-Team: Scots \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "ERRIR" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "Descrieftion" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "" #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" msgstr[1] "" #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Cast: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "" #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Remove" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "Insta" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "Ubuntu Saftwaire Stare" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "Saftwaire Stare" software-center-13.10/po/es.po0000664000202700020270000002772712151440100016442 0ustar dobeydobey00000000000000# Spanish translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-25 22:46+0000\n" "Last-Translator: Ricardo Pérez López \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERROR" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ya no proporciona actualizaciones para «%s» en Ubuntu %s. Podrían " "aparecer actualizaciones en una próxima versión de Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical proporciona actualizaciones críticas para «%(appname)s» hasta " "%(support_end_month_str)s de %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical proporciona actualizaciones críticas suministradas por los " "desarrolladores de «%(appname)s» hasta %(support_end_month_str)s de " "%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical no proporciona actualizaciones para «%s». Otros proveedores pueden " "suministrar algunas actualizaciones." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical proporciona actualizaciones críticas para «%s»." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical proporciona actualizaciones críticas proporcionadas por los " "desarrolladores de «%s»." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical no proporciona actualizaciones para «%s». La comunidad de Ubuntu " "puede proporcionar algunas actualizaciones." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "La aplicación «%s» está en estado de mantenimiento desconocido." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Descripción" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "No se encuentra disponible con los datos actuales" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "No se encuentra disponible para su arquitectura hardware." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Captura de pantalla" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versión: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s está instalado en este equipo." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Este es usado por %s pieza de software instalada." msgstr[1] "Este es usado por %s piezas de software instaladas." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Sitio web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Desconocido" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Código abierto" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Privativa" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licencia: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Gratuito" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Precio: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Captura de pantalla" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Para desinstalar %s, también se deben desinstalar estos elementos:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Desinstalar todo" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Si desinstala %s, las futuras actualizaciones no incluirán elementos del " "conjunto %s. ¿Desea continuar?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Desinstalar de todos modos" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s es una aplicación principal de Ubuntu. Desinstalarla podría causar que " "futuras actualizaciones queden incompletas. ¿Desea continuar?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Actualizar" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Desinstalar" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Instalar" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Activar canal" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Mantener" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Reemplazar" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Se cambió el archivo de configuración «%s»" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "¿Desea usar la nueva versión?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Obtener software libre" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s coincidencia" msgstr[1] "%s coincidencias" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s elemento disponible" msgstr[1] "%s elementos disponibles" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Departamentos" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Detalles" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Cancelar" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependencia" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Software instalado" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s elemento instalado" msgstr[1] "%s elementos instalados" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "En progreso (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copiar enlace a _web" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Le permite elegir entre miles de aplicaciones libres disponibles para Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Reconstruyendo catálogo de aplicaciones..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Buscar..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Ay_uda del Centro de software" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Centro de software de Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Todas las aplicaciones" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "Aplicaciones mantenidas por _Canonical" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Editar" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Archivo" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "Ay_uda" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Instalar" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Orígenes del software..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Ver" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "disponible" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "instalado" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "pendiente" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Le permite elegir entre miles de aplicaciones libres disponibles para Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Centro de software" #, python-format #~ msgid "%s items available" #~ msgstr "%s elementos disponibles" #~ msgid "Search" #~ msgstr "Buscar" #~ msgid "Install and remove software" #~ msgstr "Instalar y eliminar software" #~ msgid "Homepage" #~ msgstr "Sitio web" #~ msgid "Categories" #~ msgstr "Categorías" #~ msgid "Get new software" #~ msgstr "Adquirir nuevo software" #~ msgid "Installed software" #~ msgstr "Software instalado" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Pendiente (%i)" #, python-format #~ msgid "Search in %s" #~ msgstr "Buscar en %s" #~ msgid "All" #~ msgstr "Todo" #~ msgid "Get Free software" #~ msgstr "Obtener sofware gratuito" #~ msgid "Software Store" #~ msgstr "Tienda de software" #~ msgid "Ubuntu Software Store" #~ msgstr "Tienda de software de Ubuntu" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s depende de otro software en el sistema. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s es un componente principal" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Desinstalándolo significa que el software adicional a continuación necesita " #~ "ser removido." software-center-13.10/po/fr.po0000664000202700020270000002504512151440100016431 0ustar dobeydobey00000000000000# French translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-10-12 07:53+0000\n" "Last-Translator: Claude Paroz \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2009-10-13 07:01+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERREUR" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ne fournit plus de mises à jour de %s pour Ubuntu %s. Elles " "peuvent être disponibles pour une version d'Ubuntu plus récente." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical propose des mises à jour critiques pour %(appname)s jusqu'en " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical propose des mises à jour critiques fournies par les développeurs " "de %(appname)s jusqu'en %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical ne propose pas de mises à jour pour %s. Certaines mises à jour " "peuvent être proposées par des vendeurs tiers." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical propose des mises à jour critiques pour %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical propose des mises à jour critiques fournies par les développeurs " "de %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical ne propose pas de mise à jour pour %s. Certaines mises à jour " "peuvent être fournies par la communauté Ubuntu." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "L'application %s n'a pas de mode de maintenance connu." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Description" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Non disponible dans les données actuelles" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Non disponible pour votre architecture matérielle." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Version : %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s est installé sur cet ordinateur." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Il est utilisé par %s autre logiciel installé." msgstr[1] "Il est utilisé par %s autres logiciels installés." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Site Web" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Libre" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Prix : %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Capture d'écran" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s est un composant essentiel d' Ubuntu. Sa désinstallation peut entraîner " "des mises à jour incomplètes dans le futur. Voulez vous vraiment continuer ?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Mettre à jour" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Supprimer" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Installer" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Activer le dépôt" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Obtenir des logiciels libres" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s élément disponible" msgstr[1] "%s éléments disponibles" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Détails" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Annuler" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Logiciels installés" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "" msgstr[1] "" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "En cours (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copier le lien _Web" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Choisissez parmi les milliers d'applications libres disponibles pour Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "É_dition" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Fichier" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "Aid_e" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "disponible" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "installé" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "en attente" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Choisissez parmi les milliers d'applications libres disponibles pour Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "" #~ msgid "Installed software" #~ msgstr "Logiciels installés" #~ msgid "Install and remove software" #~ msgstr "Installer et supprimer des logiciels" #~ msgid "All" #~ msgstr "Tout" #~ msgid "Software Store" #~ msgstr "Software Store" #, python-format #~ msgid "%s items available" #~ msgstr "%s éléments disponibles" #, python-format #~ msgid "Search in %s" #~ msgstr "Chercher dans %s" #~ msgid "Get Free software" #~ msgstr "Obtenir des logiciels libres" #, python-format #~ msgid "Pending (%i)" #~ msgstr "En attente (%i)" software-center-13.10/po/fi.po0000664000202700020270000002666712151440100016433 0ustar dobeydobey00000000000000# Finnish translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-26 14:13+0000\n" "Last-Translator: Timo Jyrinki \n" "Language-Team: Finnish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "VIRHE" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical ei enää tarjoa %s-päivityksiä Ubuntu %s:een. Päivityksiä voi olla " "saatavilla uudemmalle Ubuntun versiolle." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tarjoaa tärkeät päivitykset ohjelmalle %(appname)s seuraavaan " "ajankohtaan saakka: %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical tarjoaa ohjelman %(appname)s kehittäjien lähettämät tärkeät " "päivitykset seuraavaan ajankohtaan saakka: %(support_end_month_str)s " "%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical ei tarjoa päivityksiä paketille %s. Kolmas osapuoli saattaa " "tarjota joitain päivityksiä." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical tarjoaa tärkeät päivitykset paketille %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical tarjoaa paketin %s kehittäjien tarjoamat tärkeät päivitykset." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical ei tarjoa päivityksiä paketille %s. Ubuntun yhteisö saattaa " "tarjota joitain päivityksiä." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Sovelluksen %s ylläpidosta ei ole tietoja." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Kuvaus" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Ei saatavilla nykyisissä tiedoissa" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Ei saatavilla laitteistoalustallesi." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Kuvakaappaus sovelluksesta" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Versio: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s on asennettu tälle tietokoneelle." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "Sitä käyttää %s asennettu ohjelmisto." msgstr[1] "Sitä käyttää %s asennettua ohjelmistoa." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "WWW-sivusto" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Tuntematon" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Avoin lähdekoodi" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Suljettu" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Lisenssi: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "ilmainen" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Hinta: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s – kuvakaappaus" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "Poistaaksesi sovelluksen %s, myös seuraavat tulee poistaa:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Poista kaikki" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "Jos poistat sovelluksen %s, tulevat päivitykset eivät sisällä uusia kohteita " "%s-valikoimasta. Haluatko silti jatkaa?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Poista silti" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s on ydinsovellus Ubuntussa. Sen asennuksen poistaminen voi johtaa tulevien " "päivitysten epäonnistumiseen. Oletko varma, että haluat jatkaa?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Päivitä" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Poista" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Asenna" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Ota kanava käyttöön" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Säilytä" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "Ko_rvaa" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Asetustiedosto â€%s†on muuttunut" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Haluatko käyttää uutta versiota?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Hae ilmaisia ohjelmistoja" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s osuma" msgstr[1] "%s osumaa" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s kohde saatavilla" msgstr[1] "%s kohdetta saatavilla" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Ryhmät" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Yksityiskohdat" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Peruuta" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Riippuvuus" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Asennetut ohjelmistot" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s asennettu" msgstr[1] "%s asennettua" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "Käynnissä (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Kopioi _WWW-linkki" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Valitse haluamasi tuhansista ilmaisista Ubuntulle saatavilla olevista " "sovelluksista." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Rakennetaan sovelluskatalogia uudelleen..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Etsi..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Sovellusvalikoiman o_hje" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntun sovellusvalikoima" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_Kaikki sovellukset" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Canonicalin ylläpitämät sovellukset" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Muokkaa" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_Tiedosto" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "O_hje" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Asenna" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Ohjelmistolähteet..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_Näytä" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "saatavilla" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "asennettu" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "odottaa" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Valitse haluamasi tuhansista ilmaisista Ubuntulle saatavilla olevista " "sovelluksista." #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Sovellusvalikoima" #, python-format #~ msgid "%s items available" #~ msgstr "%s sovellusta saatavilla" #, python-format #~ msgid "Search in %s" #~ msgstr "Hae (%s)" #~ msgid "All" #~ msgstr "Kaikki" #~ msgid "Install and remove software" #~ msgstr "Asenna tai poista sovelluksia" #~ msgid "Installed software" #~ msgstr "Asennetut ohjelmistot" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Odottaa (%i)" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s on riippuvainen muista järjestelmässä olevista ohjelmistoista. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s on ydinkomponentti" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Sen asennuksen poistaminen tarkoittaa, että myös seuraavat ylimääräiset " #~ "ohjelmistot tulee poistaa." software-center-13.10/po/en_GB.po0000664000202700020270000002657312151440100017003 0ustar dobeydobey00000000000000# English (United Kingdom) translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-29 12:20+0000\n" "Last-Translator: Dave Walker \n" "Language-Team: English (United Kingdom) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "ERROR" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "Canonical no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical provides critical updates for %s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" "Canonical provides critical updates supplied by the developers of %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "Application %s has a unknown maintenance status." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "Description" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "Not available in the current data" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "Not available for your hardware architecture." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "Application Screenshot" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "Version: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "%s is intalled on this computer." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "It is used by %s piece of installed software." msgstr[1] "It is used by %s pieces of installed software." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "Website" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "Unknown" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Open Source" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "Proprietary" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "Licence: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "Free" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "Price: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "%s - Screenshot" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "To remove %s, these items must be removed as well:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "Remove All" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "Remove Anyway" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "Upgrade" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "Remove" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "Install" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "Enable channel" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "_Keep" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "_Replace" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "Configuration file '%s' changed" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "Do you want to use the new version?" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "Get Free Software" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s matching item" msgstr[1] "%s matching items" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "%s item available" msgstr[1] "%s items available" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "Departments" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Details" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "Cancel" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "Dependency" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "Installed Software" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "%s installed item" msgstr[1] "%s installed items" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "In Progress (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "Copy _Web Link" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" "Lets you choose from thousands of free applications available for Ubuntu." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "Rebuilding application catalog..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "Search..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "Software Centre _Help" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "Ubuntu Software Centre" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "_All Applications" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "_Canonical-Maintained Applications" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_Edit" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_File" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_Help" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_Install" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_Software Sources..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_View" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "available" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "installed" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "pending" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" "Lets you choose from thousands of free applications available for Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "Software Centre" msgid "Featured Applications" msgstr "Featured Applications" #, python-format #~ msgid "%s items available" #~ msgstr "%s items available" #, python-format #~ msgid "Search in %s" #~ msgstr "Search in %s" #~ msgid "All" #~ msgstr "All" #~ msgid "Install and remove software" #~ msgstr "Install and remove software" #~ msgid "Installed software" #~ msgstr "Installed software" #~ msgid "Get Free software" #~ msgstr "Get Free software" #~ msgid "Software Store" #~ msgstr "Software Store" #, python-format #~ msgid "Pending (%i)" #~ msgstr "Pending (%i)" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s depends on other software on the system. " #, python-format #~ msgid "%s is a core component" #~ msgstr "%s is a core component" #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "" #~ "Uninstallation means that the following additional software needs to be " #~ "removed." software-center-13.10/po/th.po0000664000202700020270000004665512151440100016447 0ustar dobeydobey00000000000000# Thai translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # Sira Nokyoonhtong , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2010-02-23 20:34+0700\n" "Last-Translator: SiraNokyoongtong \n" "Language-Team: Thai \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "©2009 Canonical" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "คัดลอà¸à¹€_ว็บลิงà¸à¹Œ" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "Lets you choose from thousands of free applications available for Ubuntu." msgstr "ให้คุณเลือà¸à¹‚ปรà¹à¸à¸£à¸¡à¸Ÿà¸£à¸µà¸à¸§à¹ˆà¸²à¸žà¸±à¸™à¸£à¸²à¸¢à¸à¸²à¸£à¸ªà¸³à¸«à¸£à¸±à¸š Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "à¸à¸³à¸¥à¸±à¸‡à¸ˆà¸±à¸”ทำหมวดโปรà¹à¸à¸£à¸¡à¹ƒà¸«à¸¡à¹ˆ..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "ค้นหา..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "ตัวช่วยเ_หลือของศูนย์ซอฟต์à¹à¸§à¸£à¹Œ" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "ศูนย์ซอฟต์à¹à¸§à¸£à¹Œ Ubuntu" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "โปรà¹à¸à¸£à¸¡_ทั้งหมด" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "โปรà¹_à¸à¸£à¸¡à¸—ี่ Canonical ดูà¹à¸¥" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "à¹_à¸à¹‰à¹„ข" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "à¹_ฟ้ม" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_วิธีใช้" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_ติดตั้ง" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "à¹_หล่งซอฟต์à¹à¸§à¸£à¹Œ..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "มุม_มอง" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "ที่ติดตั้งได้" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "ที่ติดตั้งà¹à¸¥à¹‰à¸§" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "ค้างอยู่" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "Lets you choose from thousands of free applications available for Ubuntu" msgstr "ให้คุณเลือà¸à¹‚ปรà¹à¸à¸£à¸¡à¸Ÿà¸£à¸µà¸à¸§à¹ˆà¸²à¸žà¸±à¸™à¸£à¸²à¸¢à¸à¸²à¸£à¸ªà¸³à¸«à¸£à¸±à¸š Ubuntu" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "ศูนย์ซอฟต์à¹à¸§à¸£à¹Œ" #: ../data/software-center.menu.in.h:1 msgid "Admin" msgstr "ผู้ดูà¹à¸¥" #: ../data/software-center.menu.in.h:2 msgid "Base" msgstr "Base" #: ../data/software-center.menu.in.h:3 msgid "CLI Mono" msgstr "CLI Mono" #: ../data/software-center.menu.in.h:4 msgid "Databases" msgstr "à¸à¸²à¸™à¸‚้อมูล" #: ../data/software-center.menu.in.h:5 msgid "Debug" msgstr "ดีบัà¸" #: ../data/software-center.menu.in.h:6 msgid "Debugging" msgstr "à¸à¸²à¸£à¸”ีบัà¸" #: ../data/software-center.menu.in.h:7 msgid "Development" msgstr "à¸à¸²à¸£à¸žà¸±à¸’นา" #: ../data/software-center.menu.in.h:8 msgid "Documentation" msgstr "เอà¸à¸ªà¸²à¸£" #: ../data/software-center.menu.in.h:9 msgid "Editors" msgstr "à¹à¸à¹‰à¹„ขข้อความ" #: ../data/software-center.menu.in.h:10 msgid "Embedded" msgstr "Embedded" #: ../data/software-center.menu.in.h:11 msgid "Fonts" msgstr "à¹à¸šà¸šà¸­à¸±à¸à¸©à¸£" #: ../data/software-center.menu.in.h:12 msgid "Games" msgstr "เà¸à¸¡" #: ../data/software-center.menu.in.h:13 msgid "Gnome" msgstr "Gnome" #: ../data/software-center.menu.in.h:14 msgid "Graphic Interface Design" msgstr "ออà¸à¹à¸šà¸šà¸ªà¹ˆà¸§à¸™à¸•ิดต่อผู้ใช้à¹à¸šà¸šà¸à¸£à¸²à¸Ÿà¸´à¸" #: ../data/software-center.menu.in.h:15 msgid "Graphics" msgstr "à¸à¸£à¸²à¸Ÿà¸´à¸" #: ../data/software-center.menu.in.h:16 msgid "Hamradio" msgstr "Hamradio" #: ../data/software-center.menu.in.h:17 msgid "Haskell" msgstr "Haskell" #: ../data/software-center.menu.in.h:18 msgid "IDEs" msgstr "IDEs" #: ../data/software-center.menu.in.h:19 msgid "Interpreters" msgstr "อินเทอร์พรีเตอร์" #: ../data/software-center.menu.in.h:20 msgid "Java" msgstr "Java" #: ../data/software-center.menu.in.h:21 msgid "KDE" msgstr "KDE" #: ../data/software-center.menu.in.h:22 msgid "Kernel" msgstr "Kernel" #: ../data/software-center.menu.in.h:23 msgid "Libraries" msgstr "Libraries" #: ../data/software-center.menu.in.h:24 msgid "Lisp" msgstr "Lisp" #: ../data/software-center.menu.in.h:25 msgid "Mail" msgstr "เมล" #: ../data/software-center.menu.in.h:26 msgid "Mathematics" msgstr "คณิตศาสตร์" #: ../data/software-center.menu.in.h:27 msgid "Network" msgstr "เครือข่าย" #: ../data/software-center.menu.in.h:28 msgid "OCaml" msgstr "OCaml" #: ../data/software-center.menu.in.h:29 msgid "Other Packages" msgstr "à¹à¸žà¸à¹€à¸à¸ˆà¸­à¸·à¹ˆà¸™ ๆ" #: ../data/software-center.menu.in.h:30 msgid "Perl" msgstr "Perl" #: ../data/software-center.menu.in.h:31 msgid "Profiling" msgstr "Profiling" #: ../data/software-center.menu.in.h:32 msgid "Python" msgstr "Python" #: ../data/software-center.menu.in.h:33 msgid "Ruby" msgstr "Ruby" #: ../data/software-center.menu.in.h:34 msgid "Science" msgstr "วิทยาศาสตร์" #: ../data/software-center.menu.in.h:35 msgid "Shells" msgstr "โปรà¹à¸à¸£à¸¡à¹€à¸Šà¸¥à¸¥à¹Œ" #: ../data/software-center.menu.in.h:36 msgid "Sound" msgstr "เสียง" #: ../data/software-center.menu.in.h:37 msgid "System Packages" msgstr "à¹à¸žà¸à¹€à¸à¸ˆà¸£à¸°à¸šà¸š" #: ../data/software-center.menu.in.h:38 msgid "Tex" msgstr "Tex" #: ../data/software-center.menu.in.h:39 msgid "Text" msgstr "ข้อความ" #: ../data/software-center.menu.in.h:40 msgid "Translations" msgstr "à¸à¸²à¸£à¹à¸›à¸¥à¸ à¸²à¸©à¸²" #: ../data/software-center.menu.in.h:41 msgid "Utils" msgstr "Utils" #: ../data/software-center.menu.in.h:42 msgid "Version Control" msgstr "ควบคุมรุ่น" #: ../data/software-center.menu.in.h:43 msgid "Video" msgstr "วิดีโอ" #: ../data/software-center.menu.in.h:44 msgid "Web" msgstr "เว็บ" #: ../data/software-center.menu.in.h:45 msgid "Web servers" msgstr "เว็บเซิร์ฟเวอร์" #: ../data/software-center.menu.in.h:46 msgid "X11" msgstr "X11" #: ../data/software-center.menu.in.h:47 msgid "libdevel" msgstr "libdevel" #: ../data/software-center.menu.in.h:48 msgid "localization" msgstr "localization" #: ../data/software-center.menu.in.h:49 msgid "metapackages" msgstr "metapackages" #: ../data/software-center.menu.in.h:50 msgid "misc" msgstr "ทั่วไป" #: ../data/software-center.menu.in.h:51 msgid "oldlibs" msgstr "oldlibs" #: ../data/software-center.menu.in.h:52 msgid "otherosfs" msgstr "otherosfs" #: ../softwarecenter/app.py:127 msgid "Sorry, can not open the software database" msgstr "ขออภัย เปิดà¸à¸²à¸™à¸‚้อมูลซอฟต์à¹à¸§à¸£à¹Œà¹„ม่ได้" #: ../softwarecenter/app.py:128 msgid "Please re-install the 'software-center' package." msgstr "โปรดติดตั้งà¹à¸žà¸à¹€à¸à¸ˆ 'software-center' ซ้ำ" #: ../softwarecenter/app.py:415 msgid "ERROR" msgstr "ผิดพลาด" #: ../softwarecenter/backend/aptd.py:127 msgid "Error" msgstr "ผิดพลาด" #. TRANSLATORS: List of "grey-listed" words sperated with ";" #. Do not translate this list directly. Instead, #. provide a list of words in your language that people are likely #. to include in a search but that should normally be ignored in #. the search. #: ../softwarecenter/db/database.py:58 msgid "app;application;package;program;programme;suite;tool" msgstr "app;application;package;program;programme;suite;tool" #: ../softwarecenter/distro/Ubuntu.py:38 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "เพื่อถอดถอน %s รายà¸à¸²à¸£à¸™à¸µà¹‰à¸•้องถูà¸à¸–อดถอนไปด้วย:" #: ../softwarecenter/distro/Ubuntu.py:40 msgid "Remove All" msgstr "ถอดถอนทั้งหมด" #: ../softwarecenter/distro/Ubuntu.py:47 #, python-format msgid "If you uninstall %s, future updates will not include new items in %s set. Are you sure you want to continue?" msgstr "ถ้าคุณถอดถอน %s ครั้งถัดไปที่คุณอัปเดท จะไม่มีà¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทชุด %s รวมอยู่ด้วย คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸­à¹„ม่ที่จะดำเนินà¸à¸²à¸£à¸•่อ?" #: ../softwarecenter/distro/Ubuntu.py:50 #: ../softwarecenter/distro/Ubuntu.py:61 msgid "Remove Anyway" msgstr "ถอดถอนโดยไม่สนใจคำเตือน" #: ../softwarecenter/distro/Ubuntu.py:57 #, python-format msgid "%s is a core application in Ubuntu. Uninstalling it may cause future upgrades to be incomplete. Are you sure you want to continue?" msgstr "%s เป็นโปรà¹à¸à¸£à¸¡à¸«à¸¥à¸±à¸à¹ƒà¸™ Ubuntu à¸à¸²à¸£à¸–อดถอนมัน อาจทำให้à¸à¸²à¸£à¸›à¸£à¸±à¸šà¸£à¸¸à¹ˆà¸™à¸„รั้งหน้าไม่สมบูรณ์ คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸­à¹„ม่ที่จะดำเนินà¸à¸²à¸£à¸•่อ?" #. generic message #: ../softwarecenter/distro/Ubuntu.py:70 #, python-format msgid "%s is installed on this computer." msgstr "%s ได้ติดตั้งลงในคอมพิวเตอร์อยู่à¹à¸¥à¹‰à¸§" #: ../softwarecenter/distro/Ubuntu.py:78 #, python-format msgid "It is used by %s installed software package." msgid_plural "It is used by %s installed software packages." msgstr[0] "มันถูà¸à¹ƒà¸Šà¹‰à¹‚ดยà¹à¸žà¸à¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" msgstr[1] "มันถูà¸à¹ƒà¸Šà¹‰à¹‚ดยà¹à¸žà¸à¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" #: ../softwarecenter/distro/Ubuntu.py:84 #, python-format msgid "It is recommended by %s installed software package." msgid_plural "It is recommended by %s installed software packages." msgstr[0] "มันถูà¸à¹à¸™à¸°à¸™à¸³à¹‚ดยà¹à¸žà¸ˆà¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" msgstr[1] "มันถูà¸à¹à¸™à¸°à¸™à¸³à¹‚ดยà¹à¸žà¸ˆà¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" #: ../softwarecenter/distro/Ubuntu.py:90 #, python-format msgid "It is suggested by %s installed software package." msgid_plural "It is suggested by %s installed software packages." msgstr[0] "มันถูà¸à¹€à¸ªà¸™à¸­à¹‚ดยà¹à¸žà¸ˆà¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" msgstr[1] "มันถูà¸à¹€à¸ªà¸™à¸­à¹‚ดยà¹à¸žà¸ˆà¹€à¸à¸ˆ %s ที่ได้ติดตั้งอยู่" #: ../softwarecenter/distro/Ubuntu.py:103 msgid "Unknown" msgstr "ไม่ทราบ" #: ../softwarecenter/distro/Ubuntu.py:105 msgid "Open Source" msgstr "โอเพนซอร์ซ" #: ../softwarecenter/distro/Ubuntu.py:107 msgid "Proprietary" msgstr "เชิงพานิช" #: ../softwarecenter/distro/Ubuntu.py:108 #, python-format msgid "License: %s" msgstr "สัà¸à¸à¸²à¸­à¸™à¸¸à¸à¸²à¸•: %s" #: ../softwarecenter/distro/Ubuntu.py:140 #: ../softwarecenter/distro/Ubuntu.py:152 #, python-format msgid "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be available in a newer version of Ubuntu." msgstr "Canonical ไม่ได้เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทในระยะยาวสำหรับ %s ใน Ubuntu %s à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทอาจมีอีà¸à¸„รั้งใน Ubuntu รุ่นที่ใหม่à¸à¸§à¹ˆà¸²" #: ../softwarecenter/distro/Ubuntu.py:145 #, python-format msgid "Canonical provides critical updates for %(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "Canonical เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทในส่วนที่สำคัà¸à¸ªà¸³à¸«à¸£à¸±à¸š %(appname)s ไปจนถึง %(support_end_month_str)s %(support_end_year)s" #: ../softwarecenter/distro/Ubuntu.py:157 #, python-format msgid "Canonical provides critical updates supplied by the developers of %(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "Canonical เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทที่สำคัภโดยนัà¸à¸žà¸±à¸’นาของ %(appname)s ไปจนถึง %(support_end_month_str)s %(support_end_year)s" #: ../softwarecenter/distro/Ubuntu.py:167 #, python-format msgid "Canonical does not provide updates for %s. Some updates may be provided by the third party vendor." msgstr "Canonical ไม่ได้เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทสำหรับ %s à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทอาจได้รับà¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹‚ดยผู้ประà¸à¸­à¸šà¸à¸²à¸£à¸­à¸·à¹ˆà¸™" #: ../softwarecenter/distro/Ubuntu.py:171 #, python-format msgid "Canonical provides critical updates for %s." msgstr "Canonical เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทที่สำคัภสำหรับ %s" #: ../softwarecenter/distro/Ubuntu.py:173 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "Canonical เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทที่สำคัภโดยนัà¸à¸žà¸±à¸’นาของ %s" #: ../softwarecenter/distro/Ubuntu.py:176 #, python-format msgid "Canonical does not provide updates for %s. Some updates may be provided by the Ubuntu community." msgstr "Canonical ไม่ได้เป็นผู้ให้à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ท %s à¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ทอาจได้รับà¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹‚ดยà¸à¸¥à¸¸à¹ˆà¸¡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ Ubuntu" #: ../softwarecenter/distro/Ubuntu.py:179 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "ไม่ทราบสถานะà¸à¸²à¸£à¸”ูà¹à¸¥à¸‚องโปรà¹à¸à¸£à¸¡ %s" #: ../softwarecenter/view/appdetailsview.py:82 msgid "Description" msgstr "คำอธิบาย" #: ../softwarecenter/view/appdetailsview.py:186 #, python-format msgid "This software is available from the '%s' source, which you are not currently using." msgstr "ซอฟต์à¹à¸§à¸£à¹Œà¸™à¸µà¹‰à¸ªà¸²à¸¡à¸²à¸£à¸–ติดตั้งได้จาà¸à¹à¸«à¸¥à¹ˆà¸‡ '%s' ซึ่งเป็นà¹à¸«à¸¥à¹ˆà¸‡à¸—ี่คุณยังไม่ได้เปิดใช้" #: ../softwarecenter/view/appdetailsview.py:191 msgid "To show information about this item, the software catalog needs updating." msgstr "เพื่อà¹à¸ªà¸”งข้อมูลเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸£à¸²à¸¢à¸à¸²à¸£à¸™à¸µà¹‰ ต้องอัปเดทรายà¸à¸²à¸£à¸šà¸±à¸à¸Šà¸µà¸‹à¸­à¸Ÿà¸•์à¹à¸§à¸£à¹Œà¸à¹ˆà¸­à¸™" #: ../softwarecenter/view/appdetailsview.py:194 #, python-format msgid "Sorry, '%s' is not available for this type of computer (%s)." msgstr "ขออภัย '%s' ไม่รองรับคอมพิวเตอร์ประเภทนี้ (%s)" #: ../softwarecenter/view/appdetailsview.py:253 #: ../softwarecenter/view/appdetailsview.py:257 msgid "Application Screenshot" msgstr "ภาพหน้าจอโปรà¹à¸à¸£à¸¡" #: ../softwarecenter/view/appdetailsview.py:283 #, python-format msgid "Version: %s (%s)" msgstr "รุ่น: %s (%s)" #: ../softwarecenter/view/appdetailsview.py:298 msgid "Website" msgstr "เว็บไซต์" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:304 msgid "Free" msgstr "ฟรี" #: ../softwarecenter/view/appdetailsview.py:305 #, python-format msgid "Price: %s" msgstr "ราคา: %s" #: ../softwarecenter/view/appdetailsview.py:350 #, python-format msgid "%s - Screenshot" msgstr "%s - ภาพหน้าจอ" #: ../softwarecenter/view/appdetailsview.py:462 msgid "Remove" msgstr "ถอดถอน" #: ../softwarecenter/view/appdetailsview.py:465 msgid "Install" msgstr "ติดตั้ง" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:475 msgid "Use This Source" msgstr "ใช้à¹à¸«à¸¥à¹ˆà¸‡à¸™à¸µà¹‰" #: ../softwarecenter/view/appdetailsview.py:478 msgid "Update Now" msgstr "อัปเดทเดี๋ยวนี้" #. home button #: ../softwarecenter/view/availablepane.py:104 msgid "Get Free Software" msgstr "รับซอฟต์à¹à¸§à¸£à¹Œà¹€à¸ªà¸£à¸µ" #: ../softwarecenter/view/availablepane.py:202 msgid "Search" msgstr "ค้นหา" #: ../softwarecenter/view/availablepane.py:224 #: ../softwarecenter/view/installedpane.py:121 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "%s รายà¸à¸²à¸£à¸—ี่เข้าà¸à¸±à¸™" msgstr[1] "%s รายà¸à¸²à¸£à¸—ี่เข้าà¸à¸±à¸™" #: ../softwarecenter/view/availablepane.py:228 #, python-format msgid "%s application available" msgid_plural "%s applications available" msgstr[0] "%s โปรà¹à¸à¸£à¸¡à¸—ี่ติดตั้งได้" msgstr[1] "%s โปรà¹à¸à¸£à¸¡à¸—ี่ติดตั้งได้" #: ../softwarecenter/view/catview.py:77 #: ../softwarecenter/view/catview.py:82 msgid "Departments" msgstr "à¸à¹ˆà¸²à¸¢" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "รายละเอียด" #: ../softwarecenter/view/dialogs.py:83 msgid "Cancel" msgstr "ยà¸à¹€à¸¥à¸´à¸" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:91 msgid "Dependency" msgstr "สิ่งที่ต้องใช้" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:176 msgid "Installed Software" msgstr "ซอฟต์à¹à¸§à¸£à¹Œà¸—ี่ติดตั้งà¹à¸¥à¹‰à¸§" #: ../softwarecenter/view/installedpane.py:125 #, python-format msgid "%s application installed" msgid_plural "%s applications installed" msgstr[0] "%s โปรà¹à¸à¸£à¸¡à¸—ี่ติดตั้งà¹à¸¥à¹‰à¸§" msgstr[1] "%s โปรà¹à¸à¸£à¸¡à¸—ี่ติดตั้งà¹à¸¥à¹‰à¸§" #: ../softwarecenter/view/viewswitcher.py:165 msgid "Get Software" msgstr "รับซอฟต์à¹à¸§à¸£à¹Œ" #: ../softwarecenter/view/viewswitcher.py:173 msgid "Free Software" msgstr "ซอฟต์à¹à¸§à¸£à¹Œà¹€à¸ªà¸£à¸µ" #: ../softwarecenter/view/viewswitcher.py:190 #: ../softwarecenter/view/viewswitcher.py:195 #, python-format msgid "In Progress (%i)" msgstr "à¸à¸³à¸¥à¸±à¸‡à¸”ำเนินà¸à¸²à¸£ (%i)" software-center-13.10/po/pt.po0000664000202700020270000001617712151440100016453 0ustar dobeydobey00000000000000# Portuguese translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-18 14:26+0200\n" "PO-Revision-Date: 2009-09-09 09:56+0000\n" "Last-Translator: Michael Vogt \n" "Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2009-09-23 11:13+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarestore/app.py:339 ../softwarestore/view/appdetailsview.py:371 msgid "ERROR" msgstr "ERRO" #: ../softwarestore/app.py:398 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "" msgstr[1] "" #: ../softwarestore/apt/aptcache.py:174 ../softwarestore/apt/aptcache.py:186 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" #: ../softwarestore/apt/aptcache.py:179 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:191 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" #: ../softwarestore/apt/aptcache.py:201 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" #: ../softwarestore/apt/aptcache.py:205 #, python-format msgid "Canonical provides critical updates for %s." msgstr "" #: ../softwarestore/apt/aptcache.py:207 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "" #: ../softwarestore/apt/aptcache.py:210 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" #: ../softwarestore/apt/aptcache.py:213 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "" #: ../softwarestore/view/appdetailsview.py:92 msgid "Description" msgstr "Descrição" #: ../softwarestore/view/appdetailsview.py:176 #: ../softwarestore/view/appdetailsview.py:181 msgid "Not available in the current data" msgstr "" #: ../softwarestore/view/appdetailsview.py:179 msgid "Not available for your hardware architecture." msgstr "" #: ../softwarestore/view/appdetailsview.py:235 #, python-format msgid "Version: %s (%s)" msgstr "Versão: %s (%s)" #. generic message #: ../softwarestore/view/appdetailsview.py:253 #, python-format msgid "%s is installed on this computer." msgstr "%s está instalado no seu computador." #: ../softwarestore/view/appdetailsview.py:265 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "" msgstr[1] "" #: ../softwarestore/view/appdetailsview.py:270 msgid "Website" msgstr "" #: ../softwarestore/view/appdetailsview.py:273 #, python-format msgid "Price: %s" msgstr "Preço: %s" #: ../softwarestore/view/appdetailsview.py:273 msgid "Free" msgstr "" #: ../softwarestore/view/appdetailsview.py:313 #, python-format msgid "%s - Screenshot" msgstr "" #. generic removal text #: ../softwarestore/view/appdetailsview.py:329 #, python-format msgid "%s depends on other software on the system. " msgstr "" #: ../softwarestore/view/appdetailsview.py:330 msgid "" "Uninstalling it means that the following additional software needs to be " "removed." msgstr "" #: ../softwarestore/view/appdetailsview.py:335 #, python-format msgid "%s is a core component" msgstr "" #: ../softwarestore/view/appdetailsview.py:336 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" #: ../softwarestore/view/appdetailsview.py:394 msgid "Upgrade" msgstr "" #: ../softwarestore/view/appdetailsview.py:397 #: ../softwarestore/view/dialogs.py:69 msgid "Remove" msgstr "Remover" #: ../softwarestore/view/appdetailsview.py:400 msgid "Install" msgstr "Instalar" #. FIXME: deal with the EULA stuff #: ../softwarestore/view/appdetailsview.py:409 msgid "Enable channel" msgstr "" #. home button #: ../softwarestore/view/availablepane.py:101 #: ../softwarestore/view/viewswitcher.py:118 msgid "Get Free Software" msgstr "" #: ../softwarestore/view/dialogs.py:52 msgid "Details" msgstr "" #: ../softwarestore/view/dialogs.py:68 msgid "Cancel" msgstr "" #: ../softwarestore/view/dialogs.py:75 msgid "Dependencies" msgstr "" #: ../softwarestore/view/installedpane.py:98 #: ../softwarestore/view/viewswitcher.py:120 msgid "Installed Software" msgstr "" #: ../softwarestore/view/viewswitcher.py:150 #, python-format msgid "In Progress (%i)" msgstr "" #: ../softwarestore/view/viewswitcher.py:155 #, python-format msgid "Pending (%i)" msgstr "" #: ../data/ui/GBTestWidget.ui.h:1 msgid "Click me" msgstr "" #: ../data/ui/GBTestWidget.ui.h:2 msgid "button" msgstr "" #: ../data/ui/GBTestWidget.ui.h:3 msgid "label" msgstr "" #: ../data/ui/GBTestWidget.ui.h:4 msgid "page 1" msgstr "" #: ../data/ui/GBTestWidget.ui.h:5 msgid "page 2" msgstr "" #: ../data/ui/GBTestWidget.ui.h:6 msgid "page 3" msgstr "" #: ../data/ui/SoftwareStore.ui.h:1 msgid "©2009 Canonical" msgstr "" #: ../data/ui/SoftwareStore.ui.h:2 msgid "All Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:3 msgid "Canonical-Maintained Applications" msgstr "" #: ../data/ui/SoftwareStore.ui.h:4 msgid "Copy _Web Link" msgstr "" #: ../data/ui/SoftwareStore.ui.h:5 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "" #: ../data/ui/SoftwareStore.ui.h:6 msgid "Search..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:7 msgid "Software Store _Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:8 #: ../data/ubuntu-software-store.desktop.in.h:3 msgid "Ubuntu Software Store" msgstr "" #: ../data/ui/SoftwareStore.ui.h:9 msgid "View" msgstr "" #: ../data/ui/SoftwareStore.ui.h:10 msgid "_Edit" msgstr "" #: ../data/ui/SoftwareStore.ui.h:11 msgid "_File" msgstr "" #: ../data/ui/SoftwareStore.ui.h:12 msgid "_Help" msgstr "" #: ../data/ui/SoftwareStore.ui.h:13 msgid "_Software Sources..." msgstr "" #: ../data/ui/SoftwareStore.ui.h:14 msgid "available" msgstr "" #: ../data/ui/SoftwareStore.ui.h:15 msgid "installed" msgstr "" #: ../data/ui/SoftwareStore.ui.h:16 msgid "pending" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "" #: ../data/ubuntu-software-store.desktop.in.h:2 msgid "Software Store" msgstr "" #, python-format #~ msgid "%s items available" #~ msgstr "%s itens disponíveis" #~ msgid "Install and remove software" #~ msgstr "Instalar e remover software" #~ msgid "Installed software" #~ msgstr "Software instalado" software-center-13.10/po/ar.po0000664000202700020270000003235412151440100016425 0ustar dobeydobey00000000000000# Arabic translation for software-store # Copyright (c) 2009 Rosetta Contributors and Canonical Ltd 2009 # This file is distributed under the same license as the software-store package. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: software-store\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-09-25 17:10+0200\n" "PO-Revision-Date: 2009-09-28 17:55+0000\n" "Last-Translator: Khaled Hosny \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n % 100 >= " "3 && n % 100 <= 10 ? 3 : n % 100 >= 11 && n % 100 <= 99 ? 4 : 5;\n" "X-Launchpad-Export-Date: 2009-09-30 06:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../softwarecenter/app.py:349 ../softwarecenter/view/appdetailsview.py:404 msgid "ERROR" msgstr "عطل" #: ../softwarecenter/apt/aptcache.py:173 ../softwarecenter/apt/aptcache.py:185 #, python-format msgid "" "Canonical does no longer provide updates for %s in Ubuntu %s. Updates may be " "available in a newer version of Ubuntu." msgstr "" "لم تعد كانونيكال تÙÙˆÙÙØ± تحديثات Ù„ %s ÙÙŠ أوبونتو %sØŒ لكن يحتمل ØªÙˆÙØ± التحديثات " "ÙÙŠ إصدار أحدث من أوبونتو." #: ../softwarecenter/apt/aptcache.py:178 #, python-format msgid "" "Canonical provides critical updates for %(appname)s until " "%(support_end_month_str)s %(support_end_year)s." msgstr "" "تÙÙˆÙÙØ± كانونيكال التحديثات المهمة لـ %(appname)s حتى " "%(support_end_month_str)s â€%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:190 #, python-format msgid "" "Canonical provides critical updates supplied by the developers of " "%(appname)s until %(support_end_month_str)s %(support_end_year)s." msgstr "" "تÙÙˆÙÙØ± كانونيكال التحديثات المهمة من مطوري %(appname)s حتى " "%(support_end_month_str)s â€%(support_end_year)s." #: ../softwarecenter/apt/aptcache.py:200 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the third party vendor." msgstr "" "لا ØªÙˆÙØ± كانونيكال تحديثات Ù„%s. قد تكون بعض التحديثات متاحة من Ù…ÙÙ†ØªÙØ¬ آخر." #: ../softwarecenter/apt/aptcache.py:204 #, python-format msgid "Canonical provides critical updates for %s." msgstr "ØªÙˆÙØ± كانونيكال التحديثات المهمة Ù„%s." #: ../softwarecenter/apt/aptcache.py:206 #, python-format msgid "Canonical provides critical updates supplied by the developers of %s." msgstr "ØªÙˆÙØ± كانونيكال التحديثات المهمة التي ÙŠÙˆÙØ±Ù‡Ø§ مطوري %s." #: ../softwarecenter/apt/aptcache.py:209 #, python-format msgid "" "Canonical does not provide updates for %s. Some updates may be provided by " "the Ubuntu community." msgstr "" "لا ØªÙˆÙØ± كانونيكال تحديثات Ù„%s. قد تأتي بعض التحديثات من مجتمع أوبونتو." #: ../softwarecenter/apt/aptcache.py:212 #, python-format msgid "Application %s has a unkown maintenance status." msgstr "حالة صيانة التطبيق %s غير Ù…Ø¹Ø±ÙˆÙØ©." #: ../softwarecenter/view/appdetailsview.py:89 msgid "Description" msgstr "الوصÙ" #: ../softwarecenter/view/appdetailsview.py:181 #: ../softwarecenter/view/appdetailsview.py:186 msgid "Not available in the current data" msgstr "غير Ù…ØªÙˆÙØ± ÙÙŠ البيانات الحالية" #: ../softwarecenter/view/appdetailsview.py:184 msgid "Not available for your hardware architecture." msgstr "غير Ù…ØªÙˆÙØ± لبÙنية عتادك." #: ../softwarecenter/view/appdetailsview.py:211 #: ../softwarecenter/view/appdetailsview.py:215 msgid "Application Screenshot" msgstr "لقطة شاشة من التطبيق" #: ../softwarecenter/view/appdetailsview.py:239 #, python-format msgid "Version: %s (%s)" msgstr "الإصدارة: %s (%s)" #. generic message #: ../softwarecenter/view/appdetailsview.py:257 #, python-format msgid "%s is installed on this computer." msgstr "â€%s مثبت على هذا الحاسوب." #: ../softwarecenter/view/appdetailsview.py:269 #, python-format msgid "It is used by %s piece of installed software." msgid_plural "It is used by %s pieces of installed software." msgstr[0] "لا تستخدمه أي من البرمجيات المثبّتة." msgstr[1] "تستخدمه واحدة من البرمجيات المثبّتة." msgstr[2] "تستخدمه اثنتان من البرمجيات المثبّتة." msgstr[3] "تستخدمه %s من البرمجيات المثبّتة." msgstr[4] "تستخدمه %s من البرمجيات المثبّتة." msgstr[5] "تستخدمه %s من البرمجيات المثبّتة." #: ../softwarecenter/view/appdetailsview.py:274 msgid "Website" msgstr "الموقع" #: ../softwarecenter/view/appdetailsview.py:277 msgid "Unknown" msgstr "مجهول" #: ../softwarecenter/view/appdetailsview.py:279 msgid "Open Source" msgstr "Ù…ÙØªÙˆØ­ المصدر" #: ../softwarecenter/view/appdetailsview.py:281 msgid "Proprietary" msgstr "محتكر" #: ../softwarecenter/view/appdetailsview.py:282 #, python-format msgid "License: %s" msgstr "الترخيص: %s" #. TRANSLATORS: This text will be showed as price of the software #: ../softwarecenter/view/appdetailsview.py:286 msgid "Free" msgstr "حرّ" #: ../softwarecenter/view/appdetailsview.py:287 #, python-format msgid "Price: %s" msgstr "السعر: %s" #: ../softwarecenter/view/appdetailsview.py:327 #, python-format msgid "%s - Screenshot" msgstr "â€%s - لقطة شاشة" #. generic removal text #. FIXME: this text is not accurate, we look at recommends as #. well as part of the rdepends, but those do not need to #. be removed, they just may be limited in functionatlity #: ../softwarecenter/view/appdetailsview.py:346 #, python-format msgid "To remove %s, these items must be removed as well:" msgstr "لإزالة %sØŒ يجب أيضا إزالة العناصر التالية:" #: ../softwarecenter/view/appdetailsview.py:348 msgid "Remove All" msgstr "أزل الكل" #: ../softwarecenter/view/appdetailsview.py:354 #, python-format msgid "" "If you uninstall %s, future updates will not include new items in %s " "set. Are you sure you want to continue?" msgstr "" "إذا أزلت %s Ùلن ØªÙØ¶Ù…ّن العناصر الجديدة من طقم %s ÙÙŠ التحديثات " "المستقبلية. أمتأكد أنك تريد الاستمرار؟" #: ../softwarecenter/view/appdetailsview.py:357 #: ../softwarecenter/view/appdetailsview.py:368 msgid "Remove Anyway" msgstr "أزل على كل حال" #: ../softwarecenter/view/appdetailsview.py:364 #, python-format msgid "" "%s is a core application in Ubuntu. Uninstalling it may cause future " "upgrades to be incomplete. Are you sure you want to continue?" msgstr "" "â€%s مكون أساسي ÙÙŠ أوبونتو. قد تتسبب إزالته ÙÙŠ أن تكون الترقيات المستقبلية " "ناقصة. أمتأكد أنك تريد المتابعة؟" #: ../softwarecenter/view/appdetailsview.py:427 msgid "Upgrade" msgstr "رقÙÙ‘" #: ../softwarecenter/view/appdetailsview.py:430 msgid "Remove" msgstr "أزل" #: ../softwarecenter/view/appdetailsview.py:433 msgid "Install" msgstr "ثبّت" #. FIXME: deal with the EULA stuff #: ../softwarecenter/view/appdetailsview.py:442 msgid "Enable channel" msgstr "ÙØ¹Ù‘Ù„ القناة" #: ../softwarecenter/view/appdetailsview.py:469 msgid "_Keep" msgstr "أب_Ù‚" #: ../softwarecenter/view/appdetailsview.py:470 msgid "_Replace" msgstr "ا_ستبدل" #: ../softwarecenter/view/appdetailsview.py:472 #, python-format msgid "Configuration file '%s' changed" msgstr "تغيّر مل٠التضبيط '%s'" #: ../softwarecenter/view/appdetailsview.py:473 msgid "Do you want to use the new version?" msgstr "أتريد استخدام الإصدارة الجديدة؟" #. home button #: ../softwarecenter/view/availablepane.py:88 #: ../softwarecenter/view/viewswitcher.py:128 msgid "Get Free Software" msgstr "احصل على البرمجيات الحرة" #: ../softwarecenter/view/availablepane.py:138 #: ../softwarecenter/view/installedpane.py:127 #, python-format msgid "%s matching item" msgid_plural "%s matching items" msgstr[0] "لا عناصر مطابقة" msgstr[1] "عنصر واحد مطابق" msgstr[2] "عنصران مطابقان" msgstr[3] "%s عناصر مطابقة" msgstr[4] "%s عنصرا مطابقا" msgstr[5] "%s عنصر مطابق" #: ../softwarecenter/view/availablepane.py:142 #, python-format msgid "%s item available" msgid_plural "%s items available" msgstr[0] "لا عناصر Ù…ØªÙˆÙØ±Ø©" msgstr[1] "عنصر واحد Ù…ØªÙˆÙØ±" msgstr[2] "عنصران Ù…ØªÙˆÙØ±Ø§Ù†" msgstr[3] "%s عناصر Ù…ØªÙˆÙØ±Ø©" msgstr[4] "%s عنصرا Ù…ØªÙˆÙØ±Ø§" msgstr[5] "%s عنصر Ù…ØªÙˆÙØ±" #: ../softwarecenter/view/catview.py:71 ../softwarecenter/view/catview.py:106 msgid "Departments" msgstr "الأقسام" #: ../softwarecenter/view/dialogs.py:45 msgid "Details" msgstr "Ø§Ù„ØªÙØ§ØµÙŠÙ„" #: ../softwarecenter/view/dialogs.py:80 msgid "Cancel" msgstr "ألغÙ" #. FIXME: make this a generic pkgview widget #: ../softwarecenter/view/dialogs.py:88 msgid "Dependency" msgstr "الاعتمادية" #: ../softwarecenter/view/installedpane.py:74 #: ../softwarecenter/view/viewswitcher.py:130 msgid "Installed Software" msgstr "البرمجيات المثبتة" #: ../softwarecenter/view/installedpane.py:131 #, python-format msgid "%s installed item" msgid_plural "%s installed items" msgstr[0] "لا عناصر مثبتة" msgstr[1] "عنصر واحد مثبت" msgstr[2] "عنصران مثبتان" msgstr[3] "%s عناصر مثبتة" msgstr[4] "%s عنصرا مثبتا" msgstr[5] "%s عنصر مثبت" #: ../softwarecenter/view/viewswitcher.py:160 #: ../softwarecenter/view/viewswitcher.py:165 #, python-format msgid "In Progress (%i)" msgstr "ÙÙŠ تقدم (%i)" #: ../data/ui/SoftwareCenter.ui.h:1 msgid "©2009 Canonical" msgstr "© كانونيكال 2009" #: ../data/ui/SoftwareCenter.ui.h:2 msgid "Copy _Web Link" msgstr "انسخ _رابط Ø§Ù„ÙˆÙØ¨" #: ../data/ui/SoftwareCenter.ui.h:3 msgid "" "Lets you choose from thousands of free applications available for Ubuntu." msgstr "يمكنك أن تختار من بين آلا٠التطبيقات الحرّة Ø§Ù„Ù…ØªÙˆÙØ±Ø© ÙÙŠ أوبونتو." #: ../data/ui/SoftwareCenter.ui.h:4 msgid "Rebuilding application catalog..." msgstr "ÙŠÙØ¹ÙŠØ¯ بناء دليل البرامج..." #: ../data/ui/SoftwareCenter.ui.h:5 msgid "Search..." msgstr "ابحث..." #: ../data/ui/SoftwareCenter.ui.h:6 msgid "Software Center _Help" msgstr "_مساعدة مركز البرمجيات" #: ../data/ui/SoftwareCenter.ui.h:7 #: ../data/ubuntu-software-center.desktop.in.h:3 msgid "Ubuntu Software Center" msgstr "مركز برمجيات أوبونتو" #: ../data/ui/SoftwareCenter.ui.h:8 msgid "_All Applications" msgstr "جميع الت_طبيقات" #: ../data/ui/SoftwareCenter.ui.h:9 msgid "_Canonical-Maintained Applications" msgstr "التطبيقات التي _تديرها كانونيكال" #: ../data/ui/SoftwareCenter.ui.h:10 msgid "_Edit" msgstr "_تحرير" #: ../data/ui/SoftwareCenter.ui.h:11 msgid "_File" msgstr "_ملÙ" #: ../data/ui/SoftwareCenter.ui.h:12 msgid "_Help" msgstr "_مساعدة" #: ../data/ui/SoftwareCenter.ui.h:13 msgid "_Install" msgstr "_ثبّت" #: ../data/ui/SoftwareCenter.ui.h:14 msgid "_Software Sources..." msgstr "_مصادر البرمجيات..." #: ../data/ui/SoftwareCenter.ui.h:15 msgid "_View" msgstr "_عرض" #: ../data/ui/SoftwareCenter.ui.h:16 msgid "available" msgstr "Ù…ØªÙˆÙØ±" #: ../data/ui/SoftwareCenter.ui.h:17 msgid "installed" msgstr "مثبت" #: ../data/ui/SoftwareCenter.ui.h:18 msgid "pending" msgstr "ينتظر" #: ../data/ubuntu-software-center.desktop.in.h:1 msgid "" "Lets you choose from thousands of free applications available for Ubuntu" msgstr "يمكنك أن تختار من بين آلا٠التطبيقات الحرّة Ø§Ù„Ù…ØªÙˆÙØ±Ø© ÙÙŠ أوبونتو" #: ../data/ubuntu-software-center.desktop.in.h:2 msgid "Software Center" msgstr "مركز البرمجيات" #, python-format #~ msgid "Search in %s" #~ msgstr "ابحث ÙÙŠ %s" #~ msgid "Installed software" #~ msgstr "البرمجيات المثبتة" #~ msgid "Get Free software" #~ msgstr "احصل على برمجيات مجانية" #, python-format #~ msgid "%s items available" #~ msgstr "%s عناصر Ù…ØªÙˆÙØ±Ø©" #~ msgid "Install and remove software" #~ msgstr "ثبّت Ùˆ أزل برمجيات" #, python-format #~ msgid "Pending (%i)" #~ msgstr "ÙŠÙ†ØªØ¸ÙØ± (%i)" #~ msgid "Software Store" #~ msgstr "متجر البرمجيات" #~ msgid "Ubuntu Software Store" #~ msgstr "متجر برمجيات أوبونتو" #, python-format #~ msgid "%s is a core component" #~ msgstr "%s هو مكون أساسي" #, python-format #~ msgid "%s depends on other software on the system. " #~ msgstr "%s يعتمد على برمجيات أخرى ÙÙŠ النظام " #~ msgid "" #~ "Uninstalling it means that the following additional software needs to be " #~ "removed." #~ msgstr "إلغاء تثبيته يعني أن البرمجيات الإضاÙية التالية يجب إزالتها." #~ msgid "All" #~ msgstr "الكل" software-center-13.10/setup.cfg0000664000202700020270000000072712151440100016665 0ustar dobeydobey00000000000000[build_i18n] domain=software-center desktop_files=[ ("share/applications", ("data/ubuntu-software-center.desktop.in",) ) ] xml_files=[ ("share/app-install/desktop", ("data/software-center.menu.in", ) ), ("share/app-install/menu.d/", ("data/extra-unity-categories.menu.in", "data/featured.menu.in", "data/whats_new.menu.in", "data/top-rated.menu.in", ) ), ] schemas_files=[] software-center-13.10/TODO0000664000202700020270000000545612151440100015540 0ustar dobeydobey00000000000000 Buy something: - centralize the Install/Remove/Buy/etc/ functionality as a gtk.Action and wire it to 1) the menu, 2) applist button and 3) appdetails button (tremolux) - fetch the metadata as part of the update-software-center-agent-db - do screenshot by constructing screenshot urls from update_sc_agent via entry.screenshot_url by using the archive_id Code: - make the order of "cache, db, datadir" identical accross the various widgets (e.g. AppDetailsView and AppStore use different ordering) Webkit: - xpm images can not be loaded - only simple keyboard nagvigation in the widgets (tab, no cursor keys) - make the description -> html processing better, use code from g-a-i for this Missing functionality compared to g-a-i: - displaying EULA - enable of components (like universe) UI: - if a package is only available for a subset of the architectures, do the same as g-a-i and display that in the details UI (e.g. qlix not available on amd64) - add warning if a package is removed that causes the removal of a unreleated meta-package (that is not ubuntu-desktop) - implement the removal dialog in the way the spec calls for it (different ui design) - implement warning when a package contains of multiple apps and show the apps in this case - CellRendererTextWithActivateArrow needs a mode for right-to-left languages and a button-pressed image - refresh app list when a package gets installed (some packages may no longer be installed/not installed) Database: - to extend to full axi * change set_data() from appname to pkgname * make database.get_xapian_document() work with empty appname Missing: - when the xapian db is rebuilding (e.g. because app-center is upgraded) wait for that (just like with the apt cache) - pimping of apps (via tweaking the popcon values?) Missing (hard): - keep track of all pending transactions in the apt cache and when a transaction finishes. - when a package is marked for install, block its (and the other pkgs that get installed) action button in the app details view. - think more about all the possible cases with manipulating pkgs that are also in a queue of pending transactions and the UI implications that have: e.g.: - A depends on B - A is in the queue for install (but not installed now) - B is installed now - the user clicks on removal of B -> what should the UI say? "removal of B will result in the removal of A that will get " "installed at some point in the future?" -> what implications does cancel of transactions have? we need re-calculate the cache when a cancel happens -> what about transactions that come from outside of software-store Missing but not so important: - searching for codecs (now done by gnome-codec-install) - searching for mime-types (?) - Provide terminal in transactions (?) software-center-13.10/COPYING0000664000202700020270000010437412151440100016102 0ustar dobeydobey00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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, either version 3 of the License, or (at your option) any later version. 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . software-center-13.10/help/0000755000202700020270000000000012224614354016003 5ustar dobeydobey00000000000000software-center-13.10/help/C/0000755000202700020270000000000012224614354016165 5ustar dobeydobey00000000000000software-center-13.10/help/C/figures/0000755000202700020270000000000012224614354017631 5ustar dobeydobey00000000000000software-center-13.10/help/C/figures/placeholder.png0000664000202700020270000003747712151440100022626 0ustar dobeydobey00000000000000‰PNG  IHDR¼ Ü,¬“sRGB®Îé IDATxÚíÝytÓu¾ÿñWÓ¤iR(PÖYT@M@P@PÆeÔë¹êñê\=³è\g®ç¨WÇ9wîœY®×qœ¹n3Îâè,.à ²"Ê"›+[‘ ¥…Ò…&iš4ùýá/™¬m’.|ûéóqçM¿Í7ùæ›o“w^ùäóÍ ‡Ãa†²± @à Ðð4¼ /@à Ðð€† áhx^ +å?ðÀäºr8Žþ?//½ œ‘¿C*•J¥RM¬ÑgÚs} ‡Ã Ñe‡Cyyy4¾Y6(yyyFרû …d³Ùº´¦ÚžÎx_Üω×{9—˽|ªõ"Ë#Û{}±×ûûT×—îr‰·ïr¹äñxäv»åõzåv»“~Ž­.—+îr©ÖOuùÄšîúR-O÷säòGEEET*µ ª×ëUqq±l6›†ªââb 0@9õ™ö\ªÆÆF8qBÇŽ‹>‘ 8PápXÅÅÅ]ÒðfÚ„fšÆ%6,---ц%Ql3“XÛJp%©¥¥¥Õ 3nÔ³ý¨;]s—É›ƒT1dÚ§{ÜZ[¯­f8Ýö¤{Üc?hk›[{,[»/©åÖîkd{ZÛ·‰·“î2™\G&Û•nß¶¶3ù8*öÿ­½ÑHu™|ìù—ê˜IÜÏé®7ñï½µÛKÜ©þVRG™»éîªý–Ézé¶¶+Ýí¥ÛOm]>ÓýyþM·=©n'ñþ¤{ŽŽ]¯­¿÷ÄÇ7ÕãM¥R;¯FÂÔ²²2M˜0A’4xð`ÙíöÎmx#ÚšštäÈ}ñŪ««Sß¾}äv»år¹rî¾³ip3mBS5_­=¹Ç>ÙfÒôdº­é^ RÝtÉc¦Í]º¾ÖöOâõ$&‹©ÑTדê…*ñ&1MLPÛzüß ¤KbSÝNªõZÛÿ‘Ç­­ëM·ý©~ßÚvµ¶½‰Énk/èm½À§Û™î·tÇGªß'^ObÝÚå“ç`0˜ñí¦ª±×—înâåòóó’†s$ÖÄD=Õõ·¶~k÷/UrŸîz3¹|âöerùÈåbïgâö&^OªË§Ú_©¶?’зµ¿bkäò±Ÿd²¿3½þL†ßP©Ôö×ÈóƒÝnW^^žŽ?.I4hŠ‹‹Õ«W¯¬ûÌœÞúúz?~\GŽQcc£¼^¯ 5pà@õíÛ·KÝÈ u.ë§z7‘˜´•êe“`f’À¦J¢²IZ3IC¡PÊ1ñ¾Ä&Ï‘¡,Ù$§©öaºÔ7Ý}J×øÇ&á­½‰‰½žL“´tovÚSsIæÚJ$[KóÚJ8Óý¿­0Ó¤5q¿gòxgòf4ÝãšíþÎäñMu´¶^kÇMª¤0ÛË·ö÷‘í ToäÚz£–øÆ%ÕÛTo@Úz–êù§µOÏߘ¥»|sss»öGº7¶@€†„J킆7ò÷ …tøða 0@GŽÑСC;ÿKkápXÍÍÍ ‡Ãòz½jnnŽŽóz½Ñßwd¹žØÆ6›oíE¾H—ª9¬[[K=—'®ùâï[k¶/×Z»míIxc¯?ö / / / / / /•z*käï2ÄõG¡PH>ŸO½{÷îÜ„7//Ov»=®Ùmii‘ßïWss³¼^o4~îhéšÊHSe³ÙRÖÈz6›-iýt_¢ÊÏÏþœ*AÎdV‚ÄËE®«-ùùùÑË[a oAAcxÃË^Æð2†—1¼Tj—Ô`0(Ç£úúúh˜ÔÐÐÐ5_Z ‡Ã ƒÑ&(Ò”E𹂂ƒÁ6‡tFÜZm+¥ ‡ÃÑF3±&6ª­5×± 2³40K³40K³40K•JͼºÝnUUUéĉÚºuktHƒÃá$¹ÝîèHƒ.Ix›››SŽíŒ¼ØXyZ²tòmœsiŠÓ5؉5×wI™^¯ÝnOY™‡7·©î˜‡—yx™‡—yx©TjnÕï÷Ëçóéí·ßVQQ‘ªªª …¢}¦Çãéú„7v˜@¤veÂÛeÚ`›Rc›ýSQM—í‰L"se··FÞÜvtmï€È“awý8¯°°°]Õårµ«ºÝn>V¥R©§¤¶´´èرc_Ê„×çó‘ð k–Kx].×—+'Œáu¹\$¼Èš%^IqgZ“D €œX2á ‡ÃqgZ ‡Ã$¼È /ŒféYbÇð2KrÁ, 0šeÞüüü¸J €\XöLkÁ`0®r¦5äÂÒ o¤—HxK'¼¡Pˆ„íbÉ„7ÒyÇV^äÂ’³4D’ÝØÊ, È…%Ï´–——'»ÝW9ÓraÙyxgi á@.,›ðÆÎÃK €\Yz oìl $¼ÈóðÀh–Jx‡|>Ÿ$Å%¼’äóùäp8HxK%¼@ .á­……… $¼ÈŠ¥^»Ýw¦µØÚÔÔ¢ ȦϴTÂët:SÎÒàt:Ix5ËáõûýÑÎ;¶úý~Æð k–Kx $%á-(( á@Ö,›ð&Žá%á@.,9†7ÇÍÃ‡à €œX.ámjj’”<oSS /²Æ<¼0š%Þtóð’ð [$¼0šå^ŸÏí¼C¡P´óöù|$¼Èšå^—Ëí¼c«Ëå"á@Ö,›ð&Žá%á@.,ðJ"á@»X2á …BÑùwƒÁ B¡ /rb©„·¹¹Y………)Þœ;oô\–Jx âÞÈÞH›kç €žËÒ ol%á@.,—ðFδ–8oSS /²f¹„×étJR\Â+IN§“„Y³\Âë÷ûãÆîFÆòúý~^dÍ’ o^^žìv{\%á@.,™ð†B¡èI&€B¡ /rbÙ„7q^^äÂ’³4¤š‡—Y æá€Ñ,•ð:ù|¾”³4ø|>9^dÅR o Ëå’”<¯ËåŠ~‘ ȦÏ$ေ,—ðFÆð&ÎÃ[XXH €¬Y.ámjjJ™ð655‘ð k–MxSÍÒ@ €lYr oªyxà €\Xr–†TgZc–ä‚„F³ä™Ö¤äyx9Óra©„·   :oKK‹B¡P´óöù|9wÞè¹,—ðFδf³Ù”——'›íË«q¹\$¼Èšå^¯×+Iq ¯$y½^^dÍr ¯Ûíþrå„„×ív“ð k–Kxc;ïØÚžÎ=—åÞØÎ;¶¶§ó@ÏeÉ„72ol%á@.,9†7UÂË^äÂrgZóz½ÑÙb«×ëåLkÈšeÞØYHx+KÎÇãÆð†Ãaæá@N,›ðÚíö¸J €\X2áMv#I/ /rÁ, 0š%giˆ$»±•Y K%¼@ .á•—ð^dŲóð¦ÃK €lY.áu¹\’$»ÝW]. /²fÙ1¼@@¡P(Úä’ð –MxcgiHxKÏÒx¦5^dËÒ³4$ÎÃK €lY6átÜ‘J €\Xr oª„—1¼ÈóðÀhœi F³\Âëñx …âÆð†B!y<^dͲ ¯Íöåê6›„9c /ŒÆ^ÍÒgZ‹­$¼È…%^Iq ¯$^ä„3­Àhœi F³ì, ‰óð’ð $¼0šå^ŸÏ‡·¥¥%:¯Ïç#á@Ö,—ðF;o›Íí¼ Ix5K%¼v»]>Ÿ/Úü†B¡h“ëóùd·ÛIxKŽá•7†WcxKŽá•7Kƒ$Æð '–Nxc+ /raÙyxƒÁ Âápt–æá@.,;oä j‘J €\Xr ol²Izà €\XúLk±óð’ð –Kx=OtþÝ–––è|¼‡„Y³\Â[TT$I²Ûí²Ùl²Ûí’¤¢¢"^dÍ’ ¯¤¸„W /rB £Y2áM5K /ra©„·¹¹Yn·[’¢wäLkn·;çÎ=—¥Þ‚‚y½Þhç …¢·×ë͹ó@Ïe©„×ï÷ÇÍÃ[].—ü~? /²b©„×étÊçóEçá­>ŸON§“„Y±lÂ;K /re¹„×ëõ¦<Óš×ë%á@Ö,—ðºÝî” ¯Ûí&á@Ö,™ð¦š‡—„¹°dÂ+%Ÿi„¹°dÂ+IÁ`P--- ƒ’D €œX6áÍÏÏ—Íf‹;Ó /²e¹3­y<žh²[ÛÓy ç²TÂÛÜÜ,·ÛMvc«Ûíιó@ÏeÉ„72;C$á …B$¼È‰%ÞHçIx#óð’ð [–KxSÎÃÛØØH €¬Y.áíÕ«—$E;îÈ, ½zõ"á@ÖHx`4Ë&¼‰gZ#á@.Hx`4Ë%¼±wlmOç €ž‹yx`4K'¼Õy ç²dÂÛÑ7z.^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„F#á ùCŽýwóÍ7³SÀ1›ÂÍ7ßœtÛÌG ãßd“ðÀX$¼@'¼‹lëŸÓéÔ AƒtÆghîܹúÞ÷¾§çž{NõõõìÀxŒ\zé¥í¾ÎK/½”êàô™öÎê¼y"Gw×ÜܬcÇŽéØ±cÚ·oŸÖ¯_/Ir»ÝºþúëuÏ=÷hôèÑì(:@gö™$¼@–¼^¯žzê){î¹züñÇÙ!tÆðäñxtÛm·ég?û;€vêÌ>3«! $¼0Õ¬Y³ô›ßü&nYcc£êëëõñÇëÝwßÕŠ+ä÷û“Öýþ÷¿¯ñãÇwÈOz*Æð¬W¯^?~|Êß-Z´HwÞy§Nœ8¡ïÿûzê©§’þ.þã?þC‹-⸠GŒá, ¤¤DO>ù¤î½÷Þ¤ßíܹS«V­b'#Æðòàƒ¦LƒixÈóðb³Ùtûí·'-ß°aC§ßvmm­ž}öYÝqÇš:uªÊÊÊäv»UXX¨Aƒéì³ÏÖõ×_¯_þò—:~üx‡ÝîŽ;ôßÿýßZ°`ÆŒ£ââbhàÀ:ÿüóuÓM7é‰'žPEEENOp¯¿þº¾ñoèœsÎQ¯^½ät:uÚi§iæÌ™zðÁõé§Ÿr॰{÷nÝwß}ºè¢‹4tèP9NhÈ!š7ožzè!íß¿ÿ”oçþýûõÐCiæÌ™>|¸ Ô·o_M™2EwÝu—vìØwy‡ÃÑa/ž§êØjiiѳÏ>«%K–hðàÁr8r:8p fΜÉÁ tuŸÎBKKK¸²²2üÚk¯…o»í¶ðÂ… ÃsæÌ /\¸0|Ûm·…_{íµpeee¸¥¥% X™¤¸‹-Êjý>ú(é:ÊÊʲºÍ›nº)ãÛ[¹rexÙ²eá‚‚‚¤ëI÷Ïét†ï¾ûî°ßïÏy?½÷Þ{á3fd|›yyyá«®º*ãëß¹sgxòäÉ]÷?ÿó?‡«ªªºÍ1’Ê¢E‹’®7/[¶,ãÇä–[n ;v¬Ýû ›c6‡=OøÛßþv8//¯Íí¼æškÂÕÕÕáp8þÞ÷¾×îýÔÙÇVìúçw^Üï>ùä“ð¤I“ÒÞÞ¤I“xº¸Ï$ár0lذ¤e555r[‹-ÒÂ… õÒK/©¹¹9ãõü~¿~úÓŸjáÂ… YßîOúSÍš5K›6mÊ*Q+((Èè²O=õ”¦L™¢mÛ¶etù?üáš0a‚>üðÃ}ì=÷Üs:ûì³õÒK/eü˜<ùä“;v¬6nÜØeÛY[[«iÓ¦é‘GÉèãÇ¿þõ¯ºà‚ ´oß>õïß¿]·ÝÕÇÖçŸýÿ¶mÛ4sæLmß¾=íåÇŽË“(ÐÅ}&cx¸\®”aÚ´iiW\\¬Q£FiÔ¨Q)·I’Þ~ûmÝ}÷ÝYÝæ~ô#}ÿûßWKKKÖÛ{íµ×¶y™?ÿùϺõÖ[S6âÔ˜1cÔ»wï¤ßUWWkÞ¼yÚ³gO<îžyæÝpà jjjJúÓéÔˆ#TVV–òMlj'´hÑ"­Y³¦Ó·³©©IK–,IÛ@–””D‡ÆÄ:xð .\˜ñ›&«[µµµ:yò¤>¬¯|å+ª­­mõòãÆãIèâ>“„ÈÁ‰'’–µ7•Jç[ßúV´™u:ºöÚkõüóÏëèÑ£ª¯¯×þýûµÿ~544hÍš5)Çþú׿֑#G2º½×_]÷ßÒò²²2=ðÀzÿý÷uäȧ$á…©ã>ò÷ûýjllTee¥>þøc­Zµ*í àå—_®[o½Õr÷ÉívÇýÜÖ7ÏSÍY:gΜÛž-[¶$-›ú¨[[çž{®œN'O¦@;tfŸI äè†nЯýë6g/èh Ú²e‹6oÞ¬½{÷êØ±cª®®VCCƒ¼^¯¼^¯<OÊ“´&qžÞÒÒÒ¤/:µGEEEÜÏ'Ožl÷sEW6¼³fÍJùeÅl|ó›ßÌúlgÕÕÕIK._"t8*--Õ_|];glGH<Û`iiiN'‘>|xV ¯Ž­!C†ð¤´ /`!óæÍÓý÷߯‹.º¨Kowݺuzì±Ç´|ùrùýþ¿þÄ“itt#___ßáۜͩ–Û«W¯^?~|»¯#[uuuq?å|û‰ëvô†Äa¹C‰ÃqºÃ±Õ·o_žv"áNÑ»Ìââb >\ãÆÓ´iÓ´téR5ªK·¥¶¶V·ß~»ž{î¹N½ÄD¸=U&rkæ²m[{L;ú1IlxsÝÖt§É¶ò±eµ/­Ýùµ—„è$‹-Òo¼a¹íª­­Õ%—\’4ŽSúò‹6çwž&Ož¬ÓN;MC† Qß¾}Õ»woõîÝ[?þñµ|ùòŒo+ñKmý7’šš®~óÐ]_Ÿ‡Û“žt‡&Ðáppl= /ÐCýÛ¿ý[R³ëv»uÏ=÷èÖ[oÕ AƒÒ®Û¯_¿¬nËétÊçóEN<[V{õíÛWÇŽ‹þóð=Ì{ï½§çŸ>nYŸ>}´qãFÝ{ï½­6»¹èÓ§OÜÏÞ¸Â!íIDATÅJ3ŒôÍ\¬“'Oæ|]‰ëvFÃÛZƒ©lÇärlfàLk@ôç?ÿ9iÙ~ô#wÞy­ŸmÃZZZ÷seeeÖI[k÷óñãÇ;ôúM5tèФÇ%—/ë577ëèÑ£qË:zfÄ“TVVæô”í˜\Ž-À $¼@´víÚ¸Ÿív»n¼ñÆŒ×Ï6å:óÌ3ã~ƒ:pà@‡ÝŸóÏ??îç@  ?þ˜º S§Nû9 åô¸8p iœv6'gÈDâi¨ý~¿>œõõìÛ·c èHx(q^ÜQ£F%MŽßÚ“Æ®]»²º½I“&%-Û´iS‡ÝŸ™3gvêõ›*ՙʲËW’Þ}÷ݤe\pA‡nkªORÁ¯­7jÙÎ̱˜„èÇ1f3§éÎ;“NЖùóç'-{å•W:ìþÌž=;é[û‰c”‘ì /L:yÛo¾™õõ$®c³Ùtá…vè¶N™2%iÙš5k²ºŽ·Þz‹c è¡Hx(ñ EÙ QøÕ¯~•S³rúé§Ç-[¾|¹ÊËË;äþ 6L—]vYܲ·ß~;ë°§)))ÑÕW_·ìå—_Nú 5•••zùå—ã–]zé¥Iãƒ;¢9O þüóÏgu¢”\UŽ-À $¼@”ØŒ”——g4òÝwßÕï~÷»œžhn½õÖ¸e@@ßùÎw’Æ~æê[ßúVÒsÊ7¿ù͸éÐìŽ;îˆû¹¹¹Y÷Þ{oÆëÿçþgRÓ™øXw›Í¦ë¯¿>nYUUUƧdþàƒôâ‹/rl= /ÐÍž=;iÙƒ>Øê:Û¶mÓ•W^©`0˜sc5`À€¸e+V¬Ðm·Ý–t&¶T>úè#­^½:íï.\¨‹/¾8nÙÖ­[µdÉ’ŒÇm666êñÇ×í·ßÞcŽ…™3gjÙ²eqË~ûÛßê¿øE›ë>úè£zâ‰'’®oéÒ¥Öœ;θe?øÁÚw|äÈ]uÕU9&[@÷G ô@×\sMÒ²'žxBßøÆ7TQQ·üÃ?Ô]wÝ¥Y³fE_Üg]ÈDqq±~øá¤åO>ù¤&L˜ ŸÿüçÚ½{·jjjTWW§ƒjÕªUz衇4yòd?^«V­jõÉì·¿ýmÒœ¿k֬љgž©»ï¾[ëÖ­SEE…<êëëU^^®µk×êá‡Ö²eË4hÐ ÝvÛm=î[ø¿ùÍoTRR·ì{ßûž.¿ür­Y³&.…‡ÃZ¿~½®ºêª¤äÓétê©§žê´SáŽ5JwÞygÜ2¿ß¯yóæéþûï×_|÷»£Gê—¿ü¥Æ¯ƒJRÒ›®L_(9¶€î­3ûLδXÔܹs5wî\½ýöÛqËŸ~úi=ýôÓ>|¸\.—ªªªÔÐÐw™©S§ê•W^ѰaòN{o¸ámÙ²Eÿû¿ÿ·|ß¾}ú÷ÿ÷v߯²²2ýå/Ñ•W^)Ç]ÞÐРŸýìgúÙÏ~ƃŸÂàÁƒõ÷¿ÿ]K—.›cyÅŠZ±b… TZZª¼¼mÚõÅ_hïÞ½IÍî9眣W_}Uƒ ÒôéÓsºÝ‡~X?üá;í~-\¸Po½õVÒ к‹.ºHo¾ùfÒ ¤/Çõ~þùç:xð`Êf×åréùçŸ×u×]×éÛYTT¤×_]cÇŽMùûãÇkÏž=ª©©‰{½;v¬V¬X¡Y³fåœplÝcxê´ÓNÓ¦M›tî¹çftùë®»N7nŒ¾Ø/Z´(çÛ¾ï¾û´fÍM›6-ãuúõë—ñÉ ¦OŸ®Ï>ûLßùÎwäp82¾üü|-]º´Sr+›9s¦>ýôSÝrË-?×^yå•úôÓOuå•WvÙv8P[¶lÉøËq×\sÞ}÷]•––ªOŸ>i›eŽ-À\Ùgæ…³h“C¡ª««µ}ûv½üòË*//WSS“ 5räH]qÅš4i’ ÔiãÀž( éïÿ»þö·¿ióæÍªªªR Pqq±F¥ /¼P7ÞxcÊ“Gt„ 6è7ÞÐÛo¿­ŠŠ ;vLÁ`P½{÷ÖðáÃ5aÂ]vÙeºâŠ+Ô«W¯¬¯¿ªªJ¯¼òŠ^}õUíÙ³GGUmm­ìv»Š‹‹UVV¦ñãÇköìÙºüòË5xð` Iû÷ïןþô'­]»VŸ|òItêºþýûkìØ±š={¶®½öZ?þ”nç'Ÿ|¢gŸ}V+W®Œ?n·[£GÖœ9stã7&-­£plÝëµ®³ú̬Þp8¬ÚÚZ­Y³F«V­Òþýû£2zôh-X°@óçÏW¿~ýHy±Îì3à K4¼Œá€±˜¥F#á€ÑHx`4^„F#á€ÑHx`4^„F#á€ÑHx`4^„FëÌ>Ón•ÎÀ©ôØcéСCºâŠ+4gΜ¤Ë|ûÛß–$Íž=[×\sM§mKWÝNO| ;ÓgŸ}¦U«V©¢¢BMMMêÓ§Î:ë,]ýõ§|ÛÚ{Üu‡íoÍ_ÿúWmذA’ôÈ#ðÇKêÌ>3«†7›Î›¦ñÂÓž¸;Îñãǵÿ~IÒÖ­[»Ý ú©p×]wI’&Ož¬n¸¡ÕËþþ÷¿×Ö­[S·k×®Õ‹/¾˜vÝÿú¯ÿR¯^½,ýîß¿_=öX\òRWW§ºº:#Ž/þ>€Î×™}& /ÐC„B!½üòË9r¤Î;Dß8P£GVEE…¦OŸ~ʶ£; ƒ’¤–––v]Ïé§Ÿ®‰'êÀjllŒ.2dˆF­‚‚‚Œ®§«ÃTÞzë-…ÃaÙl6]{íµ5j”êêêäp8Nù¶u„î¾ý@w@‹k„ Z¶l;¢ìÝ»Wk×®U0LÙhÚív}ç;ß9åÛÑ9R·Ür‹¤|º1eÊÝxãÙ=¡wÑc˜ÊçŸ.I;v¬f̘!Iø€í@§ñx<’¤’’v€œ0K€vihhЮ]»ØtŠP(}Þ·Ùlì9a– K»wïֻᆱƒÊëõÊívkÔ¨Qš3gŽÆŽ›v½ÈÇÉ÷ÝwŸ¨ŠŠ ½þúëÚ»w¯€JJJtÿý÷g¼™Î6ÐÚ7¨#×qÏ=÷¨´´T;vìÐÆuèÐ!ùý~¹\. 6LÓ¦MÓÔ©S“®û•W^ѺuëÔÜÜ,IÚ°aCô¶úõë§|0ëíÍE6Û}Gþÿ›§lïs¢mÛ¶éwÞQEE…B¡JJJ4aÂ]rÉ%***2êØÏä1¬©©Ñ›o¾©Ï>ûLõõõr8ú(º,öqM<žÛÚ¶öë---Úµk—¶oß®ƒêäÉ“ÊÏÏWÿþý5~üx]|ñÅr»Ý–Û·í9N€Ö¯_¯-[¶¨ººZyyy*--ÕŒ34cÆ åççóÂËc /¡––ýáHúØÜãñh÷îÝÚ½{·fÏž­«¯¾ºÕc´¾¾^Ç×O<ýb’¤œ^$;ÊÑ£GµjÕªè7ýcïÛž={´gÏ}üñǺ馛â~ÿÁ¨¸¸XÇ—$F¿ñß·oß.Ûþ\¶ÃétÆÍné}Ž…Bzæ™g´}ûö¸åÕÕÕZ½zµ6oÞ¬Ûo¿]Æ ë1#UUUúÅ/~!ŸÏ]æ÷ûuèÐ!:tHÕÕÕ^¾|yôïÂëõ&=®]}¬G<üðÃÑ1Å±Ï •••ª¬¬ÔÎ;u×]wuøßs®û¶½Ç©ÏçÓ¯~õ+:t(nyävwíÚ¥ÓN;Xcx ½úê«Ñfwâĉš5k–úöí«ºº:½óÎ;Ú½{·6lØ ’’]|ñÅi¯çóÏ?×Ê•+%I3gÎÔˆ#NyÃûÒK/©¶¶V#GŽÔœ9s4dÈåååéóÏ?×k¯½¦úúzmÛ¶MçœsަL™]/’HGÒ©©S§ž’¹msÙŽ÷Þ{O Yßçˆ7Þx#ÚDÌ™3G“'O–ÓéÔôÊ+¯èäÉ“zòÉ'uÏ=÷d<Bw·|ùrù|>åççkÙ²e:ãŒ3´wï^mܸQ“'OÎú1íèã+×c=bìØ±ª©©Ñ\ ³Î:K}úô‘ÇãÑúõëµ}ûvUWWkÍš5Z²d‰%öm{Óçž{.ÚìžsÎ9š;w®úô飺º:mÚ´I;vìÐÁƒy€å‘ð8qâ„V¯^-I:ÿüóuóÍ7GWZZª³Î:KO?ý´vìØ¡W_}U\pz÷îòºV¬X¡¢¢"}÷»ßUii©%î_mm­&Nœ¨¯ýëqã$‡ªQ£FéÇ?þ±B¡6oÞœ² èŽr¾Ïõõõzë­·$I—]v™.»ì²¸õ‡ ¢GyD555zÿý÷5{öìñw²wï^Iҹ瞫¹sçF—1BóæÍ³Äsw{õùóçkÁ‚r:qËGŽ©#GލªªJ~øa‡7¼¹ìÛö§Ñfùì³ÏÖ­·Þ½ÈóÞ_þò½óÎ;¼HÀòHxÑcmݺ5écÍX±ãß{ï½è@öÅ‹§¼üâÅ‹µcǃA½ÿþûºä’KR^. éºë®³L³ùû»êª«R~)hðàÁ3fŒöì٣Çõn?×û¼e˃A9ÍŸ??é÷cƌѠAƒT]]­]»võ˜†7vˆN"«ŒólﱞͦѣG«ªª*:´æTïÛö§±Ï_ùÊWR¾þ.^¼X7nä åèÏùÌÒ´aß¾}’¾üÔ AƒR^fÈ!Ññ¢{öìI{]ýúõÓ„ ,uÿ†®~ýú¥ý}ÿþý%ýcz(´ç>GßáÇ'%}±Í“$£Þ$´%ò&n×®]úì³ÏŒ{ÜÛ9"_ž<Õû¶½Çid¨BïÞ½UVV–rý^½ziøðá¼HÀò˜¥=V6'ž¨ªª’¤6çí4hêêêtäÈ‘´—9ýôÓ-·/Úº_vû—ΡPȘǿ=÷¹²²R’T^^Þæ©ªMz“Ж èé§ŸV0Ôc=¦éÓ§ëÒK/mµÁìŽÇzMM6oÞ¬òòrÕÖÖÊëõÊï÷wJ£Ûž}ÛÞãôرcÑ7ó­0`€***xQ¥1†=V6'žˆ|3Úårµz¹ÂÂBIŠ~«<+NUÕÖý2Q{îs6MlOúTê¼óÎÓu×]§¿ÿýï Ú´i“¶lÙ¢yóæéÒK/ž ¸;ëëÖ­ÓK/½ÔåoþrÙ·í=N³}Þ¬Œ1¼@–0ñ.³+µ´´´y™ž8¡{îsä8;v¬¾öµ¯uZ‘éñ¹œžgΜ©qãÆé•W^ÑÖ­[ µjÕ*}öÙgºãŽ;Nù›«ö<î~ø¡^xáI_Ž›:uªÆŒ£~ýúÉåriÍš5­~/ «÷mG§mWÌËÈ€ÛíVCCCܘ©D~*§KÔÔÔÄØ ÇÃÉ“':íôÔ.—K^¯7£Ç/Ó$®«”””èÆoÔܹsõ§?ýIGÕ¡C‡ôꫯêꫯî¶ûÚµk£ÿ¿ãŽ;4f̘¸ßwEҙ;mïqêr¹ÔØØØæ1Ès ºƒÎì3³zM +:t¨¤ŒiK§ººZRÛcÞ:B$©j+Ál:Nä‹>•••öÑv¤AÉäñ;zôhÜvYň#tçwFÇ™îØ±£[?î‘qª#GŽLjv¥®¯É¾mïq:`À€ŒŽÁ'Nð¤Ëc– ‘S×ÕÕE¿’¨²²Rõõõ’¤3Î8£Ó·)’"·6Rmm­¾øâ Ë4ߦlÇ™gž)éËd52?jG‹4TÕÕÕi9éË3^EŽÈvY‰ËåÒ¸qã$IÝúy ò¥´T§ð …B]~†¶öm{ÓÈlkkkÓ6½‘³½V×™}& /Œ1mÚ´è—B^{íµ”—‰,·Ùlºà‚ :}›"SíÛ·/å‹Q(Òßþö·.Ù?‘Ó½vÆü£VÜŽ3fDÇ-¾ð i‡º477çÜäÍœ93ú|÷׿þU@ åõGãüü|͘1ã”íûÈL©ÔÔÔ¤m»“>}úHúG¢kÓ¦Mª­­µÔ¾mïq:uêÔ¤ç·D+W®luŽ`À*à dØH-^¼X/¿ü²vîÜ©Ç\³gÏV¿~ýTWW§ 6h÷îÝ’¾œ>¨+¦aš6mš>ýôS…B!=ú裺ì²Ë4bÄ…Ãa=zTëׯWyyyôôÇiĈÚ½{·öîÝ«-[¶¨¬¬LÇ×øñã»ôqêªíèÓ§–,Y¢—_~Y•••úÉO~¢9s樴´Tyyyª««Syy¹vîÜ©ú§Òĉ³¾jÁ‚Z¹r¥öíÛ§Ÿÿüçš?¾† &éËySW¯^M/^¬’’’Sö7RUU¥'žxBÓ¦MÓ¸qãÔ·o_ù|>mݺ5:lWmܸqÚ´i“ªªªô—¿üE³fÍR0ÔŽ;´fÍõïß?Ú€Zaß¶÷8-++Ó¤I“´}ûv}ðÁjiiÑ…^¨>}ú¨¡¡AÛ¶mÓ¦M›Ô»woŸªªª  O}Â+Iùùù²ÙlÊÏÏ—Çãщ'ôöÛokðàÁòz½***’Çã¡R©T*•J¥R[­n·[UUU:qâ„<l6[ôß)Ix½^¯$)( )¨¾¾^[·nUQQ‘ìv;ñ<•J¥R©T*5ã*I---òx<ª¯¯ö™‘„×ëõvmÂ[\\,‡Ã!‡Ã¡@ ݈@  êêjÙíöè8^*•J¥R©T*5“¢5Ëf³Én·Ëáp¨¸¸¸ë^—Ë%›ÍýâZ8ŽŽç …B …BÑ1 Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License (GFDL), Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You can find a copy of the GFDL at this link or in the file COPYING-DOCS distributed with this manual. This manual is part of a collection of GNOME manuals distributed under the GFDL. If you want to distribute this manual separately from the collection, you can do so by adding a copy of the license to the manual, as described in section 6 of the license. Many of the names used by companies to distinguish their products and services are claimed as trademarks. Where those names appear in any GNOME documentation, and the members of the GNOME Documentation Project are made aware of those trademarks, then the names are in capital letters or initial capital letters. DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT ARE PROVIDED UNDER THE TERMS OF THE GNU FREE DOCUMENTATION LICENSE WITH THE FURTHER UNDERSTANDING THAT: DOCUMENT IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS FREE OF DEFECTS MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY, ACCURACY, AND PERFORMANCE OF THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS WITH YOU. SHOULD ANY DOCUMENT OR MODIFIED VERSION PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL WRITER, AUTHOR OR ANY CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER; AND UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE AUTHOR, INITIAL WRITER, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER DAMAGES OR LOSSES ARISING OUT OF OR RELATING TO USE OF THE DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. software-center-13.10/help/C/index.docbook0000664000202700020270000002711712200237544020644 0ustar dobeydobey00000000000000 ]> Ubuntu Software Center Ubuntu Documentation Project &legal; Michael Vogt mvo@ubuntu.com Matthew Paul Thomas mpt@ubuntu.com Andrew Higginson rugby471@gmail.com How to install and remove software in Ubuntu. What is Ubuntu Software Center? Ubuntu Software Center is a virtual catalog of thousands of free applications and other software to make your Ubuntu computer more useful. You can find software by category or by searching, and you can install an item with the click of a button. You can also examine which software is installed on the computer already, and remove anything you no longer need. Installing software To install something with Ubuntu Software Center, you need administrator access and a working Internet connection. (If you set up Ubuntu on this computer yourself, you have administrator access automatically.) In the Get Software section, find the thing you want to install. If you already know its name, try typing the name in the search field. Or try searching for the type of program you want (like “spreadsheet”). Otherwise, browse the list of departments. Select the item in the list, and choose More Info. Choose Install. You may need to enter your Ubuntu password before the installation can begin. How long something takes to install depends on how large it is, and the speed of your computer and Internet connection. When the screen changes to say “Installed”, the software is ready to use. Using a program once it’s installed For most programs, once they are installed, you can launch them from the Applications menu. (Utilities for changing settings may appear inside System Preferences or System Administration instead). To find out where to launch a new program, navigate to the screen for that program in Ubuntu Software Center, if it is not still being displayed. The menu location should be displayed on that screen, below the colored bar. If you are using Ubuntu Netbook Edition, an application appears in the appropriate category on the home screen. Some items, such as media playback or Web browser plugins, do not appear in the menus. They are used automatically when Ubuntu needs them. For a Web browser plugin, you may need to close the browser and reopen it before the plugin will be used. Removing software To remove software, you need administrator access. (If you set up Ubuntu on this computer yourself, you have administrator access automatically.) In the Installed Software section, find the item you want to remove. Select the item in the list, and choose Remove. You may need to enter your Ubuntu password. If a program is open when you remove it, usually it will stay open even after it is removed. As soon as you close it, it will no longer be available. Why is it asking me to remove several programs together? Sometimes if you try to remove one item, Ubuntu Software Center will warn you that other items will be removed too. There are two main reasons this happens. If you remove an application, any add-ons or plugins for that application usually will be removed too. Multiple applications are sometimes provided as a single package. If any of them are installed, all of them are installed. And if any of them are removed, all of them are removed. Ubuntu Software Center cannot remove them individually, because it has no instructions on how to separate them. You can use the Main Menu settings to hide unwanted items without removing them from the computer. In Ubuntu, you can find Main Menu inside System Preferences. In Ubuntu Netbook Edition, it is in Settings Preferences. What is “Canonical-maintained”? Some of the software available for Ubuntu is maintained by Canonical. Canonical engineers ensure that security fixes and other critical updates are provided for these programs. Other software is maintained by the Ubuntu developer community. Any updates are provided on a best-effort basis. What is “Canonical Partners”? Canonical Partners shows software from vendors who have worked with Canonical to make their software available for Ubuntu. You can install this software in the same way as any other software in Ubuntu Software Center. Do I need to pay anything? Most software in Ubuntu Software Center is free of charge. If something costs money, it has a Buy button instead of an Install button, and its price is shown on the screen for the individual item. To pay for commercial software, you need an Internet connection, and an Ubuntu Single Sign On account or Launchpad account. If you do not have an account, Ubuntu Software Center will help you register one. What if I paid for software and then lost it? If you accidentally removed software that you purchased, and you want it back, choose File Reinstall Previous Purchases. Once you sign in to the account that you used to buy the software, Ubuntu Software Center will display your purchases for reinstalling. This works even if you have reinstalled Ubuntu on the computer. What if a program I want isn’t available? First, check that View All Software is selected. If the software you want still isn’t there, but you know that it runs on Ubuntu, try contacting the original developers of the program to ask if they can help make it available. If a program is available for other operating systems but not for Ubuntu, let the developers know that you’re interested in running it on Ubuntu. What if a program doesn’t work? With tens of thousands of programs in Ubuntu Software Center, there’s always the chance that a few of them won’t work properly on your computer. If you are familiar with reporting bugs in software, you can help out by reporting the problem to the Ubuntu developers. Most programs have a Report a Problem item in their Help menu. Otherwise, look through Ubuntu Software Center for another program to do what you want. software-center-13.10/bin/0000755000202700020270000000000012224614354015623 5ustar dobeydobey00000000000000software-center-13.10/bin/software-center-dbus0000775000202700020270000000163412151440100021603 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2012 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 import logging import sys from softwarecenter.db.dataprovider import dbus_main if __name__ == "__main__": if "--debug" in sys.argv: logging.basicConfig(level=logging.DEBUG) dbus_main() software-center-13.10/bin/software-center-qml0000775000202700020270000000013612151440100021433 0ustar dobeydobey00000000000000#!/bin/sh export QT_GRAPHICSSYSTEM=raster PYTHONPATH=. python softwarecenter/ui/qml/app.py software-center-13.10/bin/software-center0000775000202700020270000001556212151440100020655 0ustar dobeydobey00000000000000#!/usr/bin/python # Copyright (C) 2009-2011 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 # take time stamp as early as python allows this import time time_entering_main = time.time() from gi.repository import Gtk, GObject import gettext import glob import logging import os import time import sys from softwarecenter.enums import * from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.utils import ( ExecutionTime, mangle_paths_if_running_in_local_checkout) from softwarecenter.version import * import softwarecenter.log import softwarecenter.paths from optparse import OptionParser # Enable Xapian's CJK tokenizer (see LP: #745243) os.environ['XAPIAN_CJK_NGRAM'] = '1' LOG = logging.getLogger("softwarecenter") if __name__ == "__main__": parser = OptionParser("usage: %prog [options] [package-name | apturl | " "deb-file]", version="%prog " + VERSION) parser.add_option("--debug", action="store_true", help="enable debug mode", default=False) parser.add_option("--debug-filter", help="show only specific messages. supported currently: " "'softwarecenter.performance'") parser.add_option("--force-rtl", action="store_true", help="force rtl mode (useful for debugging)", default=False) parser.add_option("--display-navlog", action="store_true", help="display a navigation history log (useful for " "debugging)", default=False) parser.add_option("--disable-buy", action="store_true", help="disable support to buy software", default=False) parser.add_option("--disable-apt-xapian-index", action="store_true", help="disable support for apt-xapian-index (technical " "items)", default=False) parser.add_option("--measure-startup-time", action="store_true", help="open and wait until the window is visible, " "then close, only useful for profiling", default=False) parser.add_option("--dummy-backend", action="store_true", help="run with a dummy backend, this will not actually " "install or remove anything and is useful for testing", default=False) parser.add_option("--packagekit-backend", action="store_true", help="use PackageKit backend (experimental)", default=False) parser.add_option("--profile", action="store_true", help="use cProfile to gather a profile dump for e.g. " "kcachegrind, runsnake, gprof2dot", default=False) (options, args) = parser.parse_args() # statup time measure implies "performance" in debug filters if options.measure_startup_time: options.debug_filter = "performance,traceback" if options.debug_filter: softwarecenter.log.add_filters_from_string(options.debug_filter) # implies general debug options.debug = True if options.debug: softwarecenter.log.root.setLevel(level=logging.DEBUG) else: softwarecenter.log.root.setLevel(level=logging.INFO) # packagekit if options.packagekit_backend: softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True logging.info("Using PackageKit backend") # dummy backend if options.dummy_backend: import atexit from softwarecenter.tests.utils import ( start_dummy_backend, stop_dummy_backend) start_dummy_backend() atexit.register(stop_dummy_backend) # override text direction for testing purposes if options.force_rtl: Gtk.Widget.set_default_direction(Gtk.TextDirection.RTL) # check if running locally mangle_paths_if_running_in_local_checkout() # ensure we can actually run Gtk.init_check(sys.argv) # create the app with ExecutionTime("import SoftwareCenterApp"): from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 with ExecutionTime("create SoftwareCenterApp"): app = SoftwareCenterAppGtk3(options, args) # DEBUG/PROFILE mode if options.measure_startup_time: logger = logging.getLogger("softwarecenter.performance") with ExecutionTime("show() & gtk events until visible"): def are_we_there_yet(): """ small helper that monitors the main window appearance """ global main_visible # useful to check how often this is run - not often :/ print time.time() if not main_visible and app.window_main.get_visible(): logger.debug("** main window visible after: %s seconds" % ( time.time() - time_entering_main)) main_visible = True return False return True # run watcher for main window main_visible = False from gi.repository import GLib GLib.timeout_add(100, are_we_there_yet) app.run(args) # keep monitoring the loop while not (app.available_pane.cat_view and app.available_pane.cat_view.get_visible()): Gtk.main_iteration_do(True) time_to_ready = time.time() - time_entering_main print(time_to_ready) logger.debug("** main window fully ready after: %s seconds" % time_to_ready) sys.exit(0) # run with cProfile if options.profile: # use something like "pyprof2calltree" from pypi and kcachegrind: # $ python ./pyprof2calltree.py -i software-center_*.pyprof -k # OR # $ runsnakerun software_center_*.pyprof # OR # gprof2dot (http://code.google.com/p/jrfonseca/wiki/Gprof2Dot): # $ gprof2dot.py -f pstats software-center_20120620_114618.pyprof |\ # dot -Tpng -o output.png # $ xdg-open output.png # # to analyse the data import cProfile fname = "software-center_%s.pyprof" % time.strftime("%Y%m%d_%H%M%S") cProfile.run("app.run(args)", fname) # run it normally app.run(args) Gtk.main() software-center-13.10/run_with_fake_review_api.sh0000775000202700020270000000015712151440100022437 0ustar dobeydobey00000000000000#!/bin/sh export SOFTWARE_CENTER_FAKE_REVIEW_API="1" # s-c export PYTHONPATH=$(pwd) ./bin/software-center $@ software-center-13.10/tests/0000755000202700020270000000000012224614355016216 5ustar dobeydobey00000000000000software-center-13.10/tests/test_channels.py0000664000202700020270000000174612151440100021414 0ustar dobeydobey00000000000000import unittest from tests.utils import ( get_test_db, setup_test_env, ) setup_test_env() class TestChannels(unittest.TestCase): """ tests the channels backend stuff """ def test_generic(self): from softwarecenter.backend.channel import ChannelsManager db = get_test_db() m = ChannelsManager(db) channels = m._get_channels_from_db(installed_only=False) self.assertNotEqual(channels, []) channels_installed = m._get_channels_from_db(installed_only=True) self.assertNotEqual(channels_installed, []) def test_aptchannels(self): from softwarecenter.backend.channel_impl.aptchannels import ( AptChannelsManager) db = get_test_db() m = AptChannelsManager(db) channels = m.channels self.assertNotEqual(channels, []) channels_installed = m.channels_installed_only self.assertNotEqual(channels_installed, []) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_enquire.py0000664000202700020270000000214612151440100021264 0ustar dobeydobey00000000000000import time import unittest import xapian from tests.utils import ( get_test_db, get_test_pkg_info, setup_test_env, ) setup_test_env() from softwarecenter.db.appfilter import AppFilter from softwarecenter.db.enquire import AppEnquire class TestEnquire(unittest.TestCase): def test_app_enquire(self): db = get_test_db() cache = get_test_pkg_info() xfilter = AppFilter(cache, db) enquirer = AppEnquire(cache, db) terms = [ "app", "this", "the", "that", "foo", "tool", "game", "graphic", "ubuntu", "debian", "gtk", "this", "bar", "baz"] # run a bunch of the queries in parallel for nonblocking in [False, True]: for i in range(10): for term in terms: enquirer.set_query( search_query=xapian.Query(term), limit=0, filter=xfilter, nonblocking_load=nonblocking) # give the threads a bit of time time.sleep(5) if __name__ == "__main__": unittest.main() software-center-13.10/tests/data/0000755000202700020270000000000012224614355017127 5ustar dobeydobey00000000000000software-center-13.10/tests/data/desktop/0000755000202700020270000000000012224614355020600 5ustar dobeydobey00000000000000software-center-13.10/tests/data/desktop/ubuntu-software-center.desktop0000664000202700020270000000072212151440100026606 0ustar dobeydobey00000000000000[Desktop Entry] X-AppInstall-Package=software-center X-AppInstall-Popcon=93988 X-AppInstall-Section=main Name=Ubuntu Software Center Test Name[de]=Ubuntu Software Zentrum GenericName=Software Center Comment=Lets you choose from thousands of applications available for Ubuntu Exec=/usr/bin/software-center Icon=softwarecenter Terminal=false Type=Application Categories=PackageManager;GTK;System;Settings; StartupNotify=true X-Ubuntu-Gettext-Domain=app-install-data software-center-13.10/tests/data/desktop/scintillant-orange.desktop0000664000202700020270000000047512151440100025760 0ustar dobeydobey00000000000000[Desktop Entry] Version=1.0 Type=Application Name=Scintillant Orange GenericName=Make Things Oranger Comment=Eye protection recommended. Icon=gtk-media-record Terminal=false Categories=Graphics;2DGraphics; X-AppInstall-Package=scintillant-orange X-AppInstall-Tags=implemented-in::perl, role::program, use::converting software-center-13.10/tests/data/desktop/software-center.menu0000664000202700020270000000121212151440100024554 0ustar dobeydobey00000000000000 Debtag region::de Dynamic region::${CURRENT_REGION} Dynamic AudioVideo software-center-13.10/tests/data/desktop/zynjacku.desktop0000664000202700020270000000052412151440100024014 0ustar dobeydobey00000000000000[Desktop Entry] X-AppInstall-Package=zynjacku-fake X-AppInstall-Popcon=9 X-AppInstall-Section=fake-component Type=Application Name=Zynjacku Test GenericName=LV2 synths host Comment=LV2 synths host for JACK, test edition Icon=zynjacku32x32 Exec=zynjacku Terminal=false Categories=AudioVideo;Audio; X-Ubuntu-Gettext-Domain=app-install-data software-center-13.10/tests/data/desktop/music-banshee.scope0000664000202700020270000000064512151440100024345 0ustar dobeydobey00000000000000[Scope] DBusName=com.canonical.Unity.Scope.Music DBusPath=/com/canonical/unity/scope/banshee Icon=/usr/share/unity/6/lens-nav-music.svg Name=Music (Banshee) Description=Find artists, albums, and your favorite tracks SearchHint=Search Music Collection Shortcut=m Type=music [Desktop Entry] X-AppInstall-Package=unity-lens-music X-AppInstall-Popcon=1000 X-AppInstallSection=main X-Ubuntu-Gettext-Domain=unity-lens-music software-center-13.10/tests/data/desktop/pay-app.desktop0000664000202700020270000000045112151440100023524 0ustar dobeydobey00000000000000[Desktop Entry] X-AppInstall-Package=pay-app X-AppInstall-PPA=pay-owner/pay-ppa-name X-AppInstall-Price=99$ Name=Pay App Example Comment=This is a example for a pay app from a PPA Exec=/usr/bin/software-center Icon=softwarecenter Terminal=false Type=Application Categories=Application;AudioVideo software-center-13.10/tests/data/desktop/expensive-gem.desktop0000664000202700020270000000041212151440100024726 0ustar dobeydobey00000000000000[Desktop Entry] Name=The expensive gem Comment=It does nothing Terminal=false Type=Application Categories=Application;Games; X-AppInstall-Package=expensive-gem X-AppInstall-Popcon=32217 X-AppInstall-Price=999$ X-AppInstall-Icon-Url=http://www.google.com/favicon.ico software-center-13.10/tests/data/ubuntu_dist_channel0000664000202700020270000000025312151440100023071 0ustar dobeydobey00000000000000# This is a distribution channel descriptor # For more information see http://wiki.ubuntu.com/DistributionChannelDescriptor canonical-oem-watauga-precise-amd64-20120517-2 software-center-13.10/tests/data/app-install/0000755000202700020270000000000012224614354021352 5ustar dobeydobey00000000000000software-center-13.10/tests/data/app-install/desktop/0000755000202700020270000000000012224614355023024 5ustar dobeydobey00000000000000software-center-13.10/tests/data/app-install/desktop/deja-dup:deja-dup.desktop0000664000202700020270000000453612151440100027606 0ustar dobeydobey00000000000000[Desktop Entry] X-AppInstall-Package=deja-dup X-AppInstall-Popcon=4952 X-AppInstall-Section=universe Version=1.0 Name=Déjà Dup GenericName=Backup Tool X-GNOME-FullName=Déjà Dup Backup Tool X-GNOME-FullName[ar]=أداة النسخ الإحتياطي X-GNOME-FullName[bg]=ИнÑтрумент за Ñъздаване на резервни ÐºÐ¾Ð¿Ð¸Ñ Déjà Dup X-GNOME-FullName[ca]=Eina de còpies de seguretat Déjà Dup X-GNOME-FullName[cs]=Nástroj k zálohování Déjà Dup X-GNOME-FullName[da]=Déjà Dup - sikkerhedskopieringsværktøj X-GNOME-FullName[de]=Déjà Dup Datensicherungs-Werkzeug X-GNOME-FullName[en_GB]=Déjà Dup Backup Tool X-GNOME-FullName[es]=Herramienta de respaldo Déjà Dup X-GNOME-FullName[eu]=Déjà Dup babeskopietarako tresna X-GNOME-FullName[fi]=Déjà Dup -varmuuskopiointityökalu X-GNOME-FullName[fo]=Déjà Dup trygdarritinghar amboð X-GNOME-FullName[fr]=Outil de sauvegarde Déjà Dup X-GNOME-FullName[gl]=Ferramenta de copia de seguranza do Déjà Dup X-GNOME-FullName[he]=כלי הגיבוי Déjà Dup X-GNOME-FullName[hu]=Déjà Dup biztonságimentés-kezelÅ‘ X-GNOME-FullName[id]=Peralatan Backup Déjà Dup X-GNOME-FullName[it]=Strumento di backup Déjà Dup X-GNOME-FullName[ja]=Déjà Dup ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ツール X-GNOME-FullName[lt]=Déjà Dup atsarginių kopijų įrankis X-GNOME-FullName[ml]=Déjà Dup ബാകàµà´•à´ªàµà´ªàµ ടൂളàµâ€ X-GNOME-FullName[nb]=Déjà Dup verktøy for sikkerhetskopiering X-GNOME-FullName[nl]=Déjà Dup back-up maken X-GNOME-FullName[nn]=Déjà Dup verktøy for tryggleikskopiering X-GNOME-FullName[pl]=Program do tworzenia kopii zapasowych Déjà Dup X-GNOME-FullName[pt_BR]=Ferramenta de Backup Déjà Dup X-GNOME-FullName[ru]=Сохранение данных Ñ Déjà Dup X-GNOME-FullName[sk]=Déjà Dup - zálohovací nástroj X-GNOME-FullName[sv]=Déjà Dup Säkerhetskopiering X-GNOME-FullName[th]=เครื่องมือสำรองข้อมูล Déjà Dup X-GNOME-FullName[tr]=Déjà Dup Yedekleme Aracı X-GNOME-FullName[uk]=Резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð· Déjà Dup X-GNOME-FullName[zh_CN]=Déjà Dup 备份工具 X-GNOME-FullName[zh_TW]=Déjà Dup 備份工具 Comment=Back up your files Icon=deja-dup TryExec=deja-dup Exec=deja-dup StartupNotify=true Type=Application Categories=GNOME;GTK;System;Utility;Archiving; X-Ubuntu-Gettext-Domain=app-install-data software-center-13.10/tests/data/app-install/desktop/soundkonverter:kde4__soundkonverter.desktop0000664000202700020270000000061312151440100033642 0ustar dobeydobey00000000000000[Desktop Entry] X-AppInstall-Package=soundkonverter X-AppInstall-Popcon=731 X-AppInstall-Section=universe Encoding=UTF-8 Name=soundKonverter Exec=soundkonverter %i -caption "%c" Icon=soundkonverter Type=Application DocPath=soundkonverter/soundkonverter.html GenericName=An audio file converter Terminal=false Categories=AudioVideo;AudioVideoEditing;CD; X-Ubuntu-Gettext-Domain=app-install-data software-center-13.10/tests/data/plugins/0000755000202700020270000000000012224614355020610 5ustar dobeydobey00000000000000software-center-13.10/tests/data/plugins/mock_plugin.py0000664000202700020270000000024612151440100023455 0ustar dobeydobey00000000000000 import softwarecenter.plugin class MockPlugin(softwarecenter.plugin.Plugin): """ mock plugin """ def init_plugin(self): self.i_am_happy = True software-center-13.10/tests/data/apt-history/0000755000202700020270000000000012224614355021412 5ustar dobeydobey00000000000000software-center-13.10/tests/data/apt-history/history.log0000664000202700020270000003737412151440100023616 0ustar dobeydobey00000000000000 Start-Date: 2010-06-01 09:21:00 Install: gnuplot (4.4.0-1), libjava3d-java (1.5.2+dfsg-5), libvecmath-java (1.5.2-2), gpsprune (10-1), gnuplot-x11 (4.4.0-1), gnuplot-nox (4.4.0-1), libjava3d-jni (1.5.2+dfsg-5), libimage-exiftool-perl (8.15-1) End-Date: 2010-06-01 09:22:20 xxx-bogus-entry Start-Date: 2010-06-01 09:22:21 Commandline: apt-get install 4g8 Install: 4g8:amd64 (1.0-3) Upgrade: python-apt (0.7.94.2ubuntu6, 0.7.96.1ubuntu11), apt (0.7.25.3ubuntu7, 0.8.3ubuntu6), synaptic (0.63.1ubuntu6, 0.63.1ubuntu14) Error: Sub-process /usr/bin/dpkg returned an error code (1) End-Date: 2010-06-01 09:22:22 Start-Date: 2010-06-01 09:22:44 Install: gpxviewer (0.1.5-1), python-osmgpsmap (0.6.0-1), libosmgpsmap0 (0.6.0-1) End-Date: 2010-06-01 09:23:05 Start-Date: 2010-06-01 09:30:11 Install: libgconfmm-2.6-1c2 (2.28.0-1), libgtkglext1 (1.2.0-1ubuntu1), timidity-daemon (2.13.2-38), timidity (2.13.2-38), libgtkglextmm-x11-1.2-0 (1.2.0-4ubuntu1), linthesia (0.4-1) End-Date: 2010-06-01 09:30:44 Start-Date: 2010-06-01 10:09:48 Install: nautilus (2.30.1-0ubuntu3), libglib2.0-data (2.25.7-1ubuntu2), gnome-session (2.30.0-0ubuntu1), libglib2.0-bin (2.25.7-1ubuntu2) Upgrade: libglib2.0-0 (2.24.0-0ubuntu4, 2.25.7-1ubuntu2) End-Date: 2010-06-01 10:10:21 Start-Date: 2010-06-01 10:25:02 Remove: libkworkspace4 (4.4.2-0ubuntu14), libmono-data-tds2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-c5-1.0-cil (2.4.4~svn151842-1ubuntu4), libpopt-dev (1.15-1), libwpd8c2a (0.8.14-1build1), libmono-npgsql2.0-cil (2.4.4~svn151842-1ubuntu4), libcaca-dev (0.99.beta16-3), libmono-security2.0-cil (2.4.4~svn151842-1ubuntu4), libndesk-dbus1.0-cil (0.6.0-4), gnome-js-common (0.1.1-1), libmono-wcf3.0-cil (2.4.4~svn151842-1ubuntu4), libmono-peapi2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system-data1.0-cil (2.4.4~svn151842-1ubuntu4), gnome-mahjongg (2.30.0-0ubuntu6), x11proto-resource-dev (1.1.0-1), libmono-simd2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system2.0-cil (2.4.4~svn151842-1ubuntu4), libmono1.0-cil (2.4.4~svn151842-1ubuntu4), aisleriot (2.30.0-0ubuntu6), libmono-system-ldap1.0-cil (2.4.4~svn151842-1ubuntu4), libmono0 (2.4.4~svn151842-1ubuntu4), libmono-getoptions1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-cscompmgd8.0-cil (2.4.4~svn151842-1ubuntu4), libmono-data1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-sharpzip0.84-cil (2.4.4~svn151842-1ubuntu4), libmono-sqlite2.0-cil (2.4.4~svn151842-1ubuntu4), libaudiofile-dev (0.2.6-8ubuntu1), libmono-ldap1.0-cil (2.4.4~svn151842-1ubuntu4), libnunit2.4-cil (2.4.7+dfsg-5), smbclient (3.4.7~dfsg-1ubuntu3), libart-2.0-dev (2.3.20-2build1), libmono-management2.0-cil (2.4.4~svn151842-1ubuntu4), libpolkit-gnome0 (0.9.2-2ubuntu2), libgluezilla (2.4.3-2), libmono-corlib2.0-cil (2.4.4~svn151842-1ubuntu4), libplasma-geolocation-interface4 (4.4.2-0ubuntu14), libplasmaclock4 (4.4.2-0ubuntu14), libasound2-dev (1.0.22-0ubuntu7), cli-common (0.7), libmono-system-web-mvc1.0-cil (2.4.4~svn151842-1ubuntu4), libgmime-2.0-2a (2.2.22-5), libweather-ion4 (4.4.2-0ubuntu14), libnspr4-dev (4.8.4-0ubuntu1), libmono-cairo2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-relaxng1.0-cil (2.4.4~svn151842-1ubuntu4), libndesk-dbus-glib1.0-cil (0.4.1-3), gnome-sudoku (2.30.0-0ubuntu6), libmono-i18n-west2.0-cil (2.4.4~svn151842-1ubuntu4), guile-1.8-libs (1.8.7+1-3ubuntu1), libwps-0.1-1 (0.1.2-1build1), python-gtk2-doc (2.17.0-0ubuntu2), libsqlite0 (2.8.17-6build2), kdebase-workspace-bin (4.4.2-0ubuntu14), libmono-microsoft7.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system-messaging2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system-web1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-accessibility1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-webbrowser0.5-cil (2.4.4~svn151842-1ubuntu4), libmono-posix2.0-cil (2.4.4~svn151842-1ubuntu4), mysql-server-core-5.1 (5.1.41-3ubuntu12), mono-runtime (2.4.4~svn151842-1ubuntu4), automoc (1.0~version-0.9.88-4), libmono-addins0.2-cil (0.4-6), libffi-dev (3.0.9-1), libnss3-dev (3.12.6-0ubuntu3), libprocessui4 (4.4.2-0ubuntu14), libmono-system-runtime2.0-cil (2.4.4~svn151842-1ubuntu4), libprocesscore4 (4.4.2-0ubuntu14), libglitz-glx1 (0.5.6-1build1), libsolidcontrol4 (4.4.2-0ubuntu14), mono-csharp-shell (2.4.4~svn151842-1ubuntu4), libsolidcontrolifaces4 (4.4.2-0ubuntu14), akonadi-server (1.3.1-0ubuntu3), libenchant-dev (1.6.0-0ubuntu1), libcanberra-dev (0.22-1ubuntu2), libmono-i18n1.0-cil (2.4.4~svn151842-1ubuntu4), libksgrd4 (4.4.2-0ubuntu14), libmono-oracle1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-messaging2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-rabbitmq2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-winforms1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-db2-1.0-cil (2.4.4~svn151842-1ubuntu4), libtaskmanager4 (4.4.2-0ubuntu14), libkdecorations4 (4.4.2-0ubuntu14), libmono-system-data2.0-cil (2.4.4~svn151842-1ubuntu4), libqimageblitz4 (0.0.4-4build1), libmono2.0-cil (2.4.4~svn151842-1ubuntu4), libwpd-stream8c2a (0.8.14-1build1), libmono-system-ldap2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-bytefx0.7.6.1-cil (2.4.4~svn151842-1ubuntu4), mysql-client-core-5.1 (5.1.41-3ubuntu12), libmono-data-tds1.0-cil (2.4.4~svn151842-1ubuntu4), mono-gac (2.4.4~svn151842-1ubuntu4), libmono-sharpzip2.6-cil (2.4.4~svn151842-1ubuntu4), libmono-getoptions2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-data2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-npgsql1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-ldap2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-messaging-rabbitmq2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-security1.0-cil (2.4.4~svn151842-1ubuntu4), libgsf-gnome-1-114 (1.14.16-1ubuntu1), libglitz1 (0.5.6-1build1), libmono-peapi1.0-cil (2.4.4~svn151842-1ubuntu4), mono-2.0-gac (2.4.4~svn151842-1ubuntu4), libkwineffects1 (4.4.2-0ubuntu14), libmono-system1.0-cil (2.4.4~svn151842-1ubuntu4), libgda3-common (3.0.4-1ubuntu1), libesd0-dev (0.2.41-6ubuntu1), kdebase-workspace-data (4.4.2-0ubuntu14), libmono-sharpzip0.6-cil (2.4.4~svn151842-1ubuntu4), libplasmagenericshell4 (4.4.2-0ubuntu14), libmono-cscompmgd7.0-cil (2.4.4~svn151842-1ubuntu4), libflickrnet2.2-cil (2.2.0-3), libmono-sqlite1.0-cil (2.4.4~svn151842-1ubuntu4), libmono-sharpzip2.84-cil (2.4.4~svn151842-1ubuntu4), mono-utils (2.4.4~svn151842-1ubuntu4), libmono-relaxng2.0-cil (2.4.4~svn151842-1ubuntu4), kdebase-workspace-kgreet-plugins (4.4.2-0ubuntu14), libksignalplotter4 (4.4.2-0ubuntu14), libmono-microsoft-build2.0-cil (2.4.4~svn151842-1ubuntu4), libkephal4 (4.4.2-0ubuntu14), libsoprano-dev (2.4.2+dfsg.1-0ubuntu1), plasma-dataengines-workspace (4.4.2-0ubuntu14), ksysguardd (4.4.2-0ubuntu14), libmono-cecil-private-cil (2.4.4~svn151842-1ubuntu4), libmono-corlib1.0-cil (2.4.4~svn151842-1ubuntu4), libkscreensaver5 (4.4.2-0ubuntu14), libexif-gtk5 (0.3.5-4), libphonon-dev (4.7.0really4.4.1-0ubuntu3), libmono-microsoft8.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system-web2.0-cil (2.4.4~svn151842-1ubuntu4), libgda3-bin (3.0.4-1ubuntu1), libmono-cairo1.0-cil (2.4.4~svn151842-1ubuntu4), libgda3-3 (3.0.4-1ubuntu1), libmono-accessibility2.0-cil (2.4.4~svn151842-1ubuntu4), ant-optional-gcj (1.8.0-4), libmono-i18n-west1.0-cil (2.4.4~svn151842-1ubuntu4), libwpg-0.1-1 (0.1.3-1build1), libmono-bytefx0.7.6.2-cil (2.4.4~svn151842-1ubuntu4), liblsofui4 (4.4.2-0ubuntu14), libxres-dev (1.0.4-1), libmono-system-messaging1.0-cil (2.4.4~svn151842-1ubuntu4), orbit2 (2.14.18-0.1), libkfontinst4 (4.4.2-0ubuntu14), libical-dev (0.44-3), libmono-posix1.0-cil (2.4.4~svn151842-1ubuntu4), ant-gcj (1.8.0-4), libmono-i18n2.0-cil (2.4.4~svn151842-1ubuntu4), libplasma-applet-system-monitor4 (4.4.2-0ubuntu14), kdepim-runtime (4.4.2-0ubuntu1), libmono-oracle2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system-runtime1.0-cil (2.4.4~svn151842-1ubuntu4), libnet1 (1.1.4-2), libaa1-dev (1.4p5-38build1), plasma-widgets-workspace (4.4.2-0ubuntu14), libnunit-cil-dev (2.4.7+dfsg-5), libgdiplus (2.4.2-1build1), libmono-winforms2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-cil-dev (2.4.4~svn151842-1ubuntu4), epiphany-browser-data (2.30.2-1ubuntu1), mono-gmcs (2.4.4~svn151842-1ubuntu4), gnomine (2.30.0-0ubuntu6) End-Date: 2010-06-01 10:28:04 Start-Date: 2010-06-01 10:29:59 Downgrade: libglib2.0-0 (2.25.7-1ubuntu2, 2.24.0-0ubuntu4) Remove: nautilus (2.30.1-0ubuntu3), libglib2.0-data (2.25.7-1ubuntu2), gnome-session (2.30.0-0ubuntu1), libglib2.0-bin (2.25.7-1ubuntu2) End-Date: 2010-06-01 10:30:17 Start-Date: 2010-06-01 10:33:22 Install: nautilus (2.30.1-0ubuntu3), libpangomm-1.4-dev (2.26.2-1), libgconf2-dev (2.31.3-0ubuntu1), libglibmm-2.4-dev (2.24.2-1), libglib2.0-data (2.25.7-1ubuntu2), libgconfmm-2.6-dev (2.28.0-1), gnome-session (2.30.0-0ubuntu1), libgtkmm-2.4-dev (2.20.3-1), liborbit2-dev (2.14.18-0.1), libatk1.0-dev (1.30.0-1ubuntu1), libgtkglext1-dev (1.2.0-1ubuntu1), libglib2.0-bin (2.25.7-1ubuntu2), libglib2.0-dev (2.25.7-1ubuntu2), libidl-dev (0.8.14-0.1), libpango1.0-dev (1.28.0-0ubuntu2), libgtkglextmm-x11-1.2-dev (1.2.0-4ubuntu1), orbit2 (2.14.18-0.1), libgtk2.0-dev (2.21.0-1ubuntu2) Upgrade: libatk1.0-0 (1.30.0-0ubuntu2, 1.30.0-1ubuntu1), gconf2-common (2.28.1-0ubuntu1, 2.31.3-0ubuntu1), libgail18 (2.20.0-0ubuntu4, 2.21.0-1ubuntu2), libpangomm-1.4-1 (2.26.1-1, 2.26.2-1), libgtkmm-2.4-1c2a (2.20.2-1, 2.20.3-1), gconf2 (2.28.1-3ubuntu1, 2.31.3-0ubuntu1), libglib2.0-0 (2.24.0-0ubuntu4, 2.25.7-1ubuntu2), libglibmm-2.4-1c2a (2.24.1-1, 2.24.2-1), libgconf2-4 (2.28.1-3ubuntu1, 2.31.3-0ubuntu1), gconf-defaults-service (2.28.1-0ubuntu1, 2.31.3-0ubuntu1), libidl0 (0.8.13-1, 0.8.14-0.1), libgail-common (2.20.0-0ubuntu4, 2.21.0-1ubuntu2), gtk2-engines-pixbuf (2.20.0-0ubuntu4, 2.21.0-1ubuntu2), libgtk2.0-0 (2.20.0-0ubuntu4, 2.21.0-1ubuntu2) Remove: libgtk2.0-0-dbg (2.20.0-0ubuntu4) End-Date: 2010-06-01 10:36:20 Start-Date: 2010-06-01 10:56:07 Remove: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 10:56:10 Start-Date: 2010-06-01 11:02:51 Install: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 11:02:55 Start-Date: 2010-06-01 11:03:12 Remove: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 11:03:14 Start-Date: 2010-06-01 11:10:04 Install: emacs23-lucid (23.1+1-4ubuntu7) Remove: emacs23 (23.1+1-4ubuntu7) End-Date: 2010-06-01 11:10:54 Start-Date: 2010-06-01 11:44:10 Install: 2vcard (0.5-3) End-Date: 2010-06-01 11:44:16 Start-Date: 2010-06-01 11:45:10 Install: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 11:45:14 Start-Date: 2010-06-01 12:15:36 Install: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 12:15:40 Start-Date: 2010-06-01 12:18:35 Install: gstreamer0.10-ffmpeg (0.10.10-1) End-Date: 2010-06-01 12:18:38 Start-Date: 2010-06-01 14:50:19 Install: libtext-csv-perl (1.17-1), libtext-csv-xs-perl (0.73-1) Upgrade: perl (5.10.1-8ubuntu2, 5.10.1-12ubuntu1), perl-base (5.10.1-8ubuntu2, 5.10.1-12ubuntu1), perl-modules (5.10.1-8ubuntu2, 5.10.1-12ubuntu1), libperl5.10 (5.10.1-8ubuntu2, 5.10.1-12ubuntu1) End-Date: 2010-06-01 14:52:54 Start-Date: 2010-06-01 15:29:33 Install: libgdu-dev (2.30.1-1), libnotify-dev (0.4.5-1ubuntu3), libgudev-1.0-dev (151-12), libdbus-glib-1-dev (0.86-1) End-Date: 2010-06-01 15:29:45 Start-Date: 2010-06-01 18:00:50 Upgrade: arista (0.9.3+repack-0ubuntu5, 0.9.4-1ubuntu1) End-Date: 2010-06-01 18:01:41 Start-Date: 2010-06-02 09:27:12 Install: linux-image-2.6.34-5-generic (2.6.34-5.12) Upgrade: linux (2.6.34.4.3, 2.6.34.5.4), linux-generic (2.6.34.4.3, 2.6.34.5.4), linux-image (2.6.34.4.3, 2.6.34.5.4), linux-image-generic (2.6.34.4.3, 2.6.34.5.4) End-Date: 2010-06-02 09:29:33 Start-Date: 2010-06-02 09:29:49 End-Date: 2010-06-02 09:32:00 Start-Date: 2010-06-02 09:34:04 Upgrade: nvidia-current (195.36.15-0ubuntu2, 195.36.24-0ubuntu1) End-Date: 2010-06-02 09:38:21 Start-Date: 2010-06-02 10:43:33 Install: libmono-security2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-system2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-corlib2.0-cil (2.4.4~svn151842-1ubuntu4), cli-common (0.7.1), libmono-cairo2.0-cil (2.4.4~svn151842-1ubuntu4), libmono-i18n-west2.0-cil (2.4.4~svn151842-1ubuntu4), pinta (0.3-2ubuntu1), libmono-posix2.0-cil (2.4.4~svn151842-1ubuntu4), mono-runtime (2.4.4~svn151842-1ubuntu4), mono-gac (2.4.4~svn151842-1ubuntu4), libglib2.0-cil (2.12.10-1), mono-2.0-gac (2.4.4~svn151842-1ubuntu4), libgtk2.0-cil (2.12.10-1) Upgrade: libc-bin (2.11.1-0ubuntu7, 2.12-0ubuntu2), libc6-i386 (2.11.1-0ubuntu7, 2.12-0ubuntu2), libnih-dbus1 (1.0.1-1, 1.0.2-1ubuntu1), libc6-dev (2.11.1-0ubuntu7, 2.12-0ubuntu2), libc-dev-bin (2.11.1-0ubuntu7, 2.12-0ubuntu2), libc6 (2.11.1-0ubuntu7, 2.12-0ubuntu2), libc6-dev-i386 (2.11.1-0ubuntu7, 2.12-0ubuntu2), libnih1 (1.0.1-1, 1.0.2-1ubuntu1) End-Date: 2010-06-02 10:47:38 Start-Date: 2010-06-02 13:58:54 End-Date: 2010-06-02 14:00:49 Start-Date: 2010-06-02 14:01:57 Install: linux-headers-2.6.34-5 (2.6.34-5.13), linux-headers-2.6.34-5-generic (2.6.34-5.13) Upgrade: linux-headers-generic (2.6.34.4.3, 2.6.34.5.4) End-Date: 2010-06-02 14:04:18 Start-Date: 2010-06-02 14:04:49 Remove: nvidia-glx-new (12.6.5-2ubuntu4) End-Date: 2010-06-02 14:04:51 Start-Date: 2010-06-07 09:35:01 Upgrade: metacity (2.30.1-0ubuntu1, 2.30.1-1ubuntu2) End-Date: 2010-06-07 09:35:48 Start-Date: 2010-06-07 10:11:10 Remove: timeout (1.19-1) End-Date: 2010-06-07 10:11:25 Start-Date: 2010-06-08 09:14:18 Install: git-buildpackage (0.4.67) End-Date: 2010-06-08 09:15:02 Start-Date: 2010-06-08 09:15:50 Upgrade: libdrm-nouveau1 (2.4.18-1ubuntu3, 2.4.20-2ubuntu1), libdrm-radeon1 (2.4.18-1ubuntu3, 2.4.20-2ubuntu1), libdrm2 (2.4.18-1ubuntu3, 2.4.20-2ubuntu1), libdrm-dev (2.4.18-1ubuntu3, 2.4.20-2ubuntu1), libdrm-intel1 (2.4.18-1ubuntu3, 2.4.20-2ubuntu1) End-Date: 2010-06-08 09:16:03 Start-Date: 2010-06-08 10:11:05 Upgrade: dpkg (1.15.5.6ubuntu4, 1.15.7.2ubuntu1) End-Date: 2010-06-08 10:11:17 Start-Date: 2010-06-08 10:13:16 Install: libdpkg-perl (1.15.7.2ubuntu1), libalgorithm-merge-perl (0.08-1), libalgorithm-diff-perl (1.19.02-1) Upgrade: dpkg-dev (1.15.5.6ubuntu4, 1.15.7.2ubuntu1) End-Date: 2010-06-08 10:13:35 Start-Date: 2010-06-08 16:01:36 Install: python-gtk2-doc (2.17.0-0ubuntu2) End-Date: 2010-06-08 16:02:18 Start-Date: 2010-06-08 20:39:04 Install: linux-image-2.6.35-1-generic (2.6.35-1.1) Upgrade: linux (2.6.34.5.4, 2.6.35.1.1), linux-generic (2.6.34.5.4, 2.6.35.1.1), linux-image (2.6.34.5.4, 2.6.35.1.1), linux-image-generic (2.6.34.5.4, 2.6.35.1.1) End-Date: 2010-06-08 20:40:03 Start-Date: 2010-06-08 21:29:24 Upgrade: python-debian (0.1.14ubuntu2, 0.1.16ubuntu1) End-Date: 2010-06-08 21:29:58 Start-Date: 2010-06-08 21:53:57 Upgrade: python-aptdaemon (0.31+bzr412-0ubuntu1, 0.31+bzr412-0ubuntu1) End-Date: 2010-06-08 21:54:06 Start-Date: 2010-06-09 09:01:18 Install: fatsort (0.9.14-1) End-Date: 2010-06-09 09:01:36 Start-Date: 2010-06-09 10:25:54 Install: automake1.4 (1.4-p6-13.1) End-Date: 2010-06-09 10:26:24 Start-Date: 2010-06-09 12:01:17 Remove: automake1.4 (1.4-p6-13.1) End-Date: 2010-06-09 12:01:29 Start-Date: 2010-06-09 13:27:32 Install: gcc-4.5-base (4.5.0-5ubuntu1) Upgrade: libstdc++6 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), lib32gomp1 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), binutils-dev (2.20.1-3ubuntu5, 2.20.51.20100608-0ubuntu1), g++-4.4 (4.4.3-4ubuntu5, 4.4.4-4ubuntu1), gcc-4.4-base (4.4.3-4ubuntu5, 4.4.4-4ubuntu1), libgomp1 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), gcc-4.4 (4.4.3-4ubuntu5, 4.4.4-4ubuntu1), lib32gcc1 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), libgcc1 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), cpp (4.4.3-1ubuntu1, 4.4.4-1ubuntu2), gcc (4.4.3-1ubuntu1, 4.4.4-1ubuntu2), gcc-4.4-multilib (4.4.3-4ubuntu5, 4.4.4-4ubuntu1), libgfortran3 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), cpp-4.4 (4.4.3-4ubuntu5, 4.4.4-4ubuntu1), lib32stdc++6 (4.4.3-4ubuntu5, 4.5.0-5ubuntu1), binutils (2.20.1-3ubuntu5, 2.20.51.20100608-0ubuntu1), libstdc++6-4.4-dev (4.4.3-4ubuntu5, 4.4.4-4ubuntu1) Remove: linux-image-2.6.32-16-generic (2.6.32-16.25), linux-tools (2.6.32.21.22), linux-tools-2.6.32-21 (2.6.32-21.32) End-Date: 2010-06-09 13:29:34 Start-Date: 2010-06-09 14:42:41 Upgrade: acl (2.2.49-2, 2.2.49-3) End-Date: 2010-06-09 14:42:52 Start-Date: 2010-06-09 14:50:00 Upgrade: alsa-base (1.0.23+dfsg-0ubuntu1, 1.0.23+dfsg-1ubuntu2) End-Date: 2010-06-09 14:52:00 software-center-13.10/tests/data/apt-history/history.log.1.gz0000664000202700020270000001642212151440100024363 0ustar dobeydobey00000000000000‹i¸Lì=˶ã6Žûú /“SEE¢$Köº{1Ûé3@K´¬²^¥‡ïuùöHêi‘–oúVgræœN§b‚ ‚¨/ÿjYÝ’°–wÔvlbûĦ»uŽþþhÓ/ÿU4-˲ã.bYzª99¥ÅîÛÚ[ýŸ›„ØÝ©+ÚÎùõÛ®º·—² Ñ…×õ½º»»_\˱(Áojü³±ñwV$%iY’¤E‚Ѐ‚LNœumzì*IÀzêÒ,žÀU÷*>ï~q,‡úë—±fšáÑu¾|Y„»Û9öÑõhLƒU-©® ‰ù 9 ,ê[®œJðë—ÿ©’šÅ€À–Ÿýo»øÖaÿèÖ<€ð48Úû‘5ä+K›6º€y³û…Z¡·J`î…F gÿå¿y^Þðã-buŒsò‰ ¢ö’%lÃè‰ êˆì° ®700ß±„“ŠEWòû…ÀlZrb (c[¶÷ÇÛ¥ÀÂðÎ>8Ô ×ài³)o„k7À=ð7HxÉšÕy;(ðF·" o÷èPãŠx‡£»Ÿ©<ÕRêºk°ûöÑv ØQe.l“¨Jc¡G€ùr_Ðo»Å–~JåACÔ› ”Ì uJÆËmªÀË5žEztt‚@ätt¼Ùö-‹Hì/Ç:„_O¿×ŽãFRGJ òt–b/ `Íí ©ùN^C< £:UQë€×CÃõ oø†=éÄZ“fÄKígx)ýÃTgƒ×þÃîSAL7ù ˆ½§*áyaX{ ¾ý!†}Ý4B¯3ìm瀺P€w`øÉq©Ææî xs¤©2Ö\îŽr—ÜÞfâA¡¾=~úòϺ.ëãî_݉Tuñ¦ÙýÖ5õoàáýƒ{³«yÛÕw¬ØqÞEeŒFRË>úRGÇ$”ðè¹Gß™±Ÿ4¬ÉgyÄy‡oE›å¶pã„ÈŠî”ÂQì…t„éi~ÆT<ïéT‚©ínOe Gö9ÍxsoZž;–Z‚}ü7ÙË©ía"g~/ráŸÚ!ñq®iÊì<ùpÂÚ¸TAužS~®: GG7c]]*“S—4r÷r¤dç+ D¸°I“‚e²ÊÊ–F}ÐýÔÏ©Õ%89)«6- ²–¿g铜·¬Â‘qZðz å,I£ëׯ@m ¬p¡E’b kHÚ4ç¦Øò¦Ý–ÂÿÕœåfYHØ7vÛB„R]Ì¢mÚ’ÇikG”aäÛ³@J}}ü²ä\ˆjÞ˜N¬'_uuz¾óª‘–l*_–kó8,‰¢÷¸ÀÌ`\Lþ^ñ¨Ek቙xŠLqÆ S±ŠV‘$<^§œÅ¬Z§¼D2@JŸcŒ} ‡ÍÒ¥GzŒñ³i(Q³ãà¶”þšË¨]$íôÁã2}ðÌ£ÇiT’¼Œ»Œøs[Ö }[Øüÿ œà/vÿ‹ 8|uÝAG?þÓ¸pÍÎ@L½™Û¬Ld–ËV9©I¹ì$O‹4g™ðjÈPÝB+e÷g|øY[ÉÇPÐ8)£³A´«s2¡â6[<”^—Š–ô^ŸËSÁÑuGÒITñ$ÁÝî¥ýYòô¹‰Âsy?0e@ì™lâáhÛ𿙯‘(KyÑÂùpáü‘‹…èÏ÷Kå#iÓ¶RâžøÐä@åNÞ‚ÑŠÓÿäOn'V±èÂ)n@¢Å ¶]ÂñØŽ=RñZ¬µo êŽ5fplUËÀÏe}Å­J…uìµ@²J’ØÀ­p>- þ§5÷[‡19ít󬌮+29W" –óDI[’´ºß²ï'Õ÷ \—üt@KÍËÆ´³Û3Ys©%+ŒŠï ÷UÿÕü···¨,ÎiB¢2Wg£’²aÈÌà@hFÚx‘üÞüÈôŸA- ý‡ïƒÒN×ì Œ×K¦ÓdÐSǼq%„; µþ/(òÿkè_@C?Á¦ u4&«˜’&«.ü%\d•d²orú$n“#!üŸÏ”)g "çú!ð¢Kæ?[„@ò‰ýY®ð?+Bÿè™Rè‚þ|¦žøP{Œöþ""ÜcÌó„[L-™5!6:‡#&¼çiÛº¼ò Ʌ§y,wjqGƒèºÎó¸pÜfÜ•!•¿•eåÉ4’ÊÖ°¦pÈ&„pŒAÌ{“^† !­8 šË»oˆ·±·´ˆí  r²üa2‡k}ò õ·Ñº”Éç±3b(+^40™SÆ¢+^ÝðIj† ¦| øØµiÖÓÖ–·(ÿí–ûpôMù€x=Ó{¥¿É´aRÆÄâAÔ(̘K›g¤åy•áÕ \¨8Ê€%Œ (èš–×ÊÔ8}w¿–8îú«%¨ŒWA?Çtâ¼ÌÁVäØFÆ”ÚkÔ¼IJ 'œ3©ž:ú?cfHÖ¼…]qg?¨òÏãË7{ën0‹-P×x“–±×fÄjãû–É4h¸8ŽçÕåÞ«P#`?oùÊCBŒÎÞ‚ÙOcÊ{"Ù3÷?}ÅCsÙ„€˜dÖø2à~¶XÖ0jÓÃJ0?I#¸átÿ¼‚Ø7[÷€UE£Å¨dÝ™?V`ˆäP.* <Ç, ƒ¹$HBŒ%Aš˜ùøôì™—XeÅÀò‹ËÜÞoO8fɆäöÑͽhÙ;¹¤É%ƒÀ!/’´àä:ž@¶eû²~ÙÄ'pñ$é‚cÆà?Ŧƒá­‘MÕ§*ÔÎÓX% ]˱­5òÙíª@æ™}|O¤Ö©üYjÆÒ/ÔÛY™á’‰OÉÉoû SçQ}ùîákX£e³ÓñGët¢àŸuÔôuŸ Æ+ëk^VÙù;ÓÊÕO„‹)‰'ó £Ùy¢‰Ìƒã/.'žHË@Ë7ß ˆiMaã[%R¢\«›Ê®•—¨ÂV4ÍEdÅ+‡¸½©ý¤åØ?ÕË=Àôrš±nD«}? !°6dRWsg4ä‚][$LØaì!ÆËð—‘›w,Z™°Îs5”b–i¸Ñ tTVŒ(˜'ß?¼³77NÄUÕ×'xr“Õæýåû¸5>(yÃë–âðAÉû˜90"f’/niœ2Š  À”{ •ƒu @ ‘dïz(C!>"22„×õÃlÿ$µ&V©9Á_åÀÿ`Ï o00“Y±öÒp櫲@ÿÛNþ[Ò³­i !((«SñÄH»ÂÜ„´fC°oþ3Ì…ZwHA€¶'¾þ†°„_ÓV>Žñú´JSžÛ7Vs4³à•µÀ¹J|ðБXŒ¦¬Xí9|mHOàùÓÀ Ô þÈa~êùÙ P?q/Dõ3MÜw§ÑæßeÞXÍfZðëÀÝàÁEÌâ¶’™íYysÎ’RÜ‚Ïjž×`u,&'À w;[øµÓLYüA¹ËÀë?X¶÷Gû°Jd@¡»Ð!¼Ñ }†îr !g¹™ž>½˜ 3HHB8û…„62.G›„X÷wxq¬¬3KŸÝ¾ðÚg2Jï ˆrèìýÛ6Äb˜.k;Bøþ»z{ƒ®¨±xYÄb˜.æë!è,Yº1Õ†®=„xøôñm)Qè²=ZâpïkË-zˆàµwã0Û?BL*³_Àëšö æÄÑ@½Ì03©4>ÊÄ]ú:bO{' °TxZ9Þ@Ü•–E*éàÍŽxðj<´<ºÈ·‡ð> ãr˜IKÄTý^2¬ž°OD|ÀNÉgs&mӄ竌‹Tûü¯ïK9Ìt”aóÙÓÝWŸ*eˆµ¯3âóÑ'ˆ§:ø:ãº<¯€À*öÙŠ^x–•âjÒÀ·¥ {ˆ`Æ÷V¼ÁS~³÷?[ñ´Ù:ሜê«xÃ9y~¨óŒn¬{÷¤+k÷ù$å¯G7ÍÌQ']úøu·²î6‡Ú;|\‡§<éÄc/Ç ä} aŸÉœöÈm¸LU+Ž=nÿiDZýËBqï|Û…âõjïÞc“ÇüðÍPk|)ª¶••=¼ý,RÆk~ùüs~€]ÍÝþ¶›þç$=ô(9x–ª(Ø[ŽìR2þüºLÈc«‘»:Åm Ue*¸î2-‘`1•²¦ '9[ˆëåîjIðM #ro<‹"^G©(Áp,|è`“ ÇÖ²~Nž¶h¯‡Ø£Ábd Lp+¢eÇq¥~0]}Œ8}3U|¨ãM©6¬ï‚7àT½Žì©»²#u­{ô,`vмܶ€7gAU»I†œyO|ݽÖ÷¢¿ÉHWtmEvˆ'³¢HWØF[õHYÖ÷­‚¸9hÃAE ãϸ!IôýeRÔúWE]3RýÔ>DSb4 ÛÆ|*ìª*kyCáZÃzŠ~/ðƒ7]àÞR¬Ûú‘‡ÛCˆàÿ#üjiúZWIA Ý³—4IÒ^ÿ]Äj^W8CKº ךEüÏÑÖG=ÄK•§Ý²äëñEƒ¬eǬÕwD#Ê'‚Æ$&ù4"¹J®*{é$oØŸ –'Þf~\±4 Ð×éS¢â<æg®BH7ë£p |Ϲà-¬Lµ½Îgú‘ž¯-dpÃÖs*Š-þÎò*“Í6QU ©Ò÷SwÞ<#IîA+¹m€ŸÞÃ0§5Úó‰èHæ ´ ãz“')Â"ÛùX¶w‚Átú€Y gÐGˆP}×1­ù ]N@ìô€7£ók[Xp;Âá9žâKtaEKu<¤ý -IÊ2Ækœ¹Žå¤ JöÄZœ0*ȦXâ#Í¥¯<ÐI=pE,Îù5ð s3"5Oìæ£I•Ñ`·£˜iÊ8}?£S &„öujÓÃŽq ºÛøA6*À_É•ßO%«cÒ\àü‰º¶™ƒ¼åi/<çêwùxBü¾BÄCr0/êóÌþ PÞ“ïòTx‘sI¯IPÉg¯í&ACä9³üy–Ý;¹p8Pê†àëPדŠü“åH*ͱ3]ÿ…$¼àu­Ã.1®CgY’Ö¨hÜÑÜÈz™ˆdoGÔ ƒ°s”¢ÁC|ð.ÎÖ2ý1·§ñ©k$m‡ˆk¿Ð“¤ÃýHx1-QÙE—ø¤ðY¶Ä¸5©ƒ£;øýu¶‘§sZçxó)Êõ<ô˜\¾`OØ.ÖÆ š\€û!ⵄzXU/þäØd©c/2ÒcÁfD¨òá6î ßÙM(ŠÛG ÎÅðãÒÅYuÎ6¨—êj9S°µý¬vk¨OÉ«8g?KaHº@¥¹¶e¥º[Gú€M ÔgØüE,Ò®ëßçÍ1úÏ:6ºxá;³—¡×ÏS—É•.“]ÁšŠgøUØj ¥î[“ Þ®¨X%/2\¡CøH­<ŸÓˆ[e¸fox®‰¾+3÷æ±sšGeÿ%ü÷4 Ó÷î’SÇâ½;ñ‚d!HÓpT&RFòù™G&Óø­Ì: zׯžîå©ÃÁ²mõÅ<ÊXÍÚô&ªúËþã ü`8°‡W5²1™+lÁ`3×½ ¼¥EÄד’– ë&Y™c]{Î넯‹úZð·¦íÎ2ñ%v/#t\ÄüÞ¯1éš…;~×W~ø î:¿i^ÕÛ¯2û½â訄§þ€/xUæÝõGÇë»ÇÖÐ?4¾ÓiÒ„`ÇĦ‚‹Y,ZÝy¶B‚ËâM*]ªZ5ªQ”Ìße!ÿ¢Õ]3„ì²öÆÙ׫"B)b]qÍ}Ýgì^}ÞJ-vƒýÔòÚ–Ë´|zÍa8«2v×0¡T²-AФÊ*V«çgKÕï`˦A~·¬ø…Ã&˜©W0CÆn`”ا+œ7¬P)JÇ#‡GäM{Ï8¹t9+VT Θ¢9§‰üä[ŒÈ~m{Æ“€,auJÎ¥xÞƒÄÃñäXPÂîs+$PouÚ¢ØÖ´ Aë–/û=g‡>ÖÍŒ¼Ôpí`±V1àŒ’î»°VÛæ#Â…G‰ž–g]Òi°]-šF;0uÛ辺Æ’<.uÿ펴ëV™Y9:AdÏ2ÝÄ®µe™5µîŠR3ÛæžŸÊŒÔöFA‡Ì/MÄìÁDƒ8h&ÃaCˆ[¯ÕYÏOˆ¯_gDN^4eÝô-ô÷ÛŠêú8êlŸÊY€×Eª2¾¥ü×cg˜á™Éµ+Òb°SXª°:›ó9#·²S«øLãï·N¦ô¤CÐvïí½âòÅ„…ÿ*rðGZ–421}˜+]ÕÞWÇH“ÉËÓë)›¨vºñý_ú5ïj ò6xYe°µª0±âª.ô“þ!Ov\%¹#Ì‹´lÁš&Åû¿íûw÷ièoþíß[…”é«ÿQ¾«…mÓr&qÁºZ­4¿”Ý2fQölˆ®Àk;{ý5õ Ú?+ðÂÐÓ‘m—bŽ¢è„XÚ2´õý•ûýoiG¶ã6|ïŸT«DÁvÜäs|°Y7ŽmùÚMúíe†kÀ˜T­Ô‡Uƒa`î &á¬ó!1zJ¡S¥?ö¥T]_öR­—zba^gû. _WE·“YÄw'bü²G¸@–û/ì¤ {†Rl'öwSÄá .‹¢*“0˜lwâPÆûaúY]&sAdêJ^õó®Ì…ߧŬE¨¿‡‘3ÐÐ×èÇkÐQ&'ömB «jåЛYþ+¼§Í›º0Ä“ûbô×ÒŽKס޻ZÉy¯áM¸¾M×wíÓ;[ÇÓ3“—(#„·–[Jººò†¯u_-P)¶ÇÁ//´¡F¹1ãîeBø°ãY½d»Õ`áêù`o[@Gúä5ªr™½@ÎÑT‡ú„W:X6G&Ì{e€ªøs«¼§¡‰èn´ZAäª}e¨DÞº‡oO*»Ü÷j-¥ýéÙÆ¢³N‚'aµ!Yö`ªŠ®äãXP9I‚1žX„çs˜¬°J\RÖš`b .££.—Ý'q†ÆôÓÕiõµ¯Ú¦ì­ü5f'·gû¿»àô{Çèç®etO¤’ …`×ymËïçÃßÕÔÇdÔéå‘íß<¯GÀ•üNᎩf1ù³ö‚ѹcôÃè_/GÑiÓ£#»¹Â[¹±Ý$)ÜÜà´„cÁ6v¥Ìó0ý[{¸þœ~û{‰Æ¨õŠsoftware-center-13.10/tests/data/notadeb.txt0000664000202700020270000000023012151440100021261 0ustar dobeydobey00000000000000This is not really a deb file. It was created to test that DebFileApplication follows the right code paths when opening a file which is not a deb file. software-center-13.10/tests/data/applications/0000755000202700020270000000000012224614355021615 5ustar dobeydobey00000000000000software-center-13.10/tests/data/applications/deja-dup.desktop0000664000202700020270000001345312151440100024671 0ustar dobeydobey00000000000000[Desktop Entry] Version=1.0 Name=Déjà Dup Name[ar]=ديجا دوب Name[bg]=Déjà Dup Name[ca]=Déjà Dup Name[cs]=Déjà Dup Name[da]=Déjà Dup Name[de]=Déjà Dup Name[en_GB]=Déjà Dup Name[eo]=Déjà Dup Name[es]=Déjà Dup Name[eu]=Déjà Dup Name[fi]=Déjà Dup Name[fo]=Déjà Dup Name[fr]=Déjà Dup Name[gl]=Déjà Dup Name[he]=Déjà Dup Name[hu]=Déjà Dup Name[id]=Déjà Dup Name[it]=Déjà Dup Name[ja]=Déjà Dup Name[lt]=Déjà Dup Name[ml]=Déjà Dup Name[nb]=Déjà Dup Name[nl]=Déjà Dup Name[nn]=Déjà Dup Name[pl]=Déjà Dup Name[pt_BR]=Déjà Dup Name[ru]=Déjà Dup Name[sk]=Déjà Dup Name[sv]=Déjà Dup Name[th]=Déjà Dup Name[tr]=Déjà Dup Name[uk]=Déjà Dup Name[zh_CN]=Déjà Dup Name[zh_TW]=Déjà Dup GenericName=Backup Tool GenericName[ar]=أداة النسخ الإحتياطي GenericName[bg]=ИнÑтрумент за Ñъздаване на резервни ÐºÐ¾Ð¿Ð¸Ñ GenericName[ca]=Eina de còpies de seguretat GenericName[cs]=Nástroj k zálohování GenericName[da]=Værktøj til sikkerhedskopiering GenericName[de]=Datensicherungs-Werkzeug GenericName[en_GB]=Backup Tool GenericName[es]=Herramienta de respaldo GenericName[eu]=Babeskopietarako tresna GenericName[fi]=Varmuuskopiointityökalu GenericName[fo]=Trygdarritingar amboð GenericName[fr]=Outil de sauvegarde GenericName[gl]=Ferramenta de copia de seguranza GenericName[he]=כלי גיבוי GenericName[hu]=Biztonságimentés-kezelÅ‘ GenericName[id]=Peralatan Backup GenericName[it]=Strumento di backup GenericName[ja]=ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ツール GenericName[lt]=Atsarginių kopijų įrankis GenericName[ml]=വിവര à´¸àµà´°à´•àµà´·à´¾ ഉപാധി GenericName[nb]=Verktøy for sikkerhetskopiering GenericName[nl]=Back-up maken GenericName[nn]=Verktøy for tryggleikskopiering GenericName[pl]=NarzÄ™dzie do tworzenia kopii zapasowych GenericName[pt_BR]=Ferramenta de Backup GenericName[ru]=Резервное копирование данных GenericName[sk]=Zálohovací nástroj GenericName[sv]=Säkerhetskopiering GenericName[th]=เครื่องมือสำรองข้อมูล GenericName[tr]=Yedekleme Aracı GenericName[uk]=ІнÑтрумент резервного ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ GenericName[zh_CN]=备份工具 GenericName[zh_TW]=備份工具 X-GNOME-FullName=Déjà Dup Backup Tool X-GNOME-FullName[ar]=أداة النسخ الإحتياطي X-GNOME-FullName[bg]=ИнÑтрумент за Ñъздаване на резервни ÐºÐ¾Ð¿Ð¸Ñ Déjà Dup X-GNOME-FullName[ca]=Eina de còpies de seguretat Déjà Dup X-GNOME-FullName[cs]=Nástroj k zálohování Déjà Dup X-GNOME-FullName[da]=Déjà Dup - sikkerhedskopieringsværktøj X-GNOME-FullName[de]=Déjà Dup Datensicherungs-Werkzeug X-GNOME-FullName[en_GB]=Déjà Dup Backup Tool X-GNOME-FullName[es]=Herramienta de respaldo Déjà Dup X-GNOME-FullName[eu]=Déjà Dup babeskopietarako tresna X-GNOME-FullName[fi]=Déjà Dup -varmuuskopiointityökalu X-GNOME-FullName[fo]=Déjà Dup trygdarritinghar amboð X-GNOME-FullName[fr]=Outil de sauvegarde Déjà Dup X-GNOME-FullName[gl]=Ferramenta de copia de seguranza do Déjà Dup X-GNOME-FullName[he]=כלי הגיבוי Déjà Dup X-GNOME-FullName[hu]=Déjà Dup biztonságimentés-kezelÅ‘ X-GNOME-FullName[id]=Peralatan Backup Déjà Dup X-GNOME-FullName[it]=Strumento di backup Déjà Dup X-GNOME-FullName[ja]=Déjà Dup ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ツール X-GNOME-FullName[lt]=Déjà Dup atsarginių kopijų įrankis X-GNOME-FullName[ml]=Déjà Dup ബാകàµà´•à´ªàµà´ªàµ ടൂളàµâ€ X-GNOME-FullName[nb]=Déjà Dup verktøy for sikkerhetskopiering X-GNOME-FullName[nl]=Déjà Dup back-up maken X-GNOME-FullName[nn]=Déjà Dup verktøy for tryggleikskopiering X-GNOME-FullName[pl]=Program do tworzenia kopii zapasowych Déjà Dup X-GNOME-FullName[pt_BR]=Ferramenta de Backup Déjà Dup X-GNOME-FullName[ru]=Сохранение данных Ñ Déjà Dup X-GNOME-FullName[sk]=Déjà Dup - zálohovací nástroj X-GNOME-FullName[sv]=Déjà Dup Säkerhetskopiering X-GNOME-FullName[th]=เครื่องมือสำรองข้อมูล Déjà Dup X-GNOME-FullName[tr]=Déjà Dup Yedekleme Aracı X-GNOME-FullName[uk]=Резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð· Déjà Dup X-GNOME-FullName[zh_CN]=Déjà Dup 备份工具 X-GNOME-FullName[zh_TW]=Déjà Dup 備份工具 Comment=Back up your files Comment[ar]=إنسخ Ù…Ù„ÙØ§ØªÙƒ إحتياطيا Comment[bg]=Ðаправете резервни ÐºÐ¾Ð¿Ð¸Ñ Ð½Ð° файловете Ñи Comment[ca]=Feu una còpia de seguretat dels vostres fitxers Comment[cs]=Zálohujte svá data Comment[da]=Tag sikkerhedskopier af dine filer Comment[de]=Ihre Daten sichern Comment[en_GB]=Back up your files Comment[es]=Respalde sus archivos Comment[eu]=Egin zure fitxategien babeskopia Comment[fi]=Varmuuskopioi tiedostosi Comment[fo]=Trygdarrita tínar fílur Comment[fr]=Sauvegardez vos fichiers Comment[gl]=Respalda os teus arquivos Comment[he]=גבה ×ת ×”×§×‘×¦×™× ×©×œ×š Comment[id]=Backup file anda Comment[it]=Esegue il backup dei propri file Comment[ja]=ã‚ãªãŸã®ãƒ•ァイルをãƒãƒƒã‚¯ã‚¢ãƒƒãƒ— Comment[lt]=Daryti atsargines failų kopijas Comment[ml]=ഫൈയൽ ബാകàµà´•à´ªàµà´ªàµ ചെയàµà´• Comment[nb]=Sikkerhetskopier filene dine Comment[nl]=Back-up van uw bestanden maken Comment[nn]=Ta tryggleikskopi av filene dine Comment[pl]=Tworzenie kopii zapasowych plików Comment[pt_BR]=Fazer backup de seus arquivos Comment[ru]=Резервное копирование файлов Comment[sv]=Säkerhetskopiera dina filer Comment[th]=สำรองà¹à¸Ÿà¹‰à¸¡à¸‚องคุณ Comment[tr]=Dosyalarınızı yedekleyin Comment[uk]=Резервне ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ð’Ð°ÑˆÐ¸Ñ… файлів Comment[zh_CN]=备份您的文件 Comment[zh_TW]=備份您的檔案 Icon=deja-dup TryExec=deja-dup Exec=deja-dup StartupNotify=true Type=Application Categories=GNOME;GTK;System;Utility;Archiving; software-center-13.10/tests/data/applications/kde4/0000755000202700020270000000000012224614355022444 5ustar dobeydobey00000000000000software-center-13.10/tests/data/applications/kde4/soundkonverter.desktop0000664000202700020270000000040712151440100027112 0ustar dobeydobey00000000000000[Desktop Entry] Encoding=UTF-8 Name=soundKonverter Exec=soundkonverter %i -caption "%c" Icon=soundkonverter Type=Application DocPath=soundkonverter/soundkonverter.html GenericName=An audio file converter Terminal=false Categories=AudioVideo;AudioVideoEditing;CD; software-center-13.10/tests/data/app-info-json/0000755000202700020270000000000012224614355021607 5ustar dobeydobey00000000000000software-center-13.10/tests/data/app-info-json/apps.json0000664000202700020270000000056112151440100023431 0ustar dobeydobey00000000000000[ { "application_name": "Ubiteme", "description": "One of the best strategy games you will ever play!", "price": "12.95", "package_name": "hello", "archive_root": "https://private-ppa.launchpad.net/", "archive_id": "mvo/private-test", "series": {"10.10": ["i386"], "10.04": ["i386", "amd64"]}, "categories" : "Application;Games;" } ] software-center-13.10/tests/data/appdetails/0000755000202700020270000000000012224614354021254 5ustar dobeydobey00000000000000software-center-13.10/tests/data/appdetails/var/0000755000202700020270000000000012224614354022044 5ustar dobeydobey00000000000000software-center-13.10/tests/data/appdetails/var/lib/0000755000202700020270000000000012224614354022612 5ustar dobeydobey00000000000000software-center-13.10/tests/data/appdetails/var/lib/dpkg/0000755000202700020270000000000012224614355023540 5ustar dobeydobey00000000000000software-center-13.10/tests/data/appdetails/var/lib/dpkg/status0000664000202700020270000000200212151440100024762 0ustar dobeydobey00000000000000Package: software-center Status: install ok installed Priority: optional Section: gnome Installed-Size: 1840 Maintainer: Michael Vogt Architecture: all Version: 2.1.4 Replaces: gnome-app-install, software-store Provides: gnome-app-install, software-store Recommends: lsb-release, python-launchpad-integration, apt-xapian-index, update-notifier, software-properties-gtk Conflicts: gnome-app-install (<< 1), software-store Conffiles: /etc/dbus-1/system.d/com.ubuntu.SoftwareCenter.conf d5c450e6bccfcb8177943516e8beb673 Description: Utility for browsing, installing, and removing applications The Ubuntu Software Center lets you browse and install thousands of applications available for Ubuntu. You can view available applications by category, or search quickly by name or description. You can also examine the applications already installed, and remove those you no longer need. . To install or remove software using the Center, you need administrator access on the computer. Python-Version: current software-center-13.10/tests/data/test_images/0000755000202700020270000000000012224614355021433 5ustar dobeydobey00000000000000software-center-13.10/tests/data/test_images/fallback.png0000664000202700020270000003257212151440100023673 0ustar dobeydobey00000000000000‰PNG  IHDRCÈ¥=­>tEXtSoftwareAdobe ImageReadyqÉe<fiTXtXML:com.adobe.xmp °1ªIDATxÚìkpç¹ß¼ ¢aF–h)¢ìÈvn–˜s’žÎ‘5sfÒ‰fjM;ef:sÔ‘&ê'³ŒòA>R>ÔãɸúPKµÚFõ9¥Ï9i”J±rgGJ"ÛrbKô=6Mù.Ê&u£hŠI ®} ì¾ b± îâþûóxý`ñî» îÏûß÷õ¦ÓiÔ!^”JPr€’h"%9ëë ´ßóê¦Â!_ Xð‹¾™Š„åä"¸•¼´=Ui†Ãäèèp:•òú|›¶ÐÏôs…šñÑ[Ù仿b/y!“ÜgÝÏï¼’íç/ÿËìq-ÌúVÜRxÏÞNG¼+ÚïÚ¬÷ƒI~ll4]ðu¬h۸ɲ±‹ç³ÉŸ»7¿Ÿæ|+ºŒÉï¤3Éw~yé®òòc¿›Ýó_²nÆ'ï¥#Þöζ;¾h<þ’ÜÑÙÖûy;¨öóÒ\Jüò…ÔbÔ×ÞÑúÙ»´éhØÛ(|e,[ô¶u´®ûœõž¯~œIno]{§uòµOD²¯­ÝûKÿ¯ÚQÚ&&'ÒqeÏþ5½–{NL]NÇcJòmë,““3×Ó‰¸×ßÚÒ½ÆFò”hšÇëm¹uµe²ø §“Io‹ßÐ/àt2!¶º®æ=©¤Ç×â[±ÒzÏ‘p6¹3`É)O| %gö#] é´r!yÛWXî9‹ª{—‡ur<æŠÂçõúÛ¬“ñŒñì«%‡RNŠÇ+Z"ïCÙR`×9Uãõ6‰’ó-Ý4ûÔ?kqèWƒZ–kóBø¹Ÿiý|óÉÃZÎÍÿ#/VûyþÙŸæ}V6ç§ÿKŠ¿/î"ê½DÄzþoê9?ÿßZ¬œ»d"•yKèk²/ô‹¿×Ïi.iážJ†¦³ÛùÙ[þôչߗ®ÿ£$/Š}zçŸù©rïQ· æŽ}nîô“zþ¯ÿ1µ·ñ:ûÔ?IÇøÏãðó¿Êh”hæ¸~¨çœ<*Å?ÊÝ&# ¯œ‚&w¼Ç¤c×Û<÷‡çúÙ»ðÊ3úöÓ'´xþÙŸHñO…Œw1qžîgzÛ†Oé9g~¡Å¢ Ù{¶ØþÇ_é9RþÓ¯sm^ŒŽœUÏ£²ý…§r²lA‹•ígŸV“E×EÞøc:‘Èn?wR“q o<¯·aøT*¾¨¾eá¥ßçµ-¿ª{ôüËâ&®ž¾ÈŸÿ¨#ÞzAþ-§‰†ØÇïimX|ÿ ù箕/¼ÓŠ`•Õ¹®M'>ÔãK §DRÉøå1Y’ÊÂW׈Ÿ^Éê*%g\Ï¿>!Å—4ù•¼9%®ÿüíɤP±’¢½’:žäëâ*ÉnW?+³ŸÄÌu½ Ó×Ò™q9%oLÊbרesñÜŒ¾ÿÙi]Y©ÛÕxþ¦&¡M™Râ‘$Lçe)))ÅÅÂqî‹™9}1õ¤( ê¿¬bNJ\Šs°$NŽ=u?2Y &'ÔtÇ—ÿª6W~-iqì“wÕŸømw|IÞ^0ŽŒ¼(>K-˜åh±øj‰«JüxR+ rÎÒö¨e ¹ÍEö_R›£ï¼*. ûm·j_×­KÛ,Çj F­‰+¯ó/ÿ†~¦Ÿ+ÔÏï¾–í绿bÝæFÄ_gßÊ[Õšœi?gjijMNÜ0:û¶ïg‘¯i2Õ>ÿí´:\:ó¶µ-­Ï Åàñ·ejr÷ÊÛåš™Ç.ž_¿Ôºf½Z²’s”âMk›œûøT4âIÄôz­Y~dNÜDE²h†Íg}]Úv)ž÷uf«,‹üY)tt¶mø‚¼½`Ÿø@Ü)[º>ÓÚ{O~N4¬ÕWÔX©É‰?×ÉTû=}š@ñú[ ÄÑpBܰ3e6O¯¶=¯"¢m u’LzÛÛ[×~ÎtŸ¹XH“TxÖ¸E­ÉÏWNwFŸiEG!§¼~¡ö$’7®ªWKËgÖj9r¾\ëJ\O§“ÞV¥&'o7ÔÃÄݽ¥%[“{nïÈ–ÙrÛóëU™XÑOÊ]1­ð2E©¥±ø,¥¾¥ÔäZ”çó.&å¶‹~S„š,†×§«%±ò;*÷´´fkrEó³59G¯%Ë9Æz•øý w½mzÕ*.+?3”2[¦&g’£ÅŠäR xjM®h¾è=UN¼>óklJæÒšœy~i59“Ú^ý*9¨,äm*ª©æÖZ{õ¸ègúÙÑq-Ì–5¿ôöÌ•–™kÈ󒎆k«=¹aåFëçÜhÍ|!“ *`¨C™+9Ù‹SÄWTj¬ [úŠŠä»ØžšŠégúÙÖçÚðÃcÙgâ“bƒÎÔ÷?õø'r¬ûáfŸþ¡¾]òÃÝüÙ=–üpšoÌ#ùá2ñßë>¶_þ½&Ë4œ'ç{ÓbM´i¾·äôµ¹?Ⱦ·'¤øµýk8!ãæOëž3ƒîéHñÿÕãßJq!?œ`îÔÿÓãßóÃecÉ7÷Œì3øáôXòÃÍŸù¹ë~¸°ä?3÷à éñ ¿–bÉ÷âo4»’ˆs²l!üâÓzÎY)>wR÷Éå¼zù>¹—~§Ç/[ûä"¯ŸÑã7ÿ¤Ço½¨ÅÑÑ—ôøÝWµØà“ûè--Ž×cÉÿ@‹ã—>Òã+º7NöÃ%®K>6K?\æé “øŠî{›’|oš.c5ĹA6}{*ið½™zã¤øæ§zÒ½qÉÙº84øäBÒ¯ÓYéѼ‡¥_ Õ|QŸœ›øädµš*Õ'W÷]-îãY^,?£ç$ß­öT+.÷qÑÏôsEûYò®™Waœá½›¾]ÜEZ‚«4·Ô— ÑÖ²êö¥Ûó|oš¿-ºÑüÌÒíÆ| ¯Ûrâ輯ÃÔ§Õϼc+[‘|Ãy±³³xqA{ìÑ<_ò·IϨñÉöÆ™øäìÄ?œY¼Ä'§«BÞ¸<ŸœþYrŽ¡&m·òÆåÇr{Ló%ÿ™!?]Ø%fåu«D\ª/ÍìX ˆ’€ºŸÇE?Ó·«Ö|r‘R}ró y^äQ­šh>¹ }!Õ'æJÿVåcú™~vÏ'gæ“â']òÆçŠÓb³ùáB?—=pz¹!½ßBCOȱ&ÚB¿þG5ÈøätÏ™aN¸§þY÷ÉåüpBÆæÆûmýpæóÃé± ?Üüó¿ÔcÙ÷raŸœ6Wœ§¨7NŽ%ŸÜÓ9Y¶`ôÆýV‡ ûääyàŒsÈýA%oÜÂkÏj±™O.úö9=>/ûä^ÓâÌL1¹8;‡œBìbaŸœa¹ËôøÊE=¾ú‰'e›ì3óÃÉù†øªî““|oùÞ8ís§¯iö/%Îå|oŸÜTáXÎ7›C.7A¦Ç¦O.jâ“ËÌb˜ãf>¹xA•lPÌøäòOà+*[Œ‹~n:Ÿœ‰ÎÜ''ùÞœøäL}o’OnöFË-šON÷±•Å'gâ3õÃ9òÉ)«h,Ýn8/õâ“«¨7® >¹R½q¦>9þ³FõÉÕÿ|oUPrÐJ®È:•U¡ÖÚMÎ[[ÚïÚT¼ÙôssWÃö³ùº«&ùù뢺Ü©g/_¯¥5r-­6ÚcXwµqúÙdÝÕê}! ë®BãOÎV<=ø_"#gÅ–éÁ‡ÆÿãW/}ûëc÷¯ÿDpõ»ý–ÿ>þ÷w‹dñ*Þ;ùÈn±Ÿ¹Ó'¦ÛC?{ðÉY~Væ+‡7Nž+®ÈüpzüK=–ýd†ùᆥuQŸÐdŸæóH~8OƧùä4?\rúÚüiyMXÃüpºO.çS|rÏý\:;~8)6õÃ=)ÅfsŹã‡3ÄÒüp /ës³ýpv¼qO˱î“;û[M&j±ÇÜ'·`擳1‡œ¹ONZkÕà“{Y‹ß{]?Ð×Z5úäÞÑcSŸœÉr×$Ÿœìc³å+ì{“ç‡3øÞ´XÞ®xà$ŸÜ ŸœaÞ8oœ­9ä$Ÿœì³³Öªa9Ù'gg9ÉÇ|rEjrøŠÔD^>6þ¾ø»°xa´|϶mÜÔ~×&ñÚÙ·U¼zðoá“«WŸœ>oœá¸L¹ØÄjU©­÷ó–͈_ú0µõµw´®¿Ç:ùò…t,êmëhýì]vP-l,=À{¾úqvÏkïÔ+&>¹øµO²É·ßa½g‘_ôµ¶ûm$'&ÇS±E_[»¿gCJ‹Ñ'—¸>‘©†¶û׬·Þó§W²É·­µLNÞœRkŠ-·®¶NM«ý¬=7]e0Q©ÆµøVHÆÊdÂSÈ'§ÌÄ‘)Ýi•Úbe¨èB:òz}ZyµXòâ‚ú,ìƒ4úäD·g÷l䍨B2%Â# ùɉx6¹P8?97NjËS(v«æ¼>É9UÓ4+Cè”Ù§tIºŒšxµã+Rö™ˆ«o±ô‰´ùÓ'´éjŠûŠÔ}Êm.Òž¼6 7uxÏÅ·~ò‘ÝsâkišÖØØ¨êÉ»þß¿ø›ø•‹õÛÏÖmÎíß2?ÛæÜ~ÅóÓ·ÄÜï\×óOJìg}}Ï"Þ8uŠ&¥Í6¼qs™µPÕý[zã„$ Ÿù…6TqoœÚÏógtÏ–q~8Ù÷©hDü}¯²Nö“ÉóÃÍ?û3qSLe6šNȲ¼9á´6/ˆc9[æ‡KÞ˜Tö£ågÖK2Nù ðü¯tŸ\Χøä$/Úì©©¶žôbÔè‡+¹tL9B&ÚñÉ-žÅ“sS™Ï'÷²¦0âãhm0ŸOî­ŸåõR >¹¼µV{VZ™ãM^_ÕÄ'—³-ó}1›N‹…Øs:gÿ2õÉeüpêw0ysZòÉMêù²ïmæz:cK§Óoœ‰O.{[̼Åz­U±SñϵÁÎZ«ÙWÄ'§î2mÏ'—.Õ'W÷#“jr^[Ç—ÿª 6—½2±OÞU‹mw|ÉÒOyQÑÒ™Jƒ¥ÿfñÃ7ÅÕÖÒ½F-K÷©5 ¹ÍEö¯¶Y„‘?¿0æg‰É‰::U+þú_¿ùŸ:û¶ÖK?Û¼6¢ï¼*¾´öÛ,$‘¯ëÖ¥m^êKSkrâÛÞù—Ó ×ó{jM®míûùÝ×ÄDµ&gÝæ o‹¿Ô¾•Aµ&gÌÉ_GU­Éy’©ŽÍ]ฌ8ñ¥S+gþž^ËuQ•Ð[Û|ímw~Ȩ́Ţ7Ä]Ó¿ú³jɪ¸ï-6þ¾rËIÄÛsý\$_)ÿdÊl-·­³ôÃe”7S“»ÛÒ÷¿<–šW®çÖu=Å|rŠï-.ÚœRDCÛÆ{µ;œ™-qãºzÖD‡Xúäâ—.Å ’[o¿ÃÒ'—¸6ž9ï]jM®¸ON­É)ƒ ½÷,Í1¶'‘¼qU-³µ|ævKŸœ¸”6·¶ùo[gé“Sjr™ÞÈÖäŠúä²59§åÖÛäíKcñ¹Š(Qkr+,}rÊÝ'«É™yãr±R“Kƽ-­Ùš\Qÿ™Z“Sj3Z-ÙÜ''T~¶&×ÖaéiS~ˆÿôùÄy±ôÀå×äŠúäJ©É¥•·S“³¯ä›ØØhhhPþ¡\wˆû_÷ÎýûîoÈÙ+ $ È[yÑ,v+_V°¹ŸeÇ‘‘³Wþîß\úö×ëZÆ©¿D§ïßýUe@0st5ÕÏ5'®_jìëÙq<[8^(-–gR0æÌYƦ3)H±Œ!GšA^}HžõÀ°}v¦àvc,ÍÎ0c'6[éÈ,þÔ:™Ì!ÅÆQªÂ±!Ñ:ßPÎp’ŸVR’gš(5Ž™ÅQëØ°â“Iœ°1Û…aæ “Õ¢¤ï»qÔÏdÐð‡#i›åËõ;û)9NYÇi;qÚýØÃ3šö”œìÝ1‹Íæè*’¯]ôÚvqk‘ý7yïÕn¶^_5qåc=¾&ûá¤_†9áôí‰× þòIH¿ f¿äuTµ_©drfÒôׂæ«Ó~!äùäLæŠKÊ¿v$?\Ò0oœükPþ…&­¯Ñ•}ªä9äl¨gY3Ÿ\ŽeŽ®šy}Ì•Gæ²OLNL>>Ùàb<\5p kÛOã®Pnêâzn„~–ügv×Eµu^tm†²Ÿ“9çLókl­ˆúéè”-›—¬NX›ʧäj¾dš9v(44Ø<'ÒßÓ»zïõyhÐ'7æç㻿ÖT2Γ)@^ýnÿä#»µ®À'××s­yãŒ>¹«Òö¹’â’}r6|o¥ûäêÃWªïM5k,Ÿœ ÏœŸ\Ü-Ÿ\´àv|rÚ‰O®ÂJ®~}rªš¹~ð?×ÔÌp•$<|r|÷WU‹O®Ž¯ç’½qv|rRlæûµ7N^{Tö«™ùá kþA_{TöÃ…ÏJkzJkwŠvòÉÍÏÿIöÉéö‰ùç~¦ûäró–)>¹¥µD%\øeÝ'§­û©øä^1óÆžÛÌÔgðÃ=/Å’?ìÏ…ýp†ø­ ·Çëm‹¾%ùäL¼qFŸÜÓÅÛñÉIý&­‹º ûäÞx^Ë7û Rlæ““æŠ3øä^ÑâÅw_Óc3ŸÜÅózlðÉ} ÇÒúªö|r’NžÎÌ'wÃ-Ÿœñ‚µOÎćO®žiŸœ/3Ç5­†Ë£³oëê½GXò«~¯çÆìg|rµy^ðÉU¨£ñÉA-)¹šBÜV§ûNÃ?ÙP*Bj¬~ðû-Ûé €†½ÝF–q¹óíÇ‘‘³ã»¿†Œ+ØÏ“ìž:¼GÍ૟\ÝÌ!·P%Ÿ\ÄÄ'g2Ç›Œù¹ÒýpåöÆM/;®iŸœoœ[>9[ž¹:÷É•=Æ'WÿJ®’¾¢"ïµã+ ^ýn?#ªE˜;}âÊßýÛ"ç Ÿ\­\Ï¥zãœÌ!÷”ì3™CÎÄ7wJŸ+.,ùÕL½q§Ÿ”¶ëórË~8ƒONšnöéè>¹Ü¯5!×ä_nšΓY·T÷Éåæ2NžCÎ8WÜïuŸ\Î㕊Λ{ãlÌgËg2šä‡‹Ê±a~¸ÂÞ8Ù÷•æ]3÷É=W°ÿ Þ¸×KœOÎà³ã““ûÁÄ'g♋¾=¬Ç²ONžOî}i}ÕF´866ªÇ²On\öÉ™Í'÷IÁ_2¦>99vâ“›‘|rE~9”æ“3ñÌÍׂO.aã“+@½úäħª÷*'ØÁž‡2GIÍ^ÏÍÒÏ%úä„,óÝÒ]B>>¹å|rêh|rPKJ®ºˆûè•ýýòï*°Ãê½GÔ „ 1hùÞ÷¾'ÿwdälbr¼µgƒ¦™¼mKcÁâ‡oÆ/_HÍÞðutÌ‘c-Ù¿êv³-ɱ #©…9‘¼4'~é£KßþW‰ëœ¼RY>%ú­³o«~΋Õ3hçÚqì“÷ì_‘‘“®\Zœ¸~ɸ%::¿6ÿ õ³közvÔÏãïÇ/¥ænøÚÍÚ<«Õ#£Ã‰ë—E²ÿ3=©…Yokv{ÁXüLŠ]|'™oé^³$gN‹ÓW}+ºß#ñéåø•‹z›¥œTÄǯŒÅÅé‡<©¤V?KÞ˜,ÇÆßKL^É-ÁÛÌr´Xé‰SÑHKpUfûu_g ›3“Ç'>HL]N\ýØ¿f}¡œ)9NN_M|z%žUÚÜ‘Û~sJŠ?õudkT¢7Rs3âH[ººåíÉO«qüêEÑæt"ÞÒu«¼½`,Ú,v’øôª8ƒzm,WK“ãäì q8¢”"ek[Á9Ž_ûDä‹ä–•·¦#Åóׯ3×ÓѰoe0³]ÎÏÅ•,’•g3'¥x~òÆ5Ñcéè‚·µÕÛ’Û‹ŒE×¥æC¢Uâ LÇ¢Þ.GŠã‹j¬ôFhÊ«§U© ·´äê‘1¯Ï‹3+Ú^Ðë»JÍRËIJq,*š­üKÄ=¾¯/WoK&<…âT$,š”N&¼þ6¥Þ¦Õç ÅJb‹%¹Õ,G‹E?({NH5oó|±[¥Áɤ×ç5ÉIy¼^í¼(‡N)=)mÏÔóce· ‘œÎvQ:-åäÇéÌn•=k}%ç(~8)_ü§úªlóZÕ>sõ)¯·Inî*½ó§¢ÅE|Bâ¢T/M;¾"ů“ˆ«o±ô‰´ð ¿ÑLÇrþü×+û«lŒó÷ôvmÛѽs_ÏÃG»¾±cÃߨxjjí£C"¯"ÿÄÿUñOݾjà€Ø(Þ[Ý™AÛÜþ~Ë~^z^ÔsgçÚPó® ³Xm³šì±òÉ¥3n‰ðŸ~]×óÓÖýœñÃeûù™ŸæmÏņ9äô6ÿÚÄ'yæÂÏýÜ“s±XÎ!—Š.,¼ô{Íæ"{ãä5UÃÊüÙTú9"­jæçZ(-uÿfÞ8y~¸ðóCâ.’μEóÃe|rº7N‹Ó‹ÑÈ›/hý¬ûäf®çyã”,*NèÈYÉ'w:wìaÚª¯=›ÊØzÄ«oœº¾s:³ËyãÄ=5öшf’瓽qªïM?‘çK3óÀ‰|ÕÇ)^ͼqr¬z×Ôg^7ñÉåâT|qqô%Í¥åôÉ¥2ÍX|ç>¹?¥c1µvæ–S=pªË*z^÷ÉEåùäÞC“bñ‰ š Ë8·Ü»ZœõÉeúY~jJüüÐâÄUƒO.L*驤ø™$m/ì™Ë>…“ù»”˜ÖŸÈ)虾R³ÓiÍ÷&ûä–9ÙS& £O.Uw¶|r¥>QÿOQä®FFΊ_-í÷ü……ä€>yWÜBì$«uñY6“?|³àž… ™:¼§*Ò­³okÇæ­íwmjÛ¸©¤6®½úLüò…ØØyñƒU^ºbˆ£Xwp(~å¢ý6—t€5’àľŽM[jðzv!ù£·²ÉwÅ:ùˆ¸ûú:W´ßÕg™…}+Ì.xÞß#ùaÝþù¯Zôs<&nŠ©Åˆ¯½³íŽ/ZïùW=þVoGgÛ†/X·yüýÔÜŒ¯ë3m>o™¬Tï2Íhí½Ç:ùÒGBÆùÚ:Z×ßm|eLÈ8o{GëºÖÉW/f<š­kï´ÑæÄŸk¯×g«Í×>QÝŸ­·ßa|y,JøZÛý6’Å_­TxÖ·âÿíl%Ç}míþÉ×'T'¥õÏÝħ—Óñ¸’|Û:Ëd¡æU/ Zc¶HÚH©y[n½Í29µ0'´…·¥Å·¢ËFò¼"|>­š[ìË]Pê-âú·¶$*5¹Ìß:;þE¥ˆ˜¹ÄUj,´¸CbÏ­ÖNbEŠeêjvl—Ús Z µhv*[¤³c4l¾š\Ýøä*/ãÄ=¬kÛŽÀ–í客…‡O. Ÿ Ÿ;UÉZ£*æ°í äJÆULÀU]Ò!æPr#ã„€ öØN*+BÆ 17sü`b²ìOu æPre$66zéÛ_/ëGì øÍZ4‘‘³7*·‘.°e{ÏÃGù& äÜ—qe}Rµf5\…õ\×¶«÷áË€’s !àÆw­|2.Ø?нk_½ , %7uxOùÆ[»wîëÞµŸïJÎW¾U:û¶®Þ{¤ºóº-ÐÐà̱CeR·¬€’s‡ÉGvË‹d»…/\5p ®õJbrbzð¡2uκƒCUàê[É…†…Xq}·}[{>ÚÏiÎ>!ºÈõ✿§wýãgx”%·L"#g¯~·ßõÝ®8ìh¤3—˜œ¸öÈ·\€zwí£C|1Pr%Sާü=½·?ü£F4œ|(44èî>yú%·\·Ç5ÃÌ·å˜9yýãg0ÌÔ>¾ÚiJhhÐ]×µmG3¸¾Êq˜×ùV%€úVr‰É‰™c‡\Üa° Â³Ý é3sìàÕïöOÞSaäzéÑõÓå VFW…rqƒªL&‚Vë?Sá¸> ßÚG‡:û¶ò%¨Yj¢&7wúD½Ë¸Ää„|BN•iZãbçÒí áÊ1 4”’K…C.*†j-T°Ôá'äiN§«bNˆÑ™cù’ äLqqù©`ÿ@µÖoXªÛʱ ƒM1·æÁ#nyæBCO”o±W¨o%'T‚[Ó¡ ·jà@UŽB(Ñ¥c©âÐ*?ÀªââÊcÇ)Ë ä áÖ¸ªÐ.~RU&|îTÁíU`Õ;äÁﻲ+qÕ’¤P»J.2rÖ•!HOﺃÕ\`ja¸°’«Ö«J`Ëv·Š”<ú€’Ëçæqwf,»ýáUqúßT8d¦Ø“Õ5™û„žsEs»øp1Ô½’sK¬8PÝu¥Ì†V³ÿ·ªe9Áê¿ïïé­Ù  ä\Q}[ƒýÕíA³¡U•*Zå²'8tÅAHY%—%66ê\ÒóðÑêv_‘¡UíH«>‹‡Ð»Ý;÷9ßÏìÐ|aPrWfY5p Šö8•âC«Ùœj° ºwíw>Æ*„¹åš]É¥Â!çcŽ}[«5 °Lñ¡U•ª°ª¸2ÆêÖäP¯J.ô+Ô@g“±So«…V·´¯P¥n-Èu©äæžqZ£ ö¸òÄÊ+@3*9‡#ŒBÔŠCnY²¬vžút¾Ä¬Í¥ä"#g­ÖJAnyC¥µ3Àêïé lÙ^ù€zUrïým7‰5ÒkË[4¶vX+·ýmuu9Ô“’[žúѨ…µ¹Tœ¸Äjg€5°e»Ã¡j‡'êFÉ¥Â!!€*é2'OnÖÔ dà¾û¼=:rŽï@S(9‡õ›Î¾­µ°<—гMÕ±Vl¹¿ŠçêFÉÅ.8*È9Ônˆ³ 8”uKkæ©O‡¬‹¬P7J.ú¶£‘¸ÆZUi¤VÊrÕÅ_™YtP“ó÷ô–4´š ‡B¿t¨Ëq *ás§“ýåh[ÛÆMÝ»ö•TfëØ¼Õ‰6vömå[ÐÈJ.19ádÆŠRµÂôàCµ¼œ”èŠ2•²Än…´ZûèPùúv©’ã+PE*1ºŸwòöŽÍ¥©f^´ÔiÞJ­w.Õè|…\ÉE• Úï*mBàYÑ«jg´ÄÃo߸ىpä+ÐàJ.žuòöR—vö?д§s šµm¼×ÙÉe¥€†VrNÜTË0ruïÚ¿jà@Væºwî^ê»:œYåœ?5­ä’óË/Û,ÏÅìXwp¨vÖi-7¢—Ö?~FHØe¼·µgƒ“¦&ÐàJÎIMοf™~|!ㄸYÆhcÝز]é²e«ÃÅ3bÔä[É9ÁáØßª=mÔ‘Vq\«÷q~€ÍS¼@É•FÕ§ lÙ¾áèë7­_ëumÛá|W-+¬Ùužo@Ã*9'&9OéS>È@pí£CÝ;÷5Ìi ö8QÍÃÉ+>9€FVrÎE˜[»êÞµ_¨‡¶°Zè¡J—ñŒj1%·¦—oJ®ÖQƒlÙ^§íïìÛÚ#Å€’³wÀ`ÏÃGWï=RwA¬8°öÑ¡&_ÁšZÉ©tmÛá¢Ï¬Ü¨ÓÅ5ÃŒ*Ð8J®¬J«^äQ}‰N@Ée©À &µLM Á•œ¿gƒ“·;ñã/Ê­f?·µ@'JŽU(šAÉ9ºßG+;æX­¡Õj‰H'Cº­Î4:Ô’s¸hA…ç­­ÖÐjU>Ý¡3¯có}|…\Éyœ¹©*ü@µ†V³Ÿ^ÙŠ ÃzgÛ]˜äš@É9\é¡b³æVwhµòR2ì¬ؾq3_!€ÆWrŸp¬Ø˜cu‡V+܆Ää„“Ç| O<4…’sh•«Øku‡V³m¨T]Ða¥Óá9€ºQrm79[³k¢¬±±Q‡ÊßÓ»zïçS¬UFP:\¬£ÇšCÉy—p*0æèPÙ¶l_ÿø™®m;Ö¯5~°‡V=ÔäšJÉ­Ør¿“·‡Ï*÷˜ã²Ë~¾@pÕÀž‡ªuGñºzïí?—×’rìÜé'¼ÝßÓËêM¤ä–p„²)ë˜ã²°‚fÝÁ¡`ÿ@ÞvµD·l¹SnkàÜ3'ªx6 Î”œó*ÎÌñƒeT6ËZNÈ8³ã‡,Ä\÷Î}ËØsYXÅÁ.O¶j8¬°@)9R¦rtûâ£|•ªR‡V}`ÏÃGW °BíÞµí£C¥Ž´–µ9ï¬ 'Ž%°e;_€æRr]ÛþƒÃ=Ü<~¨ +uhµ³oëúÇÏØW3"ÃÑ×KR?Êhry×jØ¡ ÜGA ù”œóVç*¤ % ­vïÜ·öÑ¡R'ÅÕjxößR¦Vçj˜¡U€fTr¥,·ÃáÊQ–³YýjLh¸î]û—ýAÁþûA”c€Õ¹fh y•œsàzYÎæÐªhù†£¯;fS}ÖÕŽ¢-Ç«sÌÐ*@ó*9O¯s17ux‹M²3´*OçB§ÛžpÎÝVq¤ÎEðÒùV Y”œà–þî!19t«=ÅÅMÛÆMë?SùbgÂ9XSáói\:û¶2!0@S+9¡J}\`)3Ç9œM…E­R—Þ*Ÿv±œpÎÅVWzlå7vðhj%çQÿÜïpBâL>ä¼%f:I}ÔtõÞ#n¨ë¢ι2À*Ôªó*¦ÐÎX€ºWrûîw®„s¸æ½™NRGT+ù„¦:á\ÁÇ)œ;Û„ê½þ˜ ÎÂ. r(9O¦âtì–L>äpÄp© ìÞ¹OÈ8çã¿ËhÉÚG‡–N8ç\òÎ;Td¹„SöMžu@Ée²À¹FI…C×ù–“=È_8Ÿ.Î…nÉL8'ëÈî]ûœì0<|Ò•§C„ò®À@3”„7NWë³gŽœqcš_¡~JZ;!ÄäÄÜé'}+ƒ]ÛvÔˆXQt8w*19¸o»“ç-Ä¡]úö×ÅÞœêý@pÃÑ×Qr(9ƒ^ßý5ç:C°zïÌøK»÷Êþ~ç㪞̈suK•P_5?;tRK“™:¼ÇÉÒHL=öWúÄßÓ‹Œ@É kÛ·ž-p«þÔ 2îð·&¢s>e 4¦’ódF]Ùúôƒ+cµõNhhÐùü,*}[¶@ÉÓ nÍÜ–˜œ¸²¿¿ÉÅœÐp®Ì™¬âÖð74¦’Så‚[ÏEÆÆF›YÌ 7ux[{ ö°Ê*J΂Œ§~Ÿ[{SÅœ+«²Ö¡¡Aeœ»'ÊA5g!Éãêwû¯L¥KÔ@pÝÁ¡æ)) ç–7Neí£CW€ÚÁW;Mqw¹zu65·žß¬eÄ‘N>²Û]ì@ÆÔ>5T“ód––¢ÄÝ}®8 tI£ž¿ÄäĵG¾åîô+m7­ü ß €ÚÇWS­ lÙîúœÓƒ uØÏ@á{éÛ_wWÆùÁ5á‹PÔVMÎãêS2þžÞÕ{4̈¡è¥™c‡BCƒ®ï™uÏPrŽ(ßL"±~¨èŸë•eu²`ÿÈ äœRÜJ]çÊWŠó`@ɹˆÐ+.®UG×¶Ý;÷»µäke˜;}bæøÁ2Í“'dܺƒC.>; M­äÔÀÓ”øÁ`ÿÝ»ös) ä¡än?Ô`zNÕpÁo`‰@É5>BÉÍ=>YïâïéíúÆ44‘’SILN„†çNŸ¨Çç!:û¶®üÆüpФJNEȸð¹SBÒÅÆFk¿µ¾@0pßýÁþžK”œNbr"<|RHºÚœ‚®kÛŽ[îlÙΕ (9 I·0|ªêFø{z;û¶"à%W2©pHˆ¹èÈ9ñZ±±W_ (Ô[Gß}â•!T@ɹƒªçÄ¿Ää„‹å:OokφŽÍ÷µÝµ©}ãfñŸt5 äÊK*Z¼0*^c”r]lì¼g`Û6nònñ­ Š %óJOJ%(9@ÉJ%(9@É ä%(9@É ä%(9” ä%(9” ä%ÐŒü®—ÀãÌ~IEND®B`‚software-center-13.10/tests/data/app-info/0000755000202700020270000000000012224614355020640 5ustar dobeydobey00000000000000software-center-13.10/tests/data/app-info/appdata.xml0000664000202700020270000000220312151440100022753 0ustar dobeydobey00000000000000 firefox.desktop firefox-bin Firefox Firefoux Web browser Navigateur web internet web browser navigateur web-browser network web text/html text/xml application/xhtml+xml application/vnd.mozilla.xul+xml text/mml application/x-xpinstall x-scheme-handler/http x-scheme-handler/https http://www.mozilla.com ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000software-center-13.10/tests/data/app-info/archive.ubuntu.com_ubuntu_dists_maverick_main_amd64_AppInfosoftware-center-13.10/tests/data/app-info/archive.ubuntu.com_ubuntu_dists_maverick_main_amd64_AppInf0000664000202700020270000000050012151440100034204 0ustar dobeydobey00000000000000Package: gnome-utils Popcon: 17939 Section: main Icon: baobab Name: Disk Usage Analyzer Name-de: Festplatten Ueberpruefer Comment: Check folder sizes and available disk space Comment-de: Überprüfen des verfügbaren Platzes GenericName: ERROR Exec: baobab Categories: GTK;GNOME;Utility; Gettext-Domain: software-centersoftware-center-13.10/tests/data/fake-applications.menu0000664000202700020270000001646312151440100023403 0ustar dobeydobey00000000000000 Applications X-GNOME-Menu-Applications.directory /etc/X11/applnk /usr/share/gnome/apps /usr/share/app-install/desktop Accessories Utility.directory Utility Accessibility System Universal Access Utility-Accessibility.directory Accessibility Settings Development Development.directory Development emacs.desktop Education Education.directory Education Science Science GnomeScience.directory Education Science Games Game.directory Game ActionGame AdventureGame ArcadeGame BoardGame BlocksGame CardGame KidsGame LogicGame RolePlaying Simulation SportsGame StrategyGame Action ActionGames.directory ActionGame Adventure AdventureGames.directory AdventureGame Arcade ArcadeGames.directory ArcadeGame Board BoardGames.directory BoardGame Blocks BlocksGames.directory BlocksGame Cards CardGames.directory CardGame Kids KidsGames.directory KidsGame Logic LogicGames.directory LogicGame Role Playing RolePlayingGames.directory RolePlaying Simulation SimulationGames.directory Simulation Sports SportsGames.directory SportsGame Strategy StrategyGames.directory StrategyGame Graphics Graphics.directory Graphics Internet Network.directory Network Multimedia AudioVideo.directory AudioVideo Office Office.directory Office System System-Tools.directory System Settings Game Other X-GNOME-Other.directory Core Settings Screensaver Debian debian-menu.menu Debian.directory ubuntu-software-center.desktop ubuntu-software-center.desktop software-center-13.10/tests/data/test_debs/0000755000202700020270000000000012224614355021103 5ustar dobeydobey00000000000000software-center-13.10/tests/data/test_debs/corrupt.deb0000664000202700020270000000004012151440100023231 0ustar dobeydobey00000000000000deliberately corrupted deb file software-center-13.10/tests/data/test_debs/gdebi-test9.deb0000664000202700020270000000111212151440100023654 0ustar dobeydobey00000000000000! debian-binary 1203540390 0 0 100644 4 ` 2.0 control.tar.gz 1203540390 0 0 100644 261 ` ‹íÒ¿NÃ0pÏ~ŠS÷¤qÉ5¢Љݸ®±jÙ‘íV<> AÊ$Ä÷[N²?Ý wåš-®uM3UÑ5Õ×ú‰1Ñ QµõôÞµM˨a¿àœ²ŒDL›àoå~úÿ£Êµ >ÇàÞ[×ßîÿn#®÷/Äf<ª°ÿÅí¥:I£{2ýd‹¬SÞòG“ ¾'QVü>ªg›µÊç8Ƥs|ÃÅtêÉÕʸþüÑY•¯_w:©h‡üÖmj>Ìóèâ<’ ÞûM§8÷ i¤õ)SÔÒÑp2üVhõ9pÅÀÿñ ?+«( data.tar.gz 1203540390 0 0 100644 132 ` ‹íÑ; …@ …á,eV ™LÖ3…ÜFîcÿŽ…`£ÞFAø¿æ„$ÝiZ¹œVn¶dtÓm®$ÖWÏÖ¥º÷ì“ü>ßòAú×4ýݪi‡2”ëûÏ)ýÕª³F55 Jÿ°k¸¢t(software-center-13.10/tests/data/test_debs/gdebi-test2.deb0000664000202700020270000000105212151440100023650 0ustar dobeydobey00000000000000! debian-binary 1130166397 0 0 100644 4 ` 2.0 control.tar.gz 1130166397 0 0 100644 260 ` ‹íѱnÂ0`ÏyŠ{‡8XbCêtêœk°HíÈ>¤öíÂÒv€. !ýßò[¾ÓÝpåJÝ]5³M³äìo.oS­kkìÆ´ëùßZÓ(jÔœ²t‰H¥åZß­ú“*W.Iq¼óýÛvsNc›êg.êÚü¾¿1ukU¼?1\ë»UR¯;voièyïµp–ºxã”} [2eUì’;xa'§4·uãX¼ðÄ¡Ï[šŽƒ–C'ºœuˆ¢ùÓgÉsGvÉO² 9Ïœ.kè=¦Ë&Òä?¦˜³ßLý2‘ƒû*ü×7—#J±(data.tar.gz 1130166397 0 0 100644 102 ` ‹íÊ»Â0P•¢ °„Ñ\=®À3þô­"½—ìÍî=¦ôwå­õ¼|f¿k™ŸQãUëÝG”–rKœû±l9§m]o¿vè «= ª(software-center-13.10/tests/data/test_debs/gdebi-test1.deb0000664000202700020270000000164612151440100023660 0ustar dobeydobey00000000000000! debian-binary 1130166157 0 0 100644 4 ` 2.0 control.tar.gz 1130166157 0 0 100644 243 ` ‹íÑMnÂ0`¯sйÁ8–Ø¡ö]uŸ¬Fqdêõë!°¨@Bzßfü33›W/ÅÃÉÌh]jv]ËYÉUc”Yéfî3Fµ‚´x‚câ.‰ßê»÷ÿ¢ê¥ #Ç0<8ÿ¶]ÏU-/k‘c?ç¿nsþJ5ù‰ä3ów}oõÝûQýîz·¡~ç¾ü‚]bU}º˜|7¤jYm£=xv–1·uÃP½…q?xË)_'®Þ]²ÑO\&æÓi'íC<­¥Ù¿úñ|(sþç)Q>î( data.tar.gz 1130166157 0 0 100644 498 ` ‹ÓÓg 90ssSmhnj€L᱑¹¡¹±©‰PÜÜÌÌœAÁ” ´¸$±HA!5=?Ÿ:BòCèé륤&Ñ>þÍLL`ñN˜#ÿf†Àtbh`bfÈ `@Ïø/ÊÏ/Á§ŽüŠ6‰EÉv\ÀD™˜§›”™—XT©  `hhl`hŒ s€†à¨TP0Q€.#=®äü¼’¢ü=``ê¥WÖodb ×/ßÍq óÛ‹¾y‡ XÖwíd8Èg1íÆÂo ±¥ó¥„׸¦ð¿úúZBTQlà §ªûiŒg‡ë=|òLFlVYìéÈ“¡ÉS"_¦½3vÛÚ´¥¢)ù‘žP§ÓýW»¿ÿ_ôj)¯òq‹ÿÛbχJêêgOL¶¾?¯øŸ—éÏÎ'ÆŸËk%ò¿Þý](?÷ﻪí ëžïûÓ›Z»kF„ù’¬ÈÜŶeÓú ·—ØÛZh_qZ3^}ݽØMýcÔž±^ÎTël³v)ëÍý’¿>Öh3a„õ¿çâšvï4FÁ(£`Œ‚Q0 FÁ(£`Œ‚Q0 FÁ(£`Œ‚Q0 FÁ(£`Œ‚Q0 FÁ( Å9Mš(software-center-13.10/tests/data/test_debs/gdebi-test3.deb0000664000202700020270000000107212151440100023653 0ustar dobeydobey00000000000000! debian-binary 1130166629 0 0 100644 4 ` 2.0 control.tar.gz 1130166629 0 0 100644 275 ` ‹íÑ¿jÃ0pÍ~ í ÄòŸ¼úº+²êˆ¸’ÎÐB¾¶³´¥$Sï·|BwÜ ·Ù²«+gªmלýÍõ-˺RR5m­æ¥ªŠñ–ÝÀ”I'ÎY Îõ]ªß©ÍÖO)ŒW¾ÿn×,)U[þÌUÕ4¿ï/e¥jÆË[ÞßÁŸë»T¿S/Úõ`;>ôvïÙLuñjSvÁw\nÊâ)™ƒ#khJs›ÇâÙFëûÜñx4‰>Ø,| a?\¦ü¥#Í]Ù$i´Ì§Uü-¤Ó6.xHbHaŠüÁ½Ç³ÛVôë|ëÍç2è±`ð¯o&g( data.tar.gz 1130166629 0 0 100644 102 ` ‹íÊ»Â0P•¢ °„Ñ\=®À3þô­"½—ìÍî=¦ôwå­õ¼|f¿k™ŸQãUëÝG”–rKœû±l9§m]o¿vè «= ª(software-center-13.10/tests/data/aptroot/0000755000202700020270000000000012224614354020616 5ustar dobeydobey00000000000000software-center-13.10/tests/data/aptroot/etc/0000755000202700020270000000000012224614354021371 5ustar dobeydobey00000000000000software-center-13.10/tests/data/aptroot/etc/apt/0000755000202700020270000000000012224614355022156 5ustar dobeydobey00000000000000software-center-13.10/tests/data/aptroot/etc/apt/sources.list0000664000202700020270000000026112200237544024532 0ustar dobeydobey00000000000000deb http://archive.ubuntu.com/ubuntu precise main deb http://de.archive.ubuntu.com/ubuntu precise main deb http://ppa.launchpad.net/ubuntu-mozilla-daily/ppa/ubuntu precise main software-center-13.10/tests/data/aptroot/etc/apt/trusted.gpg0000664000202700020270000001715212151440100024337 0ustar dobeydobey00000000000000™¢ADŸ?».ËF° û1ÅÙÞ| Þ¼*«€D°eê¸ÀÚ÷aÂ^û*CÏ:½ÎaäûèRÊšºÂ_ Û“6+Î ît•¸jçcƒƒ1ô˜>á¡çõù‘‰8Ö-‰Þb cÀ ű)Q«P!}°dmeCŽVþžûàEèX(“ýµ‡û¢‡ ­Q6W0$»™Xvðú hÔ#ˆW¦ÔZÌÊ7uÄ“;m——nÏúˆT£ü°HÎ"¿'9õ,Ã-<¶À¨R”ÐF,§ÒQÀb@q*ö¥¤þ‡pt´Ž ¿%Å‹ie×¥²î¡[P 羈þµ¼½óâ’Fp­i0~¤°8ð\„•?†ßîûWÚ][-Z«æ›õ»„î)ó£ØmÙ$Í`Ô+RÅa¯·K)±X½Â_ˆ‡H˜öÒ±G6 #|9F¥…<å 'Kuϧ´7 Çm”Ç-$±ö7û© (Cv,_ÁbÕÕ>b.(ÂJ†>˼$aÖY1ÊŸ×n%8»/ÌÁÿVç ]Á´;Ubuntu Archive Automatic Signing Key ˆ^ADŸ? € @—n¯C}µ$ÍŸSÁH÷÷W«2Ç’äZ³ñÈí‡ïŸ\6;ìyðH­íN¢s ‰º*àÝF°‰AQgß ×Ãñ1«*‘õ}›Í1Ã!Ö™¶\ºdÇ–)ªKÏ&ûAÞê9æ>ƒ ¿Äe ìì¼o¦ã¿†;ȰˆFC¾¬† 膧$eûÇ¢¦ I¢‹×˜êÑ;ãYjÜŽô/87`2ŸvF÷%» æXŒÑçI¢ŸÊß°ˆFCûKá äíj€û8ÛxUŸV§%‚œ’5GÕòJš¦ÖËCŸ@Þ’ë@ÉzkŽÕ>[k-ŸaÕâ’°ˆFDÊ(ì þTmí.A\b®Ÿ\_:N¬p"Þ¤L„¡Ë)º&»*†%»æ¤‡ÃØ EáÆ”‰*›(°ˆFEÂê [Àl3ß; Žß]çXnüX ȇ{ o2^К٠¹b:X*t¿ˆò€6,¯ó)³e À°ˆFEÎà à/›m4 í¨ ž"¿ `SØ:†~“±·£Í¦éK²Kž, d-óò˜•ån£ÎƯö¢]f°ˆFEÏ0 ‘µy‡ä†_?ºÏ4¸ñ¶WåP0õX„pïD1 ”ÇòÊÎZná阜N–"} °ˆFEÏB  9`˧f“œ (²@s!ê‚äKÒÚ‹žž'8ó€<VaKXý‰;-Ý¡°ˆFEÏ` .¬–¥Pç„¢" ÚÎ$µÇt™ ä¼§ˆ•V8ܦìŸuëÖÁ-¹‹(q²±ôÒÆŸ4•°ˆFEgÚ| •â­~K\—T|ž1ðr?ò¤sn'ÃÔ”’¯#˜Õž7È%ªà‡?·}_ ^âž5£El °ˆFE ) Y¹Ò‡KëÈ* Ÿ)Ïl¹GÆVº2ê"ç½ØŸVNF…0£òÈä o7ä¦YÓâk|°ˆFE´ }FÖ<íãµe>Ÿj8¬¦Eq×°”›E=—ó*Dr¤ž3õ•§¶¯>ÔP]†iZz£›°ˆFEù1¿ >;àíÿ€î_ ÄuL^¬wÊRØÞ_øWfÛ‘AÊ}ʾCi@ÚmìlHÞ¾Ìñ°ˆFF®ö öÝ3úLÑ|žŸBL4u°›öeÚs>§\Ûqè7ääÉä…ö‹×¨ö« ÄMs°ˆFB…¾ 0kUDÆ‘<ö ŸScÙr{¼yuâTMuÏ ´–ò(žŸQòá8O¨,É” °*UOa°ˆFBœ¿ P×&<áx\ý †Øžz{®€b"‹‹¾¡Ç€Ýö ³ma›Ðª‹‹³£³õ6Ðà#ª­?°ˆI E›pÀ %² ­Æ"ŸJ/²í‘˜•5Èü’}»_M ° „•I¦5ÂåxgòÎn 'ö1Z°‰G4Æ•  û„?'/[¬°±— æ@|˜}aÏ®˜~°½ã›•ô®ñÁD==W#âšzµ#Gg NüÊÅTôof£y`CNÇ@þ—Ư6°µ)œ@”2›kÕ\¯“‹«1XÆÎ 7ÁƒQ‹ˆàÇ)Y‘ÍŠ‰†vli}1c~aêG òTPV>Þœ#uú‹ŠÃ埂)"ÔXjN¸öû-òö.`¢`ƒ¶võâR{È4ï¿zB5W~Y*öˆ…^ t¼AeŽ¥Œ ŠÝz ­|e ÅÌ‘Ãi‹“­ý ^°ÁïVƒr¾N»T#mÆ9Ü—õëX!™G-jßo¦·qéí'm†íö6!yPsL# ×5¥T®5ËMÅ)™L«¬‡ô>Ï9ÇjbœeÔa«QÒôt«wlÆP?mõ¥C ŸZȃ#.8×ì@èî¢ §®º ZÒ«¢Ï¶zœ ÓZA‡xÝçžb"¹ÏWà±ë4äÿAGÏšs‡ç–÷Þü¨§>w¾Ϥ®´åB- ip6Åôž[ŒÖ÷~n‹ºâÀæ@G\HÍDq¾kÉ“þöAz£ÜÒÄ6²1·oºFÔ3· ×(Õ8Ž¿Ýö!àý;Þp扗eV¡Ld<5ëل®½hAŽ«q7«´3Ö fÈö7MËŸjît¥°¹ ADŸGÈ%½KëLQ똶@n$Ø)êf‘Q¤òK”ÚŠZÚ$Rœßñiª7¹ÎñoSyíZ²„0E€XîyìÒJè(båp¶Âúq3’þujŽ‹r¾]Z½<ײæ–ð¥ØÝJyä¤r‰- N;„r¢È¶(Žj~¬ÂÚV¢UlE!¦ :&|;R L†v•hHûà’æU*ö¸Â!b­ê‚ά”¬„FŠfˆ}åg¥ P…D»+¦Æ“OªBu&Jhá,HÃ/ b$=RM<ÿšü†§–m›,ñÂ;èªÄ|LˆN1_O,V,ñV ÃØ°µÓO9i°ÏkŸÅ¿‚'‡Ñ+Âó €²Ø€5ÁÉŸås郂Aã#z)"ædÇÿ0í®ùNV²=Qm`êqABÖ ø`Ú¤Ü5õÃSØKR-·'à ±œƒ§h³³«÷ˆü¹Ú‘Ð rV“U³e¶¶ž¦JNÆûªþÐÎË(¶y€…­mÌgþ;t2ÆB'¬Ç3BDA=ˆ¿=ïb¾%ª4+Ý#á—ÒLZÕ g{Õ]¤æ[ÞuXaÆê,/W ëmþó„LCjà8rÇÒüG¯2Ýøîäüt7=s(¼êp]}:ÌÈzÑ6l˜z®Ÿñ7€N:b#ÛªæQ~JHÔæô݈d#Ïû_=ˆI ADŸG @—n¯C}µÜX›O±mì{ëò ¿š¢tD¸3 ‚¤2£ì5O z ¢ÅûzÅæwõt°™¢AÔRx¡½æ ±q7ý€ÿi¶UÑxUÛ¯ÁjÌ=€]©Äç|‘â=ŒkËé껜Z‰:\À¬mŽ„5߆c@þàæºƒ•–»Ä´Ýxh±vhþTçíöºêµPèC{õ-Œ6ѧÖ˹¨7°/DK'UD–y{¯@rܽ)L÷©­#787 Ÿþ[xÍQ´ ÿMg÷…„ÿ”§]·ÞÖOè¢JªÚ–nÉ$úEFà¬#’šµÌ5–¶VÔÙµ(dÌËl³‹nCE›á ã>[5KŠ•ç5õ×ñ;^É0eâ0ù6~ŒI`R[K¸¿>©ïü"„]ö•G;rÖŽp/–/ö‡ÿÐ`I–•BÛ£¤Áz{S™.ÆÿbÀ«üùÅ®‡_|0à´™œÕwu¶F"¾aþw1-`ûdr+',ÕŒê |îÇŸáÝ$x#ÚVÏûj¼,âQöc“ŸšÂs;-ñáÅ7ÅÝ#ýX©&_Û _„ðyeúÏ;[dqÉù®I¿(À›¡¶œûºâÝ‘Þ%* A´:Ubuntu CD Image Automatic Signing Key ˆ^AÔRx € F3û·TQ®ÔŸyPwð°˜oJæ„(MÝ_u£œ@úþÎ:”ƒFö勞‰uÔï°ˆFAÔ ¡ öÝ3úLÑïΠ†rð´hŠvú«eŸúú¨» Ã(ž4PÛ‚sbÊqYÜdv “}Èrå°ˆFD’Ù ‚×ÕŠ°¨`+Üš$L¼wž^‘Oh'¦+åU´Ë.žKŸaãžÁŸ™m6½U-q®Ü¦Bÿó=ƒ°ˆFD”/± :ŽaáS·‚ ÿQ£š(/Í$ôj.lê¼tQž4azýê_ð¸nCÛáÖczlÉ/°ˆFE [Àl3ßœ\›²=íëSLßž3™ea9xÛÔš ØNqäg¯pŠÜ¬èpf[Üv°ˆFE y Y¹Ò‡KëȦŒ ŽøEåwõ†Í›h£¹™ûJž/¤±@–>`pêcdËÈ9lif°ˆFE÷k ví> ®¶$P'\O *¹`c8#Ùé©p?°ˆI DÂf ™æ´UX (¨ † eU ñýT©¿'S_4ƷľÆð͵=^I޳|›•‹§'ıݰˆI E›pè %² ­ÆÛÞž2¨ž¼e(Dð¯òò´´loÞÙ‘ž0®q/¤P€ÊN°=ÛC¥D°|ëh°ˆÜCod ÑÙR é¡ÕÔ ´Å._k¦³ª‡(m9:ã‘?Æÿr†cÁý±6Fyc%ŠEu\×o|q\ZJ`ç’ö!/ÁpµÜ5¼ÝÁãâÍ:éïtÍÁoº3 Ã% ê@Âwâ軦Kow°‘§À £Îb3'ÛEŠ8WѽÝy†zg4SМÏ>Gï÷0e3 ×o|››tP¹"vê^ëå4΂çe«íPP80—žá ⬞M-ݯ½¸îʵˆaÁooö°‰Dã¨ö ig"s,Ñ 6þ?^ÍQœ3»ô¬_ÄöõY,ÎŒ)Q¬Ê¼FH…—Ú«cQY·§—•¶'Ø'bÉ”åÙâÏÞáä1Ò9qYµA±e6“WÌ­ÁÏÿ~<,…±PÚ歷2;ŽsE¼RØw2œ³;t®¤°¥«aŽáúG„O ’,öêÿJE›à´Í<ÎP÷k© %Žû¶%­r̈—š¸Ðicþ«ÚªVÁô 9Úþæ¢mñufž¥¨ôê{!ù’Î`k³„jkÊÆÁ®,4ÄOÁ—¾ÚŸ<¥'ë]C@câêm•»å*~»&>×íýðtÓ„ìa&z™}“ù„x„×f‡îsÝ뮫°‰Dã²- ig"s,Ñ žºƒ|CMÓmAF߯ÑĬ·ï½Æ“V}»œQkÿ4vÁª‚cÀR¬æÜ¯1¥öù‹ÂÂpj¡:tt÷¼æ%^i&·Ž}Û3âÖÐ%>vÄKØÎ'A¶j¹~AÞc°Ù"†$ÈÈÙ÷~»åcîØ¸*cY=õ¥€{ÍãáõîŒæ+›1îâ)H7™ ­ƒ¸ÕšÉ|æ2Ê¢}í¬ašì(ízÚõ…Öt^Ц—·%²¥³Ññã)¡OµJxx—#)#Óåþû•­»SåãGéo”¬¤ø•µì^{/'±_ûR¥Óz#²[Û¦°‰0Dãª! ig"s,Ñ ×ÿk÷õOŽ8Üx; P»t¿9¶_ä €1ÎÑ üÑ]_÷âa϶V·¼N• Ô ÄËyoêûŒð¿¦ÂêΈôÇuMß;œ¶‹BÖÇÄ2ŸéÁ;|æ=ü|'ª}Œ¿ÀÜ2ïª&_ÑYsZŽŠvHè/1š©}]Twáá5èC/B£¿òÙ?\Ý ˆ+v2i“¸ì8çtCìåR³¯ñ2ìUÂÙ|)ô¼k¤Ô‰gíÏZQIrp(sz ¶V}5oÐ$'— P‰{ìÿa<ü?Ùü èá“ñ^a?Å A–>ê.¶ì…‚1 HcIÀþ&†ÚH´Ü°‰0Dã´» ig"s,Ñ ½;ýTÌLÐìÓË›Ž5æƒ*ÜðØ tóGÖ& 4F õDFK™Ê»`n5ÔÙˆv€V³oðí®œ(Ô;²pIvU7rÌKŠO f˜[ƱZþe¦Ët¼o¤ªz:³(Æá^Ѿ.”—8=ÜMIg9HNL½Î·Mó×¼†4g¾p8‚ú‚a<Ë.k‰ÓÆ|Ö¢¢ª;‡`[s¨¦¶úÝKn¶x¨`8g>D‡Úy•ê / rç³YᦞOØØ5û}ö¸|y  †Ï½Òû@+Ñë0ÒÔúÿÆtÌd Ž“ÚÄÌ©V”Ïí«¹ × »»¿ W;@°‰G4Æž  û„?'/[Þ¦þ?lüdÒ‘(=ØVp§»“Æp‡Q˜ñl ÝÌÿ;ÍM|KÄÎpþÂ{lµLn~åP 2µš?5¨]¥<‚òî,t Åä«Â?ä{AÙìíp¶îˆG~l×Þ»Œ4—]@Õ6,°ð½0š Rrß+8úoauSHÂBÎ)|[r{K¥óü”›X)FX\›b‹u}°'ƒ@‰¾ìâÕIpDk»íÞ°r4Ç¥AË"m\&°b¾;nì&‘7$ñ$—g'‰h *‡~[IpØ‹ÎÚ^z²Ù«ù‰<ã sn‘ËðÍ ¡Ã¶:¸Ð¸“tøeÔi¸s_üþaÔ­Xù„èºѺ–¾*Šwp%½Œ;B°¨Ûl†Óok\„ý±(k‚·¨qõ•îN$ô‡2QÑݹÑD¸âU­­˜Z…á‡A×,Ãû© Oj¿X„ΞÞÄgC;Ðâ3(ÉCv|­#3éVL·Æ)k\K_„ J1¿·ž— )ûÀ£‚Aevîá\Ðk¢¿ÍJ–aÐSy÷Ù¯Ÿš*sçÉL{ÕZå‚DzóóyòUFG2lQµ›ú·Ñ—DÌñð4©ƒÍ¬§„…‹)ÖRÚ&UŽ"6Ê ,ÄZ šŽé‚ý¶Ëâžoªr%(ÞôC1t¦›NÇYÒ`Õ;=BǦ𰙢L—iÚÓR†•s3FÞ¶¬÷ùê„u]¹`ãÆ-0nF—"cJõ>XUSê,ðíD*l£NÚrB0Ì6¥‹<ï^n›€•V®ü_+îOΣ ' ~IÔÎôN!&ÎßÇœXSj-=ŽÐYÁŒpèc2v€¤çÛF‰~jþð'/3Î×.¥Q<([ ´„kmŒô¬ ãzï˜ÆrbÖûü +™Eë}é>½Æéµ*Ÿî$¤â ¦˜¤Eùû _“…˜]ÿõ»V0ÜuòÇ$‰‘Gü¹=•céˆöQ˜­½Ä—U¹A`¬ ˜2“¸½ý ’H_©µíì­rÄnÞÛWÃz¢oñôš[!Ì r,CS ŒÃ­(‚‰þ.@Eð³YŠ#D¢ž:™9é Ëýê‚2_f»cVðëo¥j`BdÈǾ”ê8 9ÈpOœâv†V=ðcówER÷œ‚ ñ=ž<ù¿á?Yù'(lg?óYEh\]®1¸¬¹ás†[YÀöIy¹`fï «ÚØß´BUbuntu Extras Archive Automatic Signing Key ˆ` L—iÚ € m:>\’Ë‚ŸK--A¾Œ8§ê匨'™Õ!£ £æC =¨èƤðЫ¬ù°˜IŠ:ó¤ (’& HÿÛûnKCXÏ_åøXçС©MXdI|vL'ú@´?|óÀ +¼¿PºkÙl7ݨgC° ÞLÁÅ9Þ£¨Ý½ÂÈRDzüøAíÃÜë·ó®): %¡Ñ4@ØAº9÷ÂΛ\$YfMA¾~§n(§‡hÝ´1Launchpad PPA for Ubuntu Mozilla Daily Build Teamˆ^M#±ÿ ü’3ÿÆsĈiÿD k>›¥¿A¬»ÐwÝò>'4ðiÍóeÅgþ™Ê®®â:ºž[;ê¿S“¤Z?ô `ŽA½ó’“,/”Cò°ˆ¶ IŠ:ó € ïA†þ$u¾Ø¡ÿq}NçN²;Æ}â¾¥,9zwpLbLíØzû¢O¥¬hôx½1žò«{âá•E V¢­H^u¢¸ºîƒ}5 EM)&0‡3AdFü^¢ª ”*SÞ$>§b'q“Y=°À3r"üóuÎs É_X¢;1H=±qPNþˆ°software-center-13.10/tests/test_origin.py0000664000202700020270000000211612151440100021100 0ustar dobeydobey00000000000000import apt import os import unittest from tests.utils import ( DATA_DIR, setup_test_env, url_accessable, ) setup_test_env() class TestOrigins(unittest.TestCase): """ tests the origin code """ @unittest.skipUnless( url_accessable("http://de.archive.ubuntu.com/ubuntu", "dists/"), "Can not access the network, skipping") def test_origin(self): # get a cache cache = apt.Cache(rootdir=os.path.join(DATA_DIR, "aptroot")) cache.update() cache.open() # PPA origin origins = cache["firefox-trunk"].candidate.origins self.assertEqual(origins[0].site, "ppa.launchpad.net") self.assertEqual(origins[0].origin, "LP-PPA-ubuntu-mozilla-daily") # archive origin origins = cache["apt"].candidate.origins self.assertEqual(origins[0].site, "archive.ubuntu.com") self.assertEqual(origins[0].origin, "Ubuntu") self.assertEqual(origins[1].site, "de.archive.ubuntu.com") self.assertEqual(origins[1].origin, "Ubuntu") if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_application.py0000664000202700020270000000230612151440100022115 0ustar dobeydobey00000000000000import unittest from mock import ( patch, Mock, ) from tests.utils import ( setup_test_env, get_test_db, ) setup_test_env() from softwarecenter.db.application import ( Application, AppDetails, ) # FIXME: move application/appdetails tests from test_database.py # into this file for better structure class ApplicationTestCase(unittest.TestCase): def test_application_name(self): app1 = Application(appname="The AppName", pkgname="pkgapp") self.assertEqual(app1.name, "The AppName") app2 = Application(appname="", pkgname="pkgapp2") self.assertEqual(app2.name, "pkgapp2") def test_appdetails(self): app = Application("Foo app", "dpkg") db = get_test_db() appdetails = app.get_details(db) # patching properties is a bit cumbersome with patch.object(AppDetails, "raw_price") as mock_price: with patch.object(AppDetails, "currency") as mock_currency: mock_price.__get__ = Mock(return_value="2.99") mock_currency.__get__ = Mock(return_value="USD") self.assertEqual("USD 2.99", appdetails.price) if __name__ == "__main__": unittest.main() software-center-13.10/tests/disabled_test_where_is_it.py0000664000202700020270000000764212151440100023752 0ustar dobeydobey00000000000000import logging import os import unittest from tests.utils import ( DATA_DIR, setup_test_env, ) setup_test_env() from softwarecenter.paths import XAPIAN_BASE_PATH from softwarecenter.ui.gtk3.gmenusearch import GMenuSearcher from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.db.database import StoreDatabase from softwarecenter.db.application import Application from softwarecenter.enums import PkgStates class TestWhereIsit(unittest.TestCase): """ tests the "where is it in the menu" code """ def setUp(self): cache = get_pkg_info() cache.open() xapian_base_path = XAPIAN_BASE_PATH pathname = os.path.join(xapian_base_path, "xapian") self.db = StoreDatabase(pathname, cache) self.db.open() # mvo: disabled for now (2011-06-06) because the new gnome-panel # does not have "System" anymore and its not clear to me yet # where those items will appear. Once that is settled it # should be re-enabled def disabled_for_now_test_where_is_it_in_system(self): app = Application("Hardware Drivers", "jockey-gtk") details = app.get_details(self.db) self.assertEqual(details.desktop_file, "/usr/share/app-install/desktop/jockey-gtk.desktop") # search the settings menu searcher = GMenuSearcher() found = searcher.get_main_menu_path(details.desktop_file) self.assertEqual(found[0].get_name(), "Desktop") self.assertEqual(found[0].get_icon(), "preferences-other") self.assertEqual(found[1].get_name(), "Administration") self.assertEqual(found[1].get_icon(), "preferences-system") def test_where_is_it_in_applications(self): app = Application("Calculator", "gcalctool") details = app.get_details(self.db) self.assertEqual(details.desktop_file, "/usr/share/app-install/desktop/gcalctool:gcalctool.desktop") # search the settings menu searcher = GMenuSearcher() found = searcher.get_main_menu_path( details.desktop_file, [os.path.join(DATA_DIR, "fake-applications.menu")]) self.assertEqual(found[0].get_name(), "Applications") self.assertEqual(found[0].get_icon().get_names()[0], "applications-other") self.assertEqual(found[1].get_name(), "Accessories") self.assertEqual(found[1].get_icon().get_names()[0], "applications-utilities") def test_where_is_it_kde4(self): app = Application("", "ark") details = app.get_details(self.db) self.assertEqual(details.desktop_file, "/usr/share/app-install/desktop/ark:kde4__ark.desktop") # search the settings menu searcher = GMenuSearcher() found = searcher.get_main_menu_path( details.desktop_file, [os.path.join(DATA_DIR, "fake-applications.menu")]) self.assertEqual(found[0].get_name(), "Applications") self.assertEqual(found[0].get_icon().get_names()[0], "applications-other") self.assertEqual(found[1].get_name(), "Accessories") self.assertEqual(found[1].get_icon().get_names()[0], "applications-utilities") def test_where_is_it_real_system(self): app = Application("", "gedit") details = app.get_details(self.db) if details.pkg_state != PkgStates.INSTALLED: logging.warn("gedit not installed, skipping real menu test") self.skipTest("gedit not installed") return self.assertEqual(details.desktop_file, "/usr/share/app-install/desktop/gedit:gedit.desktop") # search the *real* menu searcher = GMenuSearcher() found = searcher.get_main_menu_path(details.desktop_file) self.assertNotEqual(found, None) if __name__ == "__main__": unittest.main() software-center-13.10/tests/Makefile0000664000202700020270000000060412151440100017640 0ustar dobeydobey00000000000000#!/usr/bin/make all: check #mago_test check: ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS))) xvfb-run ./test-all.sh endif clean: find ../data/xapian/ ./data/test.db/ -type f | xargs rm -f rm -rf ./data/aptroot/var/ rm -f .coverage* rm -rf coverage_html rm -rf output # mago tests mago_test: # -sv makes mago not buffer output PYTHONPATH=.. mago -sv mago/mago_simple.py software-center-13.10/tests/test_pyflakes.py0000664000202700020270000000060312200237544021441 0ustar dobeydobey00000000000000import subprocess import unittest class TestPyflakesClean(unittest.TestCase): """ ensure that the tree is pyflakes clean """ def test_pyflakes_clean(self): self.assertEqual(subprocess.call( ["pyflakes", "bin", "contrib", "softwarecenter", "tests", "utils"]), 0) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_database.py0000664000202700020270000011661612200237544021403 0ustar dobeydobey00000000000000import apt import os import re import tempfile import time import unittest import xapian from gi.repository import GLib from piston_mini_client import PistonResponseObject from mock import Mock, patch from tests.utils import ( DATA_DIR, get_test_db, get_test_db_from_app_install_data, get_test_pkg_info, do_events, make_software_center_agent_subscription_dict, make_software_center_agent_app_dict, setup_test_env, ) setup_test_env() import softwarecenter.paths import softwarecenter.distro from softwarecenter.db.application import Application, AppDetails from softwarecenter.db.database import StoreDatabase from softwarecenter.db.enquire import AppEnquire from softwarecenter.db.database import parse_axi_values_file from softwarecenter.db.pkginfo import get_pkg_info, _Version from softwarecenter.db.update import ( update_from_app_install_data, update_from_var_lib_apt_lists, update_from_appstream_xml, update_from_json_string, update_from_software_center_agent, SCAPurchasedApplicationParser, SCAApplicationParser, ) from softwarecenter.db.utils import ( get_installed_package_list, get_installed_apps_list, ) from softwarecenter.enums import ( NonAppVisibility, PkgStates, XapianValues, ) from softwarecenter.region import ( REGION_BLACKLIST_TAG, REGION_WHITELIST_TAG, REGIONTAG, ) PKD_DIR = os.path.join(DATA_DIR, 'appdetails', 'var', 'lib', 'dpkg', 'status') TEST_DB = os.path.join(DATA_DIR, "test.db") class TestDatabase(unittest.TestCase): """ tests the store database """ def setUp(self): apt.apt_pkg.config.set("Dir::State::status", PKD_DIR) self.cache = get_pkg_info() self.cache.open() def test_multiple_versions_sorting(self): db = get_test_db() app = Application("", "software-center") details = AppDetails(db, application=app) details._pkg = Mock() details._pkg.installed = Mock() details._pkg.installed.version = "2.0" self.assertEqual(details.version, "2.0") v0 = { "version" : None, } v1 = { "version" : "1.0", } v2 = { "version" : "2.0", } v3 = { "version" : "3.0", } screenshots_list = [ v0, v1, v2, v3 ] res = details._sort_screenshots_by_best_version(screenshots_list) self.assertEqual(res, [ v2, v1, v0 ]) def _get_db_from_test_app_install_data(self): db = xapian.inmemory_open() res = update_from_app_install_data(db, self.cache, datadir=os.path.join(DATA_DIR, "desktop")) self.assertTrue(res) self.assertEqual(db.get_doccount(), 5) return db def test_update_from_desktop_file(self): # ensure we index with german locales to test i18n os.environ["LANGUAGE"] = "de" datadir = os.path.join(DATA_DIR, "desktop") db = get_test_db_from_app_install_data(datadir) # test if Name[de] was picked up i=0 for it in db.postlist("AAUbuntu Software Zentrum"): i+=1 def test_update_includes_scope_files(self): datadir = os.path.join(DATA_DIR, "desktop") db = get_test_db_from_app_install_data(datadir) for it in db.postlist("APMunity_lens_music"): doc = db.get_document(it.docid) self.assertEqual( doc.get_value(XapianValues.APPNAME), "Music (Banshee)") break else: self.fail("Did not find scope file in Xapian database") def test_regression_index_terms(self): """ this tests for a regression that we had in the term indexer that would index hundrets of size 1 terms due to a bug in AppInfoParserBase._set_doc_from_key """ db = xapian.WritableDatabase(TEST_DB, xapian.DB_CREATE_OR_OVERWRITE) update_from_app_install_data(db, self.cache, datadir=os.path.join(DATA_DIR, "desktop")) for it in db.postlist("APsoftware-center"): docid = it.docid break # this is the important part, ensure no signle char terms for t in db.termlist(docid): self.assertFalse(len(t.term) == 1) def test_update_from_appstream_xml(self): db = xapian.inmemory_open() res = update_from_appstream_xml(db, self.cache, os.path.join(DATA_DIR, "app-info")) self.assertTrue(res) self.assertEqual(db.get_doccount(), 1) # FIXME: improve tests for p in db.postlist(""): doc = db.get_document(p.docid) for term in doc.termlist(): self.assertIsInstance(term, xapian.TermListItem) self.assertIsInstance(term.term, basestring) for value in doc.values(): self.assertIsInstance(value, xapian.ValueItem) self.assertIsInstance(value.num, long) self.assertIsInstance(value.value, basestring) @unittest.skip("Unreliable: incorrect indices get downloaded") def test_update_from_var_lib_apt_lists(self): # ensure we index with german locales to test i18n os.environ["LANGUAGE"] = "de" db = xapian.inmemory_open() res = update_from_var_lib_apt_lists(db, self.cache, listsdir=os.path.join(DATA_DIR, "app-info")) self.assertTrue(res) self.assertEqual(db.get_doccount(), 1) # test if Name-de was picked up i=0 for it in db.postlist("AAFestplattenbelegung analysieren"): i+=1 self.assertEqual(i, 1) # test if gettext worked found_gettext_translation = False for it in db.postlist("AAFestplattenbelegung analysieren"): doc = db.get_document(it.docid) for term_iter in doc.termlist(): # a german term from the app-info file to ensure that # it got indexed in german if term_iter.term == "festplattenbelegung": found_gettext_translation = True break self.assertTrue(found_gettext_translation) def test_update_from_json_string(self): db = xapian.inmemory_open() cache = apt.Cache() p = os.path.join(DATA_DIR, "app-info-json", "apps.json") res = update_from_json_string(db, cache, open(p).read(), origin=p) self.assertTrue(res) self.assertEqual(db.get_doccount(), 1) @patch("softwarecenter.backend.ubuntusso.UbuntuSSO" ".find_oauth_token_sync") def test_build_from_software_center_agent(self, mock_find_oauth): # pretend we have no token mock_find_oauth.return_value = None db = xapian.inmemory_open() cache = apt.Cache() # monkey patch distro to ensure we get data distro = softwarecenter.distro.get_distro() distro.get_codename = lambda: "natty" # we test against the real https://software-center.ubuntu.com here # so we need network res = update_from_software_center_agent(db, cache, ignore_cache=True) # check results self.assertTrue(res) self.assertTrue(db.get_doccount() > 1) for p in db.postlist(""): doc = db.get_document(p.docid) ppa = doc.get_value(XapianValues.ARCHIVE_PPA) self.assertTrue(ppa.startswith("commercial-ppa") and ppa.count("/") == 1, "ARCHIVE_PPA value incorrect, got '%s'" % ppa) self.assertTrue( "-icon-" in doc.get_value(XapianValues.ICON)) # check support url in the DB url=doc.get_value(XapianValues.SUPPORT_SITE_URL) if url: self.assertTrue(url.startswith("http") or url.startswith("mailto:")) def test_license_string_data_from_software_center_agent(self): #os.environ["SOFTWARE_CENTER_DEBUG_HTTP"] = "1" #os.environ["SOFTWARE_CENTER_AGENT_HOST"] = "http://sc.staging.ubuntu.com/" # staging does not have a valid cert os.environ["PISTON_MINI_CLIENT_DISABLE_SSL_VALIDATION"] = "1" cache = get_test_pkg_info() db = xapian.WritableDatabase(TEST_DB, xapian.DB_CREATE_OR_OVERWRITE) res = update_from_software_center_agent(db, cache, ignore_cache=True) self.assertTrue(res) for p in db.postlist(""): doc = db.get_document(p.docid) license = doc.get_value(XapianValues.LICENSE) self.assertNotEqual(license, "") self.assertNotEqual(license, None) #del os.environ["SOFTWARE_CENTER_AGENT_HOST"] def test_application(self): db = StoreDatabase("/var/cache/software-center/xapian", self.cache) # fail if AppDetails(db) without document= or application= # is run self.assertRaises(ValueError, AppDetails, db) def test_application_details(self): db = xapian.WritableDatabase(TEST_DB, xapian.DB_CREATE_OR_OVERWRITE) res = update_from_app_install_data(db, self.cache, datadir=os.path.join(DATA_DIR, "desktop")) self.assertTrue(res) db = StoreDatabase(TEST_DB, self.cache) db.open(use_axi=False, use_agent=False) self.assertEqual(len(db), 6) # test details app = Application("Ubuntu Software Center Test", "software-center") details = app.get_details(db) self.assertNotEqual(details, None) # mvo: disabled, we can reenable this once there is a static # apt rootdir and we do not rely on the test system to # have software-center from the main archive and not from # e.g. a custom repo like the ADT environment #self.assertEqual(details.component, "main") self.assertEqual(details.pkgname, "software-center") # get the first document for doc in db: if doc.get_data() == "Ubuntu Software Center Test": appdetails = AppDetails(db, doc=doc) break # test get_appname and get_pkgname self.assertEqual(db.get_appname(doc), "Ubuntu Software Center Test") self.assertEqual(db.get_pkgname(doc), "software-center") # test appdetails self.assertEqual(appdetails.name, "Ubuntu Software Center Test") self.assertEqual(appdetails.pkgname, "software-center") # FIXME: add a dekstop file with a real channel to test # and monkey-patch/modify the APP_INSTALL_CHANNELS_PATH self.assertEqual(appdetails.channelname, None) self.assertEqual(appdetails.channelfile, None) self.assertNotEqual(appdetails.pkg, None) # from the fake test/data/appdetails/var/lib/dpkg/status self.assertEqual(appdetails.pkg.is_installed, True) self.assertTrue(appdetails.pkg_state in (PkgStates.INSTALLED, PkgStates.UPGRADABLE)) # FIXME: test description for unavailable pkg self.assertTrue( appdetails.description.startswith("Ubuntu Software Center lets you")) # FIXME: test appdetails.website self.assertEqual(appdetails.icon, "softwarecenter") # crude, crude self.assertTrue(len(appdetails.version) > 2) # FIXME: screenshots will only work on ubuntu self.assertTrue(re.match( "http://screenshots.ubuntu.com/screenshot-with-version/software-center/[\d.]+", appdetails.screenshot)) self.assertTrue(re.match( "http://screenshots.ubuntu.com/thumbnail-with-version/software-center/[\d.]+", appdetails.thumbnail)) # FIXME: add document that has a price self.assertEqual(appdetails.price, "Free") self.assertEqual(appdetails.raw_price, "") # mvo: disabled, we can reenable this once there is a static # apt rootdir and we do not rely on the test system to # have software-center from the main archive and not from # e.g. a custom repo like the ADT environment #self.assertEqual(appdetails.license, "Open source") # test lazy history loading for installation date self.ensure_installation_date_and_lazy_history_loading(appdetails) # test apturl replacements # $kernel app = Application("", "linux-headers-$kernel", "channel=$distro-partner") self.assertEqual(app.pkgname, 'linux-headers-'+os.uname()[2]) # $distro details = app.get_details(db) distro = softwarecenter.distro.get_distro().get_codename() self.assertEqual(app.request, 'channel=' + distro + '-partner') def ensure_installation_date_and_lazy_history_loading(self, appdetails): # we run two tests, the first is to ensure that we get a # result from installation_data immediately (at this point the # history is not loaded yet) so we expect "None" self.assertEqual(appdetails.installation_date, None) # then we need to wait until the history is loaded in the idle # handler context = GLib.main_context_default() while context.pending(): context.iteration() # ... and finally we test that its really there # FIXME: this will only work if software-center is installed self.assertNotEqual(appdetails.installation_date, None) def test_package_states(self): db = xapian.WritableDatabase(TEST_DB, xapian.DB_CREATE_OR_OVERWRITE) res = update_from_app_install_data(db, self.cache, datadir=os.path.join(DATA_DIR, "desktop")) self.assertTrue(res) db = StoreDatabase(TEST_DB, self.cache) db.open(use_axi=False) # test PkgStates.INSTALLED # FIXME: this will only work if software-center is installed app = Application("Ubuntu Software Center Test", "software-center") appdetails = app.get_details(db) self.assertTrue(appdetails.pkg_state in (PkgStates.INSTALLED, PkgStates.UPGRADABLE)) # test PkgStates.UNINSTALLED # test PkgStates.UPGRADABLE # test PkgStates.REINSTALLABLE # test PkgStates.INSTALLING # test PkgStates.REMOVING # test PkgStates.UPGRADING # test PkgStates.NEEDS_SOURCE app = Application("Zynjacku Test", "zynjacku-fake") appdetails = app.get_details(db) self.assertEqual(appdetails.pkg_state, PkgStates.NEEDS_SOURCE) # test PkgStates.NEEDS_PURCHASE app = Application("The expensive gem", "expensive-gem") appdetails = app.get_details(db) self.assertEqual(appdetails.pkg_state, PkgStates.NEEDS_PURCHASE) self.assertEqual(appdetails.icon_url, "http://www.google.com/favicon.ico") self.assertEqual(appdetails.icon, "expensive-gem-icon-favicon") # test PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED # test PkgStates.UNKNOWN app = Application("Scintillant Orange", "scintillant-orange") appdetails = app.get_details(db) self.assertEqual(appdetails.pkg_state, PkgStates.NOT_FOUND) expected = ['use::converting', 'role::program', 'implemented-in::perl'] self.assertEqual(appdetails.tags, set(expected)) def test_packagename_is_application(self): db = StoreDatabase("/var/cache/software-center/xapian", self.cache) db.open() # apt has no app self.assertEqual(db.get_apps_for_pkgname("apt"), set()) # but software-center has self.assertEqual(len(db.get_apps_for_pkgname("software-center")), 1) @unittest.skipIf(not os.path.exists("/var/lib/apt-xapian-index/index"), "Need populated apt-xapian-index for this test") def test_whats_new(self): db = StoreDatabase("/var/cache/software-center/xapian", self.cache) db.open() query = xapian.Query("") enquire = xapian.Enquire(db.xapiandb) enquire.set_query(query) value_time = db._axi_values["catalogedtime"] enquire.set_sort_by_value(value_time, reverse=True) matches = enquire.get_mset(0, 20) last_time = 0 for m in matches: doc = m.document doc.get_value(value_time) >= last_time last_time = doc.get_value(value_time) @patch("softwarecenter.backend.ubuntusso.UbuntuSSO" ".find_oauth_token_sync") def test_for_purchase_apps_date_published(self, mock_find_oauth): # pretend we have no token mock_find_oauth.return_value = None #os.environ["SOFTWARE_CENTER_DEBUG_HTTP"] = "1" #os.environ["SOFTWARE_CENTER_AGENT_HOST"] = "http://sc.staging.ubuntu.com/" # staging does not have a valid cert os.environ["PISTON_MINI_CLIENT_DISABLE_SSL_VALIDATION"] = "1" cache = get_test_pkg_info() db = xapian.inmemory_open() res = update_from_software_center_agent(db, cache, ignore_cache=True) self.assertTrue(res) for p in db.postlist(""): doc = db.get_document(p.docid) date_published = doc.get_value(XapianValues.DATE_PUBLISHED) # make sure that a date_published value is provided self.assertNotEqual(date_published, "") self.assertNotEqual(date_published, None) #del os.environ["SOFTWARE_CENTER_AGENT_HOST"] def test_hardware_requirements_satisfied(self): with patch.object(AppDetails, 'hardware_requirements') as mock_hw: # setup env db = get_test_db() app = Application("", "software-center") mock_hw.__get__ = Mock() # not good mock_hw.__get__.return_value={ 'hardware::gps' : 'no', 'hardware::video:opengl' : 'yes', } details = AppDetails(db, application=app) self.assertFalse(details.hardware_requirements_satisfied) # this if good mock_hw.__get__.return_value={ 'hardware::video:opengl' : 'yes', } self.assertTrue(details.hardware_requirements_satisfied) # empty is satisfied mock_hw.__get__.return_value={} self.assertTrue(details.hardware_requirements_satisfied) @patch("softwarecenter.db.application.get_region_cached") def test_region_requirements_satisfied(self, mock_region_discover): mock_region_discover.return_value = { 'country' : 'Germany', 'countrycode' : 'DE', } with patch.object(AppDetails, 'tags') as mock_tags: # setup env db = get_test_db() app = Application("", "software-center") mock_tags.__get__ = Mock() # not good mock_tags.__get__.return_value = [REGIONTAG+"ZM"] details = AppDetails(db, application=app) self.assertFalse(details.region_requirements_satisfied) # this if good mock_tags.__get__.return_value = [REGIONTAG+"DE"] self.assertTrue(details.region_requirements_satisfied) # empty is satisfied mock_tags.__get__.return_value=["other::tag"] self.assertTrue(details.region_requirements_satisfied) def test_parse_axi_values_file(self): s = """ # This file contains the mapping between names of numeric values indexed in the # APT Xapian index and their index # # Xapian allows to index numeric values as well as keywords and to use them for # all sorts of useful querying tricks. However, every numeric value needs to # have a unique index, and this configuration file is needed to record which # indices are allocated and to provide a mnemonic name for them. # # The format is exactly like /etc/services with name, number and optional # aliases, with the difference that the second column does not use the # "/protocol" part, which would be meaningless here. version 0 # package version catalogedtime 1 # Cataloged timestamp installedsize 2 # installed size packagesize 3 # package size app-popcon 4 # app-install .desktop popcon rank """ fname = "axi-test-values" with open(fname, "w") as f: f.write(s) self.addCleanup(os.remove, fname) #db = StoreDatabase("/var/cache/software-center/xapian", self.cache) axi_values = parse_axi_values_file(fname) self.assertNotEqual(axi_values, {}) #print axi_values @unittest.skipIf(os.path.exists("/var/lib/debtags/package-tags"), "a-x-i will has not run apttags.py plugin") def test_appdetails(self): db = get_test_db() # see "apt-cache show casper|grep ^Tag" details = AppDetails(db, application=Application("", "casper")) self.assertTrue(len(details.tags) > 2) def test_app_enquire(self): db = StoreDatabase(cache=self.cache) db.open() # test the AppEnquire engine enquirer = AppEnquire(self.cache, db) enquirer.set_query(xapian.Query("a"), nonblocking_load=False) self.assertTrue(len(enquirer.get_docids()) > 0) # FIXME: test more of the interface def test_is_pkgname_known(self): db = StoreDatabase(cache=self.cache) db.open() self.assertTrue(db.is_pkgname_known("apt")) self.assertFalse(db.is_pkgname_known("i+am-not-a-pkg")) class UtilsTestCase(unittest.TestCase): def test_utils_get_installed_package_list(self): installed_pkgs = get_installed_package_list() self.assertTrue(len(installed_pkgs) > 0) def test_utils_get_installed_apps_list(self): db = get_test_db() # installed pkgs installed_pkgs = get_installed_package_list() # the installed apps installed_apps = get_installed_apps_list(db) self.assertTrue(len(installed_apps) > 0) self.assertTrue(len(installed_pkgs) > len(installed_apps)) def make_purchased_app_details(db=None, supported_series=None): """Return an AppDetail instance with the required attributes.""" app = make_software_center_agent_app_dict() subscription = make_software_center_agent_subscription_dict(app) if supported_series != None: subscription['application']['series'] = supported_series else: # If no supportod_series kwarg was provided, we ensure the # current series/arch is supported. distro = softwarecenter.distro.get_distro() subscription['application']['series'] = { distro.get_codename(): [distro.get_architecture()] } item = PistonResponseObject.from_dict(subscription) parser = SCAPurchasedApplicationParser(item) if db is None: db = get_test_db() doc = parser.make_doc(db._aptcache) app_details = AppDetails(db, doc) return app_details class AppDetailsSCAApplicationParser(unittest.TestCase): def setUp(self): self.db = get_test_db() def _get_app_details_from_override(self, override_dict): app_dict = make_software_center_agent_app_dict() app_dict.update(override_dict) app_details = self._get_app_details_from_app_dict(app_dict) return app_details def _get_app_details_from_app_dict(self, app_dict): item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) app_details = AppDetails(self.db, doc) return app_details def test_currency(self): app_details = self._get_app_details_from_override({ "price": "24.95"}) self.assertEqual("US$", app_details.currency) self.assertEqual("24.95", app_details.raw_price) @patch('os.path.exists') def test_channel_detection_partner(self, mock): # we need to patch os.path.exists as "AppDetails.channelname" will # check if there is a matching channel description file on disk os.path.exists.return_value = True app_details = self._get_app_details_from_override({ "archive_root": "http://archive.canonical.com/"}) # ensure that archive.canonical.com archive roots are detected # as the partner channel dist = softwarecenter.distro.get_distro().get_codename() self.assertEqual(app_details.channelname, "%s-partner" % dist) @patch('os.path.exists') def test_channel_detection_extras(self, mock): # we need to patch os.path.exists as "AppDetails.channelname" will # check if there is a matching channel description file on disk os.path.exists.return_value = True # setup dict app_details = self._get_app_details_from_override({ "archive_root": "http://extras.ubuntu.com/"}) # ensure that archive.canonical.com archive roots are detected # as the partner channel self.assertEqual(app_details.channelname, "ubuntu-extras") def test_date_no_published(self): app_details = self._get_app_details_from_override({ "date_published": "None"}) # ensure that archive.canonical.com archive roots are detected # as the partner channel self.assertEqual(app_details.date_published, "") # and again app_details = self._get_app_details_from_override({ "date_published": "2012-01-21 02:15:10.358926"}) # ensure that archive.canonical.com archive roots are detected # as the partner channel self.assertEqual(app_details.date_published, "2012-01-21 02:15:10") @patch("softwarecenter.db.update.get_region_cached") def test_no_region_tags_passes(self, get_region_cached_mock): """Do not skip apps with no white or blacklist tags.""" get_region_cached_mock.return_value = { "countrycode" : "es", } app_dict = make_software_center_agent_app_dict({ "debtags": []}) item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertNotEqual(doc, None) @patch("softwarecenter.db.update.get_region_cached") def test_region_blacklist(self, get_region_cached_mock): """Test that the region blacklist ignores blacklisted locations""" get_region_cached_mock.return_value = { "countrycode" : "es", } app_dict = make_software_center_agent_app_dict({ "debtags": ["%s%s" % (REGION_BLACKLIST_TAG, "es")]}) item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertEqual(doc, None) @patch("softwarecenter.db.update.get_region_cached") def test_region_blacklist_blacklists(self, get_region_cached_mock): """Test that the region blacklist adds non-blacklisted locations""" get_region_cached_mock.return_value = { "countrycode" : "de", } app_dict = make_software_center_agent_app_dict({ "debtags": ["%s%s" % (REGION_BLACKLIST_TAG, "ES")]}) item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertNotEqual(doc, None) @patch("softwarecenter.db.update.get_region_cached") def test_region_whitelist_whitelists(self, get_region_cached_mock): """Test that the whitelist adds whitelisted locations""" get_region_cached_mock.return_value = { "countrycode" : "es", } app_dict = make_software_center_agent_app_dict({ "debtags": ["%s%s" % (REGION_WHITELIST_TAG, "ES"), "%s%s" % (REGION_BLACKLIST_TAG, "CA"), "%s%s" % (REGION_WHITELIST_TAG, "US")]}) item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertNotEqual(doc, None) @patch("softwarecenter.db.update.get_region_cached") def test_region_whitelist_blacklists(self, get_region_cached_mock): """Test that the whitelist ignores non-whitelist locations""" get_region_cached_mock.return_value = { "countrycode" : "de", } app_dict = make_software_center_agent_app_dict({ "debtags": ["%s%s" % (REGION_WHITELIST_TAG, "ES"), "%s%s" % (REGION_BLACKLIST_TAG, "CA"), "%s%s" % (REGION_WHITELIST_TAG, "US")]}) # see _get_app_details_from_app_dict item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertEqual(doc, None) @patch("softwarecenter.db.update.get_region_cached") def test_region_whiteandblack_blacklists(self, get_region_cached_mock): """Ignore regions that are in both black and whitelists.""" get_region_cached_mock.return_value = { "countrycode" : "de", } app_dict = make_software_center_agent_app_dict({ "debtags": ["%s%s" % (REGION_WHITELIST_TAG, "DE"), "%s%s" % (REGION_BLACKLIST_TAG, "DE"), "%s%s" % (REGION_BLACKLIST_TAG, "US"), "%s%s" % (REGION_WHITELIST_TAG, "ES")]}) # see _get_app_details_from_app_dict item = PistonResponseObject.from_dict(app_dict) parser = SCAApplicationParser(item) doc = parser.make_doc(self.db._aptcache) self.assertEqual(doc, None) class AppDetailsPkgStateTestCase(unittest.TestCase): @classmethod def setUpClass(cls): # Set these as class attributes as we don't modify either # during the tests. cls.distro = softwarecenter.distro.get_distro() cls.db = get_test_db() def test_package_state_purchased_enable_repo(self): # If the current series is supported by the app, the state should # be PURCHASED_BUT_REPO_MUST_BE_ENABLED. app_details = make_purchased_app_details(self.db, supported_series={ 'current-1': ['i386', 'amd64'], self.distro.get_codename(): [self.distro.get_architecture()] }) state = app_details.pkg_state self.assertEqual( PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, state) def test_package_state_purchased_not_available(self): # If the current series is NOT supported by the app, the state should # be PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES. app_details = make_purchased_app_details(self.db, supported_series={ 'current-1': ['i386', 'amd64'], self.distro.get_codename(): ['newarch', 'amdm128'], }) state = app_details.pkg_state self.assertEqual( PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES, state) def test_package_state_no_series(self): # Until the fix for bug 917109 is deployed on production, we # should default to the current (broken) behaviour of # indicating that the repo just needs enabling. app_details = make_purchased_app_details(self.db, supported_series=None) state = app_details.pkg_state self.assertEqual( PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, state) def test_package_state_arch_any(self): # In the future the supported arches returned by sca will include # any - let's not break when that happens. app_details = make_purchased_app_details(self.db, supported_series={ 'current-1': ['i386', 'amd64'], self.distro.get_codename(): ['newarch', 'any'], }) state = app_details.pkg_state self.assertEqual( PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, state) class MultipleVersionsSupportTestCase(unittest.TestCase): def _make_version(self, not_automatic): ver = Mock(_Version) ver.description ="not_automatic: %s" % not_automatic ver.summary ="summary not_automatic: %s" % not_automatic ver.version = "version not_automatic: %s" % not_automatic mock_origin = Mock() if not_automatic: mock_origin.archive = "precise-backports" else: mock_origin.archive = "precise" ver.origins = [ mock_origin ] ver.not_automatic = not_automatic return ver def test_not_automatic_channel_support(self): db = get_test_db() app = Application("", "software-center") details = app.get_details(db) versions = [ self._make_version(not_automatic=True), self._make_version(not_automatic=False) ] details._pkg.versions = versions details._pkg.candidate = versions[1] self.assertEqual( details.get_not_automatic_archive_versions(), [ (versions[1].version, "precise"), (versions[0].version, "precise-backports") ]) def test_multiple_version_pkg_states(self): db = get_test_db() app = Application("", "software-center") details = app.get_details(db) normal_version = self._make_version(not_automatic=False) not_automatic_version = self._make_version(not_automatic=True) details._pkg.versions = [normal_version, not_automatic_version] details._pkg.installed = normal_version details._pkg.is_installed = True # disabled for now #details._pkg.is_upgradable = True self.assertEqual(details.pkg_state, PkgStates.INSTALLED) app.archive_suite = not_automatic_version self.assertEqual(details.pkg_state, PkgStates.FORCE_VERSION) def test_not_automatic_version(self): db = get_test_db() app = Application("", "software-center") details = app.get_details(db) normal_version = self._make_version(not_automatic=False) not_automatic_version = self._make_version(not_automatic=True) details._pkg.versions = [normal_version, not_automatic_version] # force not-automatic with invalid data self.assertRaises( ValueError, details.force_not_automatic_archive_suite, "random-string") # force not-automatic with valid data self.assertTrue(details.force_not_automatic_archive_suite( not_automatic_version.origins[0].archive)) # ensure we get the description of the not-automatic version self.assertEqual(details.description, not_automatic_version.description) self.assertEqual(details.summary, not_automatic_version.summary) self.assertEqual(details.version, not_automatic_version.version) self.assertEqual(app.archive_suite, not_automatic_version.origins[0].archive) # clearing works details.force_not_automatic_archive_suite("") self.assertEqual(app.archive_suite, "") class XapianQueryParserWorkarounds(unittest.TestCase): """This TestCase demonstrates the issues around the query parser wildcard support if the "-" char is part of the pkgname and tests the workaround for this (http://trac.xapian.org/ticket/128) """ def setUp(self): datadir = os.path.join(DATA_DIR, "desktop") self.db = get_test_db_from_app_install_data(datadir) def test_name_mangling_for_query_parser(self): # test that pkgnames with "-" get added in a mangled form i=0 for it in self.db.postlist("APMsoftware_center"): i+=1 self.assertEqual(i, 1) def test_query_parser_wildcard(self): enquire = xapian.Enquire(self.db) parser = xapian.QueryParser() parser.set_database(self.db) parser.add_prefix("pkg_wildcard", "AP") # this demonstrates the xapian bug with the query parser # and "-" special chars, note that once this test fails (i.e. # the returned mset is "1" we can remove this workaround query = parser.parse_query( "pkg_wildcard:software-*", xapian.QueryParser.FLAG_WILDCARD) enquire.set_query(query) mset = enquire.get_mset(0, 100) self.assertEqual(len(mset), 0) # and the workaround parser.add_prefix("pkg_wildcard", "APM") query = parser.parse_query( "pkg_wildcard:software_*", xapian.QueryParser.FLAG_WILDCARD) enquire.set_query(query) mset = enquire.get_mset(0, 100) self.assertEqual(len(mset), 1) class TrackDBTestCase(unittest.TestCase): def test_track_db_open(self): tmpdir = tempfile.mkdtemp() tmpstamp = os.path.join(tmpdir, "update-stamp") open(tmpstamp, "w") softwarecenter.paths.APT_XAPIAN_INDEX_UPDATE_STAMP_PATH = tmpstamp softwarecenter.paths.APT_XAPIAN_INDEX_DB_PATH = \ softwarecenter.paths.XAPIAN_PATH db = get_test_db() db._axi_stamp_monitor = None db._on_axi_stamp_changed = Mock() self.assertFalse(db._on_axi_stamp_changed.called) db.open(use_axi=True) do_events() self.assertFalse(db._on_axi_stamp_changed.called) do_events() #print "modifiyng stampfile: ", tmpstamp os.utime(tmpstamp, (0, 0)) # wait up to 5s until the gvfs delivers the signal for i in range(50): do_events() time.sleep(0.1) if db._on_axi_stamp_changed.called: break self.assertTrue(db._on_axi_stamp_changed.called) class DBSearchTestCase(unittest.TestCase): APP_INFO_JSON=""" [ { "application_name": "The apt", "package_name": "apt", "description": "meep" } ] """ @classmethod def setUpClass(cls): cache = get_pkg_info() cache.open() db = xapian.WritableDatabase(TEST_DB, xapian.DB_CREATE_OR_OVERWRITE) update_from_json_string(db, cache, cls.APP_INFO_JSON, origin="local") db.close() def setUp(self): # create a fake database to simualte a run of software-center-agent # create a StoreDatabase and add our other db self.db = get_test_db() self.db.add_database(xapian.Database(TEST_DB)) self.db.open(use_axi=True) self.enquire = AppEnquire(self.db._aptcache, self.db) def test_search_app_pkgname_duplication_lp891613(self): # simulate a pkg "apt" that is both in the agent and in the x-a-i db search_term = "apt" search_query = self.db.get_query_list_from_search_entry(search_term) self.enquire.set_query(search_query, nonblocking_load=False) self.assertTrue(len(self.enquire._matches) > 2) for m in self.enquire._matches: doc = m.document # ensure that all hits are "apps" and do not come from a-x-i self.assertNotEqual( doc.get_value(XapianValues.PKGNAME), "") def test_search_custom_pkgs_list_lp1043159(self): # simulate a pkg "apt" that is both in the agent and in the x-a-i db pkgs = ["apt","gedit"] search_query = self.db.get_query_for_pkgnames(pkgs) self.enquire.set_query(search_query, # custom package lists are always in this mode nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE, nonblocking_load=False) self.assertEqual(len(self.enquire._matches), 2) if __name__ == "__main__": unittest.main() software-center-13.10/tests/__init__.py0000664000202700020270000000000012151440100020277 0ustar dobeydobey00000000000000software-center-13.10/tests/gtk3/0000755000202700020270000000000012224614355017066 5ustar dobeydobey00000000000000software-center-13.10/tests/gtk3/test_spinner.py0000664000202700020270000001637512151440100022153 0ustar dobeydobey00000000000000import os import unittest from gi.repository import Gtk from mock import patch from tests.utils import setup_test_env setup_test_env() from softwarecenter.ui.gtk3.widgets import spinner class SpinnerNotebookTestCase(unittest.TestCase): """The test case for the SpinnerNotebook.""" _fake_timeout_id = object() def setUp(self): # helpers to check the timeout's callbacks self._interval = None self._callback = None self.content = Gtk.Label('My test') self.content.show() self.addCleanup(self.content.hide) self.addCleanup(self.content.destroy) self.obj = spinner.SpinnerNotebook(self.content) self.addCleanup(self.obj.hide) self.addCleanup(self.obj.destroy) assert spinner.SOFTWARE_CENTER_DEBUG_TABS not in os.environ self.obj.show() def _fake_timeout_add(self, interval, callback): self._interval = interval self._callback = callback return self._fake_timeout_id def _fake_source_remove(self, event_id): if event_id is self._fake_timeout_id: self._fake_timeout_id = None return True def test_no_borders(self): """The notebook has no borders.""" self.assertFalse(self.obj.get_show_border()) def test_no_tabs(self): """The notebook has no visible tabs.""" self.assertFalse(self.obj.get_show_tabs()) def test_tabs_if_debug_set(self): """The notebook has visible tabs if debug is set.""" with patch.object(spinner, 'SOFTWARE_CENTER_DEBUG_TABS', True): self.obj = spinner.SpinnerNotebook(self.content) self.assertTrue(self.obj.get_show_tabs()) def test_has_two_pages(self): """The notebook has two pages.""" self.assertEqual(self.obj.get_n_pages(), 2) def test_has_content(self): """The notebook has the given content.""" self.assertEqual(self.obj.get_nth_page(self.obj.CONTENT_PAGE), self.content) def test_has_spinner(self): """The notebook has the spinner view.""" self.assertEqual(self.obj.get_nth_page(self.obj.SPINNER_PAGE), self.obj.spinner_view) self.assertTrue(self.obj.spinner_view.get_visible()) def test_show_content_by_default(self): """The content tab is shown by default.""" self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) def test_show_spinner(self): """The spinner is shown only after the timeout occurs.""" assert self._interval is None assert self._callback is None with patch.object(spinner.GLib, 'timeout_add', self._fake_timeout_add): self.obj.show_spinner() # this must hold before the callback is fired self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) self.assertFalse(self.obj.spinner_view.spinner.get_visible()) self.assertEqual(self._interval, 250) self.assertEqual(self._callback, self.obj._unmask_view_spinner) result = self._callback() # fire the timeout # this must hold after the callback is fired self.assertFalse(result, 'The timeout callback should return False.') self.assertTrue(self.obj.spinner_view.spinner.get_property('active')) self.assertTrue(self.obj.spinner_view.spinner.get_visible()) self.assertEqual(self.obj.get_current_page(), self.obj.SPINNER_PAGE) def test_show_spinner_twice(self): """The spinner is not hiden/shown if its already visible.""" with patch.object(self.obj.spinner_view, "stop_and_hide") as m: with patch.object(spinner.GLib, 'timeout_add', lambda t,f: f()): self.obj.show_spinner("meep") self.obj.show_spinner("baap") # ensure that it only called the hide once self.assertEqual(m.call_count, 1) # and that it updated the text self.assertEqual(self.obj.spinner_view.get_text(), "baap") def test_show_spinner_with_msg(self): """The spinner is shown with the given message.""" message = 'Something I want to show' with patch.object(spinner.GLib, 'timeout_add', lambda *a: None): self.obj.show_spinner(msg=message) self.assertEqual(self.obj.spinner_view.get_text(), message) def test_hide_spinner_before_timeout(self): """The spinner is hidden cancelling the timeout.""" with patch.object(spinner.GLib, 'timeout_add', self._fake_timeout_add): self.obj.show_spinner() with patch.object(spinner.GLib, 'source_remove', self._fake_source_remove): self.obj.hide_spinner() # hide_spinner should call source_remove with the proper event id, # which in turn will set the _fake_timeout_id to None self.assertTrue(self._fake_timeout_id is None, 'The timeout should be removed by calling GLib.source_remove') # the content page is shown self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) # the spinner is stoppped and hidden self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) self.assertFalse(self.obj.spinner_view.spinner.get_visible()) def test_hide_spinner_after_timeout(self): """The spinner is hidden without cancelling the timeout.""" with patch.object(spinner.GLib, 'timeout_add', self._fake_timeout_add): self.obj.show_spinner() self._callback() # fake the timeout being fired with patch.object(spinner.GLib, 'source_remove', self._fake_source_remove): self.obj.hide_spinner() self.assertTrue(self._fake_timeout_id is not None, 'GLib.source_remove should not be called if already fired.') # the content page is shown self.assertEqual(self.obj.get_current_page(), self.obj.CONTENT_PAGE) # the spinner is stoppped and hidden self.assertFalse(self.obj.spinner_view.spinner.get_property('active')) self.assertFalse(self.obj.spinner_view.spinner.get_visible()) def test_specify_spinner_size(self): # check small spinner case notebook = spinner.SpinnerNotebook(self.content, "test spinner label", spinner.SpinnerView.SMALL) self.assertEqual(notebook.spinner_view.spinner.get_size_request(), (24, 24)) # check large spinner case (default) notebook = spinner.SpinnerNotebook(self.content, "test spinner label") self.assertEqual(notebook.spinner_view.spinner.get_size_request(), (48, 48)) # check that specifying an invalid spinner_size value raises # an exception with self.assertRaises(ValueError): notebook = spinner.SpinnerNotebook(self.content, "test spinner label", 48) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_appdetailsview.py0000664000202700020270000005427712151440100023521 0ustar dobeydobey00000000000000import os import unittest from gettext import gettext as _ from tests.utils import ( do_events, do_events_with_sleep, get_mock_app_from_real_app, get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache, make_recommend_app_data, setup_test_env, ) # required for the review tests os.environ["SOFTWARE_CENTER_DISTRO_CODENAME"] = "precise" setup_test_env() from mock import Mock, patch from softwarecenter.db.application import Application from softwarecenter.enums import PkgStates from softwarecenter.ui.gtk3.widgets.labels import HardwareRequirementsBox from softwarecenter.region import REGION_WARNING_STRING from tests.gtk3.windows import get_test_window_appdetails from tests.test_database import make_purchased_app_details from softwarecenter.distro import get_distro from softwarecenter.netstatus import ( NetState, test_ping, ) class BaseViewTestCase(unittest.TestCase): db = get_test_db() app_name = "software-center" pkg_state = PkgStates.UNINSTALLED def setUp(self): self.win = get_test_window_appdetails() self.addCleanup(self.win.destroy) self.view = self.win.get_data("view") app = Application("", self.app_name) self.app_mock = get_mock_app_from_real_app(app) self.app_mock.details.pkg_state = self.pkg_state def set_mock_app_and_details(self, app_name="software-center", **kwargs): app = Application("", app_name) mock_app = get_mock_app_from_real_app(app) mock_details = mock_app.get_details(None) for attr, value in kwargs.iteritems(): setattr(mock_details, attr, value) self.view.app = mock_app self.view.app_details = mock_details class TestAppdetailsView(BaseViewTestCase): def test_videoplayer(self): # show app with no video app = Application("", "2vcard") self.view.show_app(app) do_events() self.assertFalse(self.view.videoplayer.get_property("visible")) # create app with video and ensure its visible self.set_mock_app_and_details( app_name="synaptic", # this is a example html - any html5 video will do video_url="http://people.canonical.com/~mvo/totem.html") self.view.show_app(self.view.app) do_events() self.assertTrue(self.view.videoplayer.get_property("visible")) @patch("softwarecenter.ui.gtk3.views.appdetailsview" ".network_state_is_connected") def test_page_pkgstates(self, mock_network_state_is_connected): mock_network_state_is_connected.return_value = True # show app app = Application("", "abiword") self.view.show_app(app) do_events() # check that the action bar is given initial focus in the view self.assertTrue(self.view.pkg_statusbar.button.is_focus()) # create mock app self.set_mock_app_and_details( app_name="abiword", purchase_date="2011-11-20 17:45:01", _error_not_found="error not found", price="US$ 1.00", pkgname="abiword", error="error-text") mock_app = self.view.app mock_details = self.view.app_details # the states and what labels we expect in the pkgstatusbar # first string is status text, second is button text pkg_states_to_labels = { PkgStates.INSTALLED: ("Purchased on 2011-11-20", "Remove"), PkgStates.UNINSTALLED: ('Free', 'Install'), 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 and test if the right ui # elements are visible and have the right text for var in vars(PkgStates): 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( self.view.pkg_statusbar.get_label(), label.decode("utf-8")) 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_visible()) self.assertFalse(self.view.reviews.get_visible()) def test_app_icon_loading(self): # get icon self.set_mock_app_and_details( cached_icon_file_path="download-icon-test", icon="favicon.ico", icon_url="http://de.wikipedia.org/favicon.ico") self.view.show_app(self.view.app) do_events() # ensure the icon is there # FIXME: ensure that the icon is really downloaded #self.assertTrue(os.path.exists(mock_details.cached_icon_file_path)) #os.unlink(mock_details.cached_icon_file_path) def test_add_where_is_it(self): app = Application("", "software-center") self.view.show_app(app) self.view._add_where_is_it_commandline("apt") do_events() self.view._add_where_is_it_launcher( "/usr/share/applications/ubuntu-software-center.desktop") do_events() def test_reviews_page(self): # show s-c and click on more review app = Application("", "software-center") self.view.show_app(app) self.assertEqual(self.view._reviews_server_page, 1) self.view._on_more_reviews_clicked(None) self.assertEqual(self.view._reviews_server_page, 2) # show different app, ensure page is reset app = Application("", "apt") self.view.show_app(app) self.assertEqual(self.view._reviews_server_page, 1) def test_human_readable_name_in_view(self): model = self.view.reviews.review_language.get_model() self.assertEqual(model[0][0], "English") def test_switch_language_resets_page(self): self.view._reviews_server_page = 4 self.view.reviews.emit("different-review-language-clicked", 'my') self.assertEqual(1, self.view._reviews_server_page) def test_switch_reviews_sort_method_resets_page(self): self.view._reviews_server_page = 4 self.view.reviews.emit("review-sort-changed", 1) self.assertEqual(1, self.view._reviews_server_page) @patch('softwarecenter.backend.reviews.rnr.ReviewLoaderSpawningRNRClient' '.get_reviews') def test_no_reviews_returned_attempts_relaxing(self, mock_get_reviews): """AppDetailsView._reviews_ready_callback will attempt to drop the origin and distroseries restriction if no reviews are returned with the restrictions in place. """ self.view._do_load_reviews() self.assertEqual(1, mock_get_reviews.call_count) kwargs = mock_get_reviews.call_args[1] self.assertEqual(False, kwargs['relaxed']) self.assertEqual(1, kwargs['page']) # Now we come back with no data application = mock_get_reviews.call_args[0][0] self.view.review_loader.emit("get-reviews-finished", application, []) self.assertEqual(2, mock_get_reviews.call_count) kwargs = mock_get_reviews.call_args[1] self.assertEqual(True, kwargs['relaxed']) self.assertEqual(1, kwargs['page']) @patch('softwarecenter.backend.reviews.rnr.ReviewLoaderSpawningRNRClient' '.get_reviews') def test_all_duplicate_reviews_keeps_going(self, mock_get_reviews): """AppDetailsView._reviews_ready_callback will fetch another page if all data returned was already displayed in the reviews list. """ # Fixme: Do we have a test factory? review = Mock() review.rating = 3 review.date_created = "2011-01-01 18:00:00" review.version = "1.0" review.summary = 'some summary' review.review_text = 'Some text' review.reviewer_username = "name" review.reviewer_displayname = "displayname" reviews = [review] self.view.reviews.reviews = reviews self.view._do_load_reviews() self.assertEqual(1, mock_get_reviews.call_count) kwargs = mock_get_reviews.call_args[1] self.assertEqual(False, kwargs['relaxed']) self.assertEqual(1, kwargs['page']) # Now we come back with no NEW data application = mock_get_reviews.call_args[0][0] self.view.review_loader.emit("get-reviews-finished", application, reviews) self.assertEqual(2, mock_get_reviews.call_count) kwargs = mock_get_reviews.call_args[1] self.assertEqual(False, kwargs['relaxed']) self.assertEqual(2, kwargs['page']) @unittest.skipIf(test_ping() != NetState.NM_STATE_CONNECTED_GLOBAL, "need network") @patch('softwarecenter.backend.spawn_helper.SpawnHelper.run') def test_submit_new_review_disables_button(self, mock_run): app = Application("", "2vcard") self.view.show_app(app) button = self.view.reviews.new_review self.assertTrue(button.is_sensitive()) button.emit('clicked') self.assertFalse(button.is_sensitive()) def test_new_review_dialog_closes_reenables_submit_button(self): button = self.view.reviews.new_review button.disable() self.view._submit_reviews_done_callback(None, 0) self.assertTrue(button.is_sensitive()) def test_show_app_twice_plays_video(self): video_url = "http://people.canonical.com/~mvo/totem.html" self.set_mock_app_and_details(video_url=video_url) self.view.show_app(self.view.app) self.assertEqual(self.view.videoplayer.uri, video_url) self.view.videoplayer.uri = None self.view.show_app(self.view.app) self.assertEqual(self.view.videoplayer.uri, video_url) class MultipleVersionsTestCase(BaseViewTestCase): def test_multiple_versions_automatic_button(self): # normal app self.view.show_app(self.app_mock) self.assertFalse(self.view.pkg_statusbar.combo_multiple_versions.get_visible()) # switch to not-automatic app with different description self.app_mock.details.get_not_automatic_archive_versions = lambda: [ ("5.0", "precise"), ("12.0", "precise-backports"), ] self.view.show_app(self.app_mock) self.assertTrue(self.view.pkg_statusbar.combo_multiple_versions.get_visible()) text = self.view.pkg_statusbar.combo_multiple_versions.get_active_text() self.assertEqual(text, "v5.0 (default)") def test_combo_multiple_versions(self): self.app_mock.details.get_not_automatic_archive_versions = lambda: [ ("5.0", "precise"), ("12.0", "precise-backports") ] # ensure that the right method is called self.app_mock.details.force_not_automatic_archive_suite = Mock() self.view.show_app(self.app_mock) # test combo box switch self.view.pkg_statusbar.combo_multiple_versions.set_active(1) self.assertTrue( self.app_mock.details.force_not_automatic_archive_suite.called) call_args = self.app_mock.details.force_not_automatic_archive_suite.call_args self.assertEqual(call_args, (("precise-backports",), {})) def test_installed_multiple_version_default(self): self.app_mock.details.get_not_automatic_archive_versions = lambda: [ ("5.0", "precise"), ("12.0", "precise-backports") ] self.app_mock.details.pkg_state = PkgStates.INSTALLED self.app_mock.details.version = "12.0" # FIXME: do we really need this or should the backend derive this # automatically? self.app_mock.archive_suite = "precise-backports" self.app_mock.details.force_not_automatic_archive_suite = Mock() self.view.show_app(self.app_mock) active = self.view.pkg_statusbar.combo_multiple_versions.get_active_text() # ensure that the combo points to "precise-backports" self.assertEqual(active, "v12.0 (precise-backports)") # now change the installed version from 12.0 to 5.0 self.app_mock.details.force_not_automatic_archive_suite.reset_mock() def _side_effect(*args): self.app_mock.archive_suite="precise" self.app_mock.details.pkg_state = PkgStates.FORCE_VERSION self.app_mock.details.force_not_automatic_archive_suite.side_effect = _side_effect self.view.pkg_statusbar.combo_multiple_versions.set_active(0) # ensure that now the default version is forced self.assertTrue( self.app_mock.details.force_not_automatic_archive_suite.called) #call_args = self.app_mock.details.force_not_automatic_archive_suite.call_args #self.assertEqual(call_args, (("precise",), {})) # ensure the button changes self.assertEqual(self.view.pkg_statusbar.button.get_label(), "Change") class HardwareRequirementsTestCase(BaseViewTestCase): def test_show_hardware_requirements(self): self.app_mock.details.hardware_requirements = { 'hardware::video:opengl': 'yes', 'hardware::gps': 'no', } self.app_mock.details.hardware_requirements_satisfied = False self.view.show_app(self.app_mock) do_events() # ensure we have the data self.assertTrue( self.view.hardware_info.value_label.get_property("visible")) self.assertEqual( type(HardwareRequirementsBox()), type(self.view.hardware_info.value_label)) self.assertEqual( self.view.hardware_info.key, _("Also requires")) # ensure that the button is correct self.assertEqual( self.view.pkg_statusbar.button.get_label(), "Install Anyway") # and again for purchase self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE self.view.show_app(self.app_mock) self.assertEqual( self.view.pkg_statusbar.button.get_label(), _(u"Buy Anyway\u2026").encode("utf-8")) # check if the warning bar is displayed self.assertTrue(self.view.pkg_warningbar.get_property("visible")) self.assertEqual(self.view.pkg_warningbar.label.get_text(), _('This software requires a GPS, ' 'but the computer does not have one.')) def test_no_show_hardware_requirements(self): self.app_mock.details.hardware_requirements = {} self.app_mock.details.hardware_requirements_satisfied = True self.view.show_app(self.app_mock) do_events() # ensure we do not show anything if there are no HW requirements self.assertFalse( self.view.hardware_info.get_property("visible")) # ensure that the button is correct self.assertEqual( self.view.pkg_statusbar.button.get_label(), _("Install")) # and again for purchase self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE self.view.show_app(self.app_mock) self.assertEqual( self.view.pkg_statusbar.button.get_label(), _(u'Buy\u2026').encode("utf-8")) # check if the warning bar is invisible self.assertFalse(self.view.pkg_warningbar.get_property("visible")) class RegionRequirementsTestCase(BaseViewTestCase): def test_show_region_requirements(self): self.app_mock.details.region_requirements_satisfied = False self.view.show_app(self.app_mock) do_events() # ensure that the button is correct self.assertEqual( self.view.pkg_statusbar.button.get_label(), "Install Anyway") # and again for purchase self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE self.view.show_app(self.app_mock) self.assertEqual( self.view.pkg_statusbar.button.get_label(), _(u"Buy Anyway\u2026").encode("utf-8")) # check if the warning bar is displayed self.assertTrue(self.view.pkg_warningbar.get_property("visible")) self.assertEqual(self.view.pkg_warningbar.label.get_text(), REGION_WARNING_STRING) class PurchasedAppDetailsStatusBarTestCase(BaseViewTestCase): def _make_statusbar_view_for_state(self, state): app_details = make_purchased_app_details(db=self.db) # XXX 2011-01-23 It's unfortunate we need multiple mocks to test this # correctly, but I don't know the code well enough to refactor # dependencies yet so that it wouldn't be necessary. In this case, we # need a *real* app details object for displaying in the view, but want # to specify its state for the purpose of the test. As an Application # normally loads its details from the database, we patch # Application.get_details also. Patch app_details.pkg_state for the # test. pkg_state_fn = 'softwarecenter.db.application.AppDetails.pkg_state' pkg_state_patcher = patch(pkg_state_fn) self.addCleanup(pkg_state_patcher.stop) mock_pkg_state = pkg_state_patcher.start() mock_pkg_state.__get__ = Mock(return_value=state) get_details_fn = 'softwarecenter.db.application.Application.get_details' get_details_patcher = patch(get_details_fn) self.addCleanup(get_details_patcher.stop) mock_get_details = get_details_patcher.start() mock_get_details.return_value = app_details app = app_details._app details_view = self.win.get_data("view") details_view.show_app(app) do_events() statusbar_view = details_view.pkg_statusbar statusbar_view.configure(app_details, state) return statusbar_view def test_NOT_AVAILABLE_FOR_SERIES_no_action_for_click_event(self): statusbar_view = self._make_statusbar_view_for_state( PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES) mock_app_manager = Mock() statusbar_view.app_manager = mock_app_manager statusbar_view._on_button_clicked(Mock()) self.assertEqual([], mock_app_manager.method_calls) def test_NOT_AVAILABLE_FOR_SERIES_sets_label_and_button(self): statusbar_view = self._make_statusbar_view_for_state( PkgStates.PURCHASED_BUT_NOT_AVAILABLE_FOR_SERIES) self.assertEqual( "Purchased on 2011-09-16 but not available for your current " "Ubuntu version. Please contact the vendor for an update.", statusbar_view.label.get_text()) self.assertFalse(statusbar_view.button.get_visible()) def test_actions_for_purchased_apps(self): button_to_function_tests = ( (PkgStates.INSTALLED, "remove"), (PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, "reinstall_purchased"), (PkgStates.NEEDS_PURCHASE, "buy_app"), (PkgStates.UNINSTALLED, "install"), (PkgStates.REINSTALLABLE, "install"), (PkgStates.UPGRADABLE, "upgrade"), (PkgStates.NEEDS_SOURCE, "enable_software_source") ) for state, func in button_to_function_tests: statusbar_view = self._make_statusbar_view_for_state(state) mock_app_manager = Mock() statusbar_view.app_manager = mock_app_manager statusbar_view._on_button_clicked(Mock()) # If we want to also check the args/kwargs, we can update the above # button_to_function_tests. all_method_calls = [method_name for method_name, args, kwargs in ( mock_app_manager.method_calls)] self.assertEqual( [method_name], all_method_calls) class AppRecommendationsTestCase(BaseViewTestCase): app_name = "pitivi" def on_query_done(self, recagent, data): # print "query done, data: '%s'" % data self.loop.quit() def on_query_error(self, recagent, error): # print "query error received: ", error self.loop.quit() self.error = True # patch out the agent query method to avoid making the actual server call @patch('softwarecenter.backend.recagent.RecommenderAgent' '.query_recommend_app') def test_show_recommendations_for_app(self, mock_query): self.view.show_app(self.app_mock) do_events() panel = self.view.recommended_for_app_panel panel._update_app_recommendations_content() do_events() # we fake the callback from the agent here panel.app_recommendations_cat._recommend_app_result(None, make_recommend_app_data()) self.assertNotEqual( panel.app_recommendations_cat.get_documents(self.db), []) class TestRegression(unittest.TestCase): def test_regression_lp1041004(self): from softwarecenter.ui.gtk3.views import appdetailsview db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() distro = get_distro() view = appdetailsview.AppDetailsView(db, distro, icons, cache) cache.emit("query-total-size-on-install-done", "apt", 10, 10) do_events_with_sleep() self.assertEqual(view.totalsize_info.value_label.get_text(), "") if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_search.py0000664000202700020270000000267112151440100021734 0ustar dobeydobey00000000000000import unittest from tests.utils import do_events_with_sleep, setup_test_env setup_test_env() from tests.gtk3.windows import ( get_test_window_availablepane, get_test_window_installedpane, ) class TestSearch(unittest.TestCase): def test_installedpane(self): win = get_test_window_installedpane() self.addCleanup(win.destroy) installedpane = win.get_data("pane") do_events_with_sleep() installedpane.on_search_terms_changed(None, "the") do_events_with_sleep() model = installedpane.app_view.tree_view.get_model() len1 = len(model) installedpane.on_search_terms_changed(None, "nosuchsearchtermforsure") do_events_with_sleep() len2 = len(model) self.assertTrue(len2 < len1) def test_availablepane(self): win = get_test_window_availablepane() self.addCleanup(win.destroy) pane = win.get_data("pane") do_events_with_sleep() pane.on_search_terms_changed(None, "the") do_events_with_sleep() sortmode = pane.app_view.sort_methods_combobox.get_active_text() self.assertEqual(sortmode, "By Relevance") model = pane.app_view.tree_view.get_model() len1 = len(model) pane.on_search_terms_changed(None, "nosuchsearchtermforsure") do_events_with_sleep() len2 = len(model) self.assertTrue(len2 < len1) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_app_view.py0000664000202700020270000000665012151440100022302 0ustar dobeydobey00000000000000import unittest import xapian from tests.utils import ( do_events, get_test_db, get_test_enquirer_matches, get_test_gtk3_icon_cache, get_test_pkg_info, setup_test_env, ) setup_test_env() from softwarecenter.db.enquire import AppEnquire from softwarecenter.enums import SortMethods from tests.gtk3.windows import get_test_window_appview class TestAppView(unittest.TestCase): """ test the app view """ def setUp(self): self.cache = get_test_pkg_info() self.icons = get_test_gtk3_icon_cache() self.db = get_test_db() def test_app_view(self): enquirer = AppEnquire(self.cache, self.db) enquirer.set_query(xapian.Query(""), sortmode=SortMethods.BY_CATALOGED_TIME, limit=10, nonblocking_load=False) # get test window win = get_test_window_appview() self.addCleanup(win.destroy) appview = win.get_data("appview") # set matches appview.clear_model() appview.display_matches(enquirer.matches) do_events() # verify that the order is actually the correct one model = appview.tree_view.get_model() docs_in_model = [item[0] for item in model] docs_from_enquirer = [m.document for m in enquirer.matches] self.assertEqual(len(docs_in_model), len(docs_from_enquirer)) for i in range(len(docs_in_model)): self.assertEqual(self.db.get_pkgname(docs_in_model[i]), self.db.get_pkgname(docs_from_enquirer[i])) def test_appview_search_combo(self): # test if combox sort option "by relevance" vanishes for non-searches # LP: #861778 expected_normal = ["By Name", "By Top Rated", "By Newest First"] expected_search = ["By Name", "By Top Rated", "By Newest First", "By Relevance"] # setup goo win = get_test_window_appview() self.addCleanup(win.destroy) appview = win.get_data("appview") #entry = win.get_data("entry") do_events() # get the model model = appview.sort_methods_combobox.get_model() # test normal window (no search) matches = get_test_enquirer_matches(appview.helper.db) appview.display_matches(matches, is_search=False) appview.configure_sort_method(is_search=False) do_events() in_model = [] for item in model: in_model.append(model.get_value(item.iter, 0)) self.assertEqual(in_model, expected_normal) # now repeat and simulate a search matches = get_test_enquirer_matches(appview.helper.db) appview.display_matches(matches, is_search=True) appview.configure_sort_method(is_search=True) do_events() in_model = [] for item in model: in_model.append(model.get_value(item.iter, 0)) self.assertEqual(in_model, expected_search) # and back again to no search matches = get_test_enquirer_matches(appview.helper.db) appview.display_matches(matches, is_search=False) appview.configure_sort_method(is_search=False) do_events() # collect items in the model in_model = [] for item in model: in_model.append(model.get_value(item.iter, 0)) self.assertEqual(expected_normal, in_model) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/windows.py0000664000202700020270000010770112216063163021136 0ustar dobeydobey00000000000000from __future__ import print_function import logging import os import sys import urllib import xapian from gi.repository import Gdk, Gtk, GLib from mock import Mock, patch import softwarecenter.distro import softwarecenter.log import softwarecenter.paths from softwarecenter.backend import channel from softwarecenter.db import ( appfilter, application, database, enquire, pkginfo, ) from softwarecenter.enums import ( BUY_SOMETHING_HOST, NonAppVisibility, ) from softwarecenter.ui.gtk3 import em from softwarecenter.ui.gtk3.dialogs import dependency_dialogs from softwarecenter.ui.gtk3.models import appstore2 from softwarecenter.ui.gtk3.panes import ( availablepane, globalpane, historypane, installedpane, pendingpane, viewswitcher, ) from softwarecenter.ui.gtk3.session import ( appmanager, displaystate, ) from softwarecenter.ui.gtk3.utils import ( get_sc_icon_theme, ) from softwarecenter.ui.gtk3.views import ( appview, appdetailsview, catview, lobbyview, pkgnamesview, purchaseview, ) from softwarecenter.ui.gtk3.widgets import ( backforward, buttons, containers, description, exhibits, labels, oneconfviews, recommendations, reviews, searchentry, spinner, stars, symbolic_icons, thumbnail, videoplayer, ) from softwarecenter.utils import ExecutionTime from tests.utils import ( do_events, get_test_categories, get_test_db, get_test_gtk3_icon_cache, get_test_gtk3_viewmanager, get_test_install_backend, get_test_pkg_info, patch_datadir, ) if os.environ.get('SOFTWARE_CENTER_PERFORMANCE_DEBUG', False): softwarecenter.log.root.setLevel(level=logging.DEBUG) softwarecenter.log.add_filters_from_string("performance") fmt = logging.Formatter("%(name)s - %(message)s", None) softwarecenter.log.handler.setFormatter(fmt) if os.environ.get('SOFTWARE_CENTER_DEBUG', False): softwarecenter.log.root.setLevel(level=logging.DEBUG) fmt = logging.Formatter("%(name)s - %(message)s", None) softwarecenter.log.handler.setFormatter(fmt) # compat window with functions for {get,set}_data() as this # got removed from python gobject API in 12.10 class TestWindow(Gtk.Window): def __init__(self): super(TestWindow, self).__init__() self.set_position(Gtk.WindowPosition.CENTER) self._data = {} def get_data(self, key): return self._data.get(key) def set_data(self, key, value): self._data[key] = value def get_test_window(child, width=600, height=800, border_width=0, title=None): win = TestWindow() win.set_size_request(width, height) win.set_border_width(border_width) win.add(child) if title is None: title = child.__class__.__name__ win.set_title(title) win.show_all() return win def get_test_window_dependency_dialog(): icons = get_test_gtk3_icon_cache() db = get_test_db() depends = ["apt", "synaptic"] app = application.Application("", "software-center") primary = "primary text" button_text = "button_text" dia = dependency_dialogs._get_confirm_internal_dialog( parent=None, app=app, db=db, icons=icons, primary=primary, button_text=button_text, depends=depends, cache=db._aptcache) return dia def get_test_window_confirm_remove(): # test real remove dialog icons = get_test_gtk3_icon_cache() db = get_test_db() app = application.Application("", "p7zip-full") return dependency_dialogs.confirm_remove(None, app, db, icons) def get_query_from_search_entry(search_term): if not search_term: return xapian.Query("") parser = xapian.QueryParser() user_query = parser.parse_query(search_term) return user_query def on_entry_changed(widget, data): def _work(): new_text = widget.get_text() (view, enquirer) = data 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) store = view.tree_view.get_model() if store is None: return with ExecutionTime("store.clear()"): store.clear() with ExecutionTime("store.set_from_matches()"): store.set_from_matches(enquirer.matches) with ExecutionTime("model settle (size=%s)" % len(store)): do_events() if widget.stamp: GLib.source_remove(widget.stamp) widget.stamp = GLib.timeout_add(250, _work) def get_test_window_appview(): db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() # create a filter app_filter = appfilter.AppFilter(db, cache) app_filter.set_supported_only(False) app_filter.set_installed_only(True) # appview enquirer = enquire.AppEnquire(cache, db) store = appstore2.AppListStore(db, cache, icons) view = appview.AppView(db, cache, icons, show_ratings=True) view.set_model(store) entry = Gtk.Entry() entry.stamp = 0 entry.connect("changed", on_entry_changed, (view, enquirer)) entry.set_text("gtk3") box = Gtk.VBox() box.pack_start(entry, False, True, 0) box.pack_start(view, True, True, 0) win = get_test_window(child=box) win.set_data("appview", view) win.set_data("entry", entry) return win def get_test_window_apptreeview(): cache = get_test_pkg_info() db = get_test_db() icons = get_test_gtk3_icon_cache() # create a filter app_filter = appfilter.AppFilter(db, cache) app_filter.set_supported_only(False) app_filter.set_installed_only(True) # get the TREEstore store = appstore2.AppTreeStore(db, cache, icons) # populate from data cats = get_test_categories(db) for cat in cats[:3]: 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 app_view = appview.AppView(db, cache, icons, show_ratings=True) app_view.set_model(store) box = Gtk.VBox() box.pack_start(app_view, True, True, 0) win = get_test_window(child=box) return win def get_test_window_availablepane(): # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() assert vm is not None db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() backend = get_test_install_backend() distro = softwarecenter.distro.get_distro() manager = appmanager.get_appmanager() if manager is None: # create global AppManager instance manager = appmanager.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) zl = "softwarecenter.backend.zeitgeist_logger.ZeitgeistLogger"; patch(zl + ".log_install_event").start().return_value = False patch(zl + ".log_uninstall_event").start().return_value = False patch("softwarecenter.utils.is_unity_running").start().return_value = False w = availablepane.AvailablePane(cache, db, distro, icons, navhistory_back_action, navhistory_forward_action) w.init_view() w.show() win = get_test_window(child=w, width=800, height=600) # this is used later in tests win.set_data("pane", w) win.set_data("vm", vm) return win def get_test_window_globalpane(): vm = get_test_gtk3_viewmanager() db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() p = globalpane.GlobalPane(vm, db, cache, icons) win = get_test_window(child=p) win.set_data("pane", p) return win def get_test_window_pendingpane(): icons = get_test_gtk3_icon_cache() view = pendingpane.PendingPane(icons) # gui scroll = Gtk.ScrolledWindow() scroll.add_with_viewport(view) win = get_test_window(child=scroll) view.grab_focus() return win @patch_datadir('./data') def get_test_window_viewswitcher(): cache = get_test_pkg_info() db = get_test_db() icons = get_test_gtk3_icon_cache() manager = get_test_gtk3_viewmanager() view = viewswitcher.ViewSwitcher(manager, db, cache, icons) scroll = Gtk.ScrolledWindow() box = Gtk.VBox() box.pack_start(scroll, True, True, 0) win = get_test_window(child=box) scroll.add_with_viewport(view) return win def get_test_window_installedpane(): # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() vm # make pyflakes happy db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() w = installedpane.InstalledPane(cache, db, 'Ubuntu', icons) w.show() # init the view w.init_view() w.state.channel = channel.AllInstalledChannel() view_state = displaystate.DisplayState() view_state.channel = channel.AllInstalledChannel() w.display_overview_page(view_state) win = get_test_window(child=w) win.set_data("pane", w) return win def get_test_window_historypane(): # needed because available pane will try to get it vm = get_test_gtk3_viewmanager() vm # make pyflakes happy db = get_test_db() cache = get_test_pkg_info() icons = get_test_gtk3_icon_cache() widget = historypane.HistoryPane(cache, db, None, icons) widget.show() win = get_test_window(child=widget) widget.init_view() return win def get_test_window_recommendations(panel_type="lobby"): cache = get_test_pkg_info() db = get_test_db() icons = get_test_gtk3_icon_cache() from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper properties_helper = AppPropertiesHelper(db, cache, icons) if panel_type is "lobby": view = recommendations.RecommendationsPanelLobby( db, properties_helper) elif panel_type is "category": cats = get_test_categories(db) view = recommendations.RecommendationsPanelCategory( db, properties_helper, cats[0]) else: # panel_type is "details": view = recommendations.RecommendationsPanelDetails( db, properties_helper) win = get_test_window(child=view, width=600, height=300) win.set_data("rec_panel", view) return win def get_test_window_catview(db=None, selected_category="Internet"): ''' Note that selected_category must specify a category that includes subcategories, else a ValueError will be raised. ''' def on_category_selected(view, cat): print("on_category_selected view: ", view) print("on_category_selected cat: ", cat) if db is None: cache = pkginfo.get_pkg_info() cache.open() xapian_base_path = "/var/cache/software-center" pathname = os.path.join(xapian_base_path, "xapian") db = database.StoreDatabase(pathname, cache) db.open() else: cache = db._aptcache icons = get_sc_icon_theme() distro = softwarecenter.distro.get_distro() apps_filter = appfilter.AppFilter(db, cache) # gui notebook = Gtk.Notebook() lobby_view = lobbyview.LobbyView(cache, db, icons, distro, apps_filter) scroll = Gtk.ScrolledWindow() scroll.add(lobby_view) notebook.append_page(scroll, Gtk.Label(label="Lobby")) subcat_cat = None for cat in lobby_view.categories: if cat.name == selected_category: if not cat.subcategories: raise ValueError('The value specified for selected_category ' '*must* specify a ' 'category that contains subcategories!!') subcat_cat = cat break subcat_view = catview.SubCategoryView(cache, db, icons, apps_filter) subcat_view.connect("category-selected", on_category_selected) subcat_view.set_subcategory(subcat_cat) scroll = Gtk.ScrolledWindow() scroll.add(subcat_view) notebook.append_page(scroll, Gtk.Label(label="Subcats")) win = get_test_window(child=notebook, width=800, height=800) win.set_data("subcat", subcat_view) win.set_data("lobby", lobby_view) return win def get_test_catview(): def on_category_selected(view, cat): print("on_category_selected %s %s" % (view, cat)) cache = pkginfo.get_pkg_info() cache.open() xapian_base_path = "/var/cache/software-center" pathname = os.path.join(xapian_base_path, "xapian") db = database.StoreDatabase(pathname, cache) db.open() icons = get_sc_icon_theme() distro = softwarecenter.distro.get_distro() apps_filter = appfilter.AppFilter(db, cache) cat_view = lobbyview.LobbyView(cache, db, icons, distro, apps_filter) return cat_view def get_test_window_appdetails(pkgname=None): cache = pkginfo.get_pkg_info() cache.open() xapian_base_path = "/var/cache/software-center" pathname = os.path.join(xapian_base_path, "xapian") db = database.StoreDatabase(pathname, cache) db.open() icons = get_sc_icon_theme() distro = softwarecenter.distro.get_distro() # gui scroll = Gtk.ScrolledWindow() view = appdetailsview.AppDetailsView(db, distro, icons, cache) if pkgname is None: pkgname = "totem" view.show_app(application.Application("", pkgname)) #view.show_app(application.Application("Pay App Example", "pay-app")) #view.show_app(application.Application("3D Chess", "3dchess")) #view.show_app(application.Application("Movie Player", "totem")) #view.show_app(application.Application("ACE", "unace")) #~ view.show_app(application.Application("", "apt")) #view.show_app("AMOR") #view.show_app("Configuration Editor") #view.show_app("Artha") #view.show_app("cournol") #view.show_app("Qlix") scroll.add(view) scroll.show() win = get_test_window(child=scroll, width=800, height=800) win.set_data("view", view) return win def get_test_window_pkgnamesview(): cache = pkginfo.get_pkg_info() cache.open() xapian_base_path = "/var/cache/software-center" pathname = os.path.join(xapian_base_path, "xapian") db = database.StoreDatabase(pathname, cache) db.open() icons = get_sc_icon_theme() pkgs = ["apt", "software-center"] view = pkgnamesview.PackageNamesView("header", cache, pkgs, icons, 32, db) view.show() win = get_test_window(child=view) return win @patch_datadir('./tests/data') def get_test_window_purchaseview(url=None): if url is None: #url = "http://www.animiertegifs.de/java-scripts/alertbox.php" url = "http://www.ubuntu.cohtml=DUMMY_m" #d = PurchaseDialog(app=None, url="http://spiegel.de") url_args = urllib.urlencode({'archive_id': "mvo/private-test", 'arch': "i386"}) url = (BUY_SOMETHING_HOST + "/subscriptions/en/ubuntu/precise/+new/?%s" % url_args) # useful for debugging #d.connect("key-press-event", _on_key_press) #GLib.timeout_add_seconds(1, _generate_events, d) widget = purchaseview.PurchaseView() widget.config = Mock() win = get_test_window(child=widget) win.set_data("view", widget) widget.initiate_purchase(app=None, iconname=None, url=url) #widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML) return win def get_test_backforward_window(*args, **kwargs): backforward_button = backforward.BackForwardButton() win = get_test_window(child=backforward_button, *args, **kwargs) return win def get_test_container_window(): f = containers.FlowableGrid() for i in range(10): t = buttons.CategoryTile("test", "folder") f.add_child(t) scroll = Gtk.ScrolledWindow() scroll.add_with_viewport(f) win = get_test_window(child=scroll) return win def _build_channels_list(popup): for i in range(3): item = Gtk.MenuItem.new() label = Gtk.Label.new("channel_name %s" % i) box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, em.StockEms.MEDIUM) box.pack_start(label, False, False, 0) item.add(box) item.show_all() popup.attach(item, 0, 1, i, i + 1) def get_test_buttons_window(): vb = Gtk.VBox(spacing=12) link = buttons.Link("test link", uri="www.google.co.nz") vb.pack_start(link, False, False, 0) button = Gtk.Button() button.set_label("channels") channels_button = buttons.ChannelSelector(button) channels_button.parent_style_type = Gtk.Window channels_button.set_build_func(_build_channels_list) hb = Gtk.HBox() hb.pack_start(button, False, False, 0) hb.pack_start(channels_button, False, False, 0) vb.pack_start(hb, False, False, 0) win = get_test_window(child=vb) return win def get_test_description_window(): 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.""" 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 - Matroska - AVI - MPEG TS - flv - QuickTime - MPEG4 - 3GPP - MXT * Audio encoders: - Vorbis - FLAC - MP3 - AAC - AC3 - Speex - Celt * Video encoders: - Theora - Dirac - H264 - MPEG2 - MPEG4/DivX5 - xvid - 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: * Create and modify archives. * View the content of an archive. * View a file contained in an archive. * Extract files from the archive. File-roller supports the following formats: * Tar (.tar) archives, including those compressed with gzip (.tar.gz, .tgz), bzip (.tar.bz, .tbz), bzip2 (.tar.bz2, .tbz2), compress (.tar.Z, .taz), lzip (.tar.lz, .tlz), lzop (.tar.lzo, .tzo), lzma (.tar.lzma) and xz (.tar.xz) * Zip archives (.zip) * Jar archives (.jar, .ear, .war) * 7z archives (.7z) * iso9660 CD images (.iso) * 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.""" EXAMPLE3 = """This package includes the following CTAN packages: Asana-Math -- A font to typeset maths in Xe(La)TeX. albertus -- allrunes -- Fonts and LaTeX package for almost all runes. antiqua -- the URW Antiqua Condensed Font. antp -- Antykwa Poltawskiego: a Type 1 family of Polish traditional type. antt -- Antykwa Torunska: a Type 1 family of a Polish traditional type. apl -- Fonts for typesetting APL programs. ar -- Capital A and capital R ligature for Apsect Ratio. archaic -- A collection of archaic fonts. arev -- Fonts and LaTeX support files for Arev Sans. ascii -- Support for IBM "standard ASCII" font. astro -- Astronomical (planetary) symbols. atqolive -- augie -- Calligraphic font for typesetting handwriting. auncial-new -- Artificial Uncial font and LaTeX support macros. aurical -- Calligraphic fonts for use with LaTeX in T1 encoding. barcodes -- Fonts for making barcodes. bayer -- Herbert Bayers Universal Font For Metafont. bbding -- A symbol (dingbat) font and LaTeX macros for its use. bbm -- "Blackboard-style" cm fonts. bbm-macros -- LaTeX support for "blackboard-style" cm fonts. bbold -- Sans serif blackboard bold. belleek -- Free replacement for basic MathTime fonts. bera -- Bera fonts. blacklettert1 -- T1-encoded versions of Haralambous old German fonts. boisik -- A font inspired by Baskerville design. bookhands -- A collection of book-hand fonts. braille -- Support for braille. brushscr -- A handwriting script font. calligra -- Calligraphic font. carolmin-ps -- Adobe Type 1 format of Carolingian Minuscule fonts. cherokee -- A font for the Cherokee script. clarendo -- cm-lgc -- Type 1 CM-based fonts for Latin, Greek and Cyrillic. cmbright -- Computer Modern Bright fonts. cmll -- Symbols for linear logic. cmpica -- A Computer Modern Pica variant. coronet -- courier-scaled -- Provides a scaled Courier font. cryst -- Font for graphical symbols used in crystallography. cyklop -- The Cyclop typeface. dancers -- Font for Conan Doyle's "The Dancing Men". dice -- A font for die faces. dictsym -- DictSym font and macro package dingbat -- Two dingbat symbol fonts. doublestroke -- Typeset mathematical double stroke symbols. dozenal -- Typeset documents using base twelve numbering (also called "dozenal") duerer -- Computer Duerer fonts. duerer-latex -- LaTeX support for the Duerer fonts. ean -- Macros for making EAN barcodes. ecc -- Sources for the European Concrete fonts. eco -- Oldstyle numerals using EC fonts. eiad -- Traditional style Irish fonts. eiad-ltx -- LaTeX support for the eiad font. elvish -- Fonts for typesetting Tolkien Elvish scripts. epigrafica -- A Greek and Latin font. epsdice -- A scalable dice "font". esvect -- Vector arrows. eulervm -- Euler virtual math fonts. euxm -- feyn -- A font for in-text Feynman diagrams. fge -- A font for Frege's Grundgesetze der Arithmetik. foekfont -- The title font of the Mads Fok magazine. fonetika -- Support for the danish "Dania" phonetic system. fourier -- Using Utopia fonts in LaTeX documents. fouriernc -- Use New Century Schoolbook text with Fourier maths fonts. frcursive -- French cursive hand fonts. garamond -- genealogy -- A compilation genealogy font. gfsartemisia -- A modern Greek font design. gfsbodoni -- A Greek and Latin font based on Bodoni. gfscomplutum -- A Greek font with a long history. gfsdidot -- A Greek font based on Didot's work. gfsneohellenic -- A Greek font in the Neo-Hellenic style. gfssolomos -- A Greek-alphabet font. gothic -- A collection of old German-style fonts. greenpoint -- The Green Point logo. groff -- grotesq -- the URW Grotesk Bold Font. hands -- Pointing hand font. hfbright -- The hfbright fonts. hfoldsty -- Old style numerals with EC fonts. ifsym -- A collection of symbols. inconsolata -- A monospaced font, with support files for use with TeX. initials -- Adobe Type 1 decorative initial fonts. iwona -- A two-element sans-serif font. junicode -- A TrueType font for mediaevalists. kixfont -- A font for KIX codes. knuthotherfonts -- kpfonts -- A complete set of fonts for text and mathematics. kurier -- A two-element sans-serif typeface. lettrgth -- lfb -- A Greek font with normal and bold variants. libertine -- Use the font Libertine with LaTeX. libris -- Libris ADF fonts, with LaTeX support. linearA -- Linear A script fonts. logic -- A font for electronic logic design. lxfonts -- Set of slide fonts based on CM. ly1 -- Support for LY1 LaTeX encoding. marigold -- mathabx -- Three series of mathematical symbols. mathdesign -- Mathematical fonts to fit with particular text fonts. mnsymbol -- Mathematical symbol font for Adobe MinionPro. nkarta -- A "new" version of the karta cartographic fonts. ocherokee -- LaTeX Support for the Cherokee language. ogham -- Fonts for typesetting Ogham script. oinuit -- LaTeX Support for the Inuktitut Language. optima -- orkhun -- A font for orkhun script. osmanian -- Osmanian font for writing Somali. pacioli -- Fonts designed by Fra Luca de Pacioli in 1497. pclnfss -- Font support for current PCL printers. phaistos -- Disk of Phaistos font. phonetic -- MetaFont Phonetic fonts, based on Computer Modern. pigpen -- A font for the pigpen (or masonic) cipher. psafm -- punk -- Donald Knuth's punk font. recycle -- A font providing the "recyclable" logo. sauter -- Wide range of design sizes for CM fonts. sauterfonts -- Use sauter fonts in LaTeX. semaphor -- Semaphore alphabet font. simpsons -- MetaFont source for Simpsons characters. skull -- A font to draw a skull. staves -- Typeset Icelandic staves and runic letters. tapir -- A simple geometrical font. tengwarscript -- LaTeX support for using Tengwar fonts. trajan -- Fonts from the Trajan column in Rome. umtypewriter -- Fonts to typeset with the xgreek package. univers -- universa -- Herbert Bayer's 'universal' font. venturisadf -- Venturis ADF fonts collection. wsuipa -- International Phonetic Alphabet fonts. 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: * 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. * Rip straight from DVD media easily (requires libdvdcss). * Rip straight from v4l devices. * Simple terminal client for scripting. * Automatic preset updating.""" def on_clicked(widget, desc_widget, descs): widget.position += 1 if widget.position >= len(descs): widget.position = 0 desc_widget.set_description(*descs[widget.position]) descs = ((EXAMPLE0, ''), (EXAMPLE1, ''), (EXAMPLE2, ''), (EXAMPLE3, 'texlive-fonts-extra'), (EXAMPLE4, '')) vb = Gtk.VBox() b = Gtk.Button('Next test description >>') b.position = 0 vb.pack_start(b, False, False, 0) scroll = Gtk.ScrolledWindow() vb.add(scroll) d = description.AppDescription() #~ d.description.DEBUG_PAINT_BBOXES = True d.set_description(EXAMPLE0, pkgname='') scroll.add_with_viewport(d) b.connect("clicked", on_clicked, d, descs) win = get_test_window(child=vb) win.set_has_resize_grip(True) return win @patch_datadir('./data') def get_test_exhibits_window(): exhibit_banner = exhibits.ExhibitBanner() exhibits_list = [exhibits.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) exhibit_banner.set_exhibits(exhibits_list) scroll = Gtk.ScrolledWindow() scroll.add_with_viewport(exhibit_banner) win = get_test_window(child=scroll) return win def get_test_window_labels(): HW_TEST_RESULT = { 'hardware::gps': 'yes', 'hardware::video:opengl': 'no', } # add it hwbox = labels.HardwareRequirementsBox() hwbox.set_hardware_requirements(HW_TEST_RESULT) win = get_test_window(child=hwbox) return win def get_test_window_oneconfviews(): w = oneconfviews.OneConfViews(Gtk.IconTheme.get_default()) win = get_test_window(child=w) win.set_data("pane", w) # init the view w.register_computer("AAAAA", "NameA") w.register_computer("ZZZZZ", "NameZ") w.register_computer("DDDDD", "NameD") w.register_computer("CCCCC", "NameC") w.register_computer("", "This computer should be first") w.select_first() GLib.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") return win @patch_datadir('./data') def get_test_reviews_window(): appdetails_mock = Mock() appdetails_mock.version = "2.0" parent = Mock() parent.app_details = appdetails_mock review_data = Mock() review_data.app_name = "app" review_data.usefulness_favorable = 10 review_data.usefulness_total = 12 review_data.usefulness_submit_error = False review_data.reviewer_username = "name" review_data.reviewer_displayname = "displayname" review_data.date_created = "2011-01-01 18:00:00" review_data.summary = "summary" review_data.review_text = 10 * "loonng text" review_data.rating = "3.0" review_data.version = "1.0" # create reviewslist vb = reviews.UIReviewsList(parent) vb.add_review(review_data) vb.configure_reviews_ui() win = get_test_window(child=vb) return win def get_test_searchentry_window(): icons = Gtk.IconTheme.get_default() entry = searchentry.SearchEntry(icons) entry.connect("terms-changed", lambda w, terms: print(terms)) win = get_test_window(child=entry) win.entry = entry return win def get_test_spinner_window(): label = Gtk.Label("foo") spinner_notebook = spinner.SpinnerNotebook(label, "random msg") win = get_test_window(child=spinner_notebook) spinner_notebook.show_spinner("Loading for 1s ...") GLib.timeout_add_seconds(1, lambda: spinner_notebook.hide_spinner()) return win def get_test_stars_window(): vb = Gtk.VBox() vb.set_spacing(6) win = get_test_window(child=vb) vb.add(Gtk.Button()) vb.add(Gtk.Label(label="BLAHHHHHH")) star = stars.Star() star.set_n_stars(5) star.set_rating(2.5) star.set_size(stars.StarSize.SMALL) vb.pack_start(star, False, False, 0) star = stars.Star() star.set_n_stars(5) star.set_rating(2.5) star.set_size(stars.StarSize.NORMAL) vb.pack_start(star, False, False, 0) star = stars.Star() star.set_n_stars(5) star.set_rating(2.575) star.set_size(stars.StarSize.BIG) vb.pack_start(star, False, False, 0) star = stars.Star() star.set_n_stars(5) star.set_rating(3.333) star.set_size_as_pixel_value(36) vb.pack_start(star, False, False, 0) star = stars.ReactiveStar() star.set_n_stars(5) star.set_rating(3) star.set_size_as_pixel_value(em.big_em(3)) vb.pack_start(star, False, False, 0) selector = stars.StarRatingSelector() vb.pack_start(selector, False, False, 0) return win @patch_datadir('./data') def get_test_symbolic_icons_window(): hb = Gtk.HBox(spacing=12) ico = symbolic_icons.SymbolicIcon("available") hb.add(ico) ico = symbolic_icons.PendingSymbolicIcon("pending") ico.start() ico.set_transaction_count(33) hb.add(ico) ico = symbolic_icons.PendingSymbolicIcon("pending") ico.start() ico.set_transaction_count(1) hb.add(ico) win = get_test_window(child=hb) return win def get_test_screenshot_thumbnail_window(): class CycleState(object): def __init__(self): self.app_n = 0 self.apps = [application.Application("Movie Player", "totem"), application.Application("Comix", "comix"), application.Application("Gimp", "gimp"), #application.Application("ACE", "uace"), ] def testing_cycle_apps(widget, thumb, db, cycle_state): d = cycle_state.apps[cycle_state.app_n].get_details(db) if cycle_state.app_n + 1 < len(cycle_state.apps): cycle_state.app_n += 1 else: cycle_state.app_n = 0 thumb.fetch_screenshots(d) return True cycle_state = CycleState() vb = Gtk.VBox(spacing=6) win = get_test_window(child=vb) icons = get_test_gtk3_icon_cache() distro = softwarecenter.distro.get_distro() t = thumbnail.ScreenshotGallery(distro, icons) t.connect('draw', t.draw) frame = containers.FramedBox() frame.add(t) win.set_data("screenshot_thumbnail_widget", t) b = Gtk.Button('A button for cycle testing') vb.pack_start(b, False, False, 8) win.set_data("screenshot_button_widget", b) vb.pack_start(frame, True, True, 0) win.set_data("screenshot_thumbnail_cycle_test_button", b) db = get_test_db() win.show_all() testing_cycle_apps(None, t, db, cycle_state) b.connect("clicked", testing_cycle_apps, t, db, cycle_state) return win def get_test_videoplayer_window(video_url=None): Gdk.threads_init() # youtube example fragment html_youtube = """""" # vimeo example video fragment html_vimeo = """

Supertuxkart 0.6 from constantin pelikan on Vimeo.

""" # dailymotion example video fragment html_dailymotion = """""" html_dailymotion2 = """""" html_youtube # pyflakes html_dailymotion # pyflakes html_dailymotion2 # pyflakes player = videoplayer.VideoPlayer() win = get_test_window(child=player, width=500, height=400) if video_url is None: #player.uri = "http://upload.wikimedia.org/wikipedia/commons/9/9b/" \ # "Pentagon_News_Sample.ogg" #player.uri = "http://people.canonical.com/~mvo/totem.html" player.load_html_string(html_vimeo) else: player.uri = video_url return win if __name__ == '__main__': if len(sys.argv) > 1: window_name = sys.argv[1] result = None for i in ('get_test_window_%s', 'get_%s_test_window', 'get_test_%s_window'): name = i % window_name print('Trying to execute: ', name) f = locals().get(name) if f is not None: print('Success! Running the main loop and showing the window.') # pass the renaming sys.argv to the function result = f(*sys.argv[2:]) break if result is not None: if isinstance(result, Gtk.Dialog): response = result.run() result.hide() GLib.timeout_add(1, Gtk.main_quit) elif isinstance(result, Gtk.Window): result.connect("destroy", Gtk.main_quit) Gtk.main() else: print('ERROR: Found no test functions for', name) else: print("""Please provide the name of the window to test. Examples are: PYTHONPATH=. python tests/gtk3/windows.py dependency_dialog PYTHONPATH=. python tests/gtk3/windows.py videoplayer PYTHONPATH=. python tests/gtk3/windows.py videoplayer http://google.com PYTHONPATH=. python tests/gtk3/windows.py appdetails PYTHONPATH=. python tests/gtk3/windows.py appdetails firefox """.format(sys.argv[0])) software-center-13.10/tests/gtk3/disabled_test_memleak.py0000664000202700020270000001022612151440100023724 0ustar dobeydobey00000000000000import logging import unittest from mock import Mock from tests.utils import ( do_events_with_sleep, get_test_db, setup_test_env, ) setup_test_env() from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.db.application import Application from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.utils import ( TraceActiveObjectTypes, TraceMemoryUsage, ) from tests.gtk3.windows import ( get_test_window_appdetails, get_test_window_catview, ) class MemleakTestCase(unittest.TestCase): """The test suite for the recommendations .""" ITERATIONS = 2 def test_memleak_app_recommendations(self): cache = get_pkg_info() cache.open(blocking=True) win = get_test_window_appdetails() view = win.get_data("view") app = Application("", "gedit") # get baseline view.show_app(app) do_events_with_sleep() with TraceMemoryUsage("AppdetailsView.show_app()"): with TraceActiveObjectTypes("view.recommended_for_app.set_pkgname"): for i in range(self.ITERATIONS): view.recommended_for_app_panel.set_pkgname("gedit") cache.open() do_events_with_sleep() def test_memleak_appdetails(self): cache = get_pkg_info() cache.open(blocking=True) win = get_test_window_appdetails() view = win.get_data("view") app = Application("", "gedit") # get baseline view.show_app(app) do_events_with_sleep() with TraceMemoryUsage("AppdetailsView.show_app()"): for i in range(self.ITERATIONS): view.show_app(app, force=True) # this causes a huge memleak of ~35mb/run cache.open() do_events_with_sleep() def test_memleak_catview(self): db = get_test_db() win = get_test_window_catview(db) lobby = win.get_data("lobby") # get baseline do_events_with_sleep() with TraceMemoryUsage("LobbyView.on_db_reopen()"): for i in range(self.ITERATIONS): lobby._on_db_reopen(db) do_events_with_sleep() def test_memleak_subcatview(self): db = get_test_db() win = get_test_window_catview(db) lobby = win.get_data("lobby") cat = [cat for cat in lobby.categories if cat.name == "Internet"][0] subcat = win.get_data("subcat") # get baseline subcat.set_subcategory(cat) do_events_with_sleep() with TraceMemoryUsage("SubcategoryView.set_subcategory()"): for i in range(self.ITERATIONS): subcat._set_subcategory(cat, 0) do_events_with_sleep() def test_memleak_app(self): options = Mock() options.display_navlog = False args = [] # ensure the cache is fully ready before taking the baseline cache = get_pkg_info() cache.open(blocking=True) app = SoftwareCenterAppGtk3(options, args) app.window_main.show_all() do_events_with_sleep() with TraceMemoryUsage("app._on_transaction_finished"): for i in range(self.ITERATIONS): app._on_transaction_finished(None, None) cache.open() do_events_with_sleep() def test_memleak_pkginfo_open(self): cache = get_pkg_info() cache.open() do_events_with_sleep() with TraceMemoryUsage("PackageInfo.open()"): for i in range(self.ITERATIONS): cache.open() do_events_with_sleep() def test_memleak_backend_finished(self): backend = get_install_backend() backend.emit("transaction-finished", Mock()) do_events_with_sleep() with TraceMemoryUsage("backend.emit('transaction-finished')"): for i in range(self.ITERATIONS): backend.emit("transaction-finished", Mock()) do_events_with_sleep() if __name__ == "__main__": logging.basicConfig(level=logging.WARN) unittest.main() software-center-13.10/tests/gtk3/test_dialogs.py0000664000202700020270000000521112151440100022102 0ustar dobeydobey00000000000000import unittest from mock import ( Mock, patch, ) from gi.repository import Gtk, GLib from tests.utils import ( get_test_gtk3_icon_cache, setup_test_env, FakedCache, ) setup_test_env() import softwarecenter.ui.gtk3.dialogs from softwarecenter.db.application import Application from softwarecenter.ui.gtk3.dialogs.dependency_dialogs import ( confirm_remove, ) from tests.gtk3.windows import get_test_window_dependency_dialog # window destory timeout TIMEOUT=200 class TestDialogs(unittest.TestCase): """ basic tests for the various gtk3 dialogs """ def get_test_window_dependency_dialog(self): dia = get_test_window_dependency_dialog() GLib.timeout_add(TIMEOUT, lambda: dia.response(Gtk.ResponseType.ACCEPT)) dia.run() def test_confirm_repair_broken_cache(self): datadir = softwarecenter.paths.datadir GLib.timeout_add(TIMEOUT, self._close_dialog) res = softwarecenter.ui.gtk3.dialogs.confirm_repair_broken_cache( parent=None, datadir=datadir) self.assertEqual(res, False) def test_error_dialog(self): GLib.timeout_add(TIMEOUT, self._close_dialog) res = softwarecenter.ui.gtk3.dialogs.error( parent=None, primary="primary", secondary="secondary") self.assertEqual(res, False) def test_accept_tos_dialog(self): GLib.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): softwarecenter.ui.gtk3.dialogs._DIALOG.response(0) class TestDependencyDialog(unittest.TestCase): def setUp(self): self.parent = None self.db = Mock() self.db._aptcache = FakedCache() self.icons = get_test_gtk3_icon_cache() @patch("softwarecenter.ui.gtk3.dialogs.dependency_dialogs" "._get_confirm_internal_dialog") def test_removing_dependency_dialog_warning_on_ud(self, mock): app = Application("", "ubuntu-desktop") self.db._aptcache["ubuntu-desktop"] = Mock() confirm_remove(self.parent, app, self.db, self.icons) self.assertTrue(mock.called) @patch("softwarecenter.ui.gtk3.dialogs.dependency_dialogs" "._get_confirm_internal_dialog") def test_removing_dependency_dialog_warning_on_non_ud(self, mock): app = Application("Some random app", "meep") self.db._aptcache["meep"] = Mock() confirm_remove(self.parent, app, self.db, self.icons) self.assertFalse(mock.called) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_installedpane.py0000664000202700020270000000370112151440100023305 0ustar dobeydobey00000000000000import unittest from tests.utils import ( do_events_with_sleep, setup_test_env, ) setup_test_env() from tests.gtk3.windows import get_test_window_installedpane class TestInstalledPane(unittest.TestCase): def setUp(self): win = get_test_window_installedpane() self.addCleanup(win.destroy) self.pane = win.get_data("pane") def test_installedpane(self): do_events_with_sleep() # safe initial show/hide label for later initial_actionbar_label = self.pane.action_bar._label_text # do simple search self.pane.on_search_terms_changed(None, "foo") do_events_with_sleep() model = self.pane.app_view.tree_view.get_model() # FIXME: len(model) *only* counts the size of the top level # (category) hits. thats still ok, as non-apps will # add the "system" category len_only_apps = len(model) # set to show nonapps self.pane._show_nonapp_pkgs() do_events_with_sleep() len_with_nonapps = len(model) self.assertTrue(len_with_nonapps > len_only_apps) # set to hide nonapps again and ensure the size matches the # previous one self.pane._hide_nonapp_pkgs() do_events_with_sleep() self.assertEqual(len(model), len_only_apps) # clear sarch and ensure we get a expanded size again self.pane.on_search_terms_changed(None, "") do_events_with_sleep() all_apps = len(model) self.assertTrue(all_apps > len_only_apps) # ensure we have the same show/hide info as initially self.assertEqual(initial_actionbar_label, self.pane.action_bar._label_text) def test_application_selected(self): the_app = object() self.pane.on_application_selected(appview=None, app=the_app) self.assertIs(self.pane.current_appview_selection, the_app) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_custom_lists.py0000664000202700020270000000277212151440100023221 0ustar dobeydobey00000000000000import unittest from tests.utils import ( do_events_with_sleep, setup_test_env, ) setup_test_env() from softwarecenter.enums import XapianValues, ActionButtons from tests.gtk3.windows import get_test_window_availablepane class TestCustomLists(unittest.TestCase): def assertPkgInListAtIndex(self, index, model, needle): doc_name = model[index][0].get_value(XapianValues.PKGNAME) msg = "Expected %r at index %r, and custom list contained: %r" self.assertEqual(doc_name, needle, msg % (needle, index, doc_name)) @unittest.skip('by relevance sort too unstable for proper test') def test_custom_lists(self): win = get_test_window_availablepane() self.addCleanup(win.destroy) pane = win.get_data("pane") do_events_with_sleep() pane.on_search_terms_changed(None, "ark,artha,software-center") do_events_with_sleep() model = pane.app_view.tree_view.get_model() # custom list should return three items self.assertTrue(len(model) == 3) # check package names, ordering is default "by relevance" self.assertPkgInListAtIndex(0, model, "ark") self.assertPkgInListAtIndex(1, model, "artha") self.assertPkgInListAtIndex(2, model, "software-center") # check that the status bar offers to install the packages install_button = pane.action_bar.get_button(ActionButtons.INSTALL) self.assertNotEqual(install_button, None) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/__init__.py0000664000202700020270000000000012151440100021147 0ustar dobeydobey00000000000000software-center-13.10/tests/gtk3/test_install_progress.py0000664000202700020270000000145412151440100024057 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, start_dummy_backend, stop_dummy_backend, ) setup_test_env() from softwarecenter.db.application import Application from tests.gtk3.windows import get_test_window_appdetails class TestViews(unittest.TestCase): def setUpNo(self): start_dummy_backend() self.addCleanup(stop_dummy_backend) def test_install_appdetails(self): win = get_test_window_appdetails() self.addCleanup(win.destroy) view = win.get_data("view") view.show_app(Application("", "2vcard")) view.backend.emit('transaction-progress-changed', view.app_details.pkgname, 10) self.assertTrue(view.pkg_statusbar.progress.get_property("visible")) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_appstore2.py0000664000202700020270000000507512151440100022407 0ustar dobeydobey00000000000000import unittest import xapian from mock import Mock, patch from tests.utils import ( do_events, get_test_db, get_test_gtk3_icon_cache, get_test_pkg_info, setup_test_env, ) setup_test_env() from softwarecenter.ui.gtk3.models.appstore2 import AppListStore from softwarecenter.db.enquire import AppEnquire class AppStoreTestCase(unittest.TestCase): """ test the appstore """ @classmethod def setUpClass(cls): cls.cache = get_test_pkg_info() cls.icons = get_test_gtk3_icon_cache() cls.db = get_test_db() def test_lp872760(self): def monkey_(s): translations = { "Painting & Editing" : "translation for Painting & " "Editing", } return translations.get(s, s) with patch("softwarecenter.ui.gtk3.models.appstore2._", new=monkey_): model = AppListStore(self.db, self.cache, self.icons) untranslated = "Painting & Editing" translated = model._category_translate(untranslated) self.assertNotEqual(untranslated, translated) def test_app_store(self): # get a enquire object enquirer = AppEnquire(self.cache, self.db) enquirer.set_query(xapian.Query("")) # get a AppListStore and run functions on it model = AppListStore(self.db, self.cache, self.icons) # test if set from matches works self.assertEqual(len(model), 0) model.set_from_matches(enquirer.matches) self.assertTrue(len(model) > 0) # ensure the first row has a xapian doc type self.assertEqual(type(model[0][0]), xapian.Document) # lazy loading of the docs self.assertEqual(model[100][0], None) # test the load range stuff model.load_range(indices=[100], step=15) self.assertEqual(type(model[100][0]), xapian.Document) # ensure buffer_icons works and loads stuff into the cache model.buffer_icons() self.assertEqual(len(model.icon_cache), 0) do_events() self.assertTrue(len(model.icon_cache) > 0) # ensure clear works model.clear() self.assertEqual(model.current_matches, None) def test_lp971776(self): """ ensure that refresh is not called for invalid image files """ model = AppListStore(self.db, self.cache, self.icons) model.emit = Mock() model._on_image_download_complete(None, "xxx", "software-center") self.assertFalse(model.emit.called) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_globalpane.py0000664000202700020270000000072712151440100022573 0ustar dobeydobey00000000000000import unittest from tests.utils import ( do_events, setup_test_env, ) setup_test_env() from tests.gtk3.windows import get_test_window_globalpane class TestGlobalPane(unittest.TestCase): def test_spinner_available(self): win = get_test_window_globalpane() self.addCleanup(win.destroy) pane = win.get_data("pane") self.assertNotEqual(pane.spinner, None) do_events() if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_unity_launcher_integration_gui.py0000664000202700020270000002374712216063163027012 0ustar dobeydobey00000000000000import unittest from gi.repository import Gtk from mock import Mock, patch from tests.utils import ( do_events, do_events_with_sleep, setup_test_env, ) setup_test_env() from softwarecenter.backend.unitylauncher import UnityLauncherInfo from softwarecenter.config import get_config from softwarecenter.db.application import Application from softwarecenter.enums import TransactionTypes from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane from tests.gtk3.windows import get_test_window_availablepane # Tests for Ubuntu Software Center's integration with the Unity launcher, # see https://wiki.ubuntu.com/SoftwareCenter#Learning%20how%20to%20launch%20an%20application class TestUnityLauncherIntegrationGUI(unittest.TestCase): @classmethod def setUpClass(cls): # we can only have one instance of availablepane, so create it here cls.win = get_test_window_availablepane() cls.available_pane = cls.win.get_data("pane") # get the global config cls.config = get_config() # patch is_unity_running so that the test works inside a e.g. # ADT patch("softwarecenter.utils.is_unity_running").start().return_value = True patch("softwarecenter.backend.unitylauncher.UnityLauncher" "._get_launcher_dbus_iface").start().return_value = Mock() @classmethod def tearDownClass(cls): cls.win.destroy() def _simulate_install_events(self, app, result_event="transaction-finished"): # pretend we started an install self.available_pane.backend.emit("transaction-started", app.pkgname, app.appname, "testid101", TransactionTypes.INSTALL) do_events_with_sleep() # send the signal to complete the install mock_result = Mock() mock_result.pkgname = app.pkgname self.available_pane.backend.emit(result_event, mock_result) do_events_with_sleep() def _install_from_list_view(self, pkgname): self.available_pane.notebook.set_current_page(AvailablePane.Pages.LIST) do_events() self.available_pane.on_search_terms_changed(None, "ark,artha,software-center") do_events() # select the first item in the list self.available_pane.app_view.tree_view.set_cursor(Gtk.TreePath(0), None, False) # ok to just use the test app here app = Application("", pkgname) do_events() self._simulate_install_events(app) def _navigate_to_appdetails_and_install(self, pkgname): app = Application("", pkgname) self.available_pane.app_view.emit("application-activated", app) do_events() self._simulate_install_events(app) def _check_send_application_to_launcher_args(self, pkgname, launcher_info): self.assertEqual(pkgname, self.expected_pkgname) self.assertEqual(launcher_info.name, self.expected_launcher_info.name) self.assertEqual(launcher_info.icon_name, self.expected_launcher_info.icon_name) # mvo: this will fail in xvfb-run so we need to disable it for now #self.assertTrue(launcher_info.icon_x > 5) #self.assertTrue(launcher_info.icon_y > 5) # check that the icon size is one of either 32 pixels (for the # list view case) or 96 pixels (for the details view case) self.assertTrue(launcher_info.icon_size == 32 or launcher_info.icon_size == 96) self.assertEqual(launcher_info.installed_desktop_file_path, self.expected_launcher_info.installed_desktop_file_path) self.assertEqual(launcher_info.trans_id, self.expected_launcher_info.trans_id) @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.send_application_to_launcher') def test_unity_launcher_integration_list_view(self, mock_send_application_to_launcher): # test the automatic add to launcher enabled functionality when # installing an app from the list view self.config.add_to_unity_launcher = True test_pkgname = "software-center" self.expected_pkgname = test_pkgname self.expected_launcher_info = UnityLauncherInfo("software-center", "softwarecenter", 0, 0, 0, 0, # these values are set in availablepane "/usr/share/app-install/desktop/software-center:ubuntu-software-center.desktop", "testid101") self._install_from_list_view(test_pkgname) self.assertTrue(mock_send_application_to_launcher.called) args, kwargs = mock_send_application_to_launcher.call_args self._check_send_application_to_launcher_args(*args, **kwargs) @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.send_application_to_launcher') def test_unity_launcher_integration_details_view(self, mock_send_application_to_launcher): # test the automatic add to launcher enabled functionality when # installing an app from the details view self.config.add_to_unity_launcher = True test_pkgname = "software-center" self.expected_pkgname = test_pkgname self.expected_launcher_info = UnityLauncherInfo("software-center", "softwarecenter", 0, 0, 0, 0, # these values are set in availablepane "/usr/share/app-install/desktop/software-center:ubuntu-software-center.desktop", "testid101") self._navigate_to_appdetails_and_install(test_pkgname) self.assertTrue(mock_send_application_to_launcher.called) args, kwargs = mock_send_application_to_launcher.call_args self._check_send_application_to_launcher_args(*args, **kwargs) @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.send_application_to_launcher') def test_unity_launcher_integration_disabled(self, mock_send_application_to_launcher): # test the case where automatic add to launcher is disabled self.config.add_to_unity_launcher = False test_pkgname = "software-center" self._navigate_to_appdetails_and_install(test_pkgname) self.assertFalse(mock_send_application_to_launcher.called) @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.send_application_to_launcher') def test_unity_launcher_integration_launcher(self, mock_send_application_to_launcher): # this is a 3-tuple of (pkgname, desktop-file, expected_result) TEST_CASES = ( # normal app ("software-center", "/usr/share/app-install/desktop/"\ "software-center:ubuntu-software-center.desktop", True), # NoDisplay=True line ("wine1.4", "/usr/share/app-install/desktop/"\ "wine1.4:wine.desktop", False), # No Exec= line ("bzr", "/usr/share/app-install/desktop/"\ "bzr.desktop", False) ) # run the test over all test-cases self.config.add_to_unity_launcher = True for test_pkgname, app_install_desktop_file_path, res in TEST_CASES: # this is the tofu of the test self._navigate_to_appdetails_and_install(test_pkgname) # verify self.assertEqual( mock_send_application_to_launcher.called, res, "expected %s for pkg: %s but got: %s" % ( res, test_pkgname, mock_send_application_to_launcher.called)) # and reset again to ensure we don't get the call info from # the previous call(s) mock_send_application_to_launcher.reset_mock() @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.cancel_application_to_launcher') def test_unity_launcher_integration_cancelled_install(self, mock_cancel_launcher): # test the automatic add to launcher enabled functionality when # installing an app from the details view, and then cancelling # the install (see LP: #1027209) self.config.add_to_unity_launcher = True test_pkgname = "software-center" app = Application("", test_pkgname) self.available_pane.app_view.emit("application-activated", app) do_events() self._simulate_install_events(app, result_event="transaction-cancelled") # ensure that we cancel the send self.assertTrue( mock_cancel_launcher.called) @patch('softwarecenter.ui.gtk3.panes.availablepane.UnityLauncher' '.cancel_application_to_launcher') def test_unity_launcher_integration_installation_failure(self, mock_cancel_launcher): # test the automatic add to launcher enabled functionality when # a failure is detected during the transaction (aptd emits a # "transaction-stopped" signal for this case) self.config.add_to_unity_launcher = True test_pkgname = "software-center" app = Application("", test_pkgname) self.available_pane.app_view.emit("application-activated", app) do_events() # aptd will emit a "transaction-stopped" signal if a transaction # error is encountered self._simulate_install_events(app, result_event="transaction-stopped") # ensure that we cancel the send self.assertTrue( mock_cancel_launcher.called) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_exhibits.py0000664000202700020270000001547512151440100022314 0ustar dobeydobey00000000000000import os import unittest from mock import patch, Mock from tests.utils import ( FakedCache, ObjectWithSignals, setup_test_env, ) setup_test_env() from softwarecenter.db.database import StoreDatabase from softwarecenter.ui.gtk3.views import lobbyview from softwarecenter.ui.gtk3.widgets.exhibits import ( _HtmlRenderer, ) class ExhibitsTestCase(unittest.TestCase): """The test suite for the exhibits carousel.""" def setUp(self): self.cache = FakedCache() self.db = StoreDatabase(cache=self.cache) self.lobby = lobbyview.LobbyView(cache=self.cache, db=self.db, icons=None, apps_filter=None) self.addCleanup(self.lobby.destroy) def _get_banner_from_lobby(self): return self.lobby.vbox.get_children()[-1].get_child() def test_featured_exhibit_by_default(self): """Show the featured exhibit before querying the remote service.""" self.lobby._append_banner_ads() banner = self._get_banner_from_lobby() self.assertEqual(1, len(banner.exhibits)) self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit) def test_no_exhibit_if_not_available(self): """The exhibit should not be shown if the package is not available.""" exhibit = Mock() exhibit.package_names = u'foobarbaz' sca = ObjectWithSignals() sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit]) with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca): self.lobby._append_banner_ads() banner = self._get_banner_from_lobby() self.assertEqual(1, len(banner.exhibits)) self.assertIsInstance(banner.exhibits[0], lobbyview.FeaturedExhibit) def test_exhibit_if_available(self): """The exhibit should be shown if the package is available.""" exhibit = Mock() exhibit.package_names = u'foobarbaz' exhibit.banner_urls = ['banner'] exhibit.title_translated = '' self.cache[u'foobarbaz'] = Mock() sca = ObjectWithSignals() sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit]) with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca): self.lobby._append_banner_ads() banner = self._get_banner_from_lobby() self.assertEqual(1, len(banner.exhibits)) self.assertIs(banner.exhibits[0], exhibit) def test_exhibit_if_mixed_availability(self): """The exhibit should be shown even if some are not available.""" # available exhibit exhibit = Mock() exhibit.package_names = u'foobarbaz' exhibit.banner_urls = ['banner'] exhibit.title_translated = '' self.cache[u'foobarbaz'] = Mock() # not available exhibit other = Mock() other.package_names = u'not-there' sca = ObjectWithSignals() sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit, other]) with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca): self.lobby._append_banner_ads() banner = self._get_banner_from_lobby() self.assertEqual(1, len(banner.exhibits)) self.assertIs(banner.exhibits[0], exhibit) def test_exhibit_with_url(self): # available exhibit exhibit = Mock() exhibit.package_names = '' exhibit.click_url = 'http://example.com' exhibit.banner_urls = ['banner'] exhibit.title_translated = '' sca = ObjectWithSignals() sca.query_exhibits = lambda: sca.emit('exhibits', sca, [exhibit]) with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca): # add the banners self.lobby._append_banner_ads() # fake click alloc = self.lobby.exhibit_banner.get_allocation() mock_event = Mock() mock_event.x = alloc.x mock_event.y = alloc.y with patch.object(self.lobby.exhibit_banner, 'emit') as mock_emit: self.lobby.exhibit_banner.on_button_press(None, mock_event) self.lobby.exhibit_banner.on_button_release(None, mock_event) mock_emit.assert_called() signal_name = mock_emit.call_args[0][0] call_exhibit = mock_emit.call_args[0][1] self.assertEqual(signal_name, "show-exhibits-clicked") self.assertEqual(call_exhibit.click_url, "http://example.com") def test_exhibit_with_featured_exhibit(self): """ regression test for bug #1023777 """ sca = ObjectWithSignals() sca.query_exhibits = lambda: sca.emit('exhibits', sca, [lobbyview.FeaturedExhibit()]) with patch.object(lobbyview, 'SoftwareCenterAgent', lambda: sca): # add the banners self.lobby._append_banner_ads() # fake click alloc = self.lobby.exhibit_banner.get_allocation() mock_event = Mock() mock_event.x = alloc.x mock_event.y = alloc.y with patch.object(self.lobby, 'emit') as mock_emit: self.lobby.exhibit_banner.on_button_press(None, mock_event) self.lobby.exhibit_banner.on_button_release(None, mock_event) mock_emit.assert_called() signal_name = mock_emit.call_args[0][0] call_category = mock_emit.call_args[0][1] self.assertEqual(signal_name, "category-selected") self.assertEqual(call_category.name, "Our star apps") class HtmlRendererTestCase(unittest.TestCase): def test_multiple_images(self): downloader = ObjectWithSignals() downloader.download_file = lambda *args, **kwargs: downloader.emit( "file-download-complete", downloader, os.path.basename(args[0])) with patch("softwarecenter.ui.gtk3.widgets.exhibits." "SimpleFileDownloader", lambda: downloader): renderer = _HtmlRenderer() mock_exhibit = Mock() mock_exhibit.banner_urls = [ "http://example.com/path1/banner1.png", "http://example.com/path2/banner2.png", ] mock_exhibit.html = "url('/path1/banner1.png')#"\ "url('/path2/banner2.png')" renderer.set_exhibit(mock_exhibit) # assert the stuff we expected to get downloaded got downloaded self.assertEqual( renderer._downloaded_banner_images, ["banner1.png", "banner2.png"]) # test that the path mangling worked self.assertEqual( mock_exhibit.html, "url('banner1.png')#url('banner2.png')") if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_availablepane.py0000664000202700020270000000164512151440100023253 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.db.application import Application from tests.gtk3.windows import get_test_window_availablepane class AvailablePaneTestCase(unittest.TestCase): def setUp(self): win = get_test_window_availablepane() self.addCleanup(win.destroy) self.pane = win.get_data("pane") self.vm = win.get_data("vm") def test_leave_page_stops_video(self): called = [] self.pane.state.application = Application('', 'foo') self.vm.display_page(self.pane, self.pane.Pages.DETAILS, self.pane.state) assert self.pane.is_app_details_view_showing() self.pane.app_details_view.videoplayer.stop = \ lambda: called.append('stop') self.pane.leave_page(self.pane.state) self.assertEqual(called, ['stop']) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_appmanager.py0000664000202700020270000000615312151440100022601 0ustar dobeydobey00000000000000import unittest from mock import ( Mock, patch, ) from tests.utils import ( do_events, get_test_db, get_test_gtk3_icon_cache, setup_test_env, ) setup_test_env() import softwarecenter.paths from softwarecenter.db.application import Application from softwarecenter.distro import get_distro from softwarecenter.ui.gtk3.session.appmanager import ( ApplicationManager, get_appmanager) class TestAppManager(unittest.TestCase): """ tests the appmanager """ def setUp(self): # get required test stuff self.db = get_test_db() self.backend = Mock() self.distro = get_distro() self.datadir = softwarecenter.paths.datadir self.icons = get_test_gtk3_icon_cache() # create it once, it becomes global instance if get_appmanager() is None: ApplicationManager( self.db, self.backend, self.icons) def test_get_appmanager(self): app_manager = get_appmanager() self.assertNotEqual(app_manager, None) # test singleton app_manager2 = get_appmanager() self.assertEqual(app_manager, app_manager2) # test creating it twice raises a error self.assertRaises( ValueError, ApplicationManager, self.db, self.backend, self.icons) def test_appmanager(self): app_manager = get_appmanager() self.assertNotEqual(app_manager, None) # test interface app_manager.reload() app = Application("", "2vcard") # call and ensure the stuff is passed to the backend app_manager.install(app, [], []) self.assertTrue(self.backend.install.called) app_manager.remove(app, [], []) self.assertTrue(self.backend.remove.called) app_manager.upgrade(app, [], []) self.assertTrue(self.backend.upgrade.called) app_manager.apply_changes(app, [], []) self.assertTrue(self.backend.apply_changes.called) app_manager.enable_software_source(app) self.assertTrue(self.backend.enable_component.called) app_manager.reinstall_purchased(app) self.assertTrue(self.backend.add_repo_add_key_and_install_app.called) # buy is special as it needs help from the purchase view app_manager.connect("purchase-requested", self._on_purchase_requested) app_manager.buy_app(app) self.assertTrue(self._purchase_requested_signal) do_events() def _on_purchase_requested(self, *args): self._purchase_requested_signal = True def test_appmanager_requests_oauth_token(self): oauth_token = { "moo": "bar", "lala": "la", } # reset the global appmanager softwarecenter.ui.gtk3.session.appmanager._appmanager = None with patch("softwarecenter.ui.gtk3.session.appmanager.UbuntuSSO" ".find_oauth_token_sync") as m: m.return_value = oauth_token app_manager = ApplicationManager(self.db, self.backend, self.icons) self.assertEqual(app_manager.oauth_token, oauth_token) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_panes.py0000664000202700020270000000207412151440100021572 0ustar dobeydobey00000000000000import unittest from tests.utils import ( do_events, setup_test_env, ) setup_test_env() from tests.gtk3 import windows class TestPanes(unittest.TestCase): def test_availablepane(self): win = windows.get_test_window_availablepane() self.addCleanup(win.destroy) def test_globalpane(self): win = windows.get_test_window_globalpane() self.addCleanup(win.destroy) def test_pendingpane(self): win = windows.get_test_window_pendingpane() self.addCleanup(win.destroy) def test_historypane(self): win = windows.get_test_window_historypane() self.addCleanup(win.destroy) def test_installedpane(self): win = windows.get_test_window_installedpane() self.addCleanup(win.destroy) pane = win.get_data("pane") # ensure it visible self.assertTrue(pane.get_property("visible")) # ensure the treeview is there and has data do_events() self.assertTrue(len(pane.treefilter.get_model()) > 2) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_navhistory.py0000664000202700020270000002103312216112732022677 0ustar dobeydobey00000000000000import unittest from mock import Mock from tests.utils import setup_test_env setup_test_env() from softwarecenter.ui.gtk3.session.navhistory import ( NavigationHistory, NavigationItem) from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane from softwarecenter.db.categories import Category from softwarecenter.ui.gtk3.session.displaystate import DisplayState class MockButton(object): def __init__(self): self.sensitive = True def set_sensitive(self, val): self.sensitive = val def has_focus(self): return False class TestNavhistory(unittest.TestCase): """ basic tests for navigation history """ def _get_boring_stuff(self): # mock button back_forward_btn = Mock() back_forward_btn.right = MockButton() back_forward_btn.left = MockButton() # mock options options = Mock() options.display_navlog = False # create navhistory navhistory = NavigationHistory(back_forward_btn, options) # mock view manager view_manager = Mock() view_manager.navhistory = navhistory # create a NavHistory pane = Mock() pane.pane_name = "pane_name" return back_forward_btn, options, navhistory, view_manager, pane def test_nav_history(self): (back_forward_btn, options, navhistory, view_manager, pane) = self._get_boring_stuff() # first we must initialize the NavHistory with the equivalent of the initial category view item = NavigationItem(view_manager, pane, "cat_page", "cat_state") navhistory.append(item) item = NavigationItem(view_manager, pane, "a_page", "a_state") # add a new item and ensure that the button is now sensitive navhistory.append(item) self.assertFalse(back_forward_btn.right.sensitive) self.assertTrue(back_forward_btn.left.sensitive) # navigate back navhistory.nav_back() self.assertTrue(back_forward_btn.right.sensitive) self.assertFalse(back_forward_btn.left.sensitive) # navigate forward navhistory.nav_forward() self.assertFalse(back_forward_btn.right.sensitive) self.assertTrue(back_forward_btn.left.sensitive) # and reset navhistory.reset() self.assertFalse(back_forward_btn.right.sensitive) self.assertFalse(back_forward_btn.left.sensitive) self.assertEqual(len(navhistory.stack), 0) def test_navhistory_lobby_search_then_clear(self): (back_forward_btn, options, navhistory, view_manager, pane) = self._get_boring_stuff() # first we must initialize the NavHistory with the equivalent of the initial category view # NnavigationItem(view_manager, pane, page, view_state, callback) item = NavigationItem(view_manager, pane, AvailablePane.Pages.LOBBY, DisplayState()) navhistory.append(item) # append equivalent of a typed search dstate = DisplayState() dstate.search_term = "chess" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) # simulate the user clearing the search entry dstate = DisplayState() dstate.search_term = "" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LOBBY, dstate) navhistory.append(item) self.assertFalse(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) def test_navhistory_cat_search_then_clear(self): (back_forward_btn, options, navhistory, view_manager, pane) = self._get_boring_stuff() # first we must initialize the NavHistory with the equivalent of the initial category view # NnavigationItem(view_manager, pane, page, view_state, callback) item = NavigationItem(view_manager, pane, AvailablePane.Pages.LOBBY, DisplayState()) navhistory.append(item) # simulate Accessories category LIST dstate = DisplayState() dstate.category = Category('accessories', 'Accessories', 'iconname', 'query') item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) # displaying the Accessories LIST self.assertTrue(len(navhistory.stack) == 2) self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) # simulate a user search dstate = DisplayState() dstate.category = Category('accessories', 'Accessories', 'iconname', 'query') dstate.search_term = "chess" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) # should be displaying the search results for Accessories self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) self.assertTrue(len(navhistory.stack) == 3) # simulate a user clearing the search dstate = DisplayState() dstate.category = Category('accessories', 'Accessories', 'iconname', 'query') dstate.search_term = "" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) # should be returned to the Accessories list view self.assertTrue(len(navhistory.stack) == 2) def test_navhistory_subcat_search_then_clear(self): (back_forward_btn, options, navhistory, view_manager, pane) = self._get_boring_stuff() # first we must initialize the NavHistory with the equivalent of the initial category view # NnavigationItem(view_manager, pane, page, view_state, callback) item = NavigationItem(view_manager, pane, AvailablePane.Pages.LOBBY, DisplayState()) navhistory.append(item) board_games = Category('board-games', 'Board Games', 'iconname', 'query') games = Category('games', 'Games', 'iconname', 'query', subcategories=[board_games,]) # simulate Games category (with subcats) dstate = DisplayState() dstate.category = games item = NavigationItem(view_manager, pane, AvailablePane.Pages.SUBCATEGORY, dstate) navhistory.append(item) # displaying the Games SUBCATEGORY self.assertTrue(len(navhistory.stack) == 2) self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) # simulate Board Games subcategory LIST dstate = DisplayState() dstate.category = games dstate.subcategory = board_games item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) # displaying the Accessories LIST self.assertTrue(len(navhistory.stack) == 3) self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) # simulate a user search within subcat dstate = DisplayState() dstate.category = games dstate.subcategory = board_games dstate.search_term = "chess" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) # should be displaying the search results for Accessories self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) self.assertTrue(len(navhistory.stack) == 4) # simulate a user clearing the search dstate = DisplayState() dstate.category = games dstate.subcategory = board_games dstate.search_term = "" item = NavigationItem(view_manager, pane, AvailablePane.Pages.LIST, dstate) navhistory.append(item) # should be returned to the Board Games LIST view self.assertTrue(back_forward_btn.left.sensitive) self.assertFalse(back_forward_btn.right.sensitive) self.assertTrue(len(navhistory.stack) == 3) # verify that navhistory item 2 is LIST page self.assertTrue(navhistory.stack[2].page == AvailablePane.Pages.LIST) # verify that navhistory item 1 is SUBCATEGORY page self.assertTrue(navhistory.stack[1].page == AvailablePane.Pages.SUBCATEGORY) # verify that navhistory item 0 is LOBBY page self.assertTrue(navhistory.stack[0].page == AvailablePane.Pages.LOBBY) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_purchase.py0000664000202700020270000000725012151440100022277 0ustar dobeydobey00000000000000import unittest from mock import Mock, patch from tests.utils import ( do_events_with_sleep, get_mock_options, setup_test_env, ) setup_test_env() from softwarecenter.ui.gtk3.views import purchaseview from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane from tests.gtk3.windows import get_test_window_purchaseview class TestPurchase(unittest.TestCase): def test_purchase_view_log_cleaner(self): win = get_test_window_purchaseview() self.addCleanup(win.destroy) do_events_with_sleep() # get the view view = win.get_data("view") # install the mock purchaseview.LOG = mock = Mock() # run a "harmless" log message and ensure its logged normally view.wk.webkit.execute_script('console.log("foo")') self.assertTrue("foo" in mock.debug.call_args[0][0]) mock.reset_mock() # run a message that contains token info s = ('http://sca.razorgirl.info/subscriptions/19077/checkout_complete/' ' @10: {"token_key": "hiddenXXXXXXXXXX", "consumer_secret": ' '"hiddenXXXXXXXXXXXX", "api_version": 2.0, "subscription_id": ' '19077, "consumer_key": "rKhNPBw", "token_secret": ' '"hiddenXXXXXXXXXXXXXXX"}') view.wk.webkit.execute_script("console.log('%s')" % s) self.assertTrue("skipping" in mock.debug.call_args[0][0]) self.assertFalse("consumer_secret" in mock.debug.call_args[0][0]) mock.reset_mock() def test_purchase_view_tos(self): win = get_test_window_purchaseview() self.addCleanup(win.destroy) view = win.get_data("view") # install the mock mock_config = Mock() mock_config.user_accepted_tos = 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) def test_spinner_emits_signals(self): win = get_test_window_purchaseview() self.addCleanup(win.destroy) do_events_with_sleep() # get the view view = win.get_data("view") # ensure "purchase-needs-spinner" signals are send signal_mock = Mock() view.connect("purchase-needs-spinner", signal_mock) view.wk.webkit.load_uri("http://www.ubuntu.com/") do_events_with_sleep() self.assertTrue(signal_mock.called) class PreviousPurchasesTestCase(unittest.TestCase): @patch("softwarecenter.backend.ubuntusso.UbuntuSSO" ".find_oauth_token_sync") def test_reinstall_previous_purchase_display(self, mock_find_token): mock_find_token.return_value = { 'not': 'important' } mock_options = get_mock_options() app = SoftwareCenterAppGtk3(mock_options) self.addCleanup(app.destroy) # real app opens cache async app.cache.open() # .. and now pretend we clicked on the menu item app.window_main.show_all() app.available_pane.init_view() do_events_with_sleep() app.on_menuitem_reinstall_purchases_activate(None) # it can take a bit until the sso client is ready for i in range(10): if (app.available_pane.get_current_page() == AvailablePane.Pages.LIST): break do_events_with_sleep() self.assertEqual(app.available_pane.get_current_page(), AvailablePane.Pages.LIST) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_webkit.py0000664000202700020270000000413612151440100021752 0ustar dobeydobey00000000000000import unittest from gi.repository import ( GLib, Soup, WebKit, ) from mock import patch from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.enums import WEBKIT_USER_AGENT_SUFFIX from softwarecenter.ui.gtk3.widgets.webkit import SoftwareCenterWebView class TestWebkit(unittest.TestCase): def test_have_cookie_jar(self): # ensure we have a cookie jar available session = WebKit.get_default_session() cookie_jars = [feature for feature in session.get_features(Soup.SessionFeature) if isinstance(feature, Soup.CookieJar)] self.assertEqual(len(cookie_jars), 1) def test_user_agent_string(self): webview = SoftwareCenterWebView() settings = webview.get_settings() self.assertTrue( WEBKIT_USER_AGENT_SUFFIX in settings.get_property("user-agent")) @patch("softwarecenter.ui.gtk3.widgets.webkit.get_oem_channel_descriptor") def test_user_agent_oem_channel_descriptor(self, mock_oem_channel): canary = "she-loves-you-yeah-yeah-yeah" mock_oem_channel.return_value = canary webview = SoftwareCenterWebView() settings = webview.get_settings() self.assertTrue( canary in settings.get_property("user-agent")) def test_auto_fill_in_email(self): def _load_status_changed(view, status): if view.get_property("load-status") == WebKit.LoadStatus.FINISHED: loop.quit() loop = GLib.MainLoop(GLib.main_context_default()) webview = SoftwareCenterWebView() email = "foo@bar" webview.set_auto_insert_email(email) with patch.object(webview, "execute_script") as mock_execute_js: webview.connect("notify::load-status", _load_status_changed) webview.load_uri("https://login.ubuntu.com") loop.run() mock_execute_js.assert_called() mock_execute_js.assert_called_with( SoftwareCenterWebView.AUTO_FILL_EMAIL_JS % email) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/disabled_test_catview.py0000664000202700020270000004100612151440100023753 0ustar dobeydobey00000000000000import unittest from mock import patch, Mock from tests.utils import ( do_events, do_events_with_sleep, get_test_db, get_test_gtk3_icon_cache, make_recommender_agent_recommend_me_dict, setup_test_env, ) setup_test_env() import softwarecenter.distro import softwarecenter.paths from softwarecenter.db.appfilter import AppFilter from softwarecenter.enums import (SortMethods, TransactionTypes, RecommenderFeedbackActions) from softwarecenter.ui.gtk3.views import lobbyview from softwarecenter.ui.gtk3.widgets.containers import FramedHeaderBox from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook from tests.gtk3.windows import get_test_window_catview class CatViewBaseTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.db = get_test_db() def setUp(self, selected_category=None): self._cat = None self._app = None self.win = get_test_window_catview(self.db, selected_category) self.addCleanup(self.win.destroy) self.notebook = self.win.get_child() self.lobby = self.win.get_data("lobby") self.subcat_view = self.win.get_data("subcat") def _on_category_selected(self, subcatview, category): self._cat = category class TopAndWhatsNewTestCase(CatViewBaseTestCase): def test_top_rated(self): # simulate review-stats refresh self.lobby._update_top_rated_content = Mock() self.lobby.reviews_loader.emit("refresh-review-stats-finished", []) self.assertTrue(self.lobby._update_top_rated_content.called) # test clicking top_rated self.lobby.connect("category-selected", self._on_category_selected) self.lobby.top_rated_frame.more.clicked() do_events() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Top Rated") self.assertEqual(self._cat.sortmode, SortMethods.BY_TOP_RATED) def test_new(self): # test db reopen triggers whats-new update self.lobby._update_whats_new_content = Mock() self.lobby.db.emit("reopen") self.assertTrue(self.lobby._update_whats_new_content.called) # test clicking new self.lobby.connect("category-selected", self._on_category_selected) self.lobby.whats_new_frame.more.clicked() do_events() self.assertNotEqual(self._cat, None) # encoding is utf-8 (since r2218, see category.py) self.assertEqual(self._cat.name, 'What\xe2\x80\x99s New') self.assertEqual(self._cat.sortmode, SortMethods.BY_CATALOGED_TIME) def test_no_axi_cataloged_time_info_yet(self): """ ensure that we show "whats new" DB_CATALOGED_TIME data if there is no x-a-i yet """ db = get_test_db() cache = db._aptcache icons = get_test_gtk3_icon_cache() apps_filter = AppFilter(db, cache) # simulate a fresh install with no catalogedtime info in a-x-i if "catalogedtime" in db._axi_values: del db._axi_values["catalogedtime"] # create it view = lobbyview.LobbyView(cache, db, icons, softwarecenter.distro.get_distro(), apps_filter) view.show_all() # and ensure its visible self.assertTrue(view.whats_new_frame.get_property("visible")) class RecommendationsTestCase(CatViewBaseTestCase): """The test suite for the recommendations .""" # we need to use a custom setUp method for the recommendations test cases # so that everything gets configured properly @patch('softwarecenter.ui.gtk3.widgets.recommendations.get_login_backend') @patch('softwarecenter.ui.gtk3.widgets.recommendations.RecommenderAgent' '.is_opted_in') # patch out the agent query method to avoid making the actual server call @patch('softwarecenter.ui.gtk3.widgets.recommendations.RecommenderAgent' '.post_submit_profile') def setUp(self, mock_query, mock_recommender_is_opted_in, mock_sso): # patch the recommender to specify that we are not opted-in at # the start of each test mock_recommender_is_opted_in.return_value = False # we specify the "Internet" category because we do specific checks # in the following tests that depend on this being the category choice super(RecommendationsTestCase, self).setUp(selected_category="Internet") self.rec_panel = self.lobby.recommended_for_you_panel def test_recommended_for_you_opt_in_display(self): self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), FramedHeaderBox.CONTENT) self.assertTrue( self.rec_panel.recommended_for_you_content.get_property("visible")) # ensure that we are showing the opt-in view self.assertTrue(self.rec_panel.opt_in_button.get_property("visible")) label_text = self.rec_panel.opt_in_label.get_text() self.assertEqual(label_text, self.rec_panel.RECOMMENDATIONS_OPT_IN_TEXT) @patch('softwarecenter.ui.gtk3.widgets.recommendations' '.network_state_is_connected') def test_recommended_for_you_spinner_display(self, mock_network_state_is_connected): # pretend we have network even if we don't mock_network_state_is_connected.return_value = True # click the opt-in button to initiate the process, # this will show the spinner self.rec_panel.opt_in_button.clicked() do_events_with_sleep() self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), SpinnerNotebook.SPINNER_PAGE) self.assertTrue( self.rec_panel.recommended_for_you_content.get_property("visible")) @patch('softwarecenter.ui.gtk3.widgets.recommendations' '.network_state_is_connected') def test_recommended_for_you_network_not_available(self, mock_network_state_is_connected): # simulate no network available mock_network_state_is_connected.return_value = False self._opt_in_and_populate_recommended_for_you_panel() # ensure that we are showing the network not available view self.assertFalse(self.rec_panel.opt_in_button.get_property("visible")) label_text = self.rec_panel.opt_in_label.get_text() self.assertEqual(label_text, self.rec_panel.NO_NETWORK_RECOMMENDATIONS_TEXT) def test_recommended_for_you_display_recommendations(self): self._opt_in_and_populate_recommended_for_you_panel() # we fake the callback from the agent here for_you = self.rec_panel.recommended_for_you_cat for_you._recommend_me_result(None, make_recommender_agent_recommend_me_dict()) self.assertNotEqual(for_you.get_documents(self.db), []) self.assertEqual(self.rec_panel.spinner_notebook.get_current_page(), SpinnerNotebook.CONTENT_PAGE) do_events() # test clicking recommended_for_you More button self.lobby.connect("category-selected", self._on_category_selected) self.rec_panel.more.clicked() # this is delayed for some reason so we need to sleep here do_events_with_sleep() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Recommended For You") def test_recommended_for_you_display_recommendations_not_opted_in(self): # we want to work in the "subcat" view self.notebook.next_page() do_events() visible = self.subcat_view.recommended_for_you_in_cat.get_property( "visible") self.assertFalse(visible) def test_recommended_for_you_display_recommendations_opted_in(self): self._opt_in_and_populate_recommended_for_you_panel() # we want to work in the "subcat" view self.notebook.next_page() rec_cat_panel = self.subcat_view.recommended_for_you_in_cat rec_cat_panel._update_recommended_for_you_content() do_events() # 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) self.assertEqual(rec_cat_panel.spinner_notebook.get_current_page(), SpinnerNotebook.CONTENT_PAGE) # check that the tiles themselves are visible 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")) # test clicking recommended_for_you More button self.subcat_view.connect( "category-selected", self._on_category_selected) rec_cat_panel.more.clicked() # this is delayed for some reason so we need to sleep here do_events_with_sleep() self.assertNotEqual(self._cat, None) self.assertEqual(self._cat.name, "Recommended For You in Internet") def test_implicit_recommender_feedback(self): self._opt_in_and_populate_recommended_for_you_panel() # we fake the callback from the agent here for_you = self.rec_panel.recommended_for_you_cat for_you._recommend_me_result(None, make_recommender_agent_recommend_me_dict()) do_events() post_implicit_feedback_fn = ('softwarecenter.ui.gtk3.widgets' '.recommendations.RecommenderAgent' '.post_implicit_feedback') with patch(post_implicit_feedback_fn) as mock_post_implicit_feedback: # we want to grab the app that is activated, it will be in # self._app after the app is activated on the tile click self.rec_panel.recommended_for_you_content.connect( "application-activated", self._on_application_activated) # click a recommendation in the lobby self._click_first_tile_in_panel(self.rec_panel) # simulate installing the application self._simulate_install_events(self._app) # and verify that after the install has completed we have fired # the implicit feedback call to the recommender with the correct # arguments mock_post_implicit_feedback.assert_called_with( self._app.pkgname, RecommenderFeedbackActions.INSTALLED) # finally, make sure that we have cleared the application # from the recommended_apps_viewed set self.assertFalse(self._app.pkgname in self.rec_panel.recommended_apps_viewed) def test_implicit_recommender_feedback_on_item_viewed(self): self._opt_in_and_populate_recommended_for_you_panel() # we fake the callback from the agent here for_you = self.rec_panel.recommended_for_you_cat for_you._recommend_me_result(None, make_recommender_agent_recommend_me_dict()) do_events() post_implicit_feedback_fn = ('softwarecenter.ui.gtk3.widgets' '.recommendations.RecommenderAgent' '.post_implicit_feedback') with patch(post_implicit_feedback_fn) as mock_post_implicit_feedback: # we want to grab the app that is activated, it will be in # self._app after the app is activated on the tile click self.rec_panel.recommended_for_you_content.connect( "application-activated", self._on_application_activated) # click a recommendation in the lobby self._click_first_tile_in_panel(self.rec_panel) # and verify that upon selecting a recommended app we have fired # the implicit feedback call to the recommender with the correct # arguments mock_post_implicit_feedback.assert_called_with( self._app.pkgname, RecommenderFeedbackActions.VIEWED) def test_implicit_recommender_feedback_recommendations_panel_only(self): # this test insures that we only provide feedback when installing # items clicked to via the recommendations panel itself, and not # via the What's New or Top Rated panels self._opt_in_and_populate_recommended_for_you_panel() # we fake the callback from the agent here for_you = self.rec_panel.recommended_for_you_cat for_you._recommend_me_result(None, make_recommender_agent_recommend_me_dict()) do_events() post_implicit_feedback_fn = ('softwarecenter.ui.gtk3.widgets' '.recommendations.RecommenderAgent' '.post_implicit_feedback') with patch(post_implicit_feedback_fn) as mock_post_implicit_feedback: # we want to grab the app that is activated, it will be in # self._app after the app is activated on the tile click self.lobby.top_rated.connect("application-activated", self._on_application_activated) # click a tile in the Top Rated section of the lobby self._click_first_tile_in_panel(self.lobby.top_rated_frame) # simulate installing the application self._simulate_install_events(self._app) # and verify that after the install has completed we have *not* # fired the implicit feedback call to the recommender service self.assertFalse(mock_post_implicit_feedback.called) def test_implicit_recommender_feedback_not_opted_in(self): # this test verifies that we do *not* send feedback to the # recommender service if the user has not opted-in to it post_implicit_feedback_fn = ('softwarecenter.ui.gtk3.widgets' '.recommendations.RecommenderAgent' '.post_implicit_feedback') with patch(post_implicit_feedback_fn) as mock_post_implicit_feedback: # we are not opted-in from softwarecenter.db.application import Application app = Application("Calculator", "gcalctool") # simulate installing the application self._simulate_install_events(app) # and verify that after the install has completed we have *not* # fired the implicit feedback call to the recommender self.assertFalse(mock_post_implicit_feedback.called) def _opt_in_and_populate_recommended_for_you_panel(self): # click the opt-in button to initiate the process self.rec_panel.opt_in_button.clicked() do_events() # simulate a successful opt-in by setting the recommender_uuid self.rec_panel.recommender_agent._set_recommender_uuid( "35fd653e67b14482b7a8b632ea90d1b6") # and update the recommended for you panel to load it up self.rec_panel._update_recommended_for_you_content() do_events() def _on_application_activated(self, catview, app): self._app = app def _click_first_tile_in_panel(self, framed_header_box): first_tile = (framed_header_box.content_box. get_children()[0].get_children()[0]) first_tile.clicked() do_events_with_sleep() def _simulate_install_events(self, app): # pretend we started an install self.rec_panel.backend.emit("transaction-started", app.pkgname, app.appname, "testid101", TransactionTypes.INSTALL) do_events_with_sleep() # send the signal to complete the install mock_result = Mock() mock_result.pkgname = app.pkgname self.rec_panel.backend.emit("transaction-finished", mock_result) do_events() if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_buttons.py0000664000202700020270000000212012151440100022152 0ustar dobeydobey00000000000000import unittest from tests.utils import ( get_mock_app_properties_helper, setup_test_env, ) setup_test_env() from softwarecenter.ui.gtk3.widgets.buttons import FeaturedTile class TestWidgets(unittest.TestCase): """ basic tests for the TileButton widget """ def test_feature_tile_dup_symbol(self): values = {'display_price': 'US$ 1.00' } mock_property_helper = get_mock_app_properties_helper(values) # we don't really need a "doc" on second input as we mock the helper button = FeaturedTile(mock_property_helper, None) self.assertEqual( button.price.get_label(), 'US$ 1.00') def test_free_price(self): values = {'display_price': "Free"} mock_property_helper = get_mock_app_properties_helper(values) # we don't really need a "doc" on second input as we mock the helper button = FeaturedTile(mock_property_helper, None) self.assertEqual( button.price.get_label(), 'Free') if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_zeitgeist_logger_gui.py0000644000202700020270000000771512216063163024717 0ustar dobeydobey00000000000000import unittest from mock import Mock, patch from tests.utils import setup_test_env setup_test_env() from softwarecenter.db.application import Application from softwarecenter.enums import TransactionTypes from tests.gtk3.windows import get_test_window_availablepane class TestZeitgeistLoggerGUI(unittest.TestCase): @classmethod def setUpClass(cls): # we can only have one instance of availablepane, so create it here cls.win = get_test_window_availablepane() cls.available_pane = cls.win.get_data("pane") @classmethod def tearDownClass(cls): cls.win.destroy() def setUp(self): zl = "softwarecenter.backend.zeitgeist_logger.ZeitgeistLogger"; self.log_install_event = patch(zl + ".log_install_event").start() self.log_uninstall_event = patch(zl + ".log_uninstall_event").start() def __emit_backend_event(self, pkgname, event): mock_result = Mock() mock_result.pkgname = pkgname self.available_pane.backend.emit(event, mock_result) def __start_backend_transaction(self, pkgname, type=TransactionTypes.INSTALL): app = Application("", pkgname) self.available_pane.backend.emit("transaction-started", app.pkgname, app.appname, "testid101", type) def test_zeitgeist_logger_init_on_start(self): test_pkgname = "software-center" self.__start_backend_transaction(test_pkgname) self.assertFalse(self.log_install_event.called) self.assertFalse(self.log_uninstall_event.called) self.assertEqual(len(self.available_pane.transactions_queue), 1) self.assertTrue(test_pkgname in self.available_pane.transactions_queue) def test_zeitgeist_logger_cancel_on_cancel(self): test_pkgname = "software-center" self.__start_backend_transaction(test_pkgname) self.assertTrue(test_pkgname in self.available_pane.transactions_queue) self.__emit_backend_event(test_pkgname, "transaction-cancelled") self.assertEqual(len(self.available_pane.transactions_queue), 0) self.assertFalse(self.log_install_event.called) self.assertFalse(self.log_uninstall_event.called) def test_zeitgeist_logger_cancel_on_stop(self): test_pkgname = "software-center" self.__start_backend_transaction(test_pkgname) self.assertTrue(test_pkgname in self.available_pane.transactions_queue) self.__emit_backend_event(test_pkgname, "transaction-stopped") self.assertEqual(len(self.available_pane.transactions_queue), 0) self.assertFalse(self.log_install_event.called) self.assertFalse(self.log_uninstall_event.called) def test_zeitgeist_logger_logs_install_on_finished(self): test_pkgname = "software-center" self.__start_backend_transaction(test_pkgname) transaction = self.available_pane.transactions_queue[test_pkgname] self.__emit_backend_event(test_pkgname, "transaction-finished") self.assertTrue(self.log_install_event.called) self.assertFalse(self.log_uninstall_event.called) self.assertEqual(len(self.available_pane.transactions_queue), 0) [args] = self.log_install_event.call_args[0] self.assertEqual(args, transaction.real_desktop_file) def test_zeitgeist_logger_logs_uninstall_on_finished(self): test_pkgname = "software-center" self.__start_backend_transaction(test_pkgname, TransactionTypes.REMOVE) transaction = self.available_pane.transactions_queue[test_pkgname] self.__emit_backend_event(test_pkgname, "transaction-finished") self.assertFalse(self.log_install_event.called) self.assertTrue(self.log_uninstall_event.called) self.assertEqual(len(self.available_pane.transactions_queue), 0) [args] = self.log_uninstall_event.call_args[0] self.assertEqual(args, transaction.real_desktop_file) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_app.py0000664000202700020270000003135312151440100021246 0ustar dobeydobey00000000000000import os import unittest from collections import defaultdict from functools import partial import xapian from mock import Mock from tests.utils import ( FakedCache, get_mock_options, setup_test_env, do_events_with_sleep, ) setup_test_env() from softwarecenter.db import DebFileApplication, DebFileOpenError from softwarecenter.enums import PkgStates, SearchSeparators from softwarecenter.ui.gtk3 import app from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook class ParsePackagesArgsTestCase(unittest.TestCase): """Test suite for the parse_packages_args helper.""" pkg_name = 'foo' def transform_for_test(self, items): """Transform a sequence into a comma separated string.""" return app.SEARCH_PREFIX + SearchSeparators.REGULAR.join(items) def do_check(self, apps, items=None): """Check that the available_pane was shown.""" if items is None: items = self.transform_for_test(apps) search_text, result_app = app.parse_packages_args(items) self.assertEqual(SearchSeparators.REGULAR.join(apps), search_text) self.assertIsNone(result_app) def test_empty(self): """Pass an empty argument, show the 'available' view.""" self.do_check(apps=()) def test_single_empty_item(self): """Pass a single empty item, show the 'available' view.""" self.do_check(apps=('',)) def test_single_item(self): """Pass a single item, show the 'available' view.""" self.do_check(apps=(self.pkg_name,)) def test_two_items(self): """Pass two items, show the 'available' view.""" self.do_check(apps=(self.pkg_name, 'bar')) def test_several_items(self): """Pass several items, show the 'available' view.""" self.do_check(apps=(self.pkg_name, 'firefox', 'software-center')) class ParsePackageArgsAsFileTestCase(unittest.TestCase): def test_item_is_a_file(self): """Pass an item that is an existing file.""" # pass a real deb here fname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "test_debs", "gdebi-test1.deb") assert os.path.exists(fname) # test once as string and as list for items in ( fname, [fname] ): search_text, result_app = app.parse_packages_args(fname) self.assertIsInstance(result_app, DebFileApplication) def test_item_with_file_prefix_one_slash(self): """ Pass an existing filename with a file:/ prefix. """ # pass a real deb here orig_fname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "test_debs", "gdebi-test1.deb") assert os.path.exists(orig_fname) for prefix in [ "file:/", "file://", "file:///"]: fname = prefix + orig_fname # test once as string and as list for items in ( fname, [fname] ): search_text, result_app = app.parse_packages_args(fname) self.assertIsInstance(result_app, DebFileApplication) def test_item_is_invalid_file(self): """ Pass an invalid file item """ fname = __file__ assert os.path.exists(fname) self.assertRaises(DebFileOpenError, app.parse_packages_args, fname) class ParsePackagesWithAptPrefixTestCase(ParsePackagesArgsTestCase): installed = None def setUp(self): super(ParsePackagesWithAptPrefixTestCase, self).setUp() self.cache = FakedCache() self.db = app.StoreDatabase(cache=self.cache) assert self.pkg_name not in self.cache if self.installed is not None: mock_cache_entry = Mock() mock_cache_entry.website = None mock_cache_entry.license = None mock_cache_entry.installed_files = [] mock_cache_entry.candidate = Mock() mock_cache_entry.candidate.version = '1.0' mock_cache_entry.candidate.description = 'A nonsense app.' mock_cache_entry.candidate.origins = () mock_cache_entry.versions = (Mock(),) mock_cache_entry.versions[0].version = '0.99' mock_cache_entry.versions[0].origins = (Mock(),) mock_cache_entry.versions[0].origins[0].archive = 'test' mock_cache_entry.is_installed = self.installed if self.installed: mock_cache_entry.installed = Mock() mock_cache_entry.installed.version = '0.90' mock_cache_entry.installed.installed_size = 0 else: mock_cache_entry.installed = None self.cache[self.pkg_name] = mock_cache_entry self.addCleanup(self.cache.pop, self.pkg_name) def transform_for_test(self, items): """Do nothing.""" return items def check_package_availability(self, name): """Check whether the package 'name' is available.""" if name not in self.cache: state = PkgStates.NOT_FOUND elif self.cache[name].installed: state = PkgStates.INSTALLED else: state = PkgStates.UNINSTALLED return state def do_check(self, apps, items=None): """Check that the available_pane was shown.""" if items is None: items = self.transform_for_test(apps) search_text, result_app = app.parse_packages_args(items) if apps and len(apps) == 1 and apps[0] and not os.path.isfile(apps[0]): self.assertIsNotNone(result_app) app_details = result_app.get_details(self.db) self.assertEqual(apps[0], app_details.name) state = self.check_package_availability(app_details.name) self.assertEqual(state, app_details.pkg_state) else: self.assertIsNone(result_app) if apps and (len(apps) > 1 or os.path.isfile(apps[0])): self.assertEqual(SearchSeparators.PACKAGE.join(apps), search_text) else: self.assertEqual('', search_text) def test_item_with_prefix(self): """Pass a item with the item prefix.""" for prefix in ('apt:', 'apt://', 'apt:///'): for case in (self.pkg_name, app.PACKAGE_PREFIX + self.pkg_name): self.do_check(apps=(case,), items=(prefix + case,)) class ParsePackagesNotInstalledTestCase(ParsePackagesWithAptPrefixTestCase): """Test suite for parsing/searching/loading package lists.""" installed = False class ParsePackagesInstalledTestCase(ParsePackagesWithAptPrefixTestCase): """Test suite for parsing/searching/loading package lists.""" installed = True class ParsePackagesArgsStringTestCase(ParsePackagesWithAptPrefixTestCase): """Test suite for parsing/loading package lists from strings.""" def transform_for_test(self, items): """Transform a sequence into a comma separated string.""" return SearchSeparators.PACKAGE.join(items) class AppTestCase(unittest.TestCase): """Test suite for the app module.""" def setUp(self): super(AppTestCase, self).setUp() self.called = defaultdict(list) self.addCleanup(self.called.clear) orig = app.SoftwareCenterAppGtk3.START_DBUS self.addCleanup(setattr, app.SoftwareCenterAppGtk3, 'START_DBUS', orig) app.SoftwareCenterAppGtk3.START_DBUS = False orig = app.get_pkg_info self.addCleanup(setattr, app, 'get_pkg_info', orig) app.get_pkg_info = lambda: FakedCache() options = get_mock_options() self.app = app.SoftwareCenterAppGtk3(options) self.addCleanup(self.app.destroy) self.app.cache.open() # connect some signals of interest cid = self.app.installed_pane.connect('installed-pane-created', partial(self.track_calls, 'pane-created')) self.addCleanup(self.app.installed_pane.disconnect, cid) cid = self.app.available_pane.connect('available-pane-created', partial(self.track_calls, 'pane-created')) self.addCleanup(self.app.available_pane.disconnect, cid) def track_calls(self, name, *a, **kw): """Record the callback for 'name' using 'args' and 'kwargs'.""" self.called[name].append((a, kw)) class ShowPackagesTestCase(AppTestCase): """Test suite for parsing/searching/loading package lists.""" def do_check(self, packages, search_text): """Check that the available_pane was shown.""" self.app.show_available_packages(packages=packages) self.assertEqual(self.called, {'pane-created': [((self.app.available_pane,), {})]}) actual = self.app.available_pane.searchentry.get_text() self.assertEqual(search_text, actual, 'Expected search text %r (got %r instead) for packages %r.' % (search_text, actual, packages)) self.assertIsNone(self.app.available_pane.app_details_view.app_details) def test_show_available_packages_search_prefix(self): """Check that the available_pane was shown.""" self.do_check(packages='search:foo,bar baz', search_text='foo bar baz') def test_show_available_packages_apt_prefix(self): """Check that the available_pane was shown.""" for prefix in ('apt:', 'apt://', 'apt:///'): self.do_check(packages=prefix + 'foo,bar,baz', search_text='foo,bar,baz') class ShowPackagesOnePackageTestCase(AppTestCase): """Test suite for parsing/searching/loading package lists.""" pkg_name = 'foo' installed = None def setUp(self): super(ShowPackagesOnePackageTestCase, self).setUp() assert self.pkg_name not in self.app.cache if self.installed is not None: mock_cache_entry = Mock() mock_cache_entry.website = None mock_cache_entry.license = None mock_cache_entry.installed_files = [] mock_cache_entry.candidate = Mock() mock_cache_entry.candidate.version = '1.0' mock_cache_entry.candidate.description = 'A nonsense app.' mock_cache_entry.candidate.origins = () mock_cache_entry.versions = (Mock(),) mock_cache_entry.versions[0].version = '0.99' mock_cache_entry.versions[0].origins = (Mock(),) mock_cache_entry.versions[0].origins[0].archive = 'test' mock_cache_entry.is_installed = self.installed if self.installed: mock_cache_entry.installed = Mock() mock_cache_entry.installed.version = '0.90' mock_cache_entry.installed.installed_size = 0 else: mock_cache_entry.installed = None self.app.cache[self.pkg_name] = mock_cache_entry self.addCleanup(self.app.cache.pop, self.pkg_name) def check_package_availability(self, name): """Check whether the package 'name' is available.""" pane = self.app.available_pane if name not in self.app.cache: state = PkgStates.NOT_FOUND elif self.app.cache[name].installed: state = PkgStates.INSTALLED pane = self.app.installed_pane else: state = PkgStates.UNINSTALLED self.assertEqual(state, pane.app_details_view.app_details.pkg_state) return pane def test_show_available_packages(self): """Check that the available_pane was shown.""" self.app.show_available_packages(packages=self.pkg_name) expected_pane = self.check_package_availability(self.pkg_name) name = expected_pane.app_details_view.app_details.name self.assertEqual(self.pkg_name, name) self.assertEqual('', self.app.available_pane.searchentry.get_text()) self.assertEqual(self.called, {'pane-created': [((expected_pane,), {})]}) class ShowPackagesNotInstalledTestCase(ShowPackagesOnePackageTestCase): """Test suite for parsing/searching/loading package lists.""" installed = False class ShowPackagesInstalledTestCase(ShowPackagesOnePackageTestCase): """Test suite for parsing/searching/loading package lists.""" installed = True class MenuTestCase(AppTestCase): """ Test case for the menus """ def test_reinstall_previous_purchases_lp1011522(self): # pretend we have the available_for_me data (much quicker # than to do a real s-c-agent query for this) self.app.available_for_me_query = xapian.Query() self.app.on_menuitem_reinstall_purchases_activate(None) do_events_with_sleep() self.assertEqual( self.app.available_pane.spinner_notebook.get_current_page(), SpinnerNotebook.CONTENT_PAGE) if __name__ == "__main__": # avoid spawning recommender-agent, reviews, software-center-agent etc, # cuts ~5s or so os.environ["SOFTWARE_CENTER_DISABLE_SPAWN_HELPER"] = "1" unittest.main() software-center-13.10/tests/gtk3/test_debfile_view.py0000664000202700020270000000224412151440100023107 0ustar dobeydobey00000000000000import time import unittest from tests.utils import ( do_events, get_mock_options, setup_test_env, ) setup_test_env() from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane class DebFileOpenTestCase(unittest.TestCase): def test_deb_file_view_error(self): mock_options = get_mock_options() app = SoftwareCenterAppGtk3(mock_options) app.run(["./data/test_debs/gdebi-test1.deb"]) # give it time for i in range(10): time.sleep(0.1) do_events() # its ok to break out early if (app.available_pane.app_details_view and app.available_pane.app_details_view.get_property("visible")): break # check that we are on the right page self.assertEqual( app.available_pane.get_current_page(), AvailablePane.Pages.DETAILS) # this is deb that is not installable action_button = app.available_pane.app_details_view.pkg_statusbar.button self.assertFalse(action_button.get_property("visible")) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_views.py0000664000202700020270000000204112151440100021613 0ustar dobeydobey00000000000000import unittest from tests.utils import setup_test_env setup_test_env() from tests.gtk3.windows import ( get_test_window_appdetails, get_test_window_appview, get_test_window_catview, get_test_window_pkgnamesview, get_test_window_purchaseview, get_test_window_viewswitcher, ) class TestViews(unittest.TestCase): def test_viewswitcher(self): win = get_test_window_viewswitcher() self.addCleanup(win.destroy) def test_catview(self): win = get_test_window_catview() self.addCleanup(win.destroy) def test_appdetails(self): win = get_test_window_appdetails() self.addCleanup(win.destroy) def test_pkgsnames(self): win = get_test_window_pkgnamesview() self.addCleanup(win.destroy) def test_purchaseview(self): win = get_test_window_purchaseview() self.addCleanup(win.destroy) def test_appview(self): win = get_test_window_appview() self.addCleanup(win.destroy) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_widgets.py0000664000202700020270000002270212151440100022132 0ustar dobeydobey00000000000000import os import unittest from gi.repository import Gtk, GdkPixbuf, GLib from gettext import gettext as _ from mock import Mock, patch from tests.utils import ( DATA_DIR, setup_test_env, do_events, do_events_with_sleep, ) setup_test_env() from softwarecenter.netstatus import ( NetState, test_ping, ) from softwarecenter.enums import PkgStates from softwarecenter.utils import utf8 from softwarecenter.ui.gtk3.widgets import videoplayer from softwarecenter.ui.gtk3.widgets.actionbar import ActionBar from softwarecenter.ui.gtk3.widgets.imagedialog import SimpleShowImageDialog from softwarecenter.ui.gtk3.widgets.labels import ( HardwareRequirementsLabel, HardwareRequirementsBox) from tests.gtk3.windows import ( get_test_backforward_window, get_test_buttons_window, get_test_container_window, get_test_description_window, get_test_exhibits_window, get_test_reviews_window, get_test_searchentry_window, get_test_screenshot_thumbnail_window, get_test_spinner_window, get_test_stars_window, get_test_symbolic_icons_window, get_test_videoplayer_window, get_test_window, get_test_window_apptreeview, ) # window destory timeout TIMEOUT=100 class TestWidgets(unittest.TestCase): """ basic tests for the various gtk3 widget """ def test_stars(self): win = get_test_stars_window() self.addCleanup(win.destroy) def test_actionbar(self): mock = Mock() actionbar = ActionBar() actionbar.add_button("id1", "label", mock) actionbar.add_button("id2", "label", mock) actionbar.remove_button("id2") win = get_test_window(child=actionbar) self.addCleanup(win.destroy) def test_backforward(self): win = get_test_backforward_window() self.addCleanup(win.destroy) def test_backforward_lp1034894(self): win = get_test_backforward_window(width=150, height=75) backforward = win.get_children()[0] style_context = backforward.get_style_context() self.assertTrue(style_context.has_class(Gtk.STYLE_CLASS_LINKED)) self.addCleanup(win.destroy) GLib.timeout_add(TIMEOUT, Gtk.main_quit) Gtk.main() def test_containers(self): win = get_test_container_window() self.addCleanup(win.destroy) def test_description(self): win = get_test_description_window() self.addCleanup(win.destroy) def test_exhibits(self): win = get_test_exhibits_window() self.addCleanup(win.destroy) def test_show_image_dialog(self): f = os.path.join(DATA_DIR, "test_images", "fallback.png") pix = GdkPixbuf.Pixbuf.new_from_file(f) d = SimpleShowImageDialog("test caption", pix) GLib.timeout_add(TIMEOUT, lambda: d.destroy()) d.run() def test_searchentry(self): win = get_test_searchentry_window() self.addCleanup(win.destroy) s = "foo" win.entry.insert_text(s, len(s)) self.addCleanup(win.destroy) def test_spinner(self): win = get_test_spinner_window() self.addCleanup(win.destroy) def test_symbolic_icons(self): win = get_test_symbolic_icons_window() self.addCleanup(win.destroy) def test_buttons(self): win = get_test_buttons_window() self.addCleanup(win.destroy) def test_videoplayer(self): win = get_test_videoplayer_window() self.addCleanup(win.destroy) def test_apptreeview(self): win = get_test_window_apptreeview() self.addCleanup(win.destroy) @unittest.skipIf(test_ping() != NetState.NM_STATE_CONNECTED_GLOBAL, "Need network for this test") def test_screenshot_thumbnail(self): win = get_test_screenshot_thumbnail_window() self.addCleanup(win.destroy) t = win.get_data("screenshot_thumbnail_widget") b = win.get_data("screenshot_thumbnail_cycle_test_button") for i in range(5): b.clicked() do_events_with_sleep(iterations=5) # ensure that either the big screeshot is visible or the loading # spinner self.assertTrue(t.main_screenshot.get_property("visible") or t.spinner.get_property("visible")) class TestHWRequirements(unittest.TestCase): HW_TEST_RESULT = { 'hardware::gps' : 'yes', 'hardware::xxx' : 'unknown', 'hardware::input:mouse' : 'no', } def test_hardware_requirements_label(self): label = HardwareRequirementsLabel() label.set_hardware_requirement('hardware::gps', 'yes') self.assertEqual( label.get_label(), u"%sGPS" % HardwareRequirementsLabel.SUPPORTED_SYM["yes"]) # test the gtk bits self.assertEqual(type(label.get_children()[0]), Gtk.Label) # test setting it again label.set_hardware_requirement('hardware::video:opengl', 'yes') self.assertEqual(len(label.get_children()), 1) # regression test for bug #967036 @patch("softwarecenter.ui.gtk3.widgets.labels.get_hw_short_description") def test_hardware_requirements_label_utf8(self, mock_get_hw): magic_marker = u" \u1234 GPS" mock_get_hw.return_value = utf8(magic_marker) label = HardwareRequirementsLabel() label.set_hardware_requirement('hardware::gps', 'yes') self.assertEqual( label.get_label(), u"%s%s" % (HardwareRequirementsLabel.SUPPORTED_SYM["yes"], magic_marker)) def test_hardware_requirements_box(self): box = HardwareRequirementsBox() # test empty box.set_hardware_requirements({}) # test sensible box.set_hardware_requirements(self.HW_TEST_RESULT) # its 2 because we do not display "unknown" currently self.assertEqual(len(box.hw_labels), 2) # test the gtk bits self.assertEqual(len(box.get_children()), 2) # no trailing "," self.assertEqual( box.get_children()[0].get_label(), u"%smouse," % HardwareRequirementsLabel.SUPPORTED_SYM["no"]) self.assertEqual( box.get_children()[1].get_label(), u"%sGPS" % HardwareRequirementsLabel.SUPPORTED_SYM["yes"]) # test seting it again box.set_hardware_requirements(self.HW_TEST_RESULT) self.assertEqual(len(box.get_children()), 2) class TestUIReviewsList(unittest.TestCase): def setUp(self): self.win = get_test_reviews_window() self.addCleanup(self.win.destroy) self.rl = self.win.get_children()[0] def assertComboBoxTextIncludes(self, combo, option): store = combo.get_model() self.assertTrue(option in [x[0] for x in store]) def assertEmbeddedMessageLabel(self, title, message): markup = self.rl.vbox.get_children()[1].label.get_label() self.assertTrue(title in markup) self.assertTrue(message in markup) def test_reviews_includes_any_language(self): review_language = self.rl.review_language self.assertComboBoxTextIncludes(review_language, _('Any language')) def test_reviews_offers_to_relax_language(self): # No reviews found, but there are some in other languages: self.rl.clear() self.rl.global_review_stats = Mock() self.rl.global_review_stats.ratings_total = 4 self.rl.configure_reviews_ui() do_events() self.assertEmbeddedMessageLabel( _("This app has not been reviewed yet in your language"), _('Try selecting a different language, or even "Any language"' ' in the language dropdown')) @patch('softwarecenter.ui.gtk3.widgets.reviews.network_state_is_connected') def test_reviews_no_reviews_but_app_not_installed(self, mock_connected): mock_connected.return_value = True # No reviews found, and the app isn't installed self.rl.clear() self.rl.configure_reviews_ui() do_events() self.assertEmbeddedMessageLabel( _("This app has not been reviewed yet"), _('You need to install this before you can review it')) @patch('softwarecenter.ui.gtk3.widgets.reviews.network_state_is_connected') def test_reviews_no_reviews_offer_to_write_one(self, mock_connected): mock_connected.return_value = True # No reviews found, and the app is installed self.rl.clear() self.rl._parent.app_details.pkg_state = PkgStates.INSTALLED self.rl.configure_reviews_ui() do_events() self.assertEmbeddedMessageLabel( _('Got an opinion?'), _('Be the first to contribute a review for this application')) class VideoPlayerTestCase(unittest.TestCase): def setUp(self): super(VideoPlayerTestCase, self).setUp() self.webkit_uri = None self.vp = videoplayer.VideoPlayer() self.vp.webkit.load_uri = lambda uri: setattr(self, 'webkit_uri', uri) self.vp.webkit.get_uri = lambda: self.webkit_uri self.addCleanup(self.vp.destroy) def test_uri(self): self.assertEqual(self.vp.uri, '') expected_uri = 'file://test' self.vp.uri = expected_uri self.assertEqual(self.vp.uri, expected_uri) self.assertEqual(self.vp.webkit.get_uri(), self.vp.uri) def test_stop(self): self.vp.uri = 'http://foo.bar.baz' self.vp.stop() self.assertEqual(self.vp.webkit.get_uri(), '') if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_lp1048912.py0000664000202700020270000000446512151440100021656 0ustar dobeydobey00000000000000import unittest from gi.repository import ( GLib, Gtk, ) from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.ui.gtk3.widgets.containers import ( FramedHeaderBox, ) from softwarecenter.ui.gtk3.widgets.viewport import Viewport def add_tiles(tile_grid): print "add_tiles" for i in range(10): b = Gtk.Button("tile %i" % i) b.show() tile_grid.pack_start(b, False, False, 0) print "/add_tiles" class CatviewTestCase(unittest.TestCase): """This adds a test for the drawing artifact bug #1048912 Note that the bug is not fixed yet """ def test_visual_glitch_lp1048912(self): win = Gtk.Window() win.set_size_request(500, 300) scroll = Gtk.ScrolledWindow() win.add(scroll) viewport = Viewport() scroll.add(viewport) top_hbox = Gtk.HBox() viewport.add(top_hbox) right_column = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) top_hbox.pack_start(right_column, True, True, 0) apptiles = Gtk.VBox() apptiles_frame = FramedHeaderBox() apptiles_frame.set_name("apptiles top") apptiles_frame.set_header_label("Frame1") apptiles_frame.add(apptiles) right_column.pack_start(apptiles_frame, True, True, 0) apptiles_frame.header_implements_more_button() apptiles2 = Gtk.VBox() apptiles2_frame = FramedHeaderBox() apptiles2_frame.set_name("apptiles bottom") apptiles2_frame.set_header_label("Frame2") apptiles2_frame.add(apptiles2) right_column.pack_start(apptiles2_frame, True, True, 0) apptiles2_frame.header_implements_more_button() # this delayed adding of the tiles causes a visual glitch like # described in #1048912 on the top of the bottom frame the line # will not be drawn correctly - any subsequent redraw of the # window will fix it GLib.timeout_add(1000, add_tiles, apptiles2) # this "viewport.queue_draw()" will fix the glitch #GLib.timeout_add_seconds(2, lambda: viewport.queue_draw()) # stop the test GLib.timeout_add_seconds(3, Gtk.main_quit) win.connect("destroy", Gtk.main_quit) win.show_all() Gtk.main() if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_reviews.py0000664000202700020270000002214112151440100022145 0ustar dobeydobey00000000000000import unittest from time import sleep from gettext import gettext as _ from mock import Mock, patch from tests.utils import ( do_events, get_test_db, get_test_pkg_info, REAL_DATA_DIR, setup_test_env, ) setup_test_env() from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails from softwarecenter.ui.gtk3.review_gui_helper import ( TRANSMIT_STATE_DONE, GRatingsAndReviews, SubmitReviewsApp, ) class TestReviewLoader(unittest.TestCase): cache = get_test_pkg_info() db = get_test_db() def _review_stats_ready_callback(self, review_stats): self._stats_ready = True self._review_stats = review_stats # def test_review_stats_caching(self): # self._stats_ready = False # self._review_stats = [] # review_loader = ReviewLoader(self.cache, self.db) # review_loader.refresh_review_stats(self._review_stats_ready_callback) # while not self._stats_ready: # do_events() # self.assertTrue(len(self._review_stats) > 0) # self.assertTrue(os.path.exists(review_loader.REVIEW_STATS_CACHE_FILE)) # self.assertTrue(os.path.exists(review_loader.REVIEW_STATS_BSDDB_FILE)) # # once its there, get_top_rated # top_rated = review_loader.get_top_rated_apps(quantity=10) # self.assertEqual(len(top_rated), 10) # # and per-cat # top_cat = review_loader.get_top_rated_apps( # quantity=8, category="Internet") # self.assertEqual(len(top_cat), 8) def test_edit_review_screen_has_right_labels(self): """Check that LP #880255 stays fixed. """ review_app = SubmitReviewsApp(datadir=REAL_DATA_DIR, app=None, parent_xid='', iconname='accessories-calculator', origin=None, version=None, action='modify', review_id=10000) # monkey patch away login to avoid that we actually login # and the UI changes because of that review_app.login = lambda: True # run the main app review_app.run() do_events() review_app.login_successful('foobar') do_events() self.assertEqual(_('Rating:'), review_app.rating_label.get_label()) self.assertEqual(_('Summary:'), review_app.review_summary_label.get_label()) self.assertEqual(_('Review by: %s') % 'foobar', review_app.review_label.get_label()) review_app.submit_window.hide() def test_get_fade_colour_markup(self): review_app = SubmitReviewsApp(datadir=REAL_DATA_DIR, app=None, parent_xid='', iconname='accessories-calculator', origin=None, version=None, action='nothing') cases = ( (('006000', '00A000', 40, 60, 50), ('008000', 10)), (('000000', 'FFFFFF', 40, 40, 40), ('000000', 0)), (('000000', '808080', 100, 400, 40), ('000000', 360)), (('000000', '808080', 100, 400, 1000), ('808080', -600)), (('123456', '5294D6', 10, 90, 70), ('427CB6', 20)), ) for args, return_value in cases: result = review_app._get_fade_colour_markup(*args) expected = '%s' % return_value self.assertEqual(expected, result) def test_modify_review_is_the_same_supports_unicode(self): review_app = SubmitReviewsApp(datadir=REAL_DATA_DIR, app=None, parent_xid='', iconname='accessories-calculator', origin=None, version=None, action='modify', review_id=10000) self.assertTrue(review_app._modify_review_is_the_same()) cases = ('', 'e', ')!') for case in cases: modified = review_app.orig_summary_text[:-1] + case review_app.review_summary_entry.set_text(modified) self.assertFalse(review_app._modify_review_is_the_same()) review_app.review_summary_entry.set_text(review_app.orig_summary_text) for case in cases: modified = review_app.orig_review_text[:-1] + case review_app.review_buffer.set_text(modified) self.assertFalse(review_app._modify_review_is_the_same()) def test_change_status(self): review_app = SubmitReviewsApp(datadir=REAL_DATA_DIR, app=None, parent_xid='', iconname='accessories-calculator', origin=None, version=None, action='nothing') msg = 'Something completely different' cases = {'clear': (True, True, True, True, None, None), 'progress': (False, True, True, True, msg, None), 'fail': (True, False, True, True, None, msg), 'success': (True, True, False, True, msg, None), 'warning': (True, True, True, False, msg, None), } review_app.run() for status in cases: review_app._change_status(status, msg) spinner, error, success, warn, label, error_detail = cases[status] self.assertEqual(spinner, review_app.submit_spinner.get_parent() is None) self.assertEqual(error, review_app.submit_error_img.get_window() is None) self.assertEqual(success, review_app.submit_success_img.get_window() is None) self.assertEqual(warn, review_app.submit_warn_img.get_window() is None) if label: self.assertEqual(label, review_app.label_transmit_status.get_text()) if error_detail: buff = review_app.error_textview.get_buffer() self.assertEqual(error_detail, buff.get_text(buff.get_start_iter(), buff.get_end_iter(), include_hidden_chars=False)) review_app.detail_expander.get_visible() class TestGRatingsAndReviews(unittest.TestCase): def setUp(self): mock_token = {'token': 'foobar', 'token_secret': 'foobar', 'consumer_key': 'foobar', 'consumer_secret': 'foobar', } self.api = GRatingsAndReviews(mock_token) self.addCleanup(self.api.shutdown) def make_review(self): review = Mock() review.rating = 4 review.origin = 'ubuntu' review.app.appname = '' review.app.pkgname = 'foobar' review.text = 'Super awesome app!' review.summary = 'Cool' review.package_version = '1.0' review.date = '2012-01-22' review.language = 'en' return review def wait_for_worker(self): while self.api.worker_thread._transmit_state != TRANSMIT_STATE_DONE: sleep(0.1) @patch('softwarecenter.ui.gtk3.review_gui_helper.RatingsAndReviewsAPI' '.submit_review') def test_submit_review(self, mock_submit_review): mock_submit_review.return_value = ReviewDetails.from_dict( {'foo': 'bar'}) review = self.make_review() self.api.submit_review(review) self.wait_for_worker() self.assertTrue(mock_submit_review.called) review_arg = mock_submit_review.call_args[1]['review'] self.assertEqual(review.text, review_arg.review_text) @patch('softwarecenter.ui.gtk3.review_gui_helper.RatingsAndReviewsAPI' '.flag_review') def test_flag_review(self, mock_flag_review): mock_flag_review.return_value = 'Something' self.api.report_abuse(1234, 'Far too silly', 'Stop right now.') self.wait_for_worker() self.assertTrue(mock_flag_review.called) self.assertEqual(1234, mock_flag_review.call_args[1]['review_id']) @patch('softwarecenter.ui.gtk3.review_gui_helper.RatingsAndReviewsAPI' '.submit_usefulness') def test_submit_usefulness(self, mock_submit_usefulness): mock_submit_usefulness.return_value = 'Something' self.api.submit_usefulness(1234, True) self.wait_for_worker() self.assertTrue(mock_submit_usefulness.called) self.assertEqual(1234, mock_submit_usefulness.call_args[1]['review_id']) @patch('softwarecenter.ui.gtk3.review_gui_helper.RatingsAndReviewsAPI' '.modify_review') def test_modify_review(self, mock_modify_review): mock_modify_review.return_value = ReviewDetails.from_dict( {'foo': 'bar'}) review = { 'summary': 'Cool', 'review_text': 'Super awesome app!', 'rating': 4, } self.api.modify_review(1234, review) self.wait_for_worker() self.assertTrue(mock_modify_review.called) self.assertEqual(1234, mock_modify_review.call_args[1]['review_id']) self.assertEqual(4, mock_modify_review.call_args[1]['rating']) self.assertEqual('Cool', mock_modify_review.call_args[1]['summary']) @patch('softwarecenter.ui.gtk3.review_gui_helper.RatingsAndReviewsAPI' '.delete_review') def test_delete_review(self, mock_delete_review): mock_delete_review.return_value = 'Something' self.api.delete_review(1234) self.wait_for_worker() self.assertTrue(mock_delete_review.called) self.assertEqual(1234, mock_delete_review.call_args[1]['review_id']) if __name__ == "__main__": unittest.main() software-center-13.10/tests/gtk3/test_recommendations_widgets.py0000664000202700020270000000243412151440100025401 0ustar dobeydobey00000000000000import unittest from gi.repository import ( GLib, Gtk, ) from tests.utils import setup_test_env setup_test_env() from tests.gtk3.windows import get_test_window_recommendations # FIXME: the code from test_catview that tests the lobby widget should # move here as it should be fine to test it in isolation TIMEOUT=300 class TestRecommendationsWidgets(unittest.TestCase): def _on_size_allocate(self, widget, allocation): print widget, allocation.width, allocation.height def test_recommendations_lobby(self): win = get_test_window_recommendations(panel_type="lobby") child = win.get_children()[0] child.connect("size-allocate", self._on_size_allocate) self.addCleanup(win.destroy) GLib.timeout_add(TIMEOUT, Gtk.main_quit) Gtk.main() def test_recommendations_category(self): win = get_test_window_recommendations(panel_type="category") self.addCleanup(win.destroy) GLib.timeout_add(TIMEOUT, Gtk.main_quit) Gtk.main() def test_recommendations_details(self): win = get_test_window_recommendations(panel_type="details") self.addCleanup(win.destroy) GLib.timeout_add(TIMEOUT, Gtk.main_quit) Gtk.main() if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_i18n.py0000664000202700020270000000333212151440100020371 0ustar dobeydobey00000000000000import os import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.i18n import ( init_locale, get_language, get_languages, langcode_to_name) class TestI18n(unittest.TestCase): """ tests the sc i18n """ def tearDown(self): os.environ["LANGUAGE"] = "" os.environ["LC_ALL"] = "" os.environ["LANG"] = "" def test_langcode_to_name(self): self.assertEqual(langcode_to_name("de"), "German") def test_locale(self): # needs lang + country code os.environ["LANGUAGE"] = "zh_TW" self.assertEqual(get_language(), "zh_TW") # language only os.environ["LANGUAGE"] = "fr_FR" self.assertEqual(get_language(), "fr") # not existing one os.environ["LANGUAGE"] = "xx_XX" self.assertEqual(get_language(), "en") # LC_ALL, no language del os.environ["LANGUAGE"] os.environ["LC_ALL"] = "C" os.environ["LANG"] = "C" self.assertEqual(get_language(), "en") def test_invalid_get_languages(self): # set LANGUAGE to a invalid language and verify that it correctly # falls back to english os.environ["LANGUAGE"] = "yxy_YYY" self.assertEqual(get_languages(), ["en"]) os.environ["LANGUAGE"] = "es_MX:es_ES:es_AR:es_ES:en" os.environ["LANG"] = "es_MX.UTF-8" self.assertEqual(get_languages(), ["es", "en"]) def test_init_locale(self): import locale os.environ["LANGUAGE"] = "" os.environ["LC_ALL"] = "en_US.UTF-8" init_locale() self.assertEqual( locale.getlocale(locale.LC_MESSAGES), ("en_US", "UTF-8")) if __name__ == "__main__": unittest.main() software-center-13.10/tests/qml/0000755000202700020270000000000012224614355017007 5ustar dobeydobey00000000000000software-center-13.10/tests/qml/test_ui_qml_helpers.py0000664000202700020270000000727412151440100023424 0ustar dobeydobey00000000000000from gi.repository import GLib import random import os import time import unittest # ensure we set the review backend to the fake one os.environ["SOFTWARE_CENTER_IPSUM_REVIEWS"] = "1" from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.ui.qml.categoriesmodel import CategoriesModel from softwarecenter.ui.qml.pkglist import PkgListModel from softwarecenter.ui.qml.reviewslist import ReviewsListModel class TestQMLHelpers(unittest.TestCase): """ tests the helper classes for the qml code """ def setUp(self): # ensure the cache is ready cache = get_pkg_info() cache.open() def test_categories_model(self): """ test the qml categories model """ CAT_NAME_COLUMN = 0 # get the model model = CategoriesModel() # ensure we have something in it self.assertNotEqual(model.rowCount(), 0) # ensure we have "Games" names = set() for i in range(model.rowCount()): index = model.index(i, CAT_NAME_COLUMN) role = CAT_NAME_COLUMN names.add(model.data(index, role)) self.assertTrue("Games" in names) def test_pkglist_model(self): APP_NAME_COLUMN = 0 # get model model = PkgListModel() # ensure we start with a empty model by default self.assertEqual(model.rowCount(), 0) # test search model.setSearchQuery("software") # ensure we have something in it self.assertNotEqual(model.rowCount(), 0) # ensure we have "Software Center" names = set() for i in range(model.rowCount()): index = model.index(i, APP_NAME_COLUMN) role = APP_NAME_COLUMN names.add(model.data(index, role)) # en_DK/en_US fun self.assertTrue("Ubuntu Software Center" in names or "Ubuntu Software Centre" in names) # test setCategory by ensuring that setCategory() cuts the nr of # search results old_search_hits = model.rowCount() model.setCategory("Games") self.assertTrue(model.rowCount() < old_search_hits) # test clear model.clear() self.assertEqual(model.rowCount(), 0) def test_reviews_model_details(self): SUMMARY_COLUMN = 0 # get the model model = ReviewsListModel() # ensure we start with a empty model by default self.assertEqual(model.rowCount(), 0) # fetch some reviews and ensure its not random random.seed(1) model.getReviews("software-center") time.sleep(0.1) self._p() # check the results self.assertEqual(model.rowCount(), 17) summaries = [] for i in range(model.rowCount()): index = model.index(i, SUMMARY_COLUMN) role = SUMMARY_COLUMN summaries.append(model.data(index, role)) # ensure the summaries are thre self.assertEqual(summaries[0], "Medium") self.assertEqual(summaries[4], "Cool") # test clear model.clear() self.assertEqual(model.rowCount(), 0) def test_reviews_model_stats(self): def _got_review_stats_changed_signal(): self._i_am_refreshed = True # get the model model = ReviewsListModel() self._i_am_refreshed = False model.reviewStatsChanged.connect(_got_review_stats_changed_signal) model.refreshReviewStats() # ensure the signal got send self.assertTrue(self._i_am_refreshed) del self._i_am_refreshed def _p(self): context = GLib.main_context_default() while context.pending(): context.iteration() if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_pkginfo.py0000664000202700020270000000714012151440100021250 0ustar dobeydobey00000000000000import apt import time import unittest from gi.repository import GLib from mock import patch from tests.utils import ( get_test_pkg_info, setup_test_env, ) setup_test_env() import softwarecenter from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.utils import ExecutionTime class TestAptCache(unittest.TestCase): def test_open_aptcache(self): # mvo: for the performance, its critical to have a # /var/cache/apt/srcpkgcache.bin - otherwise stuff will get slow # open s-c aptcache with ExecutionTime("s-c softwarecenter.apt.AptCache"): self.sccache = get_pkg_info() # cache is opened with a timeout_add() in get_pkg_info() time.sleep(0.2) context = GLib.main_context_default() while context.pending(): context.iteration() # compare with plain apt with ExecutionTime("plain apt: apt.Cache()"): self.cache = apt.Cache() with ExecutionTime("plain apt: apt.Cache(memonly=True)"): self.cache = apt.Cache(memonly=True) def test_get_total_size(self): def _on_query_total_size_on_install_done(pkginfo, pkgname, download, space): self.need_download = download self.need_space = space loop.quit() TEST_PKG = "casper" ADDONS_TO_INSTALL = [ "lupin-casper" ] ADDONS_TO_REMOVE = [] loop = GLib.MainLoop(GLib.main_context_default()) cache = get_test_pkg_info() cache.connect( "query-total-size-on-install-done", _on_query_total_size_on_install_done) cache.query_total_size_on_install( TEST_PKG, ADDONS_TO_INSTALL, ADDONS_TO_REMOVE) loop.run() # ensure the test eventually stops and does not hang GLib.timeout_add_seconds(10, loop.quit) # work out the numbers that we at least need to get (this will # not include dependencies so it is probably lower) need_at_least_download = ( cache[TEST_PKG].candidate.size + sum([cache[pkg].candidate.size for pkg in ADDONS_TO_INSTALL])) need_at_least_installed = ( cache[TEST_PKG].candidate.installed_size + sum([cache[pkg].candidate.installed_size for pkg in ADDONS_TO_INSTALL])) self.assertTrue(self.need_download >= need_at_least_download) self.assertTrue(self.need_space >= need_at_least_installed) del self.need_download del self.need_space def test_get_total_size_with_mock(self): # get a cache cache = get_pkg_info() cache.open() # pick first uninstalled pkg for pkg in cache: if not pkg.is_installed: break # prepare args addons_to_install = addons_to_remove = [] archive_suite = "foo" with patch.object(cache.aptd_client, "commit_packages") as f_mock: cache.query_total_size_on_install( pkg.name, addons_to_install, addons_to_remove, archive_suite) # ensure it got called with the right arguments args, kwargs = f_mock.call_args to_install = args[0] self.assertTrue(to_install[0].endswith("/%s" % archive_suite)) @patch("softwarecenter.db.pkginfo_impl.aptcache.AptClient") def test_aptd_client_unavailable(self, mock_apt_client): mock_apt_client.side_effect = Exception("fake") cache = softwarecenter.db.pkginfo_impl.aptcache.AptCache() self.assertEqual(cache.aptd_client, None) if __name__ == "__main__": unittest.main() software-center-13.10/tests/disabled_test_config.py0000775000202700020270000000536712151440100022723 0ustar dobeydobey00000000000000#!/usr/bin/python import os from tempfile import NamedTemporaryFile import unittest from tests.utils import setup_test_env setup_test_env() from softwarecenter.config import ( SoftwareCenterConfig, get_config, ) class ConfigTestCase(unittest.TestCase): """ tests the config class """ def setUp(self): self.tempfile = NamedTemporaryFile(prefix="sc-test-") self.config = SoftwareCenterConfig(self.tempfile.name) def test_properties_simple_bool(self): """ test a bool property """ for value in [ True, False, True ]: self.config.add_to_unity_launcher = value self.assertEqual(self.config.add_to_unity_launcher, value) self.assertEqual( self.config.getboolean("general", "add_to_launcher"), value) def test_properties_default_bool(self): """ test default values for properties """ self.assertTrue(self.config.add_to_unity_launcher) def test_properties_default_window_size(self): """ default value for window size tuple """ self.assertEqual(self.config.app_window_size, [-1, -1]) def test_properties_window_size(self): """ test the app_window_size getter/setter """ self.config.app_window_size = [10, 10] self.assertEqual(self.config.app_window_size, [10, 10]) def test_property_simple_string(self): """ test a string property """ for value in [ "", "xxxyyy"]: self.config.recommender_uuid = value self.assertEqual(self.config.recommender_uuid, value) self.assertEqual( self.config.get("general", "recommender_uuid"), value) def test_write(self): """ test that write writes """ self.assertEqual(os.path.getsize(self.tempfile.name), 0) self.config.user_accepted_tos = True self.config.write() self.assertNotEqual(os.path.getsize(self.tempfile.name), 0) with open(self.tempfile.name) as f: content = f.read() self.assertTrue(content.startswith("[general]\n")) self.assertTrue("\naccepted_tos = True\n" in content) def test_uuid_conversion(self): """ Test that going from the old uuid format to the new works transparently """ self.config.recommender_uuid = "xx-yy-zz" self.assertEqual(self.config.recommender_uuid, "xxyyzz") def test_raise_on_unknown_property(self): """ test that we get a error for unknown properties """ with self.assertRaises(AttributeError): self.config.unknown_propertiy def test_config_singleton(self): config1 = get_config() config2 = get_config() self.assertEqual(config1, config2) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_ppa_iconfilename.py0000664000202700020270000000302312151440100023100 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.distro.ubuntu import Ubuntu class MockCache(object): def __init__(self, mock_base_url): self._baseurl = mock_base_url self._cache = [] self.ready = True def __getitem__(self, k): return MockPackage(self, k) def __contains__(self, k): return True class MockPackage(object): def __init__(self, parent, pkgname): self._parentcache = parent self.name = pkgname @property def candidate(self): return MockVersion(self) class MockVersion(object): def __init__(self, parent, version="1.0"): self._parentpkg = parent self._version = version @property def uri(self): pkgname = self._parentpkg.name baseurl = self._parentpkg._parentcache._baseurl return "%s/pool/main/%s/%s/%s_%s_all.deb" % ( baseurl, pkgname[0], pkgname, pkgname, self._version) class TestDistroUbuntu(unittest.TestCase): def setUp(self): self.distro = Ubuntu() def test_icon_download_url(self): mock_cache = MockCache("http://ppa.launchpad.net/mvo/ppa/ubuntu") pkgname = "pkg" iconname = "iconfilename" pkg_uri = mock_cache[pkgname].candidate.uri icon_url = self.distro.get_downloadable_icon_url(pkg_uri, iconname) self.assertEqual(icon_url, "http://ppa.launchpad.net/mvo/meta/ppa/iconfilename") if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_distro.py0000664000202700020270000000123312151440100021114 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.distro import get_distro class TestDistro(unittest.TestCase): """ tests the distro class """ def test_get_distro(self): distro = get_distro() self.assertNotEqual(distro, None) def test_distro_functions(self): distro = get_distro() codename = distro.get_codename() self.assertNotEqual(codename, None) myname = distro.get_app_name() self.assertTrue(len(myname) > 0) arch = distro.get_architecture() self.assertNotEqual(arch, None) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_addons.py0000664000202700020270000000443712167624525021117 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.db.pkginfo import get_pkg_info class TestSCAddons(unittest.TestCase): """ tests the addons """ def setUp(self): self.cache = get_pkg_info() self.cache.open() @unittest.skip("disabled until fixture setup is done") def test_get_addons_simple(self): # 7zip res = self.cache.get_addons("p7zip-full", ignore_installed=False) self.assertEqual(res, ([], ["p7zip-rar"])) # apt (recommends, suggests) = self.cache.get_addons( "apt", ignore_installed=False) self.assertEqual(set(suggests), set( ['lzma', 'bzip2', 'apt-doc', 'wajig', 'aptitude', 'dpkg-dev', 'python-apt', 'synaptic'])) # synaptic: FIXME: use something that changes less often #(recommends, suggests) = self.cache.get_addons( # "synaptic", ignore_installed=False) #self.assertEqual(set(recommends), set( # ['libgtk2-perl', 'rarian-compat', 'software-properties-gtk'])) #self.assertEqual(set(suggests), set( # ["apt-xapian-index", "dwww", "deborphan", "menu"])) def test_enhances(self): """Ensure that 'Enhances:' are picked up correctly """ res = self.cache.get_addons("powerwake") self.assertEqual(res, ([], ["powernap", "powerwaked"])) def test_enhances_with_virtual_pkgs(self): res = self.cache.get_addons("bibletime") self.assertTrue("sword-text-tr" in res[1]) self.assertTrue(len(res[1]) > 5) def test_lonley_dependency(self): # gets additional recommends via lonely dependency # for arduino-core, there is a dependency on avrdude, nothing # else depends on avrdude other than arduino-core, so # we want to get the recommends/suggests/enhances for # this package too # FIXME: why only for "lonley" dependencies and not all? res = self.cache.get_addons("arduino-core") self.assertEqual(res, ([], ["avrdude-doc", "arduino-mk"])) def test_addons_removal_included_depends(self): res = self.cache.get_addons("amule-gnome-support") self.assertEqual(res, (['amule-daemon'], [])) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_unity_launcher.py0000644000202700020270000000265712216063163022666 0ustar dobeydobey00000000000000import unittest from mock import ( Mock, patch, ) from softwarecenter.backend.unitylauncher import ( UnityLauncherInfo, UnityLauncher, ) from tests.utils import setup_test_env setup_test_env() class TestUnityLauncherIntegration(unittest.TestCase): """ tests the sc utils """ @patch("softwarecenter.backend.unitylauncher.UnityLauncher" "._get_launcher_dbus_iface") def test_apps_from_sc_agent(self, mock_dbus_iface): mock_iface = Mock() mock_dbus_iface.return_value = mock_iface launcher_info = UnityLauncherInfo( name="Meep", icon_name="icon-meep", icon_file_path="/tmp/icon-meep.png", icon_x=15, icon_y=18, icon_size=48, installed_desktop_file_path="software-center-agent", trans_id="dkfjklsdf") unity_launcher = UnityLauncher() unity_launcher.send_application_to_launcher("meep-pkg", launcher_info) args = mock_iface.AddLauncherItemFromPosition.call_args[0] # basic verify self.assertEqual(args[0], "Meep") # ensure that the a app from software-center-agent got a temp desktop # file self.assertTrue(args[5].startswith("/tmp/")) self.assertEqual(open(args[5]).read(),""" [Desktop Entry] Name=Meep Icon=/tmp/icon-meep.png Type=Application""") if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) unittest.main() software-center-13.10/tests/test_mime.py0000664000202700020270000000211612151440100020540 0ustar dobeydobey00000000000000import os import unittest from tests.utils import ( REAL_DATA_DIR, setup_test_env, ) setup_test_env() from softwarecenter.db.database import StoreDatabase from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.db.update import rebuild_database class TestMime(unittest.TestCase): """ tests the mime releated stuff """ def setUp(self): self.cache = get_pkg_info() self.cache.open() def test_most_popular_applications_for_mimetype(self): pathname = os.path.join(REAL_DATA_DIR, "xapian") if not os.listdir(pathname): rebuild_database(pathname) db = StoreDatabase(pathname, self.cache) db.open() # all result = db.get_most_popular_applications_for_mimetype("text/html", only_uninstalled=False, num=5) self.assertEqual(len(result), 5) # only_uninstaleld result = db.get_most_popular_applications_for_mimetype("text/html", only_uninstalled=True, num=2) self.assertEqual(len(result), 2) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_ubuntu_sso_api.py0000664000202700020270000000251712151440100022655 0ustar dobeydobey00000000000000import os import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.backend.ubuntusso import (UbuntuSSOAPIFake, UbuntuSSO, get_ubuntu_sso_backend, ) class TestSSOAPI(unittest.TestCase): """ tests the ubuntu sso backend stuff """ def test_fake_and_real_provide_similar_methods(self): """ test if the real and fake sso provide the same functions """ sso_real = UbuntuSSO sso_fake = UbuntuSSOAPIFake # ensure that both fake and real implement the same methods self.assertEqual( set([x for x in dir(sso_real) if not x.startswith("_")]), set([x for x in dir(sso_fake) if not x.startswith("_")])) def test_get_ubuntu_backend(self): # test that we get the real one self.assertEqual(type(get_ubuntu_sso_backend()), UbuntuSSO) # test that we get the fake one os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1" self.assertEqual(type(get_ubuntu_sso_backend()), UbuntuSSOAPIFake) # clean the environment del os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] if __name__ == "__main__": unittest.main() software-center-13.10/tests/channel_query.py0000664000202700020270000000175612151440100021420 0ustar dobeydobey00000000000000import os import sys import xapian if __name__ == "__main__": if len(sys.argv) > 1: channel_name = sys.argv[1] else: channel_name = "Ubuntu" xapian_base_path = "/var/cache/software-center" pathname = os.path.join(xapian_base_path, "xapian") db = xapian.Database(pathname) enquire = xapian.Enquire(db) query = xapian.Query("XOL"+channel_name) enquire.set_query(query) matches = enquire.get_mset(0, db.get_doccount()) print "Matches: %s" % len(matches) apps = set() for m in matches: doc = m.document appname = doc.get_data() apps.add(appname) #for t in doc.termlist(): # print "'%s': %s (%s); " % (t.term, t.wdf, t.termfreq), #print "\n" print ";".join(sorted(apps)) for i in db.postlist(""): doc = db.get_document(i.docid) for t in doc.termlist(): if t.term.startswith("XOL"): print "doc: '%s', term: '%s'" % (doc.get_data(), t.term) software-center-13.10/tests/test_plugin.py0000664000202700020270000000077612151440100021121 0ustar dobeydobey00000000000000import os import unittest from mock import Mock from tests.utils import ( DATA_DIR, setup_test_env, ) setup_test_env() from softwarecenter.plugin import PluginManager class TestPlugin(unittest.TestCase): def test_plugin_manager(self): app = Mock() pm = PluginManager(app, os.path.join(DATA_DIR, "plugins")) pm.load_plugins() self.assertEqual(len(pm.plugins), 1) self.assertTrue(pm.plugins[0].i_am_happy) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_webapps_activation_plugin.py0000664000202700020270000000611712151440100025056 0ustar dobeydobey00000000000000import unittest from mock import ( Mock, patch) from tests.utils import ( setup_test_env, FakedCache, ObjectWithSignals, ) setup_test_env() from softwarecenter.plugins.webapps_activation import ( UnityWebappsActivationPlugin) def make_install_result_mock(pkgname, success): mock_install_result = Mock() mock_install_result.success = success mock_install_result.pkgname = pkgname return mock_install_result def make_mock_package_with_record(pkgname, record): pkg = Mock() pkg.candidate = Mock() pkg.candidate.record = record return pkg class FakedInstallBackend(ObjectWithSignals): pass class WebappsActivationTestCase(unittest.TestCase): """Tests the webapps activation plugin """ def setUp(self): # create webapp unity_webapp = make_mock_package_with_record( "unity-webapp", { 'Ubuntu-Webapps-Domain': 'mail.ubuntu.com' }) # create a non-webapp non_unity_webapp = make_mock_package_with_record( "unity-webapp", {}) # pkginfo self.mock_pkginfo = FakedCache() self.mock_pkginfo["unity-webapp"] = unity_webapp self.mock_pkginfo["non-unity-webapp"] = non_unity_webapp # install backend self.mock_installbackend = FakedInstallBackend() @patch("softwarecenter.plugins.webapps_activation.get_install_backend") @patch("softwarecenter.plugins.webapps_activation.get_pkg_info") def _get_patched_unity_webapps_activation_plugin(self, mock_get_pkg_info, mock_install_backend): mock_install_backend.return_value = self.mock_installbackend mock_get_pkg_info.return_value = self.mock_pkginfo w = UnityWebappsActivationPlugin() w.init_plugin() patcher = patch.object(w, "activate_unity_webapp_for_domain") patcher.start() self.addCleanup(patcher.stop) return w def test_activation(self): w = self._get_patched_unity_webapps_activation_plugin() self.mock_installbackend.emit( "transaction-finished", self.mock_installbackend, make_install_result_mock("unity-webapp", True)) self.assertTrue(w.activate_unity_webapp_for_domain.called) w.activate_unity_webapp_for_domain.assert_called_with("mail.ubuntu.com") def test_no_activation_on_fail(self): w = self._get_patched_unity_webapps_activation_plugin() self.mock_installbackend.emit( "transaction-finished", self.mock_installbackend, make_install_result_mock("unity-webapp", False)) self.assertFalse(w.activate_unity_webapp_for_domain.called) def test_no_activation_on_non_webapp(self): w = self._get_patched_unity_webapps_activation_plugin() self.mock_installbackend.emit( "transaction-finished", self.mock_installbackend, make_install_result_mock("non-unity-webapp", True)) self.assertFalse(w.activate_unity_webapp_for_domain.called) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_logging.py0000664000202700020270000000307212151440100021241 0ustar dobeydobey00000000000000import os import shutil import stat import unittest from mock import patch from tests.utils import ( setup_test_env, ) setup_test_env() import softwarecenter.log import softwarecenter.paths class TestLogging(unittest.TestCase): """Tests for the sc logging facilities.""" @unittest.skipIf( os.getuid() == 0, "fails when run as root as os.access() is always successful") def test_no_write_access_for_cache_dir(self): """Test for bug LP: #688682.""" cache_dir = './foo' os.mkdir(cache_dir) self.addCleanup(shutil.rmtree, cache_dir) with patch('softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR', cache_dir): # make the test cache dir non-writeable os.chmod(cache_dir, stat.S_IRUSR) self.addCleanup(os.chmod, cache_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) self.assertFalse(os.access(cache_dir, os.W_OK)) # and then start up the logger reload(softwarecenter.log) new_cache_dir = cache_dir + ".0" self.addCleanup(shutil.rmtree, new_cache_dir) # check that the old directory was moved aside # (renamed with a ".0" appended) self.assertTrue(os.path.exists(new_cache_dir)) self.assertFalse(os.path.exists(cache_dir + ".1")) # and check that the new directory was created and is now writable self.assertTrue(os.access(new_cache_dir, os.R_OK | os.W_OK)) if __name__ == "__main__": unittest.main() software-center-13.10/tests/disabled_test_gnomekeyring.py0000664000202700020270000000374212151440100024144 0ustar dobeydobey00000000000000import unittest import gnomekeyring as gk from gi.repository import GObject class TestGnomeKeyringUsage(unittest.TestCase): APP = "gk-test" KEYRING_NAME = "gk-test-keyring" def setUp(self): GObject.set_application_name(self.APP) def test_keyring_available(self): available = gk.is_available() self.assertTrue(available) def test_keyring_populate(self): attr = { 'token' : 'the-token', 'consumer-key' : 'the-consumer-key', 'usage' : 'software-center-agent-token', } secret = 'consumer_secret=xxx&token=xxx&consumer_key=xxx&token_secret=xxx&name=s-c' keyring_names = gk.list_keyring_names_sync() print keyring_names self.assertFalse(self.KEYRING_NAME in keyring_names) gk.create_sync(self.KEYRING_NAME, "") keyring_names = gk.list_keyring_names_sync() self.assertTrue(self.KEYRING_NAME in keyring_names) res = gk.item_create_sync(self.KEYRING_NAME, gk.ITEM_GENERIC_SECRET, "Software Center Agent token", attr, secret, True) # update if exists self.assertTrue(res) # get the token from the keyring using the 'usage' field # in the attr search_attr = { 'usage' : 'software-center-agent-token', } found = gk.find_items_sync(gk.ITEM_GENERIC_SECRET, search_attr) self.assertEqual(len(found), 1) for item in found: self.assertEqual(item.keyring, self.KEYRING_NAME) #print item.item_id, item.attributes self.assertEqual(item.secret, secret) def tearDown(self): try: gk.delete_sync(self.KEYRING_NAME) except gk.NoSuchKeyringError: pass if __name__ == "__main__": unittest.main() software-center-13.10/tests/graph/0000755000202700020270000000000012224614355017317 5ustar dobeydobey00000000000000software-center-13.10/tests/graph/plot-test-coverage.py0000775000202700020270000000107312151440100023403 0ustar dobeydobey00000000000000#!/usr/bin/python import subprocess import sys if __name__ == "__main__": # write out gnuplot f=open("test-coverage.gnuplot", "w") f.write(""" set title "Software Center Test Coverage" set xlabel "Revision Number" set ylabel "Test Coverage (%)" set term png size 800,600 set out 'software-center-test-coverage.png' set yrange [0:100] set ytics 5 set grid set size 1, 1 set origin 0, 0 plot "test-coverage.dat" using 1:2 with lines title "" """) f.close() # run it res = subprocess.call(["gnuplot", "test-coverage.gnuplot"]) sys.exit(res) software-center-13.10/tests/graph/gnuplot.plot0000664000202700020270000000121012151440100021663 0ustar dobeydobey00000000000000#set title "Startup times" set xlabel "Revision number" set ylabel "Startup time [seconds]" set term png size 1600,1200 set out 'startup-times.png' # start with y-xais on 0 set yrange [0:] set grid set multiplot # plot 1 set size 1, 0.3 set origin 0, 0.6 plot "startup-times-gnuplot.dat" using 1:2 smooth csplines \ with lines lt 3 title "Startup time trend" # plot 2 set size 1, 0.3 set origin 0, 0.3 plot "startup-times-gnuplot.dat" using 1:2:3:4 with yerrorbars linestyle 1\ title "Starteup time data (with error bars)" # plot 3 set size 1, 0.3 set origin 0, 0 plot "startup-times.dat" using 1:2 with dots\ title "Starteup time raw data" software-center-13.10/tests/graph/gen-test-coverage-data.sh0000775000202700020270000000133712151440100024072 0ustar dobeydobey00000000000000#/bin/sh LOGFILE=test-coverage.dat SUMMARY="coverage_summary" BASE_BZR=lp:software-center FIRST_BZR_REV=2570 LAST_BZR_REV=$(bzr revno $BASE_BZR) if [ ! -e "$LOGFILE" ]; then echo "# test coverage log" > $LOGFILE echo "#revno coverage" >> $LOGFILE fi i=$LAST_BZR_REV while [ $i -gt $FIRST_BZR_REV ]; do # stop if we have a copy of this already, it means we # have tested that dir already if [ -d rev-$i ]; then break fi # test the new revision bzr branch -r $i $BASE_BZR rev-$i cd rev-$i/test # run the full unit test suite make c=`tail -1 $SUMMARY | awk '{ print $NF }'` echo "$i $c" >> ../../$LOGFILE cd ../../ i=$((i-1)) done # plot it ./plot-test-coverage.py software-center-13.10/tests/graph/plot-startup-data.py0000775000202700020270000000203712151440100023245 0ustar dobeydobey00000000000000#!/usr/bin/python # numpy is love import numpy import subprocess import string import sys # get the filename if len(sys.argv) > 1: fname = sys.argv[1] else: fname = "startup-times.dat" # read the data revno_to_times = {} for line in map(string.strip, open(fname)): if line.startswith("#") or line == "": continue try: (revno, time) = line.split() time = float(time) except: #print "invalid line: '%s'" % line continue if not revno in revno_to_times: revno_to_times[revno] = [] revno_to_times[revno].append(float(time)) # generate data suitable for gnuplot outfile = open("startup-times-gnuplot.dat", "w") outfile.write("#revno average-time ymin, ymax\n") for revno, times in sorted(revno_to_times.iteritems()): #print revno, numpy.mean(times), numpy.std(times), numpy.var(times) outfile.write("%s %s %s %s\n" % ( revno, numpy.mean(times), numpy.min(times), numpy.max(times))) outfile.close() # call gnuplot subprocess.call(["gnuplot","gnuplot.plot"]) software-center-13.10/tests/graph/plot-reviews-spread.py0000775000202700020270000000216312151440100023574 0ustar dobeydobey00000000000000#!/usr/bin/python import json import subprocess import sys from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI if __name__ == "__main__": rnrclient = RatingsAndReviewsAPI() piston_review_stats = rnrclient.review_stats(origin="ubuntu") # means 1 2 3 4 5 stars histogram_total = [0, 0, 0, 0, 0] for s in piston_review_stats: histogram = json.loads(s.histogram) for i in range(5): histogram_total[i] += histogram[i] print "overall distribution: ", histogram_total # write out data file f=open("reviews-spread.dat", "w") for i in range(5): f.write("%i %i\n" % (i+1, histogram_total[i])) f.close() # write out gnuplot f=open("reviews-spread.gnuplot", "w") f.write(""" set title "Reviews spread" set xlabel "Stars" set ylabel "Nr ratings" set boxwidth 0.75 set term png size 800,600 set out 'review-spread.png' plot "reviews-spread.dat" using 1:2 with boxes fs solid 0.2 title "Star distribution" """) f.close() # run it res = subprocess.call(["gnuplot", "reviews-spread.gnuplot"]) sys.exit(res) software-center-13.10/tests/graph/gen-startup-data.sh0000775000202700020270000000160212151440100023017 0ustar dobeydobey00000000000000#/bin/sh LOGFILE=startup-times.dat BASE_BZR=lp:software-center FIRST_BZR_REV=1277 LAST_BZR_REV=$(bzr revno $BASE_BZR) if [ ! -e "$LOGFILE" ]; then echo "# statup time log" > $LOGFILE echo "#revno startup-time" >> $LOGFILE fi i=$LAST_BZR_REV while [ $i -gt $FIRST_BZR_REV ]; do # stop if we have a copy of this already, it means we # have tested that dir already if [ -d rev-$i ]; then break fi # test the new revision bzr get -r $i $BASE_BZR rev-$i cd rev-$i # first run is to warm up the cache and rebuild the DB (if needed) PYTHONPATH=. ./software-center --measure-startup-time # 3 runs with the given revision for testrun in 1 2 3 4 5; do echo -n "$i " >> ../$LOGFILE PYTHONPATH=. ./software-center --measure-startup-time >> ../$LOGFILE done cd .. i=$((i-1)) done # plot it ./plot-startup-data.py software-center-13.10/tests/test_package_info.py0000664000202700020270000000612712151440100022225 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.db.pkginfo import _Package, _Version from softwarecenter.db.pkginfo_impl.aptcache import AptCache # from softwarecenter.db.pkginfo_impl.packagekit import PackagekitInfo class TestPkgInfoAptCache(unittest.TestCase): # the backend that we want to test klass = AptCache def setUp(self): self.pkginfo = self.klass() self.pkginfo.open() def test_pkg_version(self): pkginfo = self.pkginfo pkg = pkginfo['coreutils'] self.assertTrue(isinstance(pkg, _Package)) self.assertTrue(pkg.is_installed) self.assertTrue(isinstance(pkg.installed, _Version)) self.assertTrue(isinstance(pkg.candidate, _Version)) self.assertNotEqual(len(pkg.installed.origins), 0) self.assertNotEqual(len(pkg.installed.summary), '') self.assertNotEqual(len(pkg.installed.description), '') self.assertNotEqual(pkg.candidate.size, 0) self.assertNotEqual(pkg.candidate.installed_size, 0) for v in pkg.versions: self.assertTrue(isinstance(v, _Version)) def test_pkg_info(self): pkginfo = self.pkginfo self.assertTrue(pkginfo.is_installed("coreutils")) self.assertTrue(pkginfo.is_available("bash")) self.assertTrue('GNU Bourne Again' in pkginfo.get_summary('bash')) self.assertTrue(pkginfo.get_description('bash') != '') self.assertTrue(pkginfo.get_installed("coreutils") is not None) self.assertTrue(pkginfo.get_candidate("coreutils") is not None) self.assertTrue(len(pkginfo.get_versions("coreutils")) != 0) self.assertTrue('coreutils' in pkginfo) # test getitem pkg = pkginfo['coreutils'] self.assertTrue(pkg is not None) self.assertTrue(pkg.is_installed) self.assertTrue(len(pkg.versions) != 0) self.assertEqual(pkg.website, 'http://gnu.org/software/coreutils') def test_section(self): self.assertEqual(self.pkginfo.get_section('bash'), 'shells') def test_origins(self): self.assertTrue(len(self.pkginfo.get_origins("firefox")) > 0) def test_addons(self): pkginfo = self.pkginfo self.assertTrue(len(pkginfo.get_addons("firefox")) > 0) @unittest.skip("disabled due to invalid fixture data") def test_removal(self): pkginfo = self.pkginfo pkg = pkginfo['coreutils'] self.assertTrue(len(pkginfo.get_packages_removed_on_install(pkg)) == 0) self.assertTrue(len(pkginfo.get_packages_removed_on_remove(pkg)) != 0) def test_installed_files(self): pkg = self.pkginfo['coreutils'] files = pkg.installed_files self.assertTrue('/usr/bin/whoami' in files) # FIXME: Enable packagekit tests when implemented # class TestPkgInfoPackagekit(TestPkgInfoAptCache): # klass = PackagekitInfo # # FIXME: implement this in PK as well # def test_addons(self): # pass # def test_section(self): # pass # def test_removal(self): # pass if __name__ == "__main__": unittest.main() software-center-13.10/tests/utils.py0000664000202700020270000003554212151440100017723 0ustar dobeydobey00000000000000# Copyright (C) 2011 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 import copy import os import subprocess import sys import tempfile import time from collections import defaultdict from functools import wraps from urllib2 import urlopen import xapian from gi.repository import GLib, Gtk from mock import Mock import softwarecenter.paths from softwarecenter.backend.installbackend import get_install_backend from softwarecenter.db.categories import CategoriesParser from softwarecenter.db.database import StoreDatabase from softwarecenter.db.enquire import AppEnquire from softwarecenter.db.pkginfo import get_pkg_info from softwarecenter.ui.gtk3.session.viewmanager import ( ViewManager, get_viewmanager, ) from softwarecenter.ui.gtk3.utils import get_sc_icon_theme from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper from softwarecenter.utils import get_uuid from softwarecenter.db.update import update_from_app_install_data m_dbus = m_polkit = m_aptd = None REAL_DATA_DIR = os.path.abspath(os.path.join('.', 'data')) DATA_DIR = os.path.abspath(os.path.join('.', 'tests', 'data')) UTILS_DIR = os.path.abspath(os.path.join('.', 'utils')) def url_accessable(url, needle): """Return true if needle is found in the url This is useful as a unittest.skip decorator """ f = urlopen(url) content = f.read() f.close() return needle in content def start_dbus_daemon(): proc = subprocess.Popen(["dbus-daemon", "--session", "--nofork", "--print-address"], stdout=subprocess.PIPE) dbus_address = proc.stdout.readline().strip() return proc, dbus_address def kill_process(proc): """Takes a subprocess process and kills it""" do_events_with_sleep() proc.kill() proc.wait() def start_dummy_backend(): global m_dbus, m_polkit, m_aptd # get and store address m_dbus, bus_address = start_dbus_daemon() 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, } m_polkit = subprocess.Popen( ["/usr/share/aptdaemon/tests/fake-polkitd.py", "--allowed-actions=all"], env=env) # start aptd in dummy mode m_aptd = subprocess.Popen( ["/usr/sbin/aptd", "--dummy", "--session-bus", "--disable-timeout"], env=env) # the sleep here is not ideal, but we need to wait a little bit # to ensure that the fake daemon and fake polkit is ready import dbus bus = dbus.bus.BusConnection(bus_address) for i in range(10): time.sleep(0.5) if "org.debian.apt" in bus.list_names(): break else: raise Exception("Failed to see 'org.debian.apt' on the bus") def stop_dummy_backend(): global m_dbus, m_polkit, m_aptd m_aptd.terminate() m_aptd.wait() m_polkit.terminate() m_polkit.wait() m_dbus.terminate() m_dbus.wait() def get_test_gtk3_viewmanager(): vm = get_viewmanager() if not vm: notebook = Gtk.Notebook() vm = ViewManager(notebook) vm.view_to_pane = {None: None} return vm def get_test_db(): cache = get_pkg_info() cache.open() db = StoreDatabase(softwarecenter.paths.XAPIAN_PATH, cache) db.open() return db def get_test_db_from_app_install_data(datadir): db = xapian.inmemory_open() cache = get_pkg_info() cache.open() res = update_from_app_install_data(db, cache, datadir) if res is False: raise AssertionError("Failed to build db from '%s'" % datadir) return db def get_test_install_backend(): backend = get_install_backend() return backend def get_test_gtk3_icon_cache(): icons = get_sc_icon_theme() return icons def get_test_pkg_info(): cache = get_pkg_info() cache.open() return cache def get_test_datadir(): return softwarecenter.paths.datadir def get_test_categories(db): parser = CategoriesParser(db) cats = parser.parse_applications_menu() return cats def get_test_enquirer_matches(db, query=None, limit=20, sortmode=0): if query is None: query = xapian.Query("") enquirer = AppEnquire(db._aptcache, db) enquirer.set_query(query, sortmode=sortmode, limit=limit, nonblocking_load=False) return enquirer.matches def do_events(): main_loop = GLib.main_context_default() while main_loop.pending(): main_loop.iteration() def do_events_with_sleep(iterations=5, sleep=0.1): for i in range(iterations): do_events() time.sleep(sleep) 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 """ app = copy.copy(real_app) db = get_test_db() details = app.get_details(db) details_mock = Mock(details) for a in dir(details): 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 get_mock_options(): """Return a mock suitable to act as SoftwareCenterAppGtk3's options.""" mock_options = Mock() mock_options.display_navlog = False mock_options.disable_apt_xapian_index = False mock_options.disable_buy = False return mock_options def get_mock_app_properties_helper(override_values={}): """Return a mock suitable as a AppPropertiesHelper. It can be passed a "values" dict for customization. But it will return always the same data for each "doc" document (it will not even look at doc) """ # provide some defaults values = { 'appname': 'some Appname', 'pkgname': 'apkg', 'categories': 'cat1,cat2,lolcat', 'ratings_average': 3.5, 'ratings_total': 12, 'icon': None, 'display_price': '', } # override values.update(override_values) # do it mock_property_helper = Mock(AppPropertiesHelper) mock_property_helper.get_appname.return_value = values["appname"] mock_property_helper.get_pkgname.return_value = values["pkgname"] mock_property_helper.get_categories.return_value = values["categories"] mock_property_helper.get_display_price.return_value = values[ "display_price"] mock_property_helper.db = Mock() mock_property_helper.db._aptcache = FakedCache() mock_property_helper.db.get_pkgname.return_value = values["pkgname"] mock_property_helper.db.get_appname.return_value = values["appname"] mock_ratings = Mock() mock_ratings.ratings_average = values["ratings_average"] mock_ratings.ratings_total = values["ratings_total"] mock_property_helper.get_review_stats.return_value = mock_ratings mock_property_helper.get_icon_at_size.return_value = values["icon"] mock_property_helper.icons = Mock() mock_property_helper.icons.load_icon.return_value = values["icon"] return mock_property_helper def setup_test_env(): """ Setup environment suitable for running the test/* code in a checkout. This includes PYTHONPATH, sys.path and softwarecenter.paths.datadir. """ basedir = os.path.dirname(__file__) while not os.path.exists( os.path.join(basedir, "softwarecenter", "__init__.py")): basedir = os.path.abspath(os.path.join(basedir, "..")) #print basedir, __file__, os.path.realpath(__file__) sys.path.insert(0, basedir) os.environ["PYTHONPATH"] = basedir softwarecenter.paths.datadir = os.path.join(basedir, "data") softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR = tempfile.mkdtemp() # factory stuff for the agent def make_software_center_agent_app_dict(override_dict={}): app_dict = { 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 " u"social networking feeds, pictures on ...", 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'license': 'Proprietary', u'support_url': 'mailto:support@example.com', 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'categories': 'Game;LogicGame', } app_dict.update(override_dict) return app_dict def make_software_center_agent_subscription_dict(app_dict): subscription_dict = { u'application': app_dict, u'deb_line': u'deb https://some.user:ABCDEFGHIJKLMNOP@' u'private-ppa.launchpad.net/commercial-ppa-uploaders/' u'photobomb/ubuntu natty main', u'distro_series': {u'code_name': u'natty', u'version': u'11.04'}, u'failures': [], u'open_id': u'https://login.ubuntu.com/+id/ABCDEF', u'purchase_date': u'2011-09-16 06:37:52', u'purchase_price': u'2.99', u'state': u'Complete', } 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'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(): recommender_uuid = get_uuid() profile_upload_data = [ { 'uuid': recommender_uuid, 'package_list': [ u'clementine', u'hedgewars', u'mangler', u'nexuiz', u'fgo', u'musique', u'pybik', u'radiotray', u'cherrytree', 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'} return recommend_app_data def patch_datadir(newdir): def middle(f): @wraps(f) def inner(*args, **kwars): original = softwarecenter.paths.datadir softwarecenter.paths.datadir = newdir try: result = f(*args, **kwars) finally: softwarecenter.paths.datadir = original return result return inner return middle class ObjectWithSignals(object): """A faked object that you can connect to and emit signals.""" def __init__(self, *a, **kw): super(ObjectWithSignals, self).__init__() self._callbacks = defaultdict(list) def connect(self, signal, callback): """Connect a signal with a callback.""" self._callbacks[signal].append(callback) def disconnect(self, signal, callback): """Connect a signal with a callback.""" self._callbacks[signal].remove(callback) if len(self._callbacks[signal]) == 0: self._callbacks.pop(signal) def disconnect_by_func(self, callback): """Disconnect 'callback' from every signal.""" # do not use iteritems since we may change the dict inside the for for signal, callbacks in self._callbacks.items(): if callback in callbacks: self.disconnect(signal, callback) def emit(self, signal, *args, **kwargs): """Emit 'signal' passing *args, **kwargs to every callback.""" for callback in self._callbacks[signal]: callback(*args, **kwargs) class FakedCache(ObjectWithSignals, dict): """A faked cache.""" def __init__(self, *a, **kw): super(FakedCache, self).__init__() self.ready = False def open(self): """Open this cache.""" self.ready = True def component_available(self, distro_codename, component): """Return whether 'component' is available in 'distro_codename'.""" def get_addons(self, pkgname): """Return (recommended, suggested) addons for 'pkgname'.""" return ([], []) def query_total_size_on_install(self, pkgname, addons_to_install, addons_to_remove, archive_suite): """Emit a fake signal "query-total-size-on-install-done" """ self.emit("query-total-size-on-install-done", None, "", 0, 0) def get_packages_removed_on_remove(self, pkgname): return [] software-center-13.10/tests/test_zeitgeist_logger.py0000644000202700020270000000731112216063163023173 0ustar dobeydobey00000000000000import unittest from mock import patch from softwarecenter.distro import get_distro from softwarecenter.backend.zeitgeist_logger import ZeitgeistLogger from tests.utils import setup_test_env setup_test_env() class TestZeitgeistLogger(unittest.TestCase): """ tests the zeitgeist logger """ def setUp(self): from softwarecenter.backend import zeitgeist_logger if not zeitgeist_logger.HAVE_MODULE: self.skipTest("Zeitgeist module missing, impossible to test") from zeitgeist import datamodel self.distro = get_distro() self.logger = ZeitgeistLogger(self.distro) self.datamodel = datamodel def _verify_event(self, event): self.assertEqual(event.actor, "application://" + self.distro.get_app_id() + ".desktop") self.assertEqual(event.manifestation, self.datamodel.Manifestation.EVENT_MANIFESTATION.USER_ACTIVITY) def _verify_subject(self, subject, desktop): self.assertEqual(subject.interpretation, self.datamodel.Interpretation.SOFTWARE) self.assertEqual(subject.manifestation, self.datamodel.Manifestation.SOFTWARE_ITEM) self.assertEqual(subject.uri, "application://" + desktop) self.assertEqual(subject.current_uri, subject.uri) self.assertEqual(subject.mimetype, "application/x-desktop") def test_construction(self): self.assertEqual(self.logger.distro, self.distro) @patch("zeitgeist.client.ZeitgeistClient.insert_event") def test_log_install_event(self, mock_insert_event): test_desktop = "software-center.desktop" self.assertTrue(self.logger.log_install_event(test_desktop)) self.assertTrue(mock_insert_event.called) self.assertEqual(mock_insert_event.call_count, 2) [event] = mock_insert_event.call_args_list[0][0] self._verify_event(event) self.assertEqual(event.interpretation, self.datamodel.Interpretation.EVENT_INTERPRETATION.CREATE_EVENT) self.assertEqual(len(event.subjects), 1) self._verify_subject(event.subjects[0], test_desktop) [event] = mock_insert_event.call_args_list[1][0] self._verify_event(event) self.assertEqual(event.interpretation, self.datamodel.Interpretation.EVENT_INTERPRETATION.ACCESS_EVENT) self.assertEqual(len(event.subjects), 1) self._verify_subject(event.subjects[0], test_desktop) @patch("zeitgeist.client.ZeitgeistClient.insert_event") def test_log_install_event_invalid_desktop(self, mock_insert_event): self.assertFalse(self.logger.log_install_event("")) self.assertFalse(mock_insert_event.called) @patch("zeitgeist.client.ZeitgeistClient.insert_event") def test_log_uninstall_event(self, mock_insert_event): test_desktop = "software-center.desktop" self.assertTrue(self.logger.log_uninstall_event(test_desktop)) self.assertTrue(mock_insert_event.called) self.assertEqual(mock_insert_event.call_count, 1) [event] = mock_insert_event.call_args_list[0][0] self._verify_event(event) self.assertEqual(event.interpretation, self.datamodel.Interpretation.EVENT_INTERPRETATION.DELETE_EVENT) self.assertEqual(len(event.subjects), 1) self._verify_subject(event.subjects[0], test_desktop) @patch("zeitgeist.client.ZeitgeistClient.insert_event") def test_log_uninstall_event_invalid_desktop(self, mock_insert_event): self.assertFalse(self.logger.log_uninstall_event("")) self.assertFalse(mock_insert_event.called) if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) unittest.main() software-center-13.10/tests/test_gwibber.py0000664000202700020270000000440112151440100021231 0ustar dobeydobey00000000000000import os import unittest Gwibber = None try: from gi.repository import Gwibber except ImportError: pass from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.gwibber_helper import GwibberHelper, GwibberHelperMock NOT_DEFINED = object() @unittest.skipIf(Gwibber is None, "Please install the gwibber gir bindings to run this test case.") class TestGwibber(unittest.TestCase): """Tests the "where is it in the menu" code.""" patch_vars = (("SOFTWARE_CENTER_GWIBBER_MOCK_USERS", "2"), ("SOFTWARE_CENTER_GWIBBER_MOCK_NO_FAIL", "1")) def setUp(self): for env_var, value in self.patch_vars: real = os.environ.get(env_var, NOT_DEFINED) if real is NOT_DEFINED: self.addCleanup(os.environ.pop, env_var) else: self.addCleanup(os.environ.__setitem__, env_var, real) os.environ[env_var] = value def test_gwibber_helper_mock(self): gh = GwibberHelperMock() accounts = gh.accounts() self.assertEqual(len(accounts), 2) #print accounts # we can not test the real gwibber here, otherwise it will # post our test data to real services self.assertEqual(gh.send_message ("test"), True) def test_gwibber_helper(self): # readonly test as there maybe be real accounts gh = GwibberHelper() have_accounts = gh.has_accounts_in_sqlite() self.assertTrue(isinstance(have_accounts, bool)) accounts = gh.accounts() self.assertTrue(isinstance(accounts, list)) @unittest.skip('not_working_because_gi_does_not_provide_list_test_gwibber') def test_gwibber_send_message(self): service = Gwibber.Service() self.addCleanup(service.quit) # get account data accounts = Gwibber.Accounts() # print dir(accounts) self.assertTrue(len(accounts.list()) > 0) # check single account for send enabled, only do if "True" #print accounts.send_enabled(accounts.list[0]) # first check gwibber available service = Gwibber.Service() # print dir(service) service.service_available(False) service.send_message("test") if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_utils.py0000664000202700020270000003617212216063163020776 0ustar dobeydobey00000000000000import datetime import glob import multiprocessing import os import stat import subprocess import shutil import tempfile import time import unittest from tempfile import mkdtemp, NamedTemporaryFile from mock import patch from tests.utils import ( do_events, get_test_gtk3_icon_cache, setup_test_env, UTILS_DIR, DATA_DIR, ) setup_test_env() from softwarecenter.expunge import ExpungeCache from softwarecenter.utils import ( capitalize_first_word, clear_token_from_ubuntu_sso_sync, decode_xml_char_reference, ensure_file_writable_and_delete_if_not, get_file_path_from_iconname, get_http_proxy_string_from_libproxy, get_http_proxy_string_from_gsettings, get_nice_date_string, get_oem_channel_descriptor, get_title_from_html, get_uuid, get_recommender_uuid, get_desktop_id, is_no_display_desktop_file, convert_desktop_file_to_installed_location, make_string_from_list, release_filename_in_lists_from_deb_line, safe_makedirs, split_icon_ext, TraceMemoryUsage, ) EXPUNGE_SCRIPT = os.path.join(UTILS_DIR, 'expunge-cache.py') class TestSCUtils(unittest.TestCase): """ tests the sc utils """ def test_encode(self): xml = "What’s New" python = u"What\u2019s New" self.assertEqual(decode_xml_char_reference(xml), python) # fails currently #self.assertEqual(encode_for_xml(python), xml) def test_lists_filename(self): debline = "deb http://foo:pass@security.ubuntu.com/ubuntu maverick-security main restricted" self.assertEqual(release_filename_in_lists_from_deb_line(debline), "security.ubuntu.com_ubuntu_dists_maverick-security_Release") def test_get_http_proxy_from_gsettings(self): # FIXME: do something more meaningful here once I figured out # how to create a private fake gsettings proxy = get_http_proxy_string_from_gsettings() self.assertTrue(type(proxy) in [type(None), type("")]) # disabled, we don't use libproxy currently, its really rather # out of date def disabled_test_get_http_proxy_from_libproxy(self): # test url url = "http://archive.ubuntu.com" # ensure we look at environment first os.environ["PX_CONFIG_ORDER"] = "config_envvar" # normal proxy os.environ["http_proxy"] = "http://localhost:3128/" proxy = get_http_proxy_string_from_libproxy(url) self.assertEqual(proxy, "http://localhost:3128/") # direct os.environ["http_proxy"] = "" proxy = get_http_proxy_string_from_libproxy(url) self.assertEqual(proxy, "") # user/pass os.environ["http_proxy"] = "http://user:pass@localhost:3128/" proxy = get_http_proxy_string_from_libproxy(url) self.assertEqual(proxy, "http://user:pass@localhost:3128/") def test_get_title_from_html(self): html = """ Title & text

header1

""" # get the title from the html self.assertEqual(get_title_from_html(html), "Title & text") # fallback to the first h1 if there is no title html = "

foo >

bar

" self.assertEqual(get_title_from_html(html), "foo >") # broken html = "dsf" self.assertEqual(get_title_from_html(html), "") # not supported to have sub-html tags in the extractor html = "

foo bar

" self.assertEqual(get_title_from_html(html), "") html = "

foo bar x

some text

" self.assertEqual(get_title_from_html(html), "") def test_no_display_desktop_file(self): d = "/usr/share/app-install/desktop/wine1.4:wine.desktop" self.assertTrue(is_no_display_desktop_file(d)) d = "/usr/share/app-install/desktop/software-center:ubuntu-software-center.desktop" self.assertFalse(is_no_display_desktop_file(d)) def test_get_desktop_id(self): self.assertEqual(get_desktop_id("/usr/share/applications/ubuntu-software-center.desktop"), "ubuntu-software-center.desktop") self.assertEqual(get_desktop_id("/usr/share/applications/kde4-anyapp.desktop"), "kde4-anyapp.desktop") self.assertEqual(get_desktop_id("/usr/share/applications/kde4/anyapp.desktop"), "kde4-anyapp.desktop") self.assertEqual(get_desktop_id("/my/own/path/application.desktop"), "/my/own/path/application.desktop") def test_split_icon_ext(self): for unchanged in ["foo.bar.baz", "foo.bar", "foo", "foo.pngx", "foo.png.xxx"]: self.assertEqual(split_icon_ext(unchanged), unchanged) for changed in ["foo.png", "foo.tiff", "foo.jpg", "foo.jpeg"]: self.assertEqual(split_icon_ext(changed), os.path.splitext(changed)[0]) def test_get_nice_date_string(self): now = datetime.datetime.utcnow() ten_secs_ago = now + datetime.timedelta(seconds=-10) self.assertEqual(get_nice_date_string(ten_secs_ago), 'a few minutes ago') two_mins_ago = now + datetime.timedelta(minutes=-2) self.assertEqual(get_nice_date_string(two_mins_ago), '2 minutes ago') under_a_day = now + datetime.timedelta(hours=-23, minutes=-59, seconds=-59) self.assertEqual(get_nice_date_string(under_a_day), '23 hours ago') under_a_week = now + datetime.timedelta(days=-4, hours=-23, minutes=-59, seconds=-59) self.assertEqual(get_nice_date_string(under_a_week), '4 days ago') over_a_week = now + datetime.timedelta(days=-7) self.assertEqual(get_nice_date_string(over_a_week), over_a_week.isoformat().split('T')[0]) def test_get_uuid(self): uuid = get_uuid() self.assertTrue(uuid and len(uuid) > 0) def test_get_recommender_uuid(self): uuid = get_recommender_uuid() self.assertTrue("-" not in uuid and len(uuid) > 0) def test_clear_credentials(self): clear_token_from_ubuntu_sso_sync("fo") do_events() def test_make_string_from_list(self): base = "There was a problem posting this review to %s (omg!)" # test the various forms l = ["twister"] self.assertEqual( make_string_from_list(base, l), "There was a problem posting this review to twister (omg!)") # two l = ["twister", "factbook"] self.assertEqual( make_string_from_list(base, l), "There was a problem posting this review to twister and factbook (omg!)") # three l = ["twister", "factbook", "identi.catz"] self.assertEqual( make_string_from_list(base, l), "There was a problem posting this review to twister, factbook and identi.catz (omg!)") # four l = ["twister", "factbook", "identi.catz", "baz"] self.assertEqual( make_string_from_list(base, l), "There was a problem posting this review to twister, factbook, identi.catz and baz (omg!)") def test_capitalize_first_word(self): test_synopsis = "feature-rich console based todo list manager" capitalized = capitalize_first_word(test_synopsis) self.assertTrue( capitalized == "Feature-rich console based todo list manager") test_synopsis = "MPlayer's Movie Encoder" capitalized = capitalize_first_word(test_synopsis) self.assertTrue( capitalized == "MPlayer's Movie Encoder") # ensure it does not crash for empty strings, LP: #1002271 self.assertEqual(capitalize_first_word(""), "") @unittest.skipIf( os.getuid() == 0, "fails when run as root as os.access() is always successful") def test_ensure_file_writable_and_delete_if_not(self): # first test that a non-writable file (0400) is deleted test_file_not_writeable = NamedTemporaryFile() os.chmod(test_file_not_writeable.name, stat.S_IRUSR) self.assertFalse(os.access(test_file_not_writeable.name, os.W_OK)) ensure_file_writable_and_delete_if_not(test_file_not_writeable.name) self.assertFalse(os.path.exists(test_file_not_writeable.name)) # then test that a writable file (0600) is not deleted test_file_writeable = NamedTemporaryFile() os.chmod(test_file_writeable.name, stat.S_IRUSR|stat.S_IWUSR) self.assertTrue(os.access(test_file_writeable.name, os.W_OK)) ensure_file_writable_and_delete_if_not(test_file_writeable.name) self.assertTrue(os.path.exists(test_file_writeable.name)) @unittest.skipIf( os.getuid() == 0, "fails when run as root as os.chmod() is always successful") def test_safe_makedirs(self): tmp = mkdtemp() # create base dir target = os.path.join(tmp, "foo", "bar") safe_makedirs(target) # we need the patch to ensure that the code is actually executed with patch("os.path.exists") as mock_: mock_.return_value = False self.assertTrue(os.path.isdir(target)) # ensure that creating the base dir again does not crash safe_makedirs(target) self.assertTrue(os.path.isdir(target)) # ensure we still get regular errors like permission denied # (stat.S_IRUSR) os.chmod(os.path.join(tmp, "foo"), 0400) self.assertRaises(OSError, safe_makedirs, target) # set back to stat.(S_IRUSR|S_IWUSR|S_IXUSR) to make rmtree work os.chmod(os.path.join(tmp, "foo"), 0700) # cleanup shutil.rmtree(tmp) def test_get_file_path_from_iconname(self): icons = get_test_gtk3_icon_cache() icon_path = get_file_path_from_iconname( icons, "softwarecenter") self.assertEqual( icon_path, "/usr/share/icons/hicolor/48x48/apps/softwarecenter.svg") class TestExpungeCache(unittest.TestCase): def test_expunge_cache(self): dirname = tempfile.mkdtemp('s-c-testsuite') for name, content in [ ("foo-301", "status: 301"), ("foo-200", "status: 200"), ("foo-random", "random"), ]: fullpath = os.path.join(dirname, name) open(fullpath, "w").write(content) # set to 1970+1s time to ensure the cleaner finds it os.utime(fullpath, (1,1)) res = subprocess.call([EXPUNGE_SCRIPT, dirname]) # no arguments self.assertEqual(res, 1) # by status res = subprocess.call([EXPUNGE_SCRIPT, "--debug", "--by-unsuccessful-http-states", dirname]) self.assertFalse(os.path.exists(os.path.join(dirname, "foo-301"))) self.assertTrue(os.path.exists(os.path.join(dirname, "foo-200"))) self.assertTrue(os.path.exists(os.path.join(dirname, "foo-random"))) # by time res = subprocess.call([EXPUNGE_SCRIPT, "--debug", "--by-days", "1", dirname]) # now we expect the old file to be gone but the unknown one not to # be touched self.assertFalse(os.path.exists(os.path.join(dirname, "foo-200"))) self.assertTrue(os.path.exists(os.path.join(dirname, "foo-random"))) def test_expunge_cache_lock(self): def set_marker(d): time.sleep(0.5) target = os.path.join(d, "marker.%s." % os.getpid()) open(target, "w") tmpdir = tempfile.mkdtemp() # create two ExpungeCache processes e1 = ExpungeCache([tmpdir], by_days=0, by_unsuccessful_http_states=True) e1._cleanup_dir = set_marker e2 = ExpungeCache([tmpdir], by_days=0, by_unsuccessful_http_states=True) e2._cleanup_dir = set_marker t1 = multiprocessing.Process(target=e1.clean) t1.start() t2 = multiprocessing.Process(target=e2.clean) t2.start() # wait for finish t1.join() t2.join() # ensure that the second one was not called self.assertEqual(len(glob.glob(os.path.join(tmpdir, "marker.*"))), 1) def test_oem_channel_extractor(self): s = get_oem_channel_descriptor("./tests/data/ubuntu_dist_channel") self.assertEqual(s, "canonical-oem-watauga-precise-amd64-20120517-2") def test_memory_growth(self): with TraceMemoryUsage("list append"): l=[] for i in range(64*1024): l.append(i) def test_save_person_to_config(self): from softwarecenter.utils import ( save_person_to_config, get_person_from_config) save_person_to_config("meep") self.assertEqual(get_person_from_config(), "meep") class DesktopFilepathConversionTestCase(unittest.TestCase): def test_normal(self): # test 'normal' case app_install_desktop_path = os.path.join(DATA_DIR, "app-install", "desktop", "deja-dup:deja-dup.desktop") installed_desktop_path = convert_desktop_file_to_installed_location( app_install_desktop_path, "deja-dup") self.assertEqual(installed_desktop_path, os.path.join(DATA_DIR, "applications", "deja-dup.desktop")) def test_encoded_subdir(self): # test encoded subdirectory case, e.g. e.g. kde4_soundkonverter.desktop app_install_desktop_path = os.path.join(DATA_DIR, "app-install", "desktop", "soundkonverter:kde4__soundkonverter.desktop") installed_desktop_path = convert_desktop_file_to_installed_location( app_install_desktop_path, "soundkonverter") self.assertEqual(installed_desktop_path, os.path.join(DATA_DIR, "applications", "kde4", "soundkonverter.desktop")) def test_purchase_via_software_center_agent(self): # test the for-purchase case (uses "software-center-agent" as its # appdetails.desktop_file value) # FIXME: this will only work if update-manager is installed app_install_desktop_path = "software-center-agent" installed_desktop_path = convert_desktop_file_to_installed_location( app_install_desktop_path, "update-manager") self.assertEqual(installed_desktop_path, "/usr/share/applications/update-manager.desktop") def test_no_value(self): # test case where we don't have a value for app_install_desktop_path # (e.g. for a local .deb install, see bug LP: #768158) installed_desktop_path = convert_desktop_file_to_installed_location( None, "update-manager") # FIXME: this will only work if update-manager is installed self.assertEqual(installed_desktop_path, "/usr/share/applications/update-manager.desktop") if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_xapian_query.py0000664000202700020270000000461712151440100022326 0ustar dobeydobey00000000000000import sys import xapian from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.enums import XapianValues import softwarecenter.paths # this is not a test as such, more a example of how xapian search # work and useful features around them if __name__ == "__main__": if len(sys.argv) > 1: search_term = sys.argv[1] else: search_term = "app" db = xapian.Database(softwarecenter.paths.XAPIAN_PATH) parser = xapian.QueryParser() #parser.set_stemmer(xapian.Stem("english")) #parser.set_stemming_strategy(xapian.QueryParser.STEM_ALL) parser.set_database(db) #parser.add_prefix("pkg", "AP") query = parser.parse_query(search_term, xapian.QueryParser.FLAG_PARTIAL| xapian.QueryParser.FLAG_WILDCARD) enquire = xapian.Enquire(db) enquire.set_sort_by_value_then_relevance(XapianValues.POPCON) enquire.set_query(query) matches = enquire.get_mset(0, db.get_doccount()) print "Matches:" for m in matches: doc = m.document popcon = doc.get_value(XapianValues.POPCON) print doc.get_data(), "popcon:", xapian.sortable_unserialise(popcon) #for t in doc.termlist(): # print "'%s': %s (%s); " % (t.term, t.wdf, t.termfreq), #print "\n" appname = doc.get_data() # calculate a eset print "ESet:" rset = xapian.RSet() for m in matches: rset.add_document(m.docid) for m in enquire.get_eset(10, rset): print m.term # calulate the expansions completions = [] for i, m in enumerate(db.allterms(search_term)): completions.append("AP"+m.term) completions.append(m.term) if i > 10: break expansion = xapian.Query(xapian.Query.OP_OR, completions) enquire.set_query(xapian.Query(xapian.Query.OP_OR, query, expansion)) matches = enquire.get_mset(0, 10) print "\n\nExpanded Matches:" for m in matches: doc = m.document print doc.get_data() appname = doc.get_data() # popular print print "Popular: " query = xapian.Query(xapian.Query.OP_VALUE_GE, XapianValues.POPCON, "100000") enquire.set_query(query) matches = enquire.get_mset(0, 10) for m in matches: doc = m.document print doc.get_data() appname = doc.get_data() software-center-13.10/tests/create_transactions.py0000664000202700020270000000220312151440100022602 0ustar dobeydobey00000000000000#!/usr/bin/python from gi.repository import GLib import time from aptdaemon.client import AptClient from aptdaemon.gtkwidgets import AptProgressDialog # run with terminal and progress WITH_GUI=True MAX_ACTIVE=1 active = 0 def exit_handler(trans, enum): global active active -= 1 return True def run(t): if WITH_GUI: dia = AptProgressDialog(t) dia.run() dia.destroy() else: t.run() if __name__ == "__main__": #logging.basicConfig(level=logging.DEBUG) context = GLib.main_context_default() c = AptClient() for i in range(100): print "inst: 3dchess" t = c.install_packages(["3dchess"], exit_handler=exit_handler) run(t) active += 1 print "inst: 2vcard" t = c.install_packages(["2vcard"], exit_handler=exit_handler) run(t) active += 1 print "rm: 3dchess 2vcard" t = c.remove_packages(["3dchess","2vcard"], exit_handler=exit_handler) run(t) while active > MAX_ACTIVE: while context.pending(): context.iteration() time.sleep(1) software-center-13.10/tests/test_startup.py0000664000202700020270000000444212151440100021317 0ustar dobeydobey00000000000000import pickle import os import subprocess import time import unittest from tests.utils import ( setup_test_env, ) setup_test_env() # FIXME: # - need proper fixtures for history and lists # - needs stats about cold/warm disk cache class SCTestGUI(unittest.TestCase): def setUp(self): if os.path.exists("revno_to_times_list.p"): self.revno_to_times_list = pickle.load(open("revno_to_times_list.p")) else: self.revno_to_times_list = {} def tearDown(self): pickle.dump( self.revno_to_times_list, open("revno_to_times_list.p", "w")) # FIXME: debug why this sometimes hangs def disabled_for_now_untiL_hang_is_found_test_startup_time(self): for i in range(5): time_to_visible = self.create_ui_and_return_time_to_visible() self.record_test_run_data(time_to_visible) print self.revno_to_times_list def create_ui_and_return_time_to_visible(self): now = time.time() # we get the time on stdout and detailed stats on stderr p = subprocess.Popen(["./software-center", "--measure-startup-time"], cwd="..", stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.wait() # this is the time with the python statup overhead time_with_launching_python = time.time() - now # IMPORTANT: this read() needs to be outside of the timing stats, # it takes 2s (!?!) on my 3Ghz machine stdoutput = p.stdout.read() profile_data = p.stderr.read() # this is the time spend inside python time_inside_python = stdoutput.strip().split("\n")[-1] # for testing print "time inside_python: ", time_inside_python print "total with launching python: ", time_with_launching_python print profile_data print return time_with_launching_python def record_test_run_data(self, time_to_visible): # gather stats revno = subprocess.Popen( ["bzr","revno"], stdout=subprocess.PIPE).communicate()[0].strip() times_list = self.revno_to_times_list.get(revno, []) times_list.append(time_to_visible) self.revno_to_times_list[revno] = times_list if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_login_backend.py0000664000202700020270000000261712151440100022376 0ustar dobeydobey00000000000000import os import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.backend.login import get_login_backend from softwarecenter.backend.login_impl.login_sso import ( LoginBackendDbusSSO) from softwarecenter.backend.login_impl.login_fake import ( LoginBackendDbusSSOFake, ) class TestLoginBackend(unittest.TestCase): """ tests the login backend stuff """ def test_fake_and_real_provide_similar_methods(self): """ test if the real and fake login provide the same functions """ login_real = LoginBackendDbusSSO login_fake = LoginBackendDbusSSOFake # ensure that both fake and real implement the same methods self.assertEqual( set([x for x in dir(login_real) if not x.startswith("_")]), set([x for x in dir(login_fake) if not x.startswith("_")])) def test_get_login_backend(self): # test that we get the real one self.assertEqual(type(get_login_backend(None, None, None)), LoginBackendDbusSSO) # test that we get the fake one os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] = "1" self.assertEqual(type(get_login_backend(None, None, None)), LoginBackendDbusSSOFake) # clean the environment del os.environ["SOFTWARE_CENTER_FAKE_REVIEW_API"] if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_purchase_backend.py0000664000202700020270000000525512151440100023101 0ustar dobeydobey00000000000000import os import unittest from gi.repository import GLib from mock import Mock from tests.utils import ( do_events_with_sleep, setup_test_env, ) setup_test_env() from softwarecenter.db.application import Application from softwarecenter.backend.installbackend import get_install_backend class TestPurchaseBackend(unittest.TestCase): PKGNAME = "hello-license-key-x" LICENSE_KEY = "license-key-data" # this must match the binary deb (XB-LicenseKeyPath) LICENSE_KEY_PATH = "/opt/hello-license-key-x/mykey.txt" def test_add_license_key_backend(self): self._finished = False # add repo deb_line = "deb https://mvo:nopassyet@private-ppa.launchpad.net/canonical-isd-hackers/internal-qa/ubuntu oneiric main" signing_key_id = "F5410BE0" app = Application("Test app1", self.PKGNAME) # install only when runnig as root, as we require polkit promtps # otherwise # FIXME: provide InstallBackendSimulate() if os.getuid() == 0: backend = get_install_backend() backend.ui = Mock() backend.connect("transaction-finished", self._on_transaction_finished) # simulate repos becomes available for the public 20 s later GLib.timeout_add_seconds(20, self._add_pw_to_commercial_repo) # run it backend.add_repo_add_key_and_install_app(deb_line, signing_key_id, app, "icon", self.LICENSE_KEY) # wait until the pkg is installed while not self._finished: do_events_with_sleep() if os.getuid() == 0: self.assertTrue(os.path.exists(self.LICENSE_KEY_PATH)) self.assertEqual(open(self.LICENSE_KEY_PATH).read(), self.LICENSE_KEY) #time.sleep(10) def _add_pw_to_commercial_repo(self): print "making pw available now" path="/etc/apt/sources.list.d/private-ppa.launchpad.net_canonical-isd-hackers_internal-qa_ubuntu.list" content= open(path).read() passw = os.environ.get("SC_PASS") or "pass" content = content.replace("nopassyet", passw) open(path, "w").write(content) def _on_transaction_finished(self, backend, result): print "_on_transaction_finished", result.pkgname, result.success if not result.pkgname: return print "done", result.pkgname self._finished = True self.assertTrue(result.success) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_htmlize.py0000664000202700020270000000672412151440100021276 0ustar dobeydobey00000000000000from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.utils import htmlize_package_description #file-roller d1 = """ 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. * Extract files from the archive. File-roller supports the following formats: * Tar (.tar) archives, including those compressed with gzip (.tar.gz, .tgz), bzip (.tar.bz, .tbz), bzip2 (.tar.bz2, .tbz2), compress (.tar.Z, .taz), lzip (.tar.lz, .tlz), lzop (.tar.lzo, .tzo), lzma (.tar.lzma) and xz (.tar.xz) * Zip archives (.zip) * Jar archives (.jar, .ear, .war) * 7z archives (.7z) * iso9660 CD images (.iso) * 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. """ #drgeo d2 = """ This is the Gtk interactive geometry software. It allows one to create geometric figure plus the interactive manipulation of such figure in respect with their geometric constraints. It is usable in teaching situation with students from primary or secondary level. Dr. Geo comes with a complete set of tools arranged in different categories: * points * lines * geometric transformations * numeric function * macro-construction * DGS object - Dr. Geo Guile Script * DSF - Dr Geo Scheme Figure, it is interactive figure defined in a file and evaluated with the embedded Scheme interpretor, awesome! * Export facilities in the LaTeX and EPS formats Several figures and macro-constructions examples are available in the /usr/share/drgeo/examples folder. More information about Dr. Geo can be found at its web site http://www.gnu.org/software/dr_geo/dr_geo.html Installing the drgeo-doc package is also encouraged to get more of Dr. Geo. """ # totem d3 = """ Totem is a simple yet featureful media player for GNOME which can read a large number of file formats. It features : . * Shoutcast, m3u, asx, SMIL and ra playlists support * DVD (with menus), VCD and Digital CD (with CDDB) playback * TV-Out configuration with optional resolution switching * 4.0, 5.0, 5.1 and stereo audio output * Full-screen mode (move your mouse and you get nice controls) with Xinerama, dual-head and RandR support * Aspect ratio toggling, scaling based on the video's original size * Full keyboard control * Simple playlist with repeat mode and saving feature * GNOME, Nautilus and GIO integration * Screenshot of the current movie * Brightness and Contrast control * Visualisation plugin when playing audio-only files * Video thumbnailer for nautilus * Nautilus properties page * Works on remote displays * DVD, VCD and OGG/OGM subtitles with automatic language selection * Extensible with plugins """ import lxml.html import lxml.etree import unittest class TestHtmlize(unittest.TestCase): def test_htmlize(self): for decr in [d1, d2, d3]: html_descr = htmlize_package_description(decr) #print html_descr #element = lxml.html.document_fromstring(html_descr) star_count = decr.count("*") root = lxml.etree.XML("%s" % html_descr) li_count = len(root.findall(".//li")) self.assertEqual(star_count, li_count) if __name__ == "__main__": unittest.main() software-center-13.10/tests/disabled_test_dataprovider.py0000664000202700020270000001640612151440100024133 0ustar dobeydobey00000000000000import dbus import os import subprocess import time import unittest from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) from mock import ( Mock, patch, ) from tests.utils import ( kill_process, setup_test_env, start_dbus_daemon, ) setup_test_env() from softwarecenter.db.application import AppDetails from softwarecenter.db.dataprovider import ( SoftwareCenterDataProvider, DBUS_BUS_NAME, DBUS_DATA_PROVIDER_IFACE, DBUS_DATA_PROVIDER_PATH, ) def start_data_provider_daemon(dbus_address): """Start the dbus data provider as a subprocess""" os.environ["DBUS_SESSION_BUS_ADDRESS"] = dbus_address testdir = os.path.dirname(__file__) basedir = os.path.abspath(os.path.join(testdir, "..")) data_provider_bin = os.path.join(basedir, "software-center-dbus") stderr = open(os.devnull, "w") # uncomment this for a flurry of debug output #stderr = None # this is more reliable than e.g. threading.Thead or multiprocess.Process p = subprocess.Popen([data_provider_bin], stderr=stderr) return p class DbusForRealTestCase(unittest.TestCase): """Test the dataprovider over a real dbus bus""" @classmethod def setUpClass(cls): cls.dbus_daemon_proc, dbus_address = start_dbus_daemon() cls.bus = dbus.bus.BusConnection(dbus_address) cls.data_provider_proc = start_data_provider_daemon(dbus_address) time.sleep(1) @classmethod def tearDownClass(cls): kill_process(cls.data_provider_proc) kill_process(cls.dbus_daemon_proc) def setUp(self): obj = self.bus.get_object(bus_name=DBUS_BUS_NAME, object_path=DBUS_DATA_PROVIDER_PATH, follow_name_owner_changes=True) self.proxy = dbus.Interface(object=obj, dbus_interface=DBUS_DATA_PROVIDER_IFACE) def test_dbus_on_real_bus(self): result = self.proxy.GetAppDetails("", "gimp") self.assertEqual(result["pkgname"], "gimp") self.assertEqual(result["icon"], "gimp") class PropertyDictExceptionsTestCase(unittest.TestCase): """Test that the exceptions in the AppDetails for the dbus properties are handled correctly """ def setUp(self): self.mock_app_details = Mock(AppDetails) def test_simple(self): self.mock_app_details.name = "fake-app" properties = AppDetails.as_dbus_property_dict(self.mock_app_details) self.assertEqual(properties["name"], "fake-app") def test_empty_dict_set(self): self.mock_app_details.empty_set = set() self.mock_app_details.empty_dict = {} properties = AppDetails.as_dbus_property_dict(self.mock_app_details) self.assertEqual(properties["empty_set"], "") self.assertEqual(properties["empty_dict"], "") def test_normal_dict(self): self.mock_app_details.non_empty_dict = { "moo" : "bar" } properties = AppDetails.as_dbus_property_dict(self.mock_app_details) self.assertEqual(properties["non_empty_dict"], { "moo" : "bar" }) def test_normal_set(self): self.mock_app_details.non_empty_set = set(["foo", "bar", "baz"]) properties = AppDetails.as_dbus_property_dict(self.mock_app_details) self.assertEqual( sorted(properties["non_empty_set"]), ["bar", "baz", "foo"]) class DataProviderTestCase(unittest.TestCase): """ Test the methods of the dataprovider """ @classmethod def setUpClass(cls): cls.proc, dbus_address = start_dbus_daemon() cls.bus = dbus.bus.BusConnection(dbus_address) bus_name = dbus.service.BusName( 'com.ubuntu.SoftwareCenterDataProvider', cls.bus) # get the provider cls.provider = SoftwareCenterDataProvider(bus_name) @classmethod def tearDownClass(cls): cls.provider.stop() kill_process(cls.proc) def test_have_data_in_db(self): self.assertTrue(len(self.provider.db) > 100) # details def test_get_details(self): result = self.provider.GetAppDetails("", "gedit") self.assertEqual(result["component"], "main") self.assertEqual(result["icon"], "accessories-text-editor") self.assertEqual(result["name"], "gedit") self.assertEqual(result["pkgname"], "gedit") self.assertEqual(result["price"], "Free") self.assertEqual(result["raw_price"], "") # this will only work *if* the ubuntu-desktop pkg is actually installed if self.provider.db._aptcache["ubuntu-desktop"].is_installed: self.assertEqual(result["is_desktop_dependency"], True) def test_get_details_non_desktop(self): result = self.provider.GetAppDetails("", "apache2") self.assertEqual(result["is_desktop_dependency"], False) # get available categories/subcategories def test_get_categories(self): result = self.provider.GetAvailableCategories() self.assertTrue("Internet" in result) def test_get_subcategories(self): result = self.provider.GetAvailableSubcategories("Internet") self.assertTrue("Chat" in result) # get category def test_get_category_internet(self): result = self.provider.GetItemsForCategory("Internet") self.assertTrue( ("Firefox Web Browser", # app "firefox", # pkgname "firefox", # iconname "/usr/share/app-install/desktop/firefox:firefox.desktop", ) in result) def test_get_category_top_rated(self): result = self.provider.GetItemsForCategory("Top Rated") self.assertEqual(len(result), 100) def test_get_category_whats_new(self): result = self.provider.GetItemsForCategory(u"What\u2019s New") self.assertEqual(len(result), 20) class IdleTimeoutTestCase(unittest.TestCase): def setUp(self): self.loop = GLib.MainLoop(GLib.main_context_default()) # setup bus dbus_service_name = DBUS_BUS_NAME proc, dbus_address = start_dbus_daemon() bus = dbus.bus.BusConnection(dbus_address) # run the checks self.bus_name = dbus.service.BusName(dbus_service_name, bus) def tearDown(self): self.loop.quit() @patch.object(SoftwareCenterDataProvider, "IDLE_TIMEOUT") @patch.object(SoftwareCenterDataProvider, "IDLE_CHECK_INTERVAL") def test_idle_timeout(self, mock_timeout, mock_interval): mock_timeout = 1 mock_timeout # pyflakes mock_interval = 1 mock_interval # pyflakes now = time.time() provider = SoftwareCenterDataProvider( self.bus_name, main_loop=self.loop) provider # pyflakes self.loop.run() # ensure this exited within a reasonable timeout self.assertTrue((time.time() - now) < 5) def test_idle_timeout_updates(self): provider = SoftwareCenterDataProvider( self.bus_name, main_loop=self.loop) t1 = provider._activity_timestamp time.sleep(0.1) provider.GetAvailableCategories() t2 = provider._activity_timestamp self.assertTrue(t1 < t2) if __name__ == "__main__": #import logging #logging.basicConfig(level=logging.DEBUG) os.environ.pop("LANGUAGE", None) os.environ.pop("LANG", None) unittest.main() software-center-13.10/tests/test_netstatus.py0000664000202700020270000000107412151440100021645 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() class TestNetstatus(unittest.TestCase): """ tests the netstaus utils """ def test_netstaus(self): from softwarecenter.netstatus import get_network_watcher watcher = get_network_watcher() # FIXME: do something with the watcher watcher def test_testping(self): from softwarecenter.netstatus import test_ping res = test_ping() # FIXME: do something with the res res if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_aptd.py0000664000202700020270000001703312151440100020545 0ustar dobeydobey00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- import logging import os import platform import subprocess import time import unittest import dbus from gi.repository import GLib from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.backend.installbackend_impl.aptd import AptdaemonBackend from softwarecenter.db.application import Application from softwarecenter.paths import APPORT_RECOVERABLE_ERROR from defer import inline_callbacks from mock import Mock, patch import aptdaemon class TestAptdaemon(unittest.TestCase): """ tests the AptdaemonBackend """ def setUp(self): self.aptd = AptdaemonBackend() self.aptd.ui = Mock() # monkey patch self.aptd.aptd_client.install_packages = self._mock_aptd_client_install_packages self._pkgs_to_install = [] def _mock_aptd_client_install_packages(self, pkgs, reply_handler, error_handler): self._pkgs_to_install.extend(pkgs) @inline_callbacks def test_add_license_key_home(self): data = "some-data" # test HOME target = "~/.fasfasdfsdafdfsdafdsfa" self.addCleanup(lambda: os.remove(os.path.expanduser(target))) pkgname = "2vcard" json_auth = "" yield self.aptd.add_license_key(data, target, json_auth, pkgname) self.assertEqual(open(os.path.expanduser(target)).read(), data) # ensure its not written twice data2 = "other-data" yield self.aptd.add_license_key(data2, target, json_auth, pkgname) self.assertEqual(open(os.path.expanduser(target)).read(), data) @unittest.skipIf(os.getuid() != 0, "test_add_license_key_opt test needs to run as root") @unittest.skipIf(not "SC_TEST_JSON" in os.environ, "Need a SC_TEST_JSON environment with the credentials") def test_add_license_key_opt(self): # test /opt license_key = "some-data" pkgname = "hellox" path = "/opt/hellox/conf/license-key.txt" self.addCleanup(lambda: os.remove(path)) json_auth = os.environ.get("SC_TEST_JSON") or "no-json-auth" def _error(*args): print "errror", args self.aptd.ui = Mock() self.aptd.LICENSE_KEY_SERVER = "ubuntu-staging" self.aptd.ui.error = _error @inline_callbacks def run(): yield self.aptd.add_license_key( license_key, path, json_auth, pkgname) # ensure signals get delivered before quit() GLib.timeout_add(500, lambda: aptdaemon.loop.mainloop.quit()) # run the callback run() aptdaemon.loop.mainloop.run() # give the daemon time to write the file time.sleep(0.5) self.assertTrue(os.path.exists(path)) #self.assertEqual(open(os.path.expanduser(target)).read(), data) #os.remove(os.path.expanduser(target)) def test_install_multiple(self): # FIXME: this test is not great, it should really # test that there are multiple transactions, that the icons # are correct etc - that needs some work in order to figure # out how to best do that with aptdaemon/aptd.py pkgnames = ["7zip", "2vcard"] appnames = ["The 7 zip app", ""] iconnames = ["icon-7zip", ""] # need to yiel as install_multiple is a inline_callback (generator) yield self.aptd.install_multiple(pkgnames, appnames, iconnames) self.assertEqual(self._pkgs_to_install, ["7zip", "2vcard"]) self._pkgs_to_install = [] def _monkey_patched_add_vendor_key_from_keyserver(self, keyid, *args, **kwargs): self.assertTrue(keyid.startswith("0x")) return Mock() def test_download_key_from_keyserver(self): keyid = "0EB12F05" keyserver = "keyserver.ubuntu.com" self.aptd.aptd_client.add_vendor_key_from_keyserver = self._monkey_patched_add_vendor_key_from_keyserver self.aptd.add_vendor_key_from_keyserver(keyid, keyserver) def test_apply_changes(self): pkgname = "gimp" appname = "The GIMP app" iconname = "icon-gimp" addons_install = ["gimp-data-extras", "gimp-gutenprint"] addons_remove = ["gimp-plugin-registry"] yield self.aptd.apply_changes(pkgname, appname ,iconname, addons_install, addons_remove) @unittest.skipIf(platform.dist()[2] == "precise", "needs quantal or later") def test_trans_error_ui_display(self): """ test if the right error ui is displayed for various dbus errors """ error = dbus.DBusException() dbus_name_mock = Mock() with patch.object(self.aptd.ui, "error") as error_ui_mock: for dbus_name, show_error_ui in [ ("org.freedesktop.PolicyKit.Error.NotAuthorized", False), ("org.freedesktop.PolicyKit.Error.Failed", True), ("moo.baa.lalala", True), ]: error_ui_mock.reset() dbus_name_mock.return_value = dbus_name error.get_dbus_name = dbus_name_mock self.aptd._on_trans_error(error, Mock()) self.assertEqual(error_ui_mock.called, show_error_ui) @inline_callbacks def _inline_add_repo_call(self): deb_line = "deb https://foo" signing_key_id = u"xxx" app = Application(u"Elementals: The Magic Keyâ„¢", "pkgname") iconname = "iconname" yield self.aptd.add_repo_add_key_and_install_app( deb_line, signing_key_id, app, iconname, None, None) def test_add_repo_add_key_and_install_app(self): from mock import patch with patch.object(self.aptd._logger, "info") as mock: self._inline_add_repo_call() self.assertTrue( mock.call_args[0][0].startswith("add_repo_add_key")) @patch("softwarecenter.backend.installbackend_impl.aptd.Popen") def test_recoverable_error(self, mock_popen): mock_popen_instance = Mock() mock_popen_instance.communicate.return_value = ("stdout", "stderr") mock_popen_instance.returncode = 0 mock_popen.return_value = mock_popen_instance self.aptd._call_apport_recoverable_error( "msg", "traceback-error", "custom:dupes:signature") mock_popen.assert_called_with( [APPORT_RECOVERABLE_ERROR], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # check that we really send the right data args, kwargs = mock_popen_instance.communicate.call_args self.assertEqual( kwargs["input"].split("\0"), [ 'DialogBody', 'msg', 'Traceback', 'traceback-error', 'DuplicateSignature', 'custom:dupes:signature', ]) def test_ignore_bad_packages(self): mock_trans = Mock(aptdaemon.client.AptTransaction) mock_trans.error_code = aptdaemon.enums.ERROR_INVALID_PACKAGE_FILE with patch.object(self.aptd, "_call_apport_recoverable_error") as m: self.aptd._on_trans_error("some error", mock_trans) self.assertFalse(m.called) def test_ignore_dpkg_errors(self): mock_trans = Mock(aptdaemon.client.AptTransaction) mock_trans.error_code = aptdaemon.enums.ERROR_PACKAGE_MANAGER_FAILED with patch.object(self.aptd, "_call_apport_recoverable_error") as m: self.aptd._on_trans_error("some error", mock_trans) self.assertFalse(m.called) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) unittest.main() software-center-13.10/tests/test_reinstall_purchased.py0000664000202700020270000004256012151440100023653 0ustar dobeydobey00000000000000import json import platform import unittest import xapian from gi.repository import GLib from mock import patch from piston_mini_client import PistonResponseObject from tests.utils import ( get_test_pkg_info, setup_test_env, ObjectWithSignals, ) setup_test_env() from softwarecenter.enums import ( AppInfoFields, AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, XapianValues, ) from softwarecenter.db.database import get_reinstall_previous_purchases_query from softwarecenter.db.update import ( SCAPurchasedApplicationParser, SCAApplicationParser, update_from_software_center_agent, ) # Example taken from running: # PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py --output=pickle \ # --debug --needs-auth SoftwareCenterAgentAPI subscriptions_for_me # then: # f = open('my_subscriptions.pickle') # subscriptions = pickle.load(f) # completed_subs = [subs for subs in subscriptions if subs.state=='Complete'] # completed_subs[0].__dict__ SUBSCRIPTIONS_FOR_ME_JSON = """ [ { "deb_line": "deb https://username:random3atoken@private-ppa.launchpad.net/commercial-ppa-uploaders/photobomb/ubuntu natty main", "purchase_price": "2.99", "purchase_date": "2011-09-16 06:37:52", "state": "Complete", "failures": [], "open_id": "https://login.ubuntu.com/+id/ABCDEF", "application": { "archive_id": "commercial-ppa-uploaders/photobomb", "signing_key_id": "1024R/75254D99", "name": "Photobomb", "package_name": "photobomb", "description": "Easy and Social Image Editor\\nPhotobomb give you easy access to images in your social networking feeds, pictures on your computer and peripherals, and pictures on the web, and let\'s you draw, write, crop, combine, and generally have a blast mashing \'em all up. Then you can save off your photobomb, or tweet your creation right back to your social network.", "version": "1.2.1" }, "distro_series": {"code_name": "natty", "version": "11.04"} } ] """ # Taken directly from: # https://software-center.ubuntu.com/api/2.0/applications/en/ubuntu/oneiric/i386/ AVAILABLE_APPS_JSON = """ [ { "archive_id": "commercial-ppa-uploaders/fluendo-dvd", "signing_key_id": "1024R/75254D99", "license": "Proprietary", "name": "Fluendo DVD Player", "package_name": "fluendo-dvd", "support_url": "", "series": { "maverick": [ "i386", "amd64" ], "natty": [ "i386", "amd64" ], "oneiric": [ "i386", "amd64" ] }, "price": "24.95", "demo": null, "date_published": "2011-12-05 18:43:21.653868", "status": "Published", "channel": "For Purchase", "icon_data": "...", "department": [ "Sound & Video" ], "archive_root": "https://private-ppa.launchpad.net/", "screenshot_url": "http://software-center.ubuntu.com/site_media/screenshots/2011/05/fluendo-dvd-maverick_.png", "tos_url": "https://software-center.ubuntu.com/licenses/3/", "icon_url": "http://software-center.ubuntu.com/site_media/icons/2011/05/fluendo-dvd.png", "categories": "AudioVideo", "description": "Play DVD-Videos\\r\\n\\r\\nFluendo DVD Player is a software application specially designed to\\r\\nreproduce DVD on Linux/Unix platforms, which provides end users with\\r\\nhigh quality standards.\\r\\n\\r\\nThe following features are provided:\\r\\n* Full DVD Playback\\r\\n* DVD Menu support\\r\\n* Fullscreen support\\r\\n* Dolby Digital pass-through\\r\\n* Dolby Digital 5.1 output and stereo downmixing support\\r\\n* Resume from last position support\\r\\n* Subtitle support\\r\\n* Audio selection support\\r\\n* Multiple Angles support\\r\\n* Support for encrypted discs\\r\\n* Multiregion, works in all regions\\r\\n* Multiple video deinterlacing algorithms", "website": null, "version": "1.2.1", "binary_filesize": 12345 }, { "website": "", "package_name": "photobomb", "video_embedded_html_urls": [ ], "demo": null, "keywords": "photos, pictures, editing, gwibber, twitter, facebook, drawing", "video_urls": [ ], "screenshot_url": "http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot-45.png", "id": 83, "archive_id": "commercial-ppa-uploaders/photobomb", "support_url": "http://launchpad.net/photobomb", "icon_url": "http://software-center.ubuntu.com/site_media/icons/2011/08/logo_64.png", "binary_filesize": null, "version": "", "company_name": "", "department": [ "Graphics" ], "tos_url": "", "channel": "For Purchase", "status": "Published", "signing_key_id": "1024R/75254D99", "description": "Easy and Social Image Editor\\nPhotobomb give you easy access to images in your social networking feeds, pictures on your computer and peripherals, and pictures on the web, and let's you draw, write, crop, combine, and generally have a blast mashing 'em all up. Then you can save off your photobomb, or tweet your creation right back to your social network.", "price": "2.99", "debtags": [ ], "date_published": "2011-12-05 18:43:20.794802", "categories": "Graphics", "name": "Photobomb", "license": "GNU GPL v3", "screenshot_urls": [ "http://software-center.ubuntu.com/site_media/screenshots/2011/08/Screenshot-45.png" ], "archive_root": "https://private-ppa.launchpad.net/" } ] """ class SCAApplicationParserTestCase(unittest.TestCase): def _make_application_parser(self, piston_application=None): if piston_application is None: piston_application = PistonResponseObject.from_dict( json.loads(AVAILABLE_APPS_JSON)[0]) return SCAApplicationParser(piston_application) def test_parses_application_from_available_apps(self): parser = self._make_application_parser() inverse_map = dict( (val, key) for key, val in SCAApplicationParser.MAPPING.items()) # Delete the keys which are not yet provided via the API: del(inverse_map['video_embedded_html_url']) for key in inverse_map: self.assertEqual( getattr(parser.sca_application, key), parser.get_value(inverse_map[key])) def test_name_not_updated_for_non_purchased_apps(self): parser = self._make_application_parser() self.assertEqual('Fluendo DVD Player', parser.get_value(AppInfoFields.NAME)) def test_binary_filesize(self): parser = self._make_application_parser() self.assertEqual(12345, parser.get_value(AppInfoFields.DOWNLOAD_SIZE)) def test_keys_not_provided_by_api(self): parser = self._make_application_parser() self.assertIsNone(parser.get_value(AppInfoFields.VIDEO_URL)) self.assertEqual('Application', parser.get_value(AppInfoFields.TYPE)) def test_thumbnail_is_screenshot(self): parser = self._make_application_parser() self.assertEqual( "http://software-center.ubuntu.com/site_media/screenshots/" "2011/05/fluendo-dvd-maverick_.png", parser.get_value(AppInfoFields.THUMBNAIL_URL)) def test_extracts_description(self): parser = self._make_application_parser() self.assertEqual("Play DVD-Videos", parser.get_value(AppInfoFields.SUMMARY)) self.assertEqual( "Fluendo DVD Player is a software application specially designed " "to\r\nreproduce DVD on Linux/Unix platforms, which provides end " "users with\r\nhigh quality standards.\r\n\r\nThe following " "features are provided:\r\n* Full DVD Playback\r\n* DVD Menu " "support\r\n* Fullscreen support\r\n* Dolby Digital pass-through" "\r\n* Dolby Digital 5.1 output and stereo downmixing support\r\n" "* Resume from last position support\r\n* Subtitle support\r\n" "* Audio selection support\r\n* Multiple Angles support\r\n" "* Support for encrypted discs\r\n" "* Multiregion, works in all regions\r\n" "* Multiple video deinterlacing algorithms", parser.get_value(AppInfoFields.DESCRIPTION)) def test_desktop_categories_uses_department(self): parser = self._make_application_parser() self.assertEqual([u'DEPARTMENT:Sound & Video', "AudioVideo"], parser.get_categories()) def test_desktop_categories_no_department(self): piston_app = PistonResponseObject.from_dict( json.loads(AVAILABLE_APPS_JSON)[0]) del(piston_app.department) parser = self._make_application_parser(piston_app) self.assertEqual(["AudioVideo"], parser.get_categories()) def test_magic_channel(self): parser = self._make_application_parser() self.assertEqual( AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, parser.get_value(AppInfoFields.CHANNEL)) class SCAPurchasedApplicationParserTestCase(unittest.TestCase): def _make_application_parser(self, piston_subscription=None): if piston_subscription is None: piston_subscription = PistonResponseObject.from_dict( json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0]) return SCAPurchasedApplicationParser(piston_subscription) def setUp(self): get_distro_patcher = patch('softwarecenter.db.update.get_distro') self.addCleanup(get_distro_patcher.stop) mock_get_distro = get_distro_patcher.start() mock_get_distro.return_value.get_codename.return_value = 'quintessential' def test_get_desktop_subscription(self): parser = self._make_application_parser() expected_results = { AppInfoFields.DEB_LINE: "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu quintessential main", AppInfoFields.DEB_LINE_ORIG: "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu natty main", AppInfoFields.PURCHASED_DATE: "2011-09-16 06:37:52", } for key in expected_results: result = parser.get_value(key) self.assertEqual(expected_results[key], result) def test_get_desktop_application(self): # The parser passes application attributes through to # an application parser for handling. parser = self._make_application_parser() # We're testing here also that the name is updated automatically. expected_results = { AppInfoFields.NAME: "Photobomb (already purchased)", AppInfoFields.PACKAGE: "photobomb", AppInfoFields.SIGNING_KEY_ID: "1024R/75254D99", AppInfoFields.PPA: "commercial-ppa-uploaders/photobomb", } for key in expected_results.keys(): result = parser.get_value(key) self.assertEqual(expected_results[key], result) def test_has_option_desktop_includes_app_keys(self): # The SCAPurchasedApplicationParser handles application keys also # (passing them through to the composited application parser). parser = self._make_application_parser() for key in (AppInfoFields.NAME, AppInfoFields.PACKAGE, AppInfoFields.SIGNING_KEY_ID, AppInfoFields.PPA): self.assertIsNotNone(parser.get_value(key)) for key in (AppInfoFields.DEB_LINE, AppInfoFields.PURCHASED_DATE): self.assertIsNotNone(parser.get_value(key), 'Key: {0} was not an option.'.format(key)) def test_license_key_present(self): piston_subscription = PistonResponseObject.from_dict( json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0]) piston_subscription.license_key = 'abcd' piston_subscription.license_key_path = '/foo' parser = self._make_application_parser(piston_subscription) self.assertEqual('abcd', parser.get_value(AppInfoFields.LICENSE_KEY)) self.assertEqual( '/foo', parser.get_value(AppInfoFields.LICENSE_KEY_PATH)) def test_license_key_not_present(self): parser = self._make_application_parser() for key in (AppInfoFields.LICENSE_KEY, AppInfoFields.LICENSE_KEY_PATH): self.assertIsNone(parser.get_value(key)) def test_purchase_date(self): parser = self._make_application_parser() self.assertEqual( "2011-09-16 06:37:52", parser.get_value(AppInfoFields.PURCHASED_DATE)) def test_will_handle_supported_distros_when_available(self): # When the fix for bug 917109 reaches production, we will be # able to use the supported series. parser = self._make_application_parser() supported_distros = { "maverick": [ "i386", "amd64" ], "natty": [ "i386", "amd64" ], } parser.sca_application.series = supported_distros self.assertEqual( supported_distros, parser.get_value(AppInfoFields.SUPPORTED_DISTROS)) def test_update_debline_other_series(self): orig_debline = ( "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu karmic main") expected_debline = ( "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu quintessential main") self.assertEqual(expected_debline, SCAPurchasedApplicationParser.update_debline(orig_debline)) def test_update_debline_with_pocket(self): orig_debline = ( "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu karmic-security main") expected_debline = ( "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu quintessential-security main") self.assertEqual(expected_debline, SCAPurchasedApplicationParser.update_debline(orig_debline)) class TestAvailableForMeMerging(unittest.TestCase): def setUp(self): self.available_for_me = self._make_available_for_me_list() self.available = self._make_available_list() def _make_available_for_me_list(self): my_subscriptions = json.loads(SUBSCRIPTIONS_FOR_ME_JSON) return list( PistonResponseObject.from_dict(subs) for subs in my_subscriptions) def _make_available_list(self): available_apps = json.loads(AVAILABLE_APPS_JSON) return list( PistonResponseObject.from_dict(subs) for subs in available_apps) def _make_fake_scagent(self, available_data, available_for_me_data): sca = ObjectWithSignals() sca.query_available = lambda **kwargs: GLib.timeout_add( 100, lambda: sca.emit('available', sca, available_data)) sca.query_available_for_me = lambda **kwargs: GLib.timeout_add( 100, lambda: sca.emit('available-for-me', sca, available_for_me_data)) return sca def test_reinstall_purchased_mock(self): # test if the mocks are ok self.assertEqual(len(self.available_for_me), 1) self.assertEqual( self.available_for_me[0].application['package_name'], "photobomb") @patch("softwarecenter.db.update.SoftwareCenterAgent") @patch("softwarecenter.db.update.UbuntuSSO") def test_reinstall_purchased_xapian(self, mock_helper, mock_agent): small_available = [ self.available[0] ] mock_agent.return_value = self._make_fake_scagent( small_available, self.available_for_me) db = xapian.inmemory_open() cache = get_test_pkg_info() # now create purchased debs xapian index (in memory because # we store the repository passwords in here) old_db_len = db.get_doccount() update_from_software_center_agent(db, cache) # ensure we have the new item self.assertEqual(db.get_doccount(), old_db_len+2) # query query = get_reinstall_previous_purchases_query() enquire = xapian.Enquire(db) enquire.set_query(query) matches = enquire.get_mset(0, db.get_doccount()) self.assertEqual(len(matches), 1) distroseries = platform.dist()[2] for m in matches: doc = db.get_document(m.docid) self.assertEqual(doc.get_value(XapianValues.PKGNAME), "photobomb") self.assertEqual( doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID), "1024R/75254D99") self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE), "deb https://username:random3atoken@" "private-ppa.launchpad.net/commercial-ppa-uploaders" "/photobomb/ubuntu %s main" % distroseries) if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) unittest.main() software-center-13.10/tests/disabled_test_pep8.py0000664000202700020270000000113612151440100022315 0ustar dobeydobey00000000000000import os import subprocess import unittest import softwarecenter class PackagePep8TestCase(unittest.TestCase): def test_all_code(self): res = 0 testdir = os.path.dirname(__file__) res += subprocess.call( ["pep8", "--repeat", # FIXME: FIXME! "--ignore=E126,E127,E128", os.path.dirname(softwarecenter.__file__), # test the main binary too os.path.join(testdir, "..", "software-center"), ]) self.assertEqual(res, 0) if __name__ == "__main__": unittest.main() software-center-13.10/tests/disabled_test_description_norm.py0000664000202700020270000001166712151440100025031 0ustar dobeydobey00000000000000# ensure we do not fail for different locales import os os.environ["LANG"] ="C" import apt import platform import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.utils import normalize_package_description class TestAppDescriptionNormalize(unittest.TestCase): """ tests the description noramlization """ def test_description_parser_regression_test_merged_words(self): # this is a regression test for the description parser # there is a bug that the newline is stripped and two words # are merged (LP: #983831) s = """A test package The goal is to test that multi-line descriptions are handled correctly, especially with regards to sentences that span more than two lines and how spaces and line wrapping is handled. """ description_text = normalize_package_description(s) self.assertEqual( description_text, """A test package\nThe goal is to test that multi-line descriptions are handled correctly, especially with regards to sentences that span more than two lines and how spaces and line wrapping is handled.""") def test_description_parser_regression_test_moppet(self): # this is a regression test for the description parser # there is a bug that after GAME FEATURES the bullet list # is not actually displayed s = """A challenging 3D block puzzle game. Puzzle Moppet is a challenging 3D puzzle game featuring a diminutive and apparently mute creature who is lost in a mysterious floating landscape. GAME FEATURES * Save the Moppet from itself """ description_text = normalize_package_description(s) self.assertEqual( description_text, """A challenging 3D block puzzle game.\n Puzzle Moppet is a challenging 3D puzzle game featuring a diminutive and apparently mute creature who is lost in a mysterious floating landscape.\n GAME FEATURES * Save the Moppet from itself""") @unittest.skipIf(platform.dist()[2] == "precise", "needs quantal or later") def test_description_parser_selected(self): cache = apt.Cache() self.assertEqual( normalize_package_description(cache["arista"].description), """Arista is a simple multimedia transcoder, it focuses on being easy to use by making complex task of encoding for various devices simple.\n Users should pick an input and a target device, choose a file to save to and go. Features:\n * 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. * Rip straight from DVD media easily (requires libdvdcss). * Rip straight from v4l devices. * Simple terminal client for scripting. * Automatic preset updating.""") # note: bullet indentation self.assertEqual( normalize_package_description(cache["aa3d"].description), """This program generates the well-known and popular random dot stereograms in ASCII art.\n Features:\n * High quality ASCII art stereogram rendering * Highly configurable * User friendly command line interface (including full online help)""") def test_description_parser_all(self): import re def descr_cmp_filter(s): new = s for k in [r"\n\s*- ", r"\n\s*\* ", r"\n\s*o ", # actually kill off all remaining whitespace r"\s"]: new = re.sub(k, "", new) return new # test that all descriptions are parsable without failure cache = apt.Cache() for pkg in cache: if pkg.candidate: # gather the text in there description_processed = normalize_package_description(pkg.description) self.assertEqual(descr_cmp_filter(pkg.description), descr_cmp_filter(description_processed), "pkg '%s' diverge:\n%s\n\n%s\n" % ( pkg.name, descr_cmp_filter(pkg.description), descr_cmp_filter(description_processed))) def test_htmlize(self): from softwarecenter.utils import htmlize_package_description s = """A challenging 3D block puzzle game. Puzzle Moppet is a challenging 3D puzzle game featuring a diminutive and apparently mute creature who is lost in a mysterious floating landscape. GAME FEATURES * Save the Moppet from itself """ description_text = htmlize_package_description(s) self.assertEqual( description_text, """

A challenging 3D block puzzle game.

Puzzle Moppet is a challenging 3D puzzle game featuring a diminutive and apparently mute creature who is lost in a mysterious floating landscape.

GAME FEATURES

  • Save the Moppet from itself
""") if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_cmdfiner.py0000664000202700020270000000134112151440100021377 0ustar dobeydobey00000000000000import apt import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.cmdfinder import CmdFinder class TestCmdFinder(unittest.TestCase): """ tests the CmdFinder class """ def setUp(self): cache = apt.Cache() self.cmd = CmdFinder(cache) def test_cmdfinder_simple(self): cmds = self.cmd.find_cmds_from_pkgname("apt") self.assertTrue("apt-get" in cmds) self.assertTrue(len(cmds) > 2) def test_cmdfinder_find_alternatives(self): # this test ensures that alternatives are also considered cmds = self.cmd.find_cmds_from_pkgname("gawk") self.assertTrue("awk" in cmds) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_rnr_api.py0000664000202700020270000000151112151440100021241 0ustar dobeydobey00000000000000import unittest from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI from softwarecenter.backend.piston.rnrclient_fake import RatingsAndReviewsAPI as RatingsAndReviewsAPIFake class TestRNRAPI(unittest.TestCase): """ tests the rnr backend stuff """ def test_fake_and_real_provide_similar_methods(self): """ test if the real and fake sso provide the same functions """ rnr_real = RatingsAndReviewsAPI rnr_fake = RatingsAndReviewsAPIFake # ensure that both fake and real implement the same methods self.assertEqual( set([x for x in dir(rnr_real) if not x.startswith("_")]), set([x for x in dir(rnr_fake) if not x.startswith("_")])) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_apthistory.py0000664000202700020270000000643612151440100022030 0ustar dobeydobey00000000000000import apt import datetime import os import subprocess import time import unittest from gi.repository import GLib from tests.utils import ( DATA_DIR, do_events, setup_test_env, ) setup_test_env() from softwarecenter.db.history_impl.apthistory import AptHistory from softwarecenter.utils import ExecutionTime class TestAptHistory(unittest.TestCase): def setUp(self): self.basedir = os.path.join(DATA_DIR, "apt-history") apt.apt_pkg.config.set("Dir::Log", self.basedir) #apt_pkg.config.set("Dir::Log::History", "./") def _get_apt_history(self): history = AptHistory(use_cache=False) do_events() return history def test_history(self): history = self._get_apt_history() self.assertEqual(history.transactions[0].start_date, datetime.datetime.strptime("2010-06-09 14:50:00", "%Y-%m-%d %H:%M:%S")) # 186 is from "zgrep Start data/apt-history/history.log*|wc -l" #print "\n".join([str(x) for x in history.transactions]) self.assertEqual(len(history.transactions), 186) def test_apthistory_upgrade(self): history = self._get_apt_history() self.assertEqual(history.transactions[1].upgrade, ['acl (2.2.49-2, 2.2.49-3)']) def _glib_timeout(self): self._timeouts.append(time.time()) return True def _generate_big_history_file(self, new_history): # needs to ensure the date is decreasing, otherwise the rescan # code is too clever and skips it f = open(new_history,"w") date=datetime.date(2009, 8, 2) for i in range(1000): date -= datetime.timedelta(days=i) s="Start-Date: %s 14:00:00\nInstall: 2vcard\nEnd-Date: %s 14:01:00\n\n" % (date, date) f.write(s) f.close() subprocess.call(["gzip", new_history]) self.addCleanup(os.remove, new_history + ".gz") def test_apthistory_rescan_big(self): """ create big history file and ensure that on rescan the events are still processed """ self._timeouts = [] new_history = os.path.join(self.basedir, "history.log.2") history = self._get_apt_history() self.assertEqual(len(history.transactions), 186) self._generate_big_history_file(new_history) timer_id = GLib.timeout_add(100, self._glib_timeout) with ExecutionTime("rescan %s byte file" % os.path.getsize(new_history+".gz")): history._rescan(use_cache=False) GLib.source_remove(timer_id) # verify rescan self.assertTrue(len(history.transactions) > 186) # check the timeouts self.assertTrue(len(self._timeouts) > 0) for i in range(len(self._timeouts)-1): # check that we get a max timeout of 0.2s if abs(self._timeouts[i] - self._timeouts[i+1]) > 0.2: raise def test_no_history_log(self): # set to dir with no existing history.log apt.apt_pkg.config.set("Dir::Log", "/") # this should not raise history = self._get_apt_history() self.assertEqual(history.transactions, []) apt.apt_pkg.config.set("Dir::Log", self.basedir) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_region.py0000664000202700020270000000373512151440100021104 0ustar dobeydobey00000000000000import os import unittest from mock import Mock from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.region import RegionDiscover, get_region_name from softwarecenter.i18n import init_locale class TestRegion(unittest.TestCase): """ tests the region detection """ def setUp(self): self.region = RegionDiscover() def test_get_region_dump(self): os.environ["LC_ALL"] = "en_ZM.utf8" init_locale() res = self.region._get_region_dumb() self.assertEqual(res["countrycode"], "ZM") self.assertEqual(res["country"], "Zambia") os.environ["LANG"] = "" def test_get_region_name(self): self.assertEqual(get_region_name("BO"), "Bolivia") self.assertEqual(get_region_name("DE"), "Germany") @unittest.skip("real ubuntu-geoip not always reliable") def test_get_region_geoclue(self): res = self.region._get_region_geoclue() self.assertNotEqual(len(res), 0) self.assertTrue("countrycode" in res) self.assertTrue("country" in res) # helper def _mock_internal_region_finders(self): self.region._get_region_dumb = Mock() self.region._get_region_geoclue = Mock() def test_get_region_no_mocks(self): res = self.region.get_region() self.assertNotEqual(len(res), 0) def test_get_region_normal(self): self._mock_internal_region_finders() self.region.get_region() self.assertTrue(self.region._get_region_geoclue.called) self.assertFalse(self.region._get_region_dumb.called) def test_get_region_fallback(self): # test fallback (no geoclue) self._mock_internal_region_finders() self.region._get_region_geoclue.side_effect = Exception("raise test exception") self.region.get_region() self.assertTrue(self.region._get_region_dumb.called) self.assertTrue(self.region._get_region_geoclue.called) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_xapian.py0000664000202700020270000001500112167624525021114 0ustar dobeydobey00000000000000import os import platform import unittest import xapian from mock import Mock, patch from tests.utils import ( REAL_DATA_DIR, setup_test_env, ) setup_test_env() from softwarecenter.enums import XapianValues, CustomKeys from softwarecenter.db.update import rebuild_database class TestXapian(unittest.TestCase): """ tests the xapian database """ def setUp(self): # FIXME: create a fixture DB instead of using the system one # but for now that does not matter that much, only if we # call open the db is actually read and the path checked pathname = os.path.join(REAL_DATA_DIR, "xapian") if not os.path.exists(pathname): os.makedirs(pathname) if not os.listdir(pathname): rebuild_database(pathname) self.xapiandb = xapian.Database(pathname) self.enquire = xapian.Enquire(self.xapiandb) def test_exact_query(self): query = xapian.Query("APsoftware-center") self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) self.assertEqual(len(matches), 1) def test_search_term(self): search_term = "apt" parser = xapian.QueryParser() query = parser.parse_query(search_term) self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) self.assertTrue(len(matches) > 5) def test_category_query(self): query = xapian.Query("ACaudiovideo") self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) self.assertTrue(len(matches) > 5) def test_mime_query(self): query = xapian.Query("AMtext/html") self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) self.assertTrue(len(matches) > 5) pkgs = set() for match in matches: doc = match.document pkgs.add(doc.get_value(XapianValues.PKGNAME)) self.assertTrue("firefox" in pkgs) def test_eset(self): """ test finding "similar" items than the ones found before """ query = xapian.Query("foo") self.enquire.set_query(query) # this yields very few results matches = self.enquire.get_mset(0, 100) # create a relevance set from the query rset = xapian.RSet() #print "original finds: " for match in matches: #print match.document.get_data() rset.add_document(match.docid) # and use that to get a extended set eset = self.enquire.get_eset(20, rset) #print eset # build a query from the eset eset_query = xapian.Query(xapian.Query.OP_OR, [e.term for e in eset]) self.enquire.set_query(eset_query) # ensure we have more results now than before eset_matches = self.enquire.get_mset(0, 100) self.assertTrue(len(matches) < len(eset_matches)) #print "expanded finds: " #for match in eset_matches: # print match.document.get_data() def test_spelling_correction(self): """ test automatic suggestions for spelling corrections """ parser = xapian.QueryParser() parser.set_database(self.xapiandb) # mispelled search term search_term = "corect" query = parser.parse_query( search_term, xapian.QueryParser.FLAG_SPELLING_CORRECTION) self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) self.assertEqual(len(matches), 0) corrected_query_string = parser.get_corrected_query_string() self.assertEqual(corrected_query_string, "correct") # set the corrected one query = parser.parse_query(corrected_query_string) self.enquire.set_query(query) matches = self.enquire.get_mset(0, 100) #print len(matches) self.assertTrue(len(matches) > 0) class AptXapianIndexTestCase(unittest.TestCase): # this will fail on precise so we skip the test there @unittest.skipIf(platform.dist()[2] == "precise" and not os.path.exists("/var/lib/apt-xapian-index/index"), "Need populated apt-xapian-index for this test") def test_wildcard_bug1025579_workaround(self): db = xapian.Database("/var/lib/apt-xapian-index/index") enquire = xapian.Enquire(db) parser = xapian.QueryParser() parser.set_database(db) # this is the gist, the mangled version of the XPM term parser.add_prefix("pkg_wildcard", "XPM") parser.add_prefix("pkg_wildcard", "XP") parser.add_prefix("pkg_wildcard", "AP") s = 'pkg_wildcard:unity_lens_*' query = parser.parse_query(s, xapian.QueryParser.FLAG_WILDCARD) enquire.set_query(query) mset = enquire.get_mset(0, 100) for m in mset: self.assertTrue(m.document.get_data().startswith("unity-lens-")) self.assertNotEqual(len(mset), 0) class XapianPluginsTestCase(unittest.TestCase): def make_mock_document(self): doc = Mock() return doc def make_mock_package(self): pkg = Mock() ver = Mock() ver.uri = "http://archive.ubuntu.com/foo.deb" ver.record = {} for varname in vars(CustomKeys): key = getattr(CustomKeys, varname) ver.record[key] = "custom-%s" % key pkg.candidate = ver pkg.name = "meep" return pkg def test_xapian_plugin_sc(self): from apt_xapian_index_plugin.software_center import ( SoftwareCenterMetadataPlugin) plugin = SoftwareCenterMetadataPlugin() plugin.init(info=None, progress=None) # mock the indexer with patch.object(plugin, "indexer") as mock_indexer: # make mock document doc = self.make_mock_document() # ... and mock pkg/version pkg = self.make_mock_package() # go for it plugin.index(doc, pkg) # check that we got the expected calls doc.add_term.assert_any_call("AA"+"custom-AppName") # indexer mock_indexer.index_text_without_positions.assert_called() # check the xapian values calls expected_values = set([ XapianValues.APPNAME, XapianValues.ICON, XapianValues.SCREENSHOT_URLS, XapianValues.THUMBNAIL_URL]) got_values = set() for args, kwargs in doc.add_value.call_args_list: got_values.add(args[0]) self.assertTrue(expected_values.issubset(got_values)) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_debfileapplication.py0000664000202700020270000000775512151440100023445 0ustar dobeydobey00000000000000import os import unittest from tests.utils import ( DATA_DIR, get_test_db, setup_test_env, ) setup_test_env() from softwarecenter.enums import PkgStates from softwarecenter.db.debfile import DebFileApplication, DebFileOpenError DEBFILE_DIR = os.path.join(DATA_DIR, 'test_debs') DEBFILE_PATH = os.path.join(DEBFILE_DIR, 'gdebi-test9.deb') DEBFILE_NAME = 'gdebi-test9' DEBFILE_DESCRIPTION = ' provides/conflicts against "nvidia-glx"' DEBFILE_SUMMARY = 'testpackage for gdebi - provides/conflicts against real pkg' DEBFILE_VERSION = '1.0' DEBFILE_WARNING = 'Only install this file if you trust the origin.' DEBFILE_PATH_NOTFOUND = os.path.join(DEBFILE_DIR, 'notfound.deb') DEBFILE_PATH_NOTADEB = os.path.join(DATA_DIR, 'notadeb.txt') DEBFILE_PATH_CORRUPT = os.path.join(DEBFILE_DIR, 'corrupt.deb') DEBFILE_NOT_INSTALLABLE = os.path.join(DEBFILE_DIR, 'gdebi-test1.deb') class TestDebFileApplication(unittest.TestCase): """ Test the class DebFileApplication """ def setUp(self): self.db = get_test_db() def test_get_name(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.name, DEBFILE_NAME) def test_get_description(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.description, DEBFILE_DESCRIPTION) def test_get_pkg_state_uninstalled(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.pkg_state, PkgStates.UNINSTALLED) def test_get_pkg_state_not_installable(self): debfileapplication = DebFileApplication(DEBFILE_NOT_INSTALLABLE) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.pkg_state, PkgStates.ERROR) def disabled_for_now_test_get_pkg_state_reinstallable(self): # FIMXE: add hand crafted dpkg status file into the testdir so # that gdebi-test1 is marked install for the MockAptCache #debfileapplication = DebFileApplication(DEBFILE_REINSTALLABLE) #debfiledetails = debfileapplication.get_details(self.db) #self.assertEquals(debfiledetails.pkg_state, PkgStates.REINSTALLABLE) pass def test_get_pkg_state_not_found(self): debfileapplication = DebFileApplication(DEBFILE_PATH_NOTFOUND) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.pkg_state, PkgStates.NOT_FOUND) def test_get_pkg_state_not_a_deb(self): self.assertRaises(DebFileOpenError, DebFileApplication, DEBFILE_PATH_NOTADEB) def test_get_pkg_state_corrupt(self): debfileapplication = DebFileApplication(DEBFILE_PATH_CORRUPT) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.pkg_state, PkgStates.NOT_FOUND) def test_get_summary(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.summary, DEBFILE_SUMMARY) def test_get_version(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.version, DEBFILE_VERSION) def test_get_installed_size_when_uninstalled(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.installed_size, 0) def test_get_warning(self): debfileapplication = DebFileApplication(DEBFILE_PATH) debfiledetails = debfileapplication.get_details(self.db) self.assertEquals(debfiledetails.warning, DEBFILE_WARNING) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_hw.py0000664000202700020270000000302212151440100020224 0ustar dobeydobey00000000000000import unittest from mock import patch from tests.utils 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__": unittest.main() software-center-13.10/tests/test_recagent.py0000664000202700020270000001335212151440100021405 0ustar dobeydobey00000000000000import os import unittest from mock import patch from gi.repository import GLib from tests.utils import ( get_test_db, setup_test_env, ) setup_test_env() import softwarecenter from softwarecenter.backend.recagent import RecommenderAgent class MockTestRecommenderAgent(unittest.TestCase): @patch.object(softwarecenter.backend.recagent.SpawnHelper, 'run_generic_piston_helper') def test_mocked_recagent_post_submit_profile(self, mock_spawn_helper_run): recommender_agent = RecommenderAgent() recommender_agent._calc_profile_id = lambda profile: "i-am-random" db = get_test_db() recommender_agent.post_submit_profile(db) args, kwargs = mock_spawn_helper_run.call_args # ensure we have packages in the package list and the # kwargs have the names we expect self.assertNotEqual(kwargs['data'][0]['package_list'], []) class RealTestRecommenderAgent(unittest.TestCase): """ tests the recommender agent """ @unittest.skipIf(os.getuid() == 0, "this is not supported running as root") def setUp(self): self.loop = GLib.MainLoop(GLib.main_context_default()) self.error = False if "SOFTWARE_CENTER_RECOMMENDER_HOST" in os.environ: orig_host = os.environ.get("SOFTWARE_CENTER_RECOMMENDER_HOST") self.addCleanup(os.environ.__setitem__, "SOFTWARE_CENTER_RECOMMENDER_HOST", orig_host) else: self.addCleanup(os.environ.pop, "SOFTWARE_CENTER_RECOMMENDER_HOST") server = "https://rec.staging.ubuntu.com" os.environ["SOFTWARE_CENTER_RECOMMENDER_HOST"] = server # most tests need it self.recommender_agent = RecommenderAgent() self.recommender_agent.connect("error", self.on_query_error) def on_query_done(self, recagent, data): #print "query done, data: '%s'" % data self.loop.quit() self.error = False self.error_msg = "" def on_query_error(self, recagent, error): #print "query error received: ", error self.loop.quit() self.error = True self.error_msg = error def assertServerReturnsWithNoError(self): self.loop.run() self.assertFalse(self.error, "got error: '%s'" % self.error_msg) def test_recagent_query_server_status(self): self.recommender_agent.connect("server-status", self.on_query_done) self.recommender_agent.query_server_status() self.assertServerReturnsWithNoError() @unittest.skip("server returns 401") def test_recagent_post_submit_profile(self): # NOTE: This requires a working recommender host that is reachable db = get_test_db() self.recommender_agent.connect( "submit-profile-finished", self.on_query_done) self.recommender_agent.post_submit_profile(db) self.assertServerReturnsWithNoError() #print mock_request._post @unittest.skip("server returns 401") def test_recagent_query_submit_anon_profile(self): self.recommender_agent.connect( "submit-anon-profile-finished", self.on_query_done) self.recommender_agent.post_submit_anon_profile( uuid="xxxyyyzzz", installed_packages=["pitivi", "fretsonfire"], extra="") self.assertServerReturnsWithNoError() @unittest.skip("server returns 401") def test_recagent_query_profile(self): self.recommender_agent.connect("profile", self.on_query_done) self.recommender_agent.query_profile(pkgnames=["pitivi", "fretsonfire"]) self.assertServerReturnsWithNoError() @unittest.skip("server returns 401") def test_recagent_query_recommend_me(self): self.recommender_agent.connect("recommend-me", self.on_query_done) self.recommender_agent.query_recommend_me() self.assertServerReturnsWithNoError() def test_recagent_query_recommend_app(self): self.recommender_agent.connect("recommend-app", self.on_query_done) self.recommender_agent.query_recommend_app("pitivi") self.assertServerReturnsWithNoError() def test_recagent_query_recommend_all_apps(self): self.recommender_agent.connect("recommend-all-apps", self.on_query_done) self.recommender_agent.query_recommend_all_apps() self.assertServerReturnsWithNoError() def test_recagent_query_recommend_top(self): self.recommender_agent.connect("recommend-top", self.on_query_done) self.recommender_agent.query_recommend_top() self.assertServerReturnsWithNoError() def test_recagent_query_error(self): # NOTE: This tests the error condition itself! it simply forces an error # 'cuz there definitely isn't a server here :) fake_server = "https://test-no-server-here.staging.ubuntu.com" os.environ["SOFTWARE_CENTER_RECOMMENDER_HOST"] = fake_server recommender_agent = RecommenderAgent() recommender_agent.connect("recommend-top", self.on_query_done) recommender_agent.connect("error", self.on_query_error) recommender_agent.query_recommend_top() self.loop.run() self.assertTrue(self.error) @unittest.skip("server returns 401") def test_recagent_post_implicit_feedback(self): self.recommender_agent.connect("submit-implicit-feedback-finished", self.on_query_done) from softwarecenter.enums import RecommenderFeedbackActions self.recommender_agent.post_implicit_feedback( "bluefish", RecommenderFeedbackActions.INSTALLED) self.assertServerReturnsWithNoError() if __name__ == "__main__": #import logging #logging.basicConfig(level=logging.DEBUG) unittest.main() software-center-13.10/tests/disabled_test_gui_ldtp.py0000664000202700020270000000445512151440100023257 0ustar dobeydobey00000000000000#!/usr/bin/python import unittest import ooldtp import os import shutil import subprocess import time class SoftwareCenterLdtp(unittest.TestCase): WINDOW = "frmUbuntuSoftwareCenter" LAUNCHER = "software-center" CLOSE_NAME = "mnuClose" def setUp(self): env = os.environ.copy() env["PYTHONPATH="] = "." #self.atspi = subprocess.Popen(["/usr/lib/at-spi/at-spi-registryd"]) #print "starting at-spi", self.atspi #time.sleep(5) self.p = subprocess.Popen(["./software-center"], cwd="..", env=env) # wait for app self._wait_for_sc() def _wait_for_sc(self): """ wait unil the software-center window becomes ready """ while True: try: comp = ooldtp.component(self.WINDOW, self.CLOSE_NAME) close_menu_label = comp.gettextvalue() except Exception as e: print "waiting ...", e time.sleep(2) continue else: break print comp, close_menu_label def tearDown(self): self.p.kill() # remove the local db shutil.rmtree("../data/xapian") def test_search(self): application = ooldtp.context(self.WINDOW) # get search entry search = application.getchild("txtSearch") search.enterstring("ab") time.sleep(2) # check label label = application.getchild("status_text") label_str = label.gettextvalue() # make sure ab always hits the query limit (200 currently) self.assertEqual(label_str, "200 matching items") if __name__ == "__main__": # kill locale stuff for k in ["LANGUAGE", "LANG"]: if k in os.environ: del os.environ[k] # FIXME: this does not work as at-spi-registryd is not started # and starting it manually does not work for whatever reason # re-exec in xvfb if needed #if os.environ.get("DISPLAY") != ":99": # # the xvfb window can be viewed with "xwud < Xvfb_screen0" # cmd = ["xvfb-run", "-e", "xvfb.log", "-s", "-fbdir .", # "python"]+sys.argv # logging.warn("re-execing inside xvfb: %s" % cmd) # subprocess.call(cmd) #else: # unittest.main() unittest.main() software-center-13.10/tests/test_spawn_helper.py0000664000202700020270000000146712151440100022310 0ustar dobeydobey00000000000000import unittest from mock import patch from tests.utils 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__": unittest.main() software-center-13.10/tests/test_testutils.py0000664000202700020270000000441112151440100021651 0ustar dobeydobey00000000000000import unittest import dbus from tests.utils import ( do_events_with_sleep, setup_test_env, start_dummy_backend, stop_dummy_backend, get_mock_app_properties_helper, url_accessable, ) setup_test_env() from softwarecenter.db.application import Application from softwarecenter.backend.installbackend_impl.aptd import get_dbus_bus class DummyBackendTestUtilsTestCase(unittest.TestCase): def setUp(self): start_dummy_backend() def tearDown(self): stop_dummy_backend() def test_start_stop_dummy_backend(self): bus = get_dbus_bus() system_bus = dbus.SystemBus() session_bus = dbus.SessionBus() self.assertNotEqual(bus, system_bus) self.assertNotEqual(bus, session_bus) # get names and ... names = bus.list_names() # ensure we have the following: # org.freedesktop.DBus, # org.freedesktop.PolicyKit1 # org.debian.apt # (and :1.0, :1.1) for name in ["org.freedesktop.PolicyKit1", "org.debian.apt"]: self.assertTrue(name in names, "Expected name '%s' not in '%s'" % (name, names)) def test_fake_aptd(self): from softwarecenter.backend.installbackend import get_install_backend backend = get_install_backend() backend.install(Application("2vcard", ""), iconname="") do_events_with_sleep() class TestUtilsTestCase(unittest.TestCase): def test_app_properties_helper_mock_with_defaults(self): app_properties_helper = get_mock_app_properties_helper() self.assertEqual( app_properties_helper.get_pkgname(None), "apkg") def test_app_properties_helper_mock_with_custom_values(self): my_defaults = {'pkgname': 'diemoldau', } app_properties_helper = get_mock_app_properties_helper(my_defaults) self.assertEqual( app_properties_helper.get_pkgname(None), "diemoldau") def test_url_accessable(self): self.assertTrue( url_accessable("http://archive.ubuntu.com/ubuntu/", "dists/")) self.assertFalse( url_accessable("http://archive.ubuntu.com/ubuntu/", "mooobaalalala")) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_scagent.py0000664000202700020270000000573512151440100021247 0ustar dobeydobey00000000000000import unittest from gi.repository import GLib from mock import Mock, patch from tests.utils import ( setup_test_env, ) setup_test_env() from softwarecenter.backend.scagent import SoftwareCenterAgent class TestSCAgent(unittest.TestCase): """ tests software-center-agent """ def setUp(self): self.loop = GLib.MainLoop(GLib.main_context_default()) self.error = False def on_query_done(self, scagent, data): # print "query done, data: '%s'" % data self.loop.quit() def on_query_error(self, scagent, error): self.loop.quit() self.error = True def test_scagent_query_available(self): sca = SoftwareCenterAgent() sca.connect("available", self.on_query_done) sca.connect("error", self.on_query_error) sca.query_available() self.loop.run() self.assertFalse(self.error) def test_scagent_query_exhibits(self): sca = SoftwareCenterAgent() sca.connect("exhibits", self.on_query_done) sca.connect("error", self.on_query_error) sca.query_exhibits() self.loop.run() self.assertFalse(self.error) def test_scaagent_query_available_for_me_uses_complete_only(self): run_generic_piston_helper_fn = ( 'softwarecenter.backend.spawn_helper.SpawnHelper.' 'run_generic_piston_helper') with patch(run_generic_piston_helper_fn) as mock_run_piston_helper: sca = SoftwareCenterAgent() sca.query_available_for_me() mock_run_piston_helper.assert_called_with( 'SoftwareCenterAgentAPI', 'subscriptions_for_me', complete_only=True) class RegressionsTestCase(unittest.TestCase): def setUp(self): self.sca = SoftwareCenterAgent() self.sca.emit = Mock() def _get_exhibit_list_from_emit_call(self): args, kwargs = self.sca.emit.call_args scagent, exhibit_list = args return exhibit_list def test_regression_lp1004417(self): mock_ex = Mock() mock_ex.package_names = "foo,bar\n\r" results = [mock_ex] self.sca._on_exhibits_data_available(None, results) self.assertTrue(self.sca.emit.called) # and ensure we get the right list len exhibit_list = self._get_exhibit_list_from_emit_call() self.assertEqual(len(exhibit_list), 1) # and the right data in the list exhibit = exhibit_list[0] self.assertEqual(exhibit.package_names, "foo,bar") self.assertFalse(exhibit.package_names.endswith("\n\r")) def test_regression_lp1043152(self): mock_ex = Mock() mock_ex.package_names = "moo, baa, lalala" results = [mock_ex] self.sca._on_exhibits_data_available(None, results) # ensure that the right data in the list exhibit = self._get_exhibit_list_from_emit_call()[0] self.assertEqual(exhibit.package_names, "moo,baa,lalala") if __name__ == "__main__": unittest.main() software-center-13.10/tests/mago/0000755000202700020270000000000012224614355017141 5ustar dobeydobey00000000000000software-center-13.10/tests/mago/mago_simple.py0000775000202700020270000001034512151440100021777 0ustar dobeydobey00000000000000# Copyright (C) 2010 Canonical Ltd # # 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; either version 2 of the License, or # (at your option) any later version. # # 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 """Template Test This is a template to create tests for Mago To run it with: $ mago The only mandatory element is 'launcher' If no 'window_name' property is set, then mago will try to guess it from the XID of the window set setupOnce to False to launch/close the app for each test """ import ldtp import ooldtp import unittest from mago import TestCase class TestSoftwareCenter(TestCase): """The minimal test that can be written with mago """ # try local checkout first try: #import softwarecenter.enums launcher = "../software-center" print "using local checkout" except ImportError: launcher = 'software-center' window_name = 'frmUbuntuSoftwareCenter' setupOnce = True # widgets # navigation bar btnBackButton = "btnBackButton" btnForwardButton = "btnForwardButton" # available pane availablePaneAppView = "AvailablePane.app_view" availablePaneSearchEntry = "AvailablePane.searchentry" def test_search_simple(self): """ perform a basic search test """ context = ooldtp.context(self.window_name) context.enterstring(self.availablePaneSearchEntry, "apt") ldtp.wait(1) row_count = context.getrowcount(self.availablePaneAppView) # ensure we get enough hits self.assertTrue(row_count > 10) # ensure apt is the first hit row = 0 column = 0 text = context.getcellvalue(self.availablePaneAppView, row, column) self.assertEqual(text, "\nInstalled\nAdvanced front-end for dpkg") # disabled for now as it takes a long time to run def _diabled_for_now_test_search_scroll_down_hang(self): context = ooldtp.context(self.window_name) ldtp.wait(2) context.enterstring(self.availablePaneSearchEntry, "a") ldtp.wait(1) row_count = context.getrowcount(self.availablePaneAppView) # ensure we get enough hits self.assertTrue(row_count > 10) ldtp.generatekeyevent("") # repeating this 100 times will eventually hang s-c, its # currently unclear why for i in range(100): self.generate_page_down() ldtp.wait(1) ldtp.generatekeyevent("") ldtp.wait(1) ldtp.generatekeyevent("") ldtp.wait(1) def generate_page_down(self): # FIXME: currently broken #ldtp.generatekeyevent("") import pyatspi key_code=117 # pagedown pyatspi.Registry.generateKeyboardEvent( key_code, None, pyatspi.KEY_PRESS) pyatspi.Registry.generateKeyboardEvent( key_code, None, pyatspi.KEY_RELEASE) def xxx_test_TEMPLATE(self): """This a test template Add documentation for your test in the following format: caseid: XXXXX000 - Unique ID for the testcase name: Name of the test case usually the name of the method. requires: List of the packages required to run the test. command: Name of the binary to test _description: PURPOSE: 1. Describe the purpose of the test STEPS: 1. Describe the first step of the test case 2. Describe the second step of the test case 2. ... VERIFICATION: 1. Describe the expected result of the test. """ self.assertTrue(True) if __name__ == "__main__": unittest.main() software-center-13.10/tests/test_categories.py0000664000202700020270000001010512151440100021733 0ustar dobeydobey00000000000000import unittest import xapian from mock import patch from tests.utils import ( DATA_DIR, get_test_db, make_recommender_agent_recommend_me_dict, setup_test_env, ) setup_test_env() from softwarecenter.db.categories import ( CategoriesParser, RecommendedForYouCategory, get_category_by_name, get_query_for_category) class TestCategories(unittest.TestCase): def setUp(self): self.db = get_test_db() @patch('softwarecenter.db.categories.RecommenderAgent') def test_recommends_category(self, AgentMockCls): # ensure we use the same instance in test and code agent_mock_instance = AgentMockCls.return_value recommends_cat = RecommendedForYouCategory(self.db) docids = recommends_cat.get_documents(self.db) self.assertEqual(docids, []) self.assertTrue(agent_mock_instance.query_recommend_me.called) # ensure we get a query when the callback is called recommends_cat._recommend_me_result( 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_DIR) # "2" is a multimedia query # see ./test/data/desktop/software-center.menu recommends_cat = RecommendedForYouCategory(self.db, subcategory=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) self.assertNotEqual(recommendations_in_cat, []) def test_get_query(self): query = get_query_for_category(self.db, "Education") self.assertNotEqual(query, None) class TestCatParsing(unittest.TestCase): """ tests the "where is it in the menu" code """ def setUp(self): self.db = get_test_db() parser = CategoriesParser(self.db) self.cats = parser.parse_applications_menu( '/usr/share/app-install') def test_get_cat_by_name(self): cat = get_category_by_name(self.cats, 'Games') self.assertEqual(cat.untranslated_name, 'Games') cat = get_category_by_name(self.cats, 'Featured') self.assertEqual(cat.untranslated_name, 'Featured') def test_cat_has_flags(self): cat = get_category_by_name(self.cats, 'Featured') self.assertEqual(cat.flags[0], 'carousel-only') def test_get_documents(self): cat = get_category_by_name(self.cats, 'Featured') docs = cat.get_documents(self.db) self.assertNotEqual(docs, []) for doc in docs: self.assertEqual(type(doc), xapian.Document) class TestCategoryTemplates(unittest.TestCase): @classmethod def setUpClass(cls): cls.db = get_test_db() cls.parser = CategoriesParser(cls.db) cls.cats = cls.parser.parse_applications_menu(DATA_DIR) def test_category_debtags(self): cat = get_category_by_name(self.cats, 'Debtag') self.assertEqual( "%s" % cat.query, "Xapian::Query(( AND XTregion::de))") @patch("softwarecenter.db.categories.get_region_cached") def test_category_dynamic_categories(self, mock_get_region_cached): mock_get_region_cached.return_value = { "countrycode" : "us", } parser = CategoriesParser(self.db) cats = parser.parse_applications_menu(DATA_DIR, use_cache=False) cat = get_category_by_name(cats, 'Dynamic') self.assertEqual( "%s" % cat.query, "Xapian::Query(( AND XTregion::us))") if __name__ == "__main__": unittest.main() software-center-13.10/README.tests-dep80000664000202700020270000000133112151440100017713 0ustar dobeydobey00000000000000= Background = The testsuite is automatically run on the any a server that is responsible for running the DEP8 enabled testsuites for the debian packages. == Running locally == To test locally in a local VM: Build a VM that the tests can be run inside: $ bzr co lp:auto-package-testing $ cd auto-package-testing/bin $ ./prepare-testbed -r quantal i386 [downloads 200mb data and takes a long time to finish] $ ./run-adt-test software-center Login into the VM once and remove cloud-init to prevent delay on startup. Or login into the VM itself: $ kvm -snapshot -m 1024 /tmp/adt/disks/pristine-quantal-i386.img [wait a long time, login as ubuntu/ubuntu] $ bzr co lp:software-center # adt-run --built-tree=. --- adt-virt-null software-center-13.10/COPYING.LGPL0000664000202700020270000001672712151440100016643 0ustar dobeydobey00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.