Merge lp:~alexeftimie/software-center/packagekit-backend into lp:software-center

Proposed by Alex Eftimie
Status: Merged
Merged at revision: 2146
Proposed branch: lp:~alexeftimie/software-center/packagekit-backend
Merge into: lp:software-center
Prerequisite: lp:~alexeftimie/software-center/backend-refactor
Diff against target: 1955 lines (+1442/-54)
28 files modified
contrib/appstream-xml/appdata.xml (+11/-0)
run_local.sh (+1/-1)
software-center (+2/-3)
software-center-gtk3 (+10/-0)
softwarecenter/backend/channel.py (+87/-6)
softwarecenter/backend/installbackend.py (+11/-2)
softwarecenter/backend/packagekitd.py (+377/-0)
softwarecenter/backend/reviews.py (+0/-1)
softwarecenter/backend/transactionswatcher.py (+27/-2)
softwarecenter/db/appfilter.py (+6/-2)
softwarecenter/db/history.py (+6/-2)
softwarecenter/db/history_impl/apthistory.py (+0/-1)
softwarecenter/db/pkginfo.py (+9/-3)
softwarecenter/db/pkginfo_impl/packagekit.py (+257/-0)
softwarecenter/db/update.py (+7/-1)
softwarecenter/distro/SUSELINUX.py (+85/-0)
softwarecenter/distro/Ubuntu.py (+2/-0)
softwarecenter/distro/__init__.py (+2/-1)
softwarecenter/enums.py (+6/-2)
softwarecenter/ui/gtk/appdetailsview_gtk.py (+23/-2)
softwarecenter/ui/gtk/historypane.py (+3/-3)
softwarecenter/ui/gtk/pendingview.py (+18/-0)
softwarecenter/ui/gtk/viewswitcher.py (+12/-3)
softwarecenter/ui/gtk3/app.py (+1/-1)
softwarecenter/ui/gtk3/panes/installedpane.py (+426/-0)
softwarecenter/ui/gtk3/utils.py (+1/-1)
test/test_package_info.py (+40/-15)
utils/update-software-center (+12/-2)
To merge this branch: bzr merge lp:~alexeftimie/software-center/packagekit-backend
Reviewer Review Type Date Requested Status
Michael Vogt Needs Information
Review via email: mp+71446@code.launchpad.net

Description of the change

PackageKit backend implementation, made available via command-line option, --use-packagekit.

Merging this should not affect software-center (aptdaemon/aptcache) in any way, since PK is turned off by default.

I'm gathering documentation about it here: http://wiki.ubuntu.com/SoftwareCenter/PackagekitBackend

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks! That looks good, I plan to merge this (and another big branch) tomorrow and then make a 4.1.16 release with it. It would be nice if you could remerge with trunk, there are some trivial conflicts, but I'm happy to help resolving them too.

I also wonder if you have any ideas about how we could do some automatic tests for the PK backend. Is there something like the aptdaemon dummy mode that we could use to run a dummy PK on a private dbus and just expercise our backend code?

1902. By Alex Eftimie

merged from lp:software-center

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

The only testing as you can see, is done for the package information, by dynamically switching PK/AptCache in the existing test.

Dummy is a recent merge, I will look over it, and ask PK guys about a similar feature in PackageKit.

Revision history for this message
Michael Vogt (mvo) wrote :
Download full text (3.8 KiB)

Thanks! I merged with some small modifcations and pyflake fixes into lp:~mvo/software-center/packagekit-backend. I moved the backend code into the gtk3 version of s-c as it seems like
there are just too many issues mixing the old gtk2/pygtk and pygi code. This should be fine
as the gtk3 version is actually pretty good now.

I have a questions:
- in line 578 "package" is assigned but not used, what is this needef for?

I like that test_pkginfo.py now tests both implementations! However when I run it it segfaults on my up-to-date oneiric box:
#0 0x000000000045f0b5 in insertdict (mp=0x4cd8c00, key='firefox',
    hash=2252328806807869700, value=<unknown at remote 0x7fffffff3850>)
    at ../Objects/dictobject.c:526
#1 0x0000000000460496 in PyDict_SetItem (op={}, key=<optimized out>,
    value=<optimized out>) at ../Objects/dictobject.c:775
#2 0x00000000004b74b5 in PyEval_EvalFrameEx (f=<optimized out>,
    throwflag=<optimized out>) at ../Python/ceval.c:1706
#3 0x00000000004bcced in PyEval_EvalCodeEx (co=0xd2d730,
    globals=<optimized out>, locals=<optimized out>, args=<optimized out>,
    argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0xd324e8,
    defcount=2, closure=0x0) at ../Python/ceval.c:3253
#4 0x00000000004b69db in fast_function (nk=<optimized out>, na=2,
    n=<optimized out>, pp_stack=0x7fffffffb9a0, func=
    <function at remote 0xe8a1b8>) at ../Python/ceval.c:4117
#5 call_function (oparg=<optimized out>, pp_stack=0x7fffffffb9a0)
    at ../Python/ceval.c:4042
#6 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>)
    at ../Python/ceval.c:2666
#7 0x00000000004b6cf7 in fast_function (nk=<optimized out>,
    na=<optimized out>, n=<optimized out>, pp_stack=0x7fffffffbae0, func=
    <function at remote 0xe1bf50>) at ../Python/ceval.c:4107
#8 call_function (oparg=<optimized out>, pp_stack=0x7fffffffbae0)
    at ../Python/ceval.c:4042
#9 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>)
    at ../Python/ceval.c:2666
#10 0x00000000004b6cf7 in fast_function (nk=<optimized out>,
    na=<optimized out>, n=<optimized out>, pp_stack=0x7fffffffbc20, func=
    <function at remote 0xe8a5f0>) at ../Python/ceval.c:4107
#11 call_function (oparg=<optimized out>, pp_stack=0x7fffffffbc20)
    at ../Python/ceval.c:4042
#12 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>)
    at ../Python/ceval.c:2666
#13 0x00000000004bcced in PyEval_EvalCodeEx (co=0x7ffff7e1aeb0,
    globals=<optimized out>, locals=<optimized out>, args=<optimized out>,
    argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=
    0x7ffff7e477a8, defcount=1, closure=0x0) at ../Python/ceval.c:3253
#14 0x0000000000449098 in function_call (func=<optimized out>, arg=
    (<TestPkgInfoPackagekit(_testMethodName='test_addons', pkginfo=<PackagekitInfo(_cache={}, _notfound_cache=[], client=<Client at remote 0x51b1be0>, distro=<Ubuntu at remote 0xd1c4d0>) at remote 0x51b1b40>, _cleanups=[], _type_equality_funcs={<type at remote 0x844a00>: 'assertListEqual', <type at remote 0x84e000>: 'assertTupleEqual', <type at remote 0x846c60>: 'assertDictEqual', <type at remote 0x8492c0>: 'assertSetEqual', <type at rem...

Read more...

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

fwiw I have python-gobject 2.28.6-4 installed and PK 0.6.15-1build1

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

hi,

as specified in the wiki, pygobject 2.90 and PK > 0.6.16 are required.

I'l work with ximion a ppa version asap (since i kinda failed my OBS attempt).

On 8/17/11, Michael Vogt <email address hidden> wrote:
> fwiw I have python-gobject 2.28.6-4 installed and PK 0.6.15-1build1
> --
> https://code.launchpad.net/~alexeftimie/software-center/packagekit-backend/+merge/71446
> You are the owner of lp:~alexeftimie/software-center/packagekit-backend.
>

--
Alex Eftimie

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

hi,

as specified in the wiki, pygobject 2.90 and PK > 0.6.16 are required.

I'l work with ximion a ppa version asap (since i kinda failed my OBS attempt).

--
Alex Eftimie

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

Regarding line 578, package assignment isn't needed; it remained there, from a previous changset, when I was taking the package signal into consideration; thing is, it isn't of much use, since, while installing an application more than one package are emitted by PK (for example, firefox means firefox and firefox-globalmenu); the current package being processed isn't displayed in u-s-c.

What replaces it is the [sc_pkgname] metadata.

I'll fix it as soon as I get to my computer.

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

Is s-c-gtk3 broken with newer pygobject (2.90.1)? I think I saw this in a commit log, but I'm unsure, and cannot test right now.

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

Sorry for overlooking the incorrect PK dependency. I installed 0.6.16 from the debian packaging git branch now and that fixes the crash. However when I just run "python test_package_info.py" I get a bunch of test failures like:

(process:31863): PackageKit-WARNING **: failed to set package id for
(process:31863): PackageKit-DEBUG: properties changed, so getting new list
(process:31863): PackageKit-DEBUG: notify::locked
(process:31863): PackageKit-DEBUG: properties changed, so getting new list
(process:31863): PackageKit-DEBUG: emit transaction-list-changed (when idle)
DEBUG:softwarecenter.db.packagekit:blacklisted coreutils
FDEBUG:softwarecenter.db.packagekit:package_one coreutils
(process:31863): PackageKit-DEBUG: role now resolve
(process:31863): PackageKit-DEBUG: notify::locked
(process:31863): PackageKit-DEBUG: emit transaction-list-changed (when idle)
(process:31863): PackageKit-DEBUG: already processing request 0x1, so ignoring
(process:31863): PackageKit-DEBUG: already processing request 0x1, so ignoring

FAIL: test_pkg_info (__main__.TestPkgInfoPackagekit)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_package_info.py", line 42, in test_pkg_info
    self.assertTrue(pkginfo.is_installed("coreutils"))
AssertionError: False is not true

Any hints for me?

And when I click on the pkg details I get:(software-center-gtk3:31972): PackageKit-DEBUG: emit transaction-list-changed (when idle)
Traceback (most recent call last):
  File "/home/egon/devel/software-center/packagekit/softwarecenter/utils.py", line 109, in wrapper
    f(*args, **kwargs)
  File "/home/egon/devel/software-center/packagekit/softwarecenter/ui/gtk3/panes/availablepane.py", line 620, in on_application_activated
    self.display_details_page)
  File "/home/egon/devel/software-center/packagekit/softwarecenter/ui/gtk3/session/viewmanager.py", line 147, in display_page
    callback(page, view_state)
  File "/home/egon/devel/software-center/packagekit/softwarecenter/ui/gtk3/panes/availablepane.py", line 554, in display_details_page
    SoftwarePane.display_details_page(self, page, view_state)
  File "/home/egon/devel/software-center/packagekit/softwarecenter/ui/gtk3/panes/softwarepane.py", line 659, in display_details_page
    self.app_details_view.show_app(view_state.application)
  File "/home/egon/devel/software-center/packagekit/softwarecenter/ui/gtk3/views/appdetailsview_gtk.py", line 1418, in show_app
    self.pkg_state = self.app_details.pkg_state
  File "/home/egon/devel/software-center/packagekit/softwarecenter/db/application.py", line 436, in pkg_state
    if component and self._unavailable_component(component_to_check=component):
  File "/home/egon/devel/software-center/packagekit/softwarecenter/db/application.py", line 553, in _unavailable_component
    available = self._cache.component_available(distro_codename, component)
AttributeError: 'PackagekitInfo' object has no attribute 'component_available'
(software-center-gtk3:31972): PackageKit-DEBUG: notify::locked

Is still something outdated on my side?

Thanks,
 Michael

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

first, the warnings are "normal", I also see them when running the PK
backend. Must check with hugsie on this one.

Second, the component_available problem is on my side, I should fix it
in the backend code. I couldn't give -gtk3 much testing yet, due to
problems running it in natty (outdated libpango f.e.), incomplete
oneiric setup and fast development.

I will merge your branch, fix the gtk3 inconsistency and resubmit it.

--
Alex Eftimie

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

On Thu, Aug 18, 2011 at 08:02:14AM -0000, Alex Eftimie wrote:
> first, the warnings are "normal", I also see them when running the PK
> backend. Must check with hugsie on this one.

Thanks!

> Second, the component_available problem is on my side, I should fix it
> in the backend code. I couldn't give -gtk3 much testing yet, due to
> problems running it in natty (outdated libpango f.e.), incomplete
> oneiric setup and fast development.

I fixed the component_available() one now in my branch by just adding
a stub. It still does not find the package though for some reason.

Now I reinstalled the packagekit 0.6.16 from the git repo of debian
and with that I get the segfault again. I checked and I'm pretty sure
that its 0.6.16. Its a bit of a puzzle because previously it would not
crash. I keep investigating, oneiric is really moving fast.

> I will merge your branch, fix the gtk3 inconsistency and resubmit it.

Thanks!

Cheers,
 Michael

1903. By Alex Eftimie

merged from lp:~mvo/software-center/packagekit-backend

1904. By Alex Eftimie

merged from trunk

1905. By Alex Eftimie

pass addons and section in packagekit.

addons are not implemented yet

section does not have an equivalent

1906. By Alex Eftimie

merged with lp:software-center

1907. By Alex Eftimie

merged with lp:software-center

1908. By Alex Eftimie

toggle apt history and fix installed view cache problem

Revision history for this message
Alex Eftimie (alexeftimie) wrote :

Running with latest pygobject/gobject-introspection from Desktop Team
PPA, segfaults are gone.

Please reconsider the merge :)

Alex

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

Merged now! Many thanks. Performance is currently not good as the treeview checks dynamically what packages are installed or not. It would be really great if that could be improved.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'contrib/appstream-xml/appdata.xml'
--- contrib/appstream-xml/appdata.xml 2011-05-30 14:12:12 +0000
+++ contrib/appstream-xml/appdata.xml 2011-08-19 10:38:33 +0000
@@ -30,5 +30,16 @@
30 </mimetypes>30 </mimetypes>
31 <url type="homepage">http://www.mozilla.com</url>31 <url type="homepage">http://www.mozilla.com</url>
32 </application>32 </application>
33 <application>
34 <id type="desktop">cheese.desktop</id>
35 <pkgname>cheese</pkgname>
36 <name>Cheese</name>
37 <summary>Take photos and videos with your webcam, with fun graphical effects</summary>
38 <icon type="local">cheese</icon>
39 <appcategories>
40 <appcategory>GNOME</appcategory>
41 <appcategory>AudioVideo</appcategory>
42 </appcategories>
43 </application>
33 <!-- more applications here! -->44 <!-- more applications here! -->
34</applications>45</applications>
3546
=== modified file 'run_local.sh'
--- run_local.sh 2011-05-27 08:25:15 +0000
+++ run_local.sh 2011-08-19 10:38:33 +0000
@@ -9,7 +9,7 @@
9python /usr/lib/ubuntu-sso-client/ubuntu-sso-login &9python /usr/lib/ubuntu-sso-client/ubuntu-sso-login &
1010
11# s-c11# s-c
12export PYTHONPATH=$(pwd)12export PYTHONPATH=$(pwd):$PYTHONPATH
1313
14if [ ! -d "./build" ]; then14if [ ! -d "./build" ]; then
15 echo "Please run: 'python setup.py build' before $0"15 echo "Please run: 'python setup.py build' before $0"
1616
=== modified file 'software-center'
--- software-center 2011-08-17 10:37:51 +0000
+++ software-center 2011-08-19 10:38:33 +0000
@@ -42,7 +42,6 @@
4242
43from softwarecenter.enums import *43from softwarecenter.enums import *
44from softwarecenter.paths import XAPIAN_BASE_PATH44from softwarecenter.paths import XAPIAN_BASE_PATH
45from softwarecenter.utils import ExecutionTime
46from softwarecenter.version import *45from softwarecenter.version import *
4746
48import softwarecenter.log47import softwarecenter.log
@@ -79,7 +78,6 @@
79 parser.add_option("--dummy-backend", action="store_true",78 parser.add_option("--dummy-backend", action="store_true",
80 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",79 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",
81 default=False)80 default=False)
82
83 (options, args) = parser.parse_args()81 (options, args) = parser.parse_args()
8482
85 # statup time measure implies "performance" in debug filters83 # statup time measure implies "performance" in debug filters
@@ -134,6 +132,8 @@
134132
135 # create the app133 # create the app
136 from softwarecenter.ui.gtk.app import SoftwareCenterApp134 from softwarecenter.ui.gtk.app import SoftwareCenterApp
135 from softwarecenter.utils import ExecutionTime
136
137 with ExecutionTime("create SoftwareCenterApp"):137 with ExecutionTime("create SoftwareCenterApp"):
138 app = SoftwareCenterApp(datadir, xapian_base_path, options, args)138 app = SoftwareCenterApp(datadir, xapian_base_path, options, args)
139139
@@ -154,4 +154,3 @@
154154
155 # run it normally155 # run it normally
156 app.run(args)156 app.run(args)
157
158157
=== modified file 'software-center-gtk3'
--- software-center-gtk3 2011-08-15 11:25:29 +0000
+++ software-center-gtk3 2011-08-19 10:38:33 +0000
@@ -81,6 +81,9 @@
81 parser.add_option("--dummy-backend", action="store_true",81 parser.add_option("--dummy-backend", action="store_true",
82 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",82 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",
83 default=False)83 default=False)
84 parser.add_option("--packagekit-backend", action="store_true",
85 help="use PackageKit backend (experimental)",
86 default=False)
8487
85 (options, args) = parser.parse_args()88 (options, args) = parser.parse_args()
8689
@@ -97,6 +100,13 @@
97 softwarecenter.log.root.setLevel(level=logging.DEBUG)100 softwarecenter.log.root.setLevel(level=logging.DEBUG)
98 else:101 else:
99 softwarecenter.log.root.setLevel(level=logging.INFO)102 softwarecenter.log.root.setLevel(level=logging.INFO)
103
104 # packagekit
105 if options.packagekit_backend:
106 softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True
107 logging.info("Using PackageKit backend")
108 else:
109 softwarecenter.enums.USE_PACKAGEKIT_BACKEND = False
100110
101 # dummy backend111 # dummy backend
102 if options.dummy_backend:112 if options.dummy_backend:
103113
=== modified file 'softwarecenter/backend/channel.py'
--- softwarecenter/backend/channel.py 2011-08-09 08:47:43 +0000
+++ softwarecenter/backend/channel.py 2011-08-19 10:38:33 +0000
@@ -26,23 +26,100 @@
2626
27from softwarecenter.enums import (SortMethods, 27from softwarecenter.enums import (SortMethods,
28 Icons,28 Icons,
29 ViewPages,
29 )30 )
3031
31LOG = logging.getLogger(__name__)32LOG = logging.getLogger(__name__)
3233
33class ChannelsManager(object):34class ChannelsManager(object):
35 def __init__(self, db, **kwargs):
36 self.distro = get_distro()
37 self.db = db
38
34 @property39 @property
35 def channels(self):40 def channels(self):
36 return []41 return self._get_channels_from_db()
37 42
38 @property43 @property
39 def channels_installed_only(self):44 def channels_installed_only(self):
40 return []45 return self._get_channels_from_db(True)
4146
42 @staticmethod47 @classmethod
43 def channel_available(channelname):48 def channel_available(kls, channelname):
44 pass49 pass
4550
51 def _get_channels_from_db(self, installed_only=False):
52 """
53 (internal) implements 'channels()' and 'channels_installed_only()' properties
54 """
55 distro_channel_name = self.distro.get_distro_channel_name()
56
57 # gather the set of software channels and order them
58 other_channel_list = []
59 cached_origins = []
60 for channel_iter in self.db.xapiandb.allterms("XOL"):
61 if len(channel_iter.term) == 3:
62 continue
63 channel_name = channel_iter.term[3:]
64 channel_origin = ""
65
66 # get origin information for this channel
67 m = self.db.xapiandb.postlist_begin(channel_iter.term)
68 doc = self.db.xapiandb.get_document(m.get_docid())
69 for term_iter in doc.termlist():
70 if term_iter.term.startswith("XOO") and len(term_iter.term) > 3:
71 channel_origin = term_iter.term[3:]
72 break
73 LOG.debug("channel_name: %s" % channel_name)
74 LOG.debug("channel_origin: %s" % channel_origin)
75 if channel_origin not in cached_origins:
76 other_channel_list.append((channel_name, channel_origin))
77 cached_origins.append(channel_origin)
78
79 dist_channel = None
80 other_channels = []
81 unknown_channel = []
82 local_channel = None
83
84 for (channel_name, channel_origin) in other_channel_list:
85 if not channel_name:
86 unknown_channel.append(SoftwareChannel(channel_name,
87 channel_origin,
88 None,
89 installed_only=installed_only))
90 elif channel_name == distro_channel_name:
91 dist_channel = (SoftwareChannel(distro_channel_name,
92 channel_origin,
93 None,
94 installed_only=installed_only))
95 elif channel_name == "notdownloadable":
96 if installed_only:
97 local_channel = SoftwareChannel(channel_name,
98 None,
99 None,
100 installed_only=installed_only)
101 else:
102 other_channels.append(SoftwareChannel(channel_name,
103 channel_origin,
104 None,
105 installed_only=installed_only))
106
107 # set them in order
108 channels = []
109 if dist_channel is not None:
110 channels.append(dist_channel)
111 channels.extend(other_channels)
112 channels.extend(unknown_channel)
113 if local_channel is not None:
114 channels.append(local_channel)
115
116 for channel in channels:
117 if installed_only:
118 channel._channel_view_id = ViewPages.INSTALLED
119 else:
120 channel._channel_view_id = ViewPages.AVAILABLE
121 return channels
122
46class SoftwareChannel(object):123class SoftwareChannel(object):
47 """124 """
48 class to represent a software channel125 class to represent a software channel
@@ -239,8 +316,12 @@
239def get_channels_manager(db):316def get_channels_manager(db):
240 global channels_manager317 global channels_manager
241 if channels_manager is None:318 if channels_manager is None:
242 from softwarecenter.backend.aptchannels import AptChannelsManager319 from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
243 channels_manager = AptChannelsManager(db)320 if not USE_PACKAGEKIT_BACKEND:
321 from softwarecenter.backend.aptchannels import AptChannelsManager
322 channels_manager = AptChannelsManager(db)
323 else:
324 channels_manager = ChannelsManager(db)
244 return channels_manager325 return channels_manager
245326
246def is_channel_available(channelname):327def is_channel_available(channelname):
247328
=== modified file 'softwarecenter/backend/installbackend.py'
--- softwarecenter/backend/installbackend.py 2011-07-05 07:26:22 +0000
+++ softwarecenter/backend/installbackend.py 2011-08-19 10:38:33 +0000
@@ -19,6 +19,10 @@
19from softwarecenter.utils import UnimplementedError19from softwarecenter.utils import UnimplementedError
2020
21class InstallBackend(object):21class InstallBackend(object):
22 def __init__(self):
23 self.pending_transactions = {}
24 self.pending_purchases = []
25
22 def upgrade(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):26 def upgrade(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):
23 pass27 pass
24 def remove(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):28 def remove(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):
@@ -56,8 +60,13 @@
56def get_install_backend():60def get_install_backend():
57 global install_backend61 global install_backend
58 if install_backend is None:62 if install_backend is None:
59 from softwarecenter.backend.aptd import AptdaemonBackend63 from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
60 install_backend = AptdaemonBackend()64 if not USE_PACKAGEKIT_BACKEND:
65 from softwarecenter.backend.aptd import AptdaemonBackend
66 install_backend = AptdaemonBackend()
67 else:
68 from softwarecenter.backend.packagekitd import PackagekitBackend
69 install_backend = PackagekitBackend()
61 return install_backend70 return install_backend
6271
63 72
6473
=== added file 'softwarecenter/backend/packagekitd.py'
--- softwarecenter/backend/packagekitd.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/backend/packagekitd.py 2011-08-19 10:38:33 +0000
@@ -0,0 +1,377 @@
1# Copyright (C) 2009-2010 Canonical
2#
3# Authors:
4# Alex Eftimie
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; version 3.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13# details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19import logging
20import dbus
21import dbus.mainloop.glib
22
23from gi.repository import GObject
24from gi.repository import PackageKitGlib as packagekit
25
26from softwarecenter.enums import TransactionTypes
27from softwarecenter.backend.transactionswatcher import (BaseTransactionsWatcher,
28 BaseTransaction,
29 TransactionFinishedResult,
30 TransactionProgress)
31from softwarecenter.backend.installbackend import InstallBackend
32
33# temporary, must think of better solution
34from softwarecenter.db.pkginfo import get_pkg_info
35
36LOG = logging.getLogger("softwarecenter.backend.packagekit")
37
38class PackagekitTransaction(BaseTransaction):
39 _meta_data = {}
40
41 def __init__(self, trans):
42 """ trans -- a PkProgress object """
43 GObject.GObject.__init__(self)
44 self._trans = trans
45 self._setup_signals()
46
47 def _setup_signals(self):
48 """ Connect signals to the PkProgress from libpackagekitlib,
49 because PK DBus exposes only a generic Changed, without
50 specifying the property changed
51 """
52 self._trans.connect('notify::role', self._emit, 'role-changed', 'role')
53 self._trans.connect('notify::status', self._emit, 'status-changed', 'status')
54 self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
55 #self._trans.connect('notify::subpercentage', self._emit, 'progress-changed', 'subpercentage') # SC UI does not support subprogress
56 self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
57 self._trans.connect('notify::allow-cancel', self._emit, 'cancellable-changed', 'allow-cancel')
58
59 # connect the delete:
60 proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
61 trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
62 trans.connect_to_signal("Destroy", self._remove)
63
64 def _emit(self, *args):
65 prop, what = args[-1], args[-2]
66 self.emit(what, self._trans.get_property(prop))
67
68 @property
69 def tid(self):
70 return self._trans.get_property('transaction-id')
71 @property
72 def status_details(self):
73 return self.get_status_description() # FIXME
74 @property
75 def meta_data(self):
76 return self._meta_data
77 @property
78 def cancellable(self):
79 return self._trans.get_property('allow-cancel')
80 @property
81 def progress(self):
82 return self._trans.get_property('percentage')
83
84 def get_role_description(self, role=None):
85 role = role if role is not None else self._trans.get_property('role')
86 return self.meta_data.get('sc_appname', packagekit.role_enum_to_string(role))
87
88 def get_status_description(self, status=None):
89 status = status if status is not None else self._trans.get_property('status')
90 return packagekit.status_enum_to_string(status)
91
92 def is_waiting(self):
93 """ return true if a time consuming task is taking place """
94 #LOG.debug('is_waiting ' + str(self._trans.get_property('status')))
95 status = self._trans.get_property('status')
96 return status == packagekit.StatusEnum.WAIT or \
97 status == packagekit.StatusEnum.LOADING_CACHE or \
98 status == packagekit.StatusEnum.SETUP
99
100 def is_downloading(self):
101 #LOG.debug('is_downloading ' + str(self._trans.get_property('status')))
102 status = self._trans.get_property('status')
103 return status == packagekit.StatusEnum.DOWNLOAD or \
104 (status >= packagekit.StatusEnum.DOWNLOAD_REPOSITORY and \
105 status <= packagekit.StatusEnum.DOWNLOAD_UPDATEINFO)
106
107 def cancel(self):
108 proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
109 trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
110 trans.Cancel()
111
112 def _remove(self):
113 """ delete transaction from _tlist """
114 # also notify pk install backend, so that this transaction gets removed
115 # from pending_transactions
116 self.emit('deleted')
117 if self.tid in PackagekitTransactionsWatcher._tlist.keys():
118 del PackagekitTransactionsWatcher._tlist[self.tid]
119 LOG.debug("Delete transaction %s" % self.tid)
120
121class PackagekitTransactionsWatcher(BaseTransactionsWatcher):
122 _tlist = {}
123
124 def __init__(self):
125 super(PackagekitTransactionsWatcher, self).__init__()
126 self.client = packagekit.Client()
127
128 bus = dbus.SystemBus()
129 proxy = bus.get_object('org.freedesktop.PackageKit', '/org/freedesktop/PackageKit')
130 daemon = dbus.Interface(proxy, 'org.freedesktop.PackageKit')
131 daemon.connect_to_signal("TransactionListChanged",
132 self._on_transactions_changed)
133 queued = daemon.GetTransactionList()
134 self._on_transactions_changed(queued)
135
136 def _on_transactions_changed(self, queued):
137 if len(queued) > 0:
138 current = queued[0]
139 queued = queued[1:] if len(queued) > 1 else []
140 else:
141 current = None
142 self.emit("lowlevel-transactions-changed", current, queued)
143
144 def add_transaction(self, tid, trans):
145 """ return a tuple, (transaction, is_new) """
146 if tid not in PackagekitTransactionsWatcher._tlist.keys():
147 LOG.debug("Trying to setup %s" % tid)
148 if not trans:
149 trans = self.client.get_progress(tid, None)
150 trans = PackagekitTransaction(trans)
151 LOG.debug("Add return new transaction %s %s" % (tid, trans))
152 PackagekitTransactionsWatcher._tlist[tid] = trans
153 return (trans, True)
154 return (PackagekitTransactionsWatcher._tlist[tid], False)
155
156 def get_transaction(self, tid):
157 if tid not in PackagekitTransactionsWatcher._tlist.keys():
158 trans, new = self.add_transaction(tid, None)
159 return trans
160 return PackagekitTransactionsWatcher._tlist[tid]
161
162class PackagekitBackend(GObject.GObject, InstallBackend):
163
164 __gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST,
165 GObject.TYPE_NONE,
166 (str,str,str,str)),
167 # emits a TransactionFinished object
168 'transaction-finished':(GObject.SIGNAL_RUN_FIRST,
169 GObject.TYPE_NONE,
170 (GObject.TYPE_PYOBJECT, )),
171 'transaction-stopped':(GObject.SIGNAL_RUN_FIRST,
172 GObject.TYPE_NONE,
173 (GObject.TYPE_PYOBJECT,)),
174 'transactions-changed':(GObject.SIGNAL_RUN_FIRST,
175 GObject.TYPE_NONE,
176 (GObject.TYPE_PYOBJECT, )),
177 'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST,
178 GObject.TYPE_NONE,
179 (str,int,)),
180 # the number/names of the available channels changed
181 # FIXME: not emitted.
182 'channels-changed':(GObject.SIGNAL_RUN_FIRST,
183 GObject.TYPE_NONE,
184 (bool,)),
185 }
186
187 def __init__(self):
188 GObject.GObject.__init__(self)
189 InstallBackend.__init__(self)
190
191 # transaction details for setting as meta
192 self.new_pkgname, self.new_appname, self.new_iconname = '', '', ''
193
194 # this is public exposed
195 self.pending_transactions = {}
196
197 self.client = packagekit.Client()
198 self.pkginfo = get_pkg_info()
199 self.pkginfo.open()
200
201 self._transactions_watcher = PackagekitTransactionsWatcher()
202 self._transactions_watcher.connect('lowlevel-transactions-changed',
203 self._on_lowlevel_transactions_changed)
204
205 def upgrade(self, pkgname, appname, iconname, addons_install=[],
206 addons_remove=[], metadata=None):
207 pass # FIXME implement it
208 def remove(self, pkgname, appname, iconname, addons_install=[],
209 addons_remove=[], metadata=None):
210 self.remove_multiple((pkgname,), (appname,), (iconname,),
211 addons_install, addons_remove, metadata
212 )
213
214 def remove_multiple(self, pkgnames, appnames, iconnames,
215 addons_install=[], addons_remove=[], metadatas=None):
216
217 # keep track of pkg, app and icon for setting them as meta
218 self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
219
220 # temporary hack
221 pkgnames = self._fix_pkgnames(pkgnames)
222
223 self.client.remove_packages_async(pkgnames,
224 False, # allow deps
225 False, # autoremove
226 None, # cancellable
227 self._on_progress_changed,
228 None, # progress data
229 self._on_remove_ready, # callback ready
230 None # callback data
231 )
232 self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.REMOVE)
233
234 def install(self, pkgname, appname, iconname, filename=None,
235 addons_install=[], addons_remove=[], metadata=None):
236 if filename is not None:
237 LOG.error("Filename not implemented") # FIXME
238 else:
239 self.install_multiple((pkgname,), (appname,), (iconname,),
240 addons_install, addons_remove, metadata
241 )
242
243 def install_multiple(self, pkgnames, appnames, iconnames,
244 addons_install=[], addons_remove=[], metadatas=None):
245
246 # keep track of pkg, app and icon for setting them as meta
247 self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
248
249 # temporary hack
250 pkgnames = self._fix_pkgnames(pkgnames)
251
252 self.client.install_packages_async(False, # only trusted
253 pkgnames,
254 None, # cancellable
255 self._on_progress_changed,
256 None, # progress data
257 self._on_install_ready, # GAsyncReadyCallback
258 None # ready data
259 )
260 self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.INSTALL)
261
262 def apply_changes(self, pkgname, appname, iconname,
263 addons_install=[], addons_remove=[], metadata=None):
264 pass
265 def reload(self, sources_list=None, metadata=None):
266 """ reload package list """
267 pass
268
269 def _on_transaction_deleted(self, trans):
270 name = trans.meta_data.get('sc_pkgname', '')
271 if name in self.pending_transactions:
272 del self.pending_transactions[name]
273 LOG.debug("Deleted transaction " + name)
274 else:
275 LOG.error("Could not delete: " + name + str(trans))
276 # this is needed too
277 self.emit('transactions-changed', self.pending_transactions)
278 # also hack PackagekitInfo cache so that it emits a cache-ready signal
279 if hasattr(self.pkginfo, '_reset_cache'):
280 self.pkginfo._reset_cache(name)
281
282 def _on_progress_changed(self, progress, ptype, data=None):
283 """ de facto callback on transaction's progress change """
284 tid = progress.get_property('transaction-id')
285 status = progress.get_property('status')
286 if not tid:
287 LOG.debug("Progress without transaction")
288 return
289
290 trans, new = self._transactions_watcher.add_transaction(tid, progress)
291 if new:
292 trans.connect('deleted', self._on_transaction_deleted)
293 LOG.debug("new transaction" + str(trans))
294 # should add it to pending_transactions, but
295 # i cannot get the pkgname here
296 trans.meta_data['sc_appname'] = self.new_appname
297 trans.meta_data['sc_pkgname'] = self.new_pkgname
298 trans.meta_data['sc_iconname'] = self.new_iconname
299 if self.new_pkgname not in self.pending_transactions:
300 self.pending_transactions[self.new_pkgname] = trans
301
302 #LOG.debug("Progress update %s %s %s %s" % (status, ptype, progress.get_property('transaction-id'),progress.get_property('status')))
303
304 if status == packagekit.StatusEnum.FINISHED:
305 LOG.debug("Transaction finished %s" % tid)
306 self.emit("transaction-finished", TransactionFinishedResult(trans, True))
307
308 if status == packagekit.StatusEnum.CANCEL:
309 LOG.debug("Transaction canceled %s" % tid)
310 self.emit("transaction-stopped", TransactionFinishedResult(trans, True))
311
312 if ptype == packagekit.ProgressType.PACKAGE:
313 # this should be done better
314 # mvo: why getting package here at all?
315 #package = progress.get_property('package')
316 # fool sc ui about the name change
317 trans.emit('role-changed', packagekit.RoleEnum.LAST)
318
319 if ptype == packagekit.ProgressType.PERCENTAGE:
320 pkgname = trans.meta_data.get('sc_pkgname', '')
321 prog = progress.get_property('percentage')
322 if prog >= 0:
323 self.emit("transaction-progress-changed", pkgname, prog)
324 else:
325 self.emit("transaction-progress-changed", pkgname, 0)
326
327 def _on_lowlevel_transactions_changed(self, watcher, current, pending):
328 # update self.pending_transactions
329 self.pending_transactions.clear()
330
331 for tid in [current] + pending:
332 if not tid:
333 continue
334 trans = self._transactions_watcher.get_transaction(tid)
335 trans_progress = TransactionProgress(trans)
336 try:
337 self.pending_transactions[trans_progress.pkgname] = trans_progress
338 except:
339 self.pending_transactions[trans.tid] = trans_progress
340
341 self.emit('transactions-changed', self.pending_transactions)
342
343 def _on_install_ready(self, source, result, data=None):
344 LOG.debug("install done %s %s", source, result)
345
346 def _on_remove_ready(self, source, result, data=None):
347 LOG.debug("remove done %s %s", source, result)
348
349 def _fix_pkgnames(self, pkgnames):
350 is_pk_id = lambda a: ';' in a
351 res = []
352 for p in pkgnames:
353 if not is_pk_id(p):
354 version = self.pkginfo[p].candidate.version
355 p = '{name};{version};{arch};{source}'.format(name=p,
356 version=version, arch='', source=''
357 )
358 res.append(p)
359 return res
360
361if __name__ == "__main__":
362 package = 'firefox'
363
364 loop = dbus.mainloop.glib.DBusGMainLoop()
365 dbus.set_default_main_loop(loop)
366
367 backend = PackagekitBackend()
368 pkginfo = get_pkg_info()
369 if pkginfo[package].is_installed:
370 backend.remove(package, package, '')
371 backend.install(package, package, '')
372 else:
373 backend.install(package, package, '')
374 backend.remove(package, package, '')
375 import gtk;gtk.main()
376 #print backend._fix_pkgnames(('cheese',))
377
0378
=== modified file 'softwarecenter/backend/reviews.py'
--- softwarecenter/backend/reviews.py 2011-08-19 09:08:47 +0000
+++ softwarecenter/backend/reviews.py 2011-08-19 10:38:33 +0000
@@ -57,7 +57,6 @@
57 from StringIO import StringIO57 from StringIO import StringIO
58 from urllib import quote_plus58 from urllib import quote_plus
5959
60
61from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI60from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI
62from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails61from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails
63from softwarecenter.db.categories import CategoriesParser62from softwarecenter.db.categories import CategoriesParser
6463
=== modified file 'softwarecenter/backend/transactionswatcher.py'
--- softwarecenter/backend/transactionswatcher.py 2011-08-17 12:24:39 +0000
+++ softwarecenter/backend/transactionswatcher.py 2011-08-19 10:38:33 +0000
@@ -28,6 +28,26 @@
28 """28 """
29 wrapper class for install backend dbus Transaction objects29 wrapper class for install backend dbus Transaction objects
30 """30 """
31 __gsignals__ = {'progress-details-changed':(GObject.SIGNAL_RUN_FIRST,
32 GObject.TYPE_NONE,
33 (int, int, int, int, int, int)),
34 'progress-changed':(GObject.SIGNAL_RUN_FIRST,
35 GObject.TYPE_NONE,
36 (GObject.TYPE_PYOBJECT, )),
37 'status-changed':(GObject.SIGNAL_RUN_FIRST,
38 GObject.TYPE_NONE,
39 (GObject.TYPE_PYOBJECT, )),
40 'cancellable-changed':(GObject.SIGNAL_RUN_FIRST,
41 GObject.TYPE_NONE,
42 (GObject.TYPE_PYOBJECT, )),
43 'role-changed':(GObject.SIGNAL_RUN_FIRST,
44 GObject.TYPE_NONE,
45 (GObject.TYPE_PYOBJECT, )),
46 'deleted':(GObject.SIGNAL_RUN_FIRST,
47 GObject.TYPE_NONE,
48 []),
49 }
50
31 @property51 @property
32 def tid(self):52 def tid(self):
33 pass53 pass
@@ -99,6 +119,11 @@
99def get_transactions_watcher():119def get_transactions_watcher():
100 global _tw120 global _tw
101 if _tw is None:121 if _tw is None:
102 from aptd import AptdaemonTransactionsWatcher122 from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
103 _tw = AptdaemonTransactionsWatcher()123 if not USE_PACKAGEKIT_BACKEND:
124 from aptd import AptdaemonTransactionsWatcher
125 _tw = AptdaemonTransactionsWatcher()
126 else:
127 from softwarecenter.backend.packagekitd import PackagekitTransactionsWatcher
128 _tw = PackagekitTransactionsWatcher()
104 return _tw129 return _tw
105130
=== modified file 'softwarecenter/db/appfilter.py'
--- softwarecenter/db/appfilter.py 2011-08-07 07:55:40 +0000
+++ softwarecenter/db/appfilter.py 2011-08-19 10:38:33 +0000
@@ -63,8 +63,12 @@
63 return False63 return False
64 if self.installed_only:64 if self.installed_only:
65 # use the lowlevel cache here, twice as fast65 # use the lowlevel cache here, twice as fast
66 lowlevel_cache = self.cache._cache._cache66 try:
67 if (not pkgname in lowlevel_cache or67 lowlevel_cache = self.cache._cache._cache
68 except AttributeError:
69 lowlevel_cache = None
70 if lowlevel_cache and \
71 (not pkgname in lowlevel_cache or
68 not lowlevel_cache[pkgname].current_ver):72 not lowlevel_cache[pkgname].current_ver):
69 return False73 return False
70 if self.not_installed_only:74 if self.not_installed_only:
7175
=== modified file 'softwarecenter/db/history.py'
--- softwarecenter/db/history.py 2011-05-28 09:28:30 +0000
+++ softwarecenter/db/history.py 2011-08-19 10:38:33 +0000
@@ -105,6 +105,10 @@
105 """ get the global PackageHistory() singleton object """105 """ get the global PackageHistory() singleton object """
106 global pkg_history106 global pkg_history
107 if pkg_history is None:107 if pkg_history is None:
108 from history_impl.apthistory import AptHistory108 from softwarecenter.enums import USE_APT_HISTORY
109 pkg_history = AptHistory()109 if USE_APT_HISTORY:
110 from history_impl.apthistory import AptHistory
111 pkg_history = AptHistory()
112 else:
113 pkg_history = PackageHistory()
110 return pkg_history114 return pkg_history
111115
=== modified file 'softwarecenter/db/history_impl/apthistory.py'
--- softwarecenter/db/history_impl/apthistory.py 2011-08-19 08:46:09 +0000
+++ softwarecenter/db/history_impl/apthistory.py 2011-08-19 10:38:33 +0000
@@ -44,7 +44,6 @@
44except ImportError:44except ImportError:
45 import pickle45 import pickle
4646
47
48LOG = logging.getLogger(__name__)47LOG = logging.getLogger(__name__)
4948
50from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR49from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
5150
=== modified file 'softwarecenter/db/pkginfo.py'
--- softwarecenter/db/pkginfo.py 2011-08-17 12:24:39 +0000
+++ softwarecenter/db/pkginfo.py 2011-08-19 10:38:33 +0000
@@ -138,7 +138,8 @@
138 return -1138 return -1
139 def get_origins(self, pkgname):139 def get_origins(self, pkgname):
140 return []140 return []
141 def get_addons(self, pkgname, ignore_installed):141 def get_addons(self, pkgname, ignore_installed=False):
142 """ :return: a tuple of pkgnames (recommends, suggests) """
142 return ([], [])143 return ([], [])
143144
144 def get_packages_removed_on_remove(self, pkg):145 def get_packages_removed_on_remove(self, pkg):
@@ -173,6 +174,11 @@
173def get_pkg_info():174def get_pkg_info():
174 global pkginfo175 global pkginfo
175 if pkginfo is None:176 if pkginfo is None:
176 from softwarecenter.db.pkginfo_impl.aptcache import AptCache177 from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
177 pkginfo = AptCache()178 if not USE_PACKAGEKIT_BACKEND:
179 from softwarecenter.db.pkginfo_impl.aptcache import AptCache
180 pkginfo = AptCache()
181 else:
182 from softwarecenter.db.pkginfo_impl.packagekit import PackagekitInfo
183 pkginfo = PackagekitInfo()
178 return pkginfo184 return pkginfo
179185
=== added file 'softwarecenter/db/pkginfo_impl/packagekit.py'
--- softwarecenter/db/pkginfo_impl/packagekit.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/db/pkginfo_impl/packagekit.py 2011-08-19 10:38:33 +0000
@@ -0,0 +1,257 @@
1# Copyright (C) 2011 Canonical
2#
3# Authors:
4# Alex Eftimie
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; version 3.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13# details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19from gi.repository import PackageKitGlib as packagekit
20import logging
21
22from softwarecenter.db.pkginfo import PackageInfo, _Version
23from softwarecenter.distro import get_distro
24
25LOG = logging.getLogger('softwarecenter.db.packagekit')
26
27class FakeOrigin:
28 def __init__(self, name, label = None):
29 self.origin = name
30 self.trusted = True
31 self.component = 'unknown-component'
32 self.site = ''
33 self.label = name.capitalize() if not label else label
34 self.archive = name
35
36class PackagekitVersion(_Version):
37 def __init__(self, package, pkginfo):
38 self.package = package
39 self.pkginfo = pkginfo
40
41 @property
42 def description(self):
43 pkgid = self.package.get_id()
44 return self.pkginfo.get_description(pkgid)
45
46 @property
47 def downloadable(self):
48 return True #FIXME: check for an equivalent
49 @property
50 def summary(self):
51 return self.package.get_property('summary')
52 @property
53 def size(self):
54 return self.pkginfo.get_size(self.package.get_name())
55 @property
56 def installed_size(self):
57 """ In packagekit, installed_size can be fetched only for installed packages,
58 and is stored in the same 'size' property as the package size """
59 return self.pkginfo.get_installed_size(self.package.get_name())
60 @property
61 def version(self):
62 return self.package.get_version()
63 @property
64 def origins(self):
65 return self.pkginfo.get_origins(self.package.get_name())
66
67
68class PackagekitInfo(PackageInfo):
69 USE_CACHE = True
70
71 def __init__(self):
72 super(PackagekitInfo, self).__init__()
73 self.client = packagekit.Client()
74 self._cache = {} # temporary hack for decent testing
75 self._notfound_cache = []
76 self.distro = get_distro()
77
78 def __contains__(self, pkgname):
79 # setting it like this for now
80 return pkgname not in self._notfound_cache
81
82 def is_installed(self, pkgname):
83 p = self._get_one_package(pkgname)
84 if not p:
85 return False
86 return p.get_info() == packagekit.InfoEnum.INSTALLED
87
88 def is_available(self, pkgname):
89 # FIXME: i don't think this is being used
90 return True
91
92 def get_installed(self, pkgname):
93 p = self._get_one_package(pkgname)
94 if p.get_info() == packagekit.InfoEnum.INSTALLED:
95 return PackagekitVersion(p, self) if p else None
96
97 def get_candidate(self, pkgname):
98 p = self._get_one_package(pkgname, pfilter=packagekit.FilterEnum.NEWEST)
99 return PackagekitVersion(p, self) if p else None
100
101 def get_versions(self, pkgname):
102 return [PackagekitVersion(p, self) for p in self._get_packages(pkgname)]
103
104 def get_section(self, pkgname):
105 # FIXME: things are fuzzy here - group-section association
106 p = self._get_one_package(pkgname)
107 if p:
108 return packagekit.group_enum_to_string(p.get_property('group'))
109
110 def get_summary(self, pkgname):
111 p = self._get_one_package(pkgname)
112 return p.get_property('summary') if p else ''
113
114 def get_description(self, packageid):
115 p = self._get_package_details(packageid)
116 return p.get_property('description') if p else ''
117
118 def get_website(self, pkgname):
119 p = self._get_one_package(pkgname)
120 if not p:
121 return ''
122 p = self._get_package_details(p.get_id())
123 return p.get_property('url') if p else ''
124
125 def get_installed_files(self, pkgname):
126 p = self._get_one_package(pkgname)
127 if not p:
128 return []
129 res = self.client.get_files((p.get_id(),), None, self._on_progress_changed, None)
130 files = res.get_files_array()
131 if not files:
132 return []
133 return files[0].get_property('files')
134
135 def get_size(self, pkgname):
136 p = self._get_one_package(pkgname)
137 if not p:
138 return -1
139 p = self._get_package_details(p.get_id())
140 return p.get_property('size') if p else -1
141
142 def get_installed_size(self, pkgname):
143 return self.get_size(pkgname)
144
145 def get_origins(self, pkgname):
146 return [FakeOrigin(self.distro.get_distro_channel_name(), self.distro.get_distro_channel_description())]
147
148 def component_available(self, distro_codename, component):
149 # FIXME stub
150 return True
151
152 def get_addons(self, pkgname, ignore_installed=True):
153 # FIXME implement it
154 return ([], [])
155
156 def get_packages_removed_on_remove(self, pkg):
157 """ Returns a package names list of reverse dependencies
158 which will be removed if the package is removed."""
159 p = self._get_one_package(pkg.name)
160 if not p:
161 return []
162 autoremove = False
163 res = self.client.simulate_remove_packages((p.get_id(),),
164 autoremove, None,
165 self._on_progress_changed, None,
166 )
167 if not res:
168 return []
169 return [p.get_name() for p in res.get_package_array() if p.get_name() != pkg.name]
170
171 def get_packages_removed_on_install(self, pkg):
172 """ Returns a package names list of dependencies
173 which will be removed if the package is installed."""
174 p = self._get_one_package(pkg.name)
175 if not p:
176 return []
177 res = self.client.simulate_install_packages((p.get_id(),),
178 None,
179 self._on_progress_changed, None,
180 )
181 if not res:
182 return []
183 return [p.get_name() for p in res.get_package_array() if (p.get_name() != pkg.name) and p.get_info() == packagekit.InfoEnum.INSTALLED]
184
185 def get_total_size_on_install(self, pkgname, addons_install=None,
186 addons_remove=None):
187 """ Returns a tuple (download_size, installed_size)
188 with disk size in KB calculated for pkgname installation
189 plus addons change.
190 """
191 # FIXME implement it
192 return (0, 0)
193
194 @property
195 def ready(self):
196 """ No PK equivalent, simply returning True """
197 return True
198
199 """ private methods """
200 def _get_package_details(self, packageid, cache=USE_CACHE):
201 LOG.debug("package_details %s", packageid) #, self._cache.keys()
202 if (packageid in self._cache.keys()) and cache:
203 return self._cache[packageid]
204
205 result = self.client.get_details((packageid,), None, self._on_progress_changed, None)
206 pkgs = result.get_details_array()
207 if not pkgs:
208 return None
209 packageid = pkgs[0].get_property('package-id')
210 self._cache[packageid] = pkgs[0]
211 return pkgs[0]
212
213 def _get_one_package(self, pkgname, pfilter=packagekit.FilterEnum.NONE, cache=USE_CACHE):
214 LOG.debug("package_one %s", pkgname) #, self._cache.keys()
215 if (pkgname in self._cache.keys()) and cache:
216 return self._cache[pkgname]
217 ps = self._get_packages(pkgname, pfilter)
218 if not ps:
219 # also keep it in not found, to prevent further calls of resolve
220 if pkgname not in self._notfound_cache:
221 LOG.debug("blacklisted %s", pkgname)
222 self._notfound_cache.append(pkgname)
223 return None
224 self._cache[pkgname] = ps[0]
225 return ps[0]
226
227 def _get_packages(self, pkgname, pfilter=packagekit.FilterEnum.NONE):
228 """ resolve a package name into a PkPackage object or return None """
229 pfilter = 1 << pfilter
230 result = self.client.resolve(pfilter,
231 (pkgname,),
232 None,
233 self._on_progress_changed, None
234 )
235 pkgs = result.get_package_array()
236 return pkgs
237
238 def _reset_cache(self, name=None):
239 # Clean resolved packages cache
240 # This is used after finishing a transaction, so that we always
241 # have the latest package information
242 LOG.debug("[reset_cache] here: %s name: %s", self._cache.keys(), name)
243 if name and (name in self._cache.keys()):
244 del self._cache[name]
245 else:
246 # delete all
247 self._cache = {}
248 # appdetails gets refreshed:
249 self.emit('cache-ready')
250
251 def _on_progress_changed(self, progress, ptype, data=None):
252 pass
253
254if __name__=="__main__":
255 pi = PackagekitInfo()
256
257 print "Firefox, installed ", pi.is_installed('firefox')
0258
=== modified file 'softwarecenter/db/update.py'
--- softwarecenter/db/update.py 2011-08-19 08:21:58 +0000
+++ softwarecenter/db/update.py 2011-08-19 10:38:33 +0000
@@ -231,6 +231,12 @@
231 return self._parse_with_lists(key)231 return self._parse_with_lists(key)
232 else:232 else:
233 return self._parse_value(key)233 return self._parse_value(key)
234 def get_desktop_categories(self):
235 return self._get_desktop_list("Categories", split_str=',')
236 def get_desktop_mimetypes(self):
237 if not self.has_option_desktop("MimeType"):
238 return []
239 return self._get_desktop_list("MimeType", split_str=',')
234 def _parse_value(self, key):240 def _parse_value(self, key):
235 for child in self.appinfo_xml.iter(key):241 for child in self.appinfo_xml.iter(key):
236 # FIXME: deal with the i18n242 # FIXME: deal with the i18n
@@ -530,7 +536,7 @@
530 icondata = ""536 icondata = ""
531 # write it if we have data537 # write it if we have data
532 if icondata:538 if icondata:
533 # the iconcache gets mightly confused if there is a "." in the name539 # the iconcache gets mightly confused if there is a "." in the name
534 iconname = "sc-agent-%s" % entry.package_name.replace(".", "__")540 iconname = "sc-agent-%s" % entry.package_name.replace(".", "__")
535 open(os.path.join(541 open(os.path.join(
536 softwarecenter.paths.SOFTWARE_CENTER_ICON_CACHE_DIR,542 softwarecenter.paths.SOFTWARE_CENTER_ICON_CACHE_DIR,
537543
=== added file 'softwarecenter/distro/SUSELINUX.py'
--- softwarecenter/distro/SUSELINUX.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/distro/SUSELINUX.py 2011-08-19 10:38:33 +0000
@@ -0,0 +1,85 @@
1# Copyright (C) 2011 Canonical
2#
3# Authors:
4# Alex Eftimie
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; version 3.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13# details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19import os
20from gettext import gettext as _
21from softwarecenter.distro import Distro
22
23class SUSELINUX(Distro):
24 # see __init__.py description
25 DISTROSERIES = ["11.4",
26 ]
27
28 # screenshot handling
29 SCREENSHOT_THUMB_URL = "http://screenshots.ubuntu.com/thumbnail-with-version/%(pkgname)s/%(version)s"
30 SCREENSHOT_LARGE_URL = "http://screenshots.ubuntu.com/screenshot-with-version/%(pkgname)s/%(version)s"
31
32 # reviews
33 REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://reviews.ubuntu.com/reviews/api/1.0"
34 REVIEWS_URL = REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/"
35
36 REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats"
37
38 def get_app_name(self):
39 return _("Software Center")
40
41 def get_app_description(self):
42 return _("Lets you choose from thousands of applications available.")
43
44 def get_distro_channel_name(self):
45 """ The name in the Release file """
46 return "openSUSE"
47
48 def get_distro_channel_description(self):
49 """ The description of the main distro channel """
50 return _("Provided by openSUSE")
51
52 def get_removal_warning_text(self, cache, pkg, appname, depends):
53 primary = _("To remove %s, these items must be removed "
54 "as well:") % appname
55 button_text = _("Remove All")
56
57 return (primary, button_text)
58
59 def get_license_text(self, component):
60 if component in ("main", "universe", "independent"):
61 return _("Open source")
62 elif component in ("restricted", "commercial"):
63 return _("Proprietary")
64
65 def is_supported(self, cache, doc, pkgname):
66 # FIXME
67 return False
68
69 def get_supported_query(self):
70 # FIXME
71 import xapian
72 query1 = xapian.Query("XOL"+"Ubuntu")
73 query2a = xapian.Query("XOC"+"main")
74 query2b = xapian.Query("XOC"+"restricted")
75 query2 = xapian.Query(xapian.Query.OP_OR, query2a, query2b)
76 return xapian.Query(xapian.Query.OP_AND, query1, query2)
77
78 def get_maintenance_status(self, cache, appname, pkgname, component, channelname):
79 # FIXME
80 return
81
82 def get_downloadable_icon_url(self, full_archive_url, icon_filename):
83 # FIXME
84 return None
85
086
=== modified file 'softwarecenter/distro/Ubuntu.py'
--- softwarecenter/distro/Ubuntu.py 2011-07-29 15:24:17 +0000
+++ softwarecenter/distro/Ubuntu.py 2011-08-19 10:38:33 +0000
@@ -129,6 +129,8 @@
129 # (to exclude stuff in ubuntu-updates for the support time 129 # (to exclude stuff in ubuntu-updates for the support time
130 # calculation because the "Release" file time for that gets130 # calculation because the "Release" file time for that gets
131 # updated regularly)131 # updated regularly)
132 if not hasattr(cache, '_cache') or not hasattr(pkgname, '_pkg'):
133 return
132 releasef = get_release_filename_for_pkg(cache._cache, pkgname, 134 releasef = get_release_filename_for_pkg(cache._cache, pkgname,
133 "Ubuntu", 135 "Ubuntu",
134 self.get_codename())136 self.get_codename())
135137
=== modified file 'softwarecenter/distro/__init__.py'
--- softwarecenter/distro/__init__.py 2011-08-09 08:47:43 +0000
+++ softwarecenter/distro/__init__.py 2011-08-19 10:38:33 +0000
@@ -145,7 +145,8 @@
145145
146def _get_distro():146def _get_distro():
147 distro_id = subprocess.Popen(["lsb_release","-i","-s"], 147 distro_id = subprocess.Popen(["lsb_release","-i","-s"],
148 stdout=subprocess.PIPE).communicate()[0].strip()148 stdout=subprocess.PIPE).communicate()[0]
149 distro_id = distro_id.strip().replace(' ', '')
149 logging.getLogger("softwarecenter.distro").debug("get_distro: '%s'" % distro_id)150 logging.getLogger("softwarecenter.distro").debug("get_distro: '%s'" % distro_id)
150 # start with a import, this gives us only a softwarecenter module151 # start with a import, this gives us only a softwarecenter module
151 module = __import__(distro_id, globals(), locals(), [], -1)152 module = __import__(distro_id, globals(), locals(), [], -1)
152153
=== modified file 'softwarecenter/enums.py'
--- softwarecenter/enums.py 2011-08-18 16:36:07 +0000
+++ softwarecenter/enums.py 2011-08-19 10:38:33 +0000
@@ -202,5 +202,9 @@
202 DISTRO, 202 DISTRO,
203 RELEASE,203 RELEASE,
204 CODENAME)204 CODENAME)
205 205
206206# global backend switch
207USE_PACKAGEKIT_BACKEND = False
208
209# history switch (useful on non apt based distros)
210USE_APT_HISTORY = True
207211
=== modified file 'softwarecenter/ui/gtk/appdetailsview_gtk.py'
--- softwarecenter/ui/gtk/appdetailsview_gtk.py 2011-08-19 08:46:09 +0000
+++ softwarecenter/ui/gtk/appdetailsview_gtk.py 2011-08-19 10:38:33 +0000
@@ -1525,7 +1525,7 @@
1525 self.app.pkgname and 1525 self.app.pkgname and
1526 self.app.appname == app.appname and1526 self.app.appname == app.appname and
1527 self.app.pkgname == app.pkgname)1527 self.app.pkgname == app.pkgname)
1528 #print 'SameApp:', same_app1528 #print 'SameApp:', same_app, "force", force
15291529
1530 # init data1530 # init data
1531 self.app = app1531 self.app = app
@@ -1823,15 +1823,25 @@
1823 import softwarecenter.distro1823 import softwarecenter.distro
1824 distro = softwarecenter.distro.get_distro()1824 distro = softwarecenter.distro.get_distro()
18251825
1826 def handle_action(view, app, addons_install, addons_remove, action):
1827 logging.debug('[action here] %s %s' % (action, app))
1828 # action_func is one of: "install", "remove", "upgrade", "apply_changes"
1829 action_func = getattr(view.backend, action)
1830 if callable(action_func):
1831 action_func(app.pkgname, app.appname, '', addons_install=addons_install, addons_remove=addons_remove)
1832 else:
1833 LOG.error("Not a valid action in backend: '%s'" % action)
1834
1826 # gui1835 # gui
1827 win = gtk.Window()1836 win = gtk.Window()
1828 scroll = gtk.ScrolledWindow()1837 scroll = gtk.ScrolledWindow()
1829 view = AppDetailsViewGtk(db, distro, icons, cache, datadir, win)1838 view = AppDetailsViewGtk(db, distro, icons, cache, datadir, win)
1839 view.connect("application-request-action", handle_action)
1830 #view.show_app(Application("Pay App Example", "pay-app"))1840 #view.show_app(Application("Pay App Example", "pay-app"))
1831 #view.show_app(Application("3D Chess", "3dchess"))1841 #view.show_app(Application("3D Chess", "3dchess"))
1832 #view.show_app(Application("Movie Player", "totem"))1842 #view.show_app(Application("Movie Player", "totem"))
1833 #view.show_app(Application("ACE", "unace"))1843 #view.show_app(Application("ACE", "unace"))
1834 view.show_app(Application("", "apt"))1844 view.show_app(Application("", "firefox"))
18351845
1836 #view.show_app("AMOR")1846 #view.show_app("AMOR")
1837 #view.show_app("Configuration Editor")1847 #view.show_app("Configuration Editor")
@@ -1849,6 +1859,17 @@
1849 # keep it spinning to test for re-draw issues and memleaks1859 # keep it spinning to test for re-draw issues and memleaks
1850 #GObject.timeout_add_seconds(2, _show_app, view)1860 #GObject.timeout_add_seconds(2, _show_app, view)
18511861
1862 # also show pending view
1863 from softwarecenter.ui.gtk.pendingview import PendingView
1864 view2 = PendingView(icons)
1865 scroll2 = gtk.ScrolledWindow()
1866 scroll2.add(view2)
1867 win2 = gtk.Window()
1868 win2.add(scroll2)
1869 view2.grab_focus()
1870 win2.set_size_request(500,200)
1871 win2.connect('delete-event', gtk.main_quit)
1872 win2.show_all()
18521873
1853 # run it1874 # run it
1854 gtk.main()1875 gtk.main()
18551876
=== modified file 'softwarecenter/ui/gtk/historypane.py'
--- softwarecenter/ui/gtk/historypane.py 2011-08-17 11:44:31 +0000
+++ softwarecenter/ui/gtk/historypane.py 2011-08-19 10:38:33 +0000
@@ -117,7 +117,7 @@
117 removals_action.set_group(all_action)117 removals_action.set_group(all_action)
118 removals_button = removals_action.create_tool_item()118 removals_button = removals_action.create_tool_item()
119 self.toolbar.insert(removals_button, 3)119 self.toolbar.insert(removals_button, 3)
120 120
121 self._actions_list = all_action.get_group()121 self._actions_list = all_action.get_group()
122 self._set_actions_sensitive(False)122 self._set_actions_sensitive(False)
123123
@@ -129,7 +129,7 @@
129 gtk.POLICY_AUTOMATIC)129 gtk.POLICY_AUTOMATIC)
130 self.history_view.show()130 self.history_view.show()
131 self.history_view.add(self.view)131 self.history_view.add(self.view)
132 132
133 # make a spinner to display while history is loading133 # make a spinner to display while history is loading
134 self.spinner_view = SpinnerView(_('Loading history'))134 self.spinner_view = SpinnerView(_('Loading history'))
135 self.spinner_notebook = gtk.Notebook()135 self.spinner_notebook = gtk.Notebook()
@@ -147,7 +147,7 @@
147 self.view.set_model(self.store_filter)147 self.view.set_model(self.store_filter)
148 all_action.set_active(True)148 all_action.set_active(True)
149 self.last = None149 self.last = None
150 150
151 # to save (a lot of) time at startup we load history later, only when151 # to save (a lot of) time at startup we load history later, only when
152 # it is selected to be viewed152 # it is selected to be viewed
153 self.history = None153 self.history = None
154154
=== modified file 'softwarecenter/ui/gtk/pendingview.py'
--- softwarecenter/ui/gtk/pendingview.py 2011-08-17 11:44:31 +0000
+++ softwarecenter/ui/gtk/pendingview.py 2011-08-19 10:38:33 +0000
@@ -174,6 +174,13 @@
174 status = _("Downloaded %sB of %sB") % \174 status = _("Downloaded %sB of %sB") % \
175 (current_bytes_str, total_bytes_str)175 (current_bytes_str, total_bytes_str)
176 row[self.COL_STATUS] = self._render_status_text(name, status)176 row[self.COL_STATUS] = self._render_status_text(name, status)
177 elif trans.is_waiting():
178 name = row[self.COL_NAME]
179 if eta != 0:
180 status = _("ETA: %s") % eta
181 row[self.COL_STATUS] = self._render_status_text(name, status)
182 else:
183 logging.debug("neither downloading or waiting")
177184
178 def _on_progress_changed(self, trans, progress):185 def _on_progress_changed(self, trans, progress):
179 # print "_on_progress_changed: ", trans, progress186 # print "_on_progress_changed: ", trans, progress
@@ -280,6 +287,9 @@
280if __name__ == "__main__":287if __name__ == "__main__":
281 logging.basicConfig(level=logging.DEBUG)288 logging.basicConfig(level=logging.DEBUG)
282289
290 import dbus.mainloop.glib
291 dbus.set_default_main_loop(dbus.mainloop.glib.DBusGMainLoop())
292
283 icons = gtk.icon_theme_get_default()293 icons = gtk.icon_theme_get_default()
284 view = PendingView(icons)294 view = PendingView(icons)
285295
@@ -291,6 +301,14 @@
291 win.add(scroll)301 win.add(scroll)
292 view.grab_focus()302 view.grab_focus()
293 win.set_size_request(500,200)303 win.set_size_request(500,200)
304 win.connect('delete-event', gtk.main_quit)
294 win.show_all()305 win.show_all()
295306
307 backend = view.tv.get_model().backend
308 #packages = ('cheese', 'firefox')
309 packages = ('firefox',)
310 if backend.pkginfo['firefox'].is_installed:
311 backend.remove_multiple(packages, packages, ('', ''))
312 else:
313 backend.install_multiple(packages, packages, ('', ''))
296 gtk.main()314 gtk.main()
297315
=== modified file 'softwarecenter/ui/gtk/viewswitcher.py'
--- softwarecenter/ui/gtk/viewswitcher.py 2011-08-17 11:44:31 +0000
+++ softwarecenter/ui/gtk/viewswitcher.py 2011-08-19 10:38:33 +0000
@@ -415,8 +415,13 @@
415if __name__ == "__main__":415if __name__ == "__main__":
416 logging.basicConfig(level=logging.DEBUG)416 logging.basicConfig(level=logging.DEBUG)
417 import sys417 import sys
418 import apt418
419419 import dbus
420 import dbus.mainloop.glib
421
422 loop = dbus.mainloop.glib.DBusGMainLoop()
423 dbus.set_default_main_loop(loop)
424
420 if len(sys.argv) > 1:425 if len(sys.argv) > 1:
421 datadir = sys.argv[1]426 datadir = sys.argv[1]
422 elif os.path.exists("./data"):427 elif os.path.exists("./data"):
@@ -429,7 +434,10 @@
429434
430 xapian_base_path = XAPIAN_BASE_PATH435 xapian_base_path = XAPIAN_BASE_PATH
431 pathname = os.path.join(xapian_base_path, "xapian")436 pathname = os.path.join(xapian_base_path, "xapian")
432 cache = apt.Cache(apt.progress.text.OpProgress())437
438 # cache
439 from softwarecenter.db.pkginfo import get_pkg_info
440 cache = get_pkg_info()
433 db = StoreDatabase(pathname, cache)441 db = StoreDatabase(pathname, cache)
434 db.open()442 db.open()
435443
@@ -447,4 +455,5 @@
447 win.set_size_request(400,400)455 win.set_size_request(400,400)
448 win.show_all()456 win.show_all()
449457
458 view.backend.install_multiple(['cheese'],['cheese'],[''])
450 gtk.main()459 gtk.main()
451460
=== modified file 'softwarecenter/ui/gtk3/app.py'
--- softwarecenter/ui/gtk3/app.py 2011-08-18 17:42:33 +0000
+++ softwarecenter/ui/gtk3/app.py 2011-08-19 10:38:33 +0000
@@ -65,7 +65,7 @@
65try:65try:
66 from aptd_gtk3 import InstallBackendUI66 from aptd_gtk3 import InstallBackendUI
67 InstallBackendUI # pyflakes67 InstallBackendUI # pyflakes
68except ImportError:68except:
69 from softwarecenter.backend.installbackend import InstallBackendUI69 from softwarecenter.backend.installbackend import InstallBackendUI
7070
71# ui imports71# ui imports
7272
=== added file 'softwarecenter/ui/gtk3/panes/installedpane.py'
--- softwarecenter/ui/gtk3/panes/installedpane.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/ui/gtk3/panes/installedpane.py 2011-08-18 17:42:33 +0000
@@ -0,0 +1,426 @@
1# Copyright (C) 2009 Canonical
2#
3# Authors:
4# Michael Vogt
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; version 3.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13# details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19from gi.repository import Gtk
20import logging
21import xapian
22from gi.repository import GObject
23
24from gettext import gettext as _
25
26from softwarecenter.enums import (NonAppVisibility,
27 SortMethods)
28from softwarecenter.utils import wait_for_apt_cache_ready
29from softwarecenter.db.categories import (CategoriesParser,
30 categories_sorted_by_name)
31from softwarecenter.ui.gtk3.models.appstore2 import (
32 AppTreeStore, CategoryRowReference)
33from softwarepane import SoftwarePane
34from softwarecenter.db.appfilter import AppFilter
35
36LOG=logging.getLogger(__name__)
37
38def interrupt_build_and_wait(f):
39 """ decorator that ensures that a build of the categorised installed apps
40 is interrupted before a new build commences.
41 expects self._build_in_progress and self._halt_build as properties
42 """
43 def wrapper(*args, **kwargs):
44 self = args[0]
45 if self._build_in_progress:
46 LOG.debug('Waiting for build to exit...')
47 self._halt_build = True
48 GObject.timeout_add(200, lambda: wrapper(*args, **kwargs))
49 return False
50 # ready now
51 self._halt_build = False
52 f(*args, **kwargs)
53 return False
54 return wrapper
55
56
57class InstalledPane(SoftwarePane, CategoriesParser):
58 """Widget that represents the installed panel in software-center
59 It contains a search entry and navigation buttons
60 """
61
62 class Pages:
63 # page names, useful for debuggin
64 NAMES = ('list', 'details')
65 # the actual page id's
66 (LIST,
67 DETAILS) = range(2)
68 # the default page
69 HOME = LIST
70
71 __gsignals__ = {'installed-pane-created':(GObject.SignalFlags.RUN_FIRST,
72 None,
73 ())}
74
75 def __init__(self, cache, db, distro, icons, datadir):
76
77 # parent
78 SoftwarePane.__init__(self, cache, db, distro, icons, datadir, show_ratings=False)
79 CategoriesParser.__init__(self, db)
80
81 self.current_appview_selection = None
82 self.loaded = False
83 self.pane_name = _("Installed Software")
84
85 self.installed_apps = 0
86
87 # switches to terminate build in progress
88 self._build_in_progress = False
89 self._halt_build = False
90
91 self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE
92
93 def init_view(self):
94 if self.view_initialized: return
95
96 SoftwarePane.init_view(self)
97
98 self.search_aid.set_no_show_all(True)
99 self.notebook.append_page(self.box_app_list, Gtk.Label(label="installed"))
100
101 # details
102 self.notebook.append_page(self.scroll_details, Gtk.Label(label="details"))
103 # initial refresh
104 self.state.search_term = ""
105
106 # build models and filters
107 self.base_model = AppTreeStore(self.db, self.cache, self.icons)
108
109 self.treefilter = self.base_model.filter_new(None)
110 self.treefilter.set_visible_func(self._row_visibility_func,
111 AppTreeStore.COL_ROW_DATA)
112 self.app_view.set_model(self.treefilter)
113 self.app_view.tree_view.connect("row-collapsed", self._on_row_collapsed)
114
115 self.visible_docids = None
116
117 self._all_cats = self.parse_applications_menu('/usr/share/app-install')
118 self._all_cats = categories_sorted_by_name(self._all_cats)
119
120 # now we are initialized
121 self.emit("installed-pane-created")
122 self.show_all()
123
124 # hacky, hide the header
125 self.app_view.header_hbox.hide()
126
127 self.view_initialized = True
128 return False
129
130 def _on_row_collapsed(self, view, it, path):
131 return
132
133 def _row_visibility_func(self, model, it, col):
134
135 if self.visible_docids is None:
136
137 row = model.get_value(it, col)
138 if isinstance(row, CategoryRowReference):
139 row.vis_count = row.pkg_count
140 return True
141
142 row = model.get_value(it, col)
143 if isinstance(row, CategoryRowReference):
144 visible = row.untranslated_name in self.visible_cats.keys()
145
146 if visible:
147 row.vis_count = self.visible_cats[row.untranslated_name]
148
149 # process one event
150 while Gtk.events_pending():
151 Gtk.main_iteration()
152
153 return visible
154
155 if row is None: return False
156
157 return row.get_docid() in self.visible_docids
158
159 def _use_category(self, cat):
160 # System cat is large and slow to search, filter it in default mode
161
162 if ('carousel-only' in cat.flags or
163 ((self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE)
164 and cat.untranslated_name == 'System')): return False
165
166 return True
167
168 #~ @interrupt_build_and_wait
169 def _build_categorised_view(self):
170 LOG.debug('Rebuilding categorised installedview...')
171 self.cat_docid_map = {}
172 enq = self.enquirer
173 model = self.base_model # base model not treefilter
174 model.clear()
175
176 i = 0
177
178 xfilter = AppFilter(self.db, self.cache)
179 xfilter.set_installed_only(True)
180 for cat in self._all_cats:
181 # for each category do category query and append as a new
182 # node to tree_view
183 if not self._use_category(cat): continue
184 query = self.get_query_for_cat(cat)
185 LOG.debug("filter.instaleld_only: %s" % xfilter.installed_only)
186 enq.set_query(query,
187 sortmode=SortMethods.BY_ALPHABET,
188 nonapps_visible=self.nonapps_visible,
189 filter=xfilter,
190 nonblocking_load=False,
191 persistent_duplicate_filter=(i>0))
192
193 L = len(self.enquirer.matches)
194 if L:
195 i += L
196 docs = enq.get_documents()
197 self.cat_docid_map[cat.untranslated_name] = \
198 [doc.get_docid() for doc in docs]
199 model.set_category_documents(cat, docs)
200
201 # check for uncategorised pkgs
202 enq.set_query(self.state.channel.query,
203 sortmode=SortMethods.BY_ALPHABET,
204 nonapps_visible=NonAppVisibility.MAYBE_VISIBLE,
205 filter=xfilter,
206 nonblocking_load=False,
207 persistent_duplicate_filter=(i>0))
208
209 L = len(enq.matches)
210 if L:
211 # some foo for channels
212 # if no categorised results but in channel, then use
213 # the channel name for the category
214 channel_name = None
215 if not i and self.state.channel:
216 channel_name = self.state.channel.display_name
217 model.set_nocategory_documents(enq.get_documents(),
218 display_name=channel_name)
219 i += L
220
221 if i:
222 self.app_view.tree_view.set_cursor(Gtk.TreePath(),
223 None, False)
224 if i <= 10:
225 self.app_view.tree_view.expand_all()
226
227 # cache the installed app count
228 self.installed_count = i
229
230 self.app_view._append_appcount(self.installed_count, installed=True)
231
232 self.emit("app-list-changed", i)
233 return
234
235 def _check_expand(self):
236 it = self.treefilter.get_iter_first()
237 while it:
238 path = self.treefilter.get_path(it)
239 if self.state.search_term:# or path in self._user_expanded_paths:
240 self.app_view.tree_view.expand_row(path, False)
241 else:
242 self.app_view.tree_view.collapse_row(path)
243
244 it = self.treefilter.iter_next(it)
245 return
246
247 def _search(self, terms=None):
248 if not terms:
249 self.visible_docids = None
250 self.state.search_term = ""
251 self._clear_search()
252
253 elif self.state.search_term != terms:
254 self.state.search_term = terms
255 self.enquirer.set_query(self.get_query(),
256 nonapps_visible=self.nonapps_visible,
257 filter=self.apps_filter,
258 nonblocking_load=True)
259
260 self.visible_docids = self.enquirer.get_docids()
261 self.visible_cats = self._get_vis_cats(self.visible_docids)
262
263 self.treefilter.refilter()
264 if terms:
265 self.app_view.tree_view.expand_all()
266 else:
267 self._check_expand()
268 return
269
270 def get_query(self):
271 # search terms
272 return self.db.get_query_list_from_search_entry(
273 self.state.search_term)
274
275 def get_query_for_cat(self, cat):
276 LOG.debug("self.state.channel: %s" % self.state.channel)
277 if self.state.channel and self.state.channel.query:
278 query = xapian.Query(xapian.Query.OP_AND,
279 cat.query,
280 self.state.channel.query)
281 return query
282 return cat.query
283
284 def update_show_hide_nonapps(self, length=-1):
285 # override SoftwarePane.update_show_hide_nonapps
286 """
287 update the state of the show/hide non-applications control
288 in the action_bar
289 """
290 #~ appstore = self.app_view.get_model()
291 #~ if not appstore:
292 #~ self.action_bar.unset_label()
293 #~ return
294
295 # first figure out if we are only showing installed
296 enquirer = self.enquirer
297 enquirer.filter = self.state.filter
298
299 self.action_bar.unset_label()
300
301 if self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE:
302 label = _("_Hide technical software_")
303 self.action_bar.set_label(label, self._hide_nonapp_pkgs)
304 else:
305 label = _("_Display technical software_")
306 self.action_bar.set_label(label, self._show_nonapp_pkgs)
307 return
308
309 @wait_for_apt_cache_ready
310 def refresh_apps(self, *args, **kwargs):
311 """refresh the applist and update the navigation bar """
312 logging.debug("installedpane refresh_apps")
313 return
314
315 def _clear_search(self):
316 # remove the details and clear the search
317 self.searchentry.clear_with_no_signal()
318
319 def on_search_terms_changed(self, searchentry, terms):
320 """callback when the search entry widget changes"""
321 logging.debug("on_search_terms_changed: '%s'" % terms)
322
323 self._search(terms.strip())
324 self.state.search_term = terms
325 self.notebook.set_current_page(InstalledPane.Pages.LIST)
326 return
327
328 def _get_vis_cats(self, visids):
329 vis_cats = {}
330 appcount = 0
331 for cat_uname, docids in self.cat_docid_map.iteritems():
332 children = len(set(docids) & set(visids))
333 if children:
334 appcount += children
335 vis_cats[cat_uname] = children
336 self.app_view._append_appcount(appcount, installed=True)
337 return vis_cats
338
339 def on_db_reopen(self, db):
340 self.refresh_apps(rebuild=True)
341 self.app_details_view.refresh_app()
342
343 def on_application_selected(self, appview, app):
344 """callback when an app is selected"""
345 logging.debug("on_application_selected: '%s'" % app)
346 self.current_appview_selection = app
347
348 def get_callback_for_page(self, page, state):
349 if page == InstalledPane.Pages.LIST:
350 return self.display_overview_page
351 return self.display_details_page
352
353 def display_search(self):
354 model = self.app_view.get_model()
355 if model:
356 self.emit("app-list-changed", len(model))
357 self.searchentry.show()
358
359 def display_overview_page(self, page, view_state):
360 LOG.debug("view_state: %s" % view_state)
361 self._build_categorised_view()
362
363 if self.state.search_term:
364 self._search(self.state.search_term)
365
366 self.update_show_hide_nonapps()
367 return True
368
369 def get_current_app(self):
370 """return the current active application object applicable
371 to the context"""
372 return self.current_appview_selection
373
374 def is_category_view_showing(self):
375 # there is no category view in the installed pane
376 return False
377
378 def is_applist_view_showing(self):
379 """Return True if we are in the applist view """
380 return (self.notebook.get_current_page() ==
381 InstalledPane.Pages.LIST)
382
383 def is_app_details_view_showing(self):
384 """Return True if we are in the app_details view """
385 return self.notebook.get_current_page() == InstalledPane.Pages.DETAILS
386
387
388def get_test_window():
389 from softwarecenter.testutils import (get_test_db,
390 get_test_datadir,
391 get_test_gtk3_viewmanager,
392 get_test_pkg_info,
393 get_test_gtk3_icon_cache,
394 )
395 # needed because available pane will try to get it
396 vm = get_test_gtk3_viewmanager()
397 vm # make pyflakes happy
398 db = get_test_db()
399 cache = get_test_pkg_info()
400 datadir = get_test_datadir()
401 icons = get_test_gtk3_icon_cache()
402
403 w = InstalledPane(cache, db, 'Ubuntu', icons, datadir)
404 w.show()
405
406 win = Gtk.Window()
407 win.set_data("pane", w)
408 win.add(w)
409 win.set_size_request(400, 600)
410 win.connect("destroy", lambda x: Gtk.main_quit())
411
412 # init the view
413 w.init_view()
414
415 from softwarecenter.backend.channel import AllInstalledChannel
416 w.state.channel = AllInstalledChannel()
417 w.display_overview_page(None, None)
418
419 win.show_all()
420 return win
421
422
423if __name__ == "__main__":
424 win = get_test_window()
425 Gtk.main()
426
0427
=== modified file 'softwarecenter/ui/gtk3/utils.py'
--- softwarecenter/ui/gtk3/utils.py 2011-08-12 13:24:31 +0000
+++ softwarecenter/ui/gtk3/utils.py 2011-08-19 10:38:33 +0000
@@ -17,7 +17,7 @@
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1818
19import gi19import gi
20gi.require_version("Gtk", "3.0")20#gi.require_version("Gtk", "3.0")
21from gi.repository import Gtk21from gi.repository import Gtk
2222
23from softwarecenter.paths import ICON_PATH, SOFTWARE_CENTER_ICON_CACHE_DIR23from softwarecenter.paths import ICON_PATH, SOFTWARE_CENTER_ICON_CACHE_DIR
2424
=== modified file 'test/test_package_info.py'
--- test/test_package_info.py 2011-07-08 07:37:41 +0000
+++ test/test_package_info.py 2011-08-19 10:38:33 +0000
@@ -6,16 +6,21 @@
6import logging6import logging
7import unittest7import unittest
88
9from softwarecenter.db.pkginfo import get_pkg_info, _Package, _Version9from softwarecenter.db.pkginfo import _Package, _Version
1010from softwarecenter.db.pkginfo_impl.aptcache import AptCache
11class TestPkgInfo(unittest.TestCase):11from softwarecenter.db.pkginfo_impl.packagekit import PackagekitInfo
12
13class TestPkgInfoAptCache(unittest.TestCase):
14
15 # the backend that we want to test
16 klass = AptCache
1217
13 def setUp(self):18 def setUp(self):
14 pass19 self.pkginfo = self.klass()
20 self.pkginfo.open()
1521
16 def test_pkg_version(self):22 def test_pkg_version(self):
17 pkginfo = get_pkg_info()23 pkginfo = self.pkginfo
18 pkginfo.open()
1924
20 pkg = pkginfo['coreutils']25 pkg = pkginfo['coreutils']
21 self.assertTrue(isinstance(pkg, _Package))26 self.assertTrue(isinstance(pkg, _Package))
@@ -33,30 +38,50 @@
33 self.assertTrue(isinstance(v, _Version))38 self.assertTrue(isinstance(v, _Version))
3439
35 def test_pkg_info(self):40 def test_pkg_info(self):
36 pkginfo = get_pkg_info()41 pkginfo = self.pkginfo
37 pkginfo.open()
38 self.assertTrue(pkginfo.is_installed("coreutils"))42 self.assertTrue(pkginfo.is_installed("coreutils"))
39 self.assertTrue(pkginfo.is_available("bash"))43 self.assertTrue(pkginfo.is_available("bash"))
40 self.assertTrue(len(pkginfo.get_addons("firefox")) > 0)
41 self.assertEqual(pkginfo.get_section('bash'), 'shells')
42 self.assertTrue('GNU Bourne Again' in pkginfo.get_summary('bash'))44 self.assertTrue('GNU Bourne Again' in pkginfo.get_summary('bash'))
43 self.assertTrue(pkginfo.get_description('bash') != '')45 self.assertTrue(pkginfo.get_description('bash') != '')
44 self.assertTrue(len(pkginfo.get_origins("firefox")) > 0)
45 self.assertTrue(pkginfo.get_installed("coreutils") is not None)46 self.assertTrue(pkginfo.get_installed("coreutils") is not None)
46 self.assertTrue(pkginfo.get_candidate("coreutils") is not None)47 self.assertTrue(pkginfo.get_candidate("coreutils") is not None)
47 self.assertTrue(len(pkginfo.get_versions("coreutils")) != 0)48 self.assertTrue(len(pkginfo.get_versions("coreutils")) != 0)
4849
49 self.assertTrue('coreutils' in pkginfo)50 self.assertTrue('coreutils' in pkginfo)
50 51
52 # test getitem
51 pkg = pkginfo['coreutils']53 pkg = pkginfo['coreutils']
52 self.assertTrue(len(pkginfo.get_packages_removed_on_remove(pkg)) != 0)
53 self.assertTrue(len(pkginfo.get_packages_removed_on_install(pkg)) == 0)
54 self.assertTrue(pkg is not None)54 self.assertTrue(pkg is not None)
55 self.assertTrue(pkg.is_installed)55 self.assertTrue(pkg.is_installed)
56 self.assertTrue(len(pkg.versions) != 0)56 self.assertTrue(len(pkg.versions) != 0)
57 self.assertEqual(pkg.section, "utils")
58 self.assertEqual(pkg.website, 'http://gnu.org/software/coreutils')57 self.assertEqual(pkg.website, 'http://gnu.org/software/coreutils')
5958
59 def test_section(self):
60 self.assertEqual(self.pkginfo.get_section('bash'), 'shells')
61
62 def test_origins(self):
63 self.assertTrue(len(self.pkginfo.get_origins("firefox")) > 0)
64
65 def test_addons(self):
66 pkginfo = self.pkginfo
67 self.assertTrue(len(pkginfo.get_addons("firefox")) > 0)
68 pkg = pkginfo['firefox']
69 self.assertTrue(len(pkginfo.get_packages_removed_on_install(pkg)) == 0)
70 self.assertTrue(len(pkginfo.get_packages_removed_on_remove(pkg)) != 0)
71
72 def test_installed_files(self):
73 pkg = self.pkginfo['coreutils']
74 files = pkg.installed_files
75 self.assertTrue('/usr/bin/whoami' in files)
76
77class TestPkgInfoPackagekit(TestPkgInfoAptCache):
78 klass = PackagekitInfo
79
80 def test_addons(self):
81 pass
82 def test_section(self):
83 pass
84
60if __name__ == "__main__":85if __name__ == "__main__":
61 logging.basicConfig(level=logging.DEBUG)86 logging.basicConfig(level=logging.DEBUG)
62 unittest.main()87 unittest.main()
6388
=== modified file 'utils/update-software-center'
--- utils/update-software-center 2011-08-10 11:23:23 +0000
+++ utils/update-software-center 2011-08-19 10:38:33 +0000
@@ -17,6 +17,11 @@
17# this program; if not, write to the Free Software Foundation, Inc.,17# this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1919
20# We have to import Gio before everything, for dynamic PK to work.
21try:
22 from gi.repository import Gio
23except: pass
24
20import locale25import locale
21import gettext26import gettext
22import logging27import logging
@@ -31,7 +36,6 @@
31from softwarecenter.enums import *36from softwarecenter.enums import *
32from softwarecenter.paths import XAPIAN_BASE_PATH37from softwarecenter.paths import XAPIAN_BASE_PATH
33from softwarecenter.db.update import rebuild_database38from softwarecenter.db.update import rebuild_database
34
35import softwarecenter.paths39import softwarecenter.paths
3640
37# dbus may not be available during a upgrade so we 41# dbus may not be available during a upgrade so we
@@ -91,6 +95,9 @@
91 help="set different appstream xml rootdir")95 help="set different appstream xml rootdir")
92 parser.add_option("--debug", "", action="store_true", default=False,96 parser.add_option("--debug", "", action="store_true", default=False,
93 help="show debug output")97 help="show debug output")
98 parser.add_option("--use-packagekit", action="store_true",
99 help="use PackageKit backend (experimental)",
100 default=False)
94 (options, args) = parser.parse_args()101 (options, args) = parser.parse_args()
95102
96 #logging.basicConfig(level=logging.INFO)103 #logging.basicConfig(level=logging.INFO)
@@ -112,6 +119,10 @@
112 if options.appstream_xml_path:119 if options.appstream_xml_path:
113 softwarecenter.paths.APPSTREAM_XML_PATH = options.appstream_xml_path120 softwarecenter.paths.APPSTREAM_XML_PATH = options.appstream_xml_path
114121
122 if options.use_packagekit:
123 LOG.info("using the PackageKit backend")
124 softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True
125
115 # check if we are dpkg triggered because of langpack change126 # check if we are dpkg triggered because of langpack change
116 # and avoid unneeded database rebuilds by checking the timestamp127 # and avoid unneeded database rebuilds by checking the timestamp
117 # of the app-install-data mo file128 # of the app-install-data mo file
@@ -168,4 +179,3 @@
168 context = GObject.main_context_default()179 context = GObject.main_context_default()
169 while context.pending():180 while context.pending():
170 context.iteration()181 context.iteration()
171