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

Proposed by Alex Eftimie on 2011-08-13
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 2011-08-13 Needs Information on 2011-08-17
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.
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 on 2011-08-16

merged from lp:software-center

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.

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...

Michael Vogt (mvo) :
review: Needs Information
Michael Vogt (mvo) wrote :

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

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

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

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.

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.

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

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

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 on 2011-08-18

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

1904. By Alex Eftimie on 2011-08-18

merged from trunk

1905. By Alex Eftimie on 2011-08-18

pass addons and section in packagekit.

addons are not implemented yet

section does not have an equivalent

1906. By Alex Eftimie on 2011-08-18

merged with lp:software-center

1907. By Alex Eftimie on 2011-08-19

merged with lp:software-center

1908. By Alex Eftimie on 2011-08-19

toggle apt history and fix installed view cache problem

Alex Eftimie (alexeftimie) wrote :

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

Please reconsider the merge :)

Alex

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
1=== modified file 'contrib/appstream-xml/appdata.xml'
2--- contrib/appstream-xml/appdata.xml 2011-05-30 14:12:12 +0000
3+++ contrib/appstream-xml/appdata.xml 2011-08-19 10:38:33 +0000
4@@ -30,5 +30,16 @@
5 </mimetypes>
6 <url type="homepage">http://www.mozilla.com</url>
7 </application>
8+ <application>
9+ <id type="desktop">cheese.desktop</id>
10+ <pkgname>cheese</pkgname>
11+ <name>Cheese</name>
12+ <summary>Take photos and videos with your webcam, with fun graphical effects</summary>
13+ <icon type="local">cheese</icon>
14+ <appcategories>
15+ <appcategory>GNOME</appcategory>
16+ <appcategory>AudioVideo</appcategory>
17+ </appcategories>
18+ </application>
19 <!-- more applications here! -->
20 </applications>
21
22=== modified file 'run_local.sh'
23--- run_local.sh 2011-05-27 08:25:15 +0000
24+++ run_local.sh 2011-08-19 10:38:33 +0000
25@@ -9,7 +9,7 @@
26 python /usr/lib/ubuntu-sso-client/ubuntu-sso-login &
27
28 # s-c
29-export PYTHONPATH=$(pwd)
30+export PYTHONPATH=$(pwd):$PYTHONPATH
31
32 if [ ! -d "./build" ]; then
33 echo "Please run: 'python setup.py build' before $0"
34
35=== modified file 'software-center'
36--- software-center 2011-08-17 10:37:51 +0000
37+++ software-center 2011-08-19 10:38:33 +0000
38@@ -42,7 +42,6 @@
39
40 from softwarecenter.enums import *
41 from softwarecenter.paths import XAPIAN_BASE_PATH
42-from softwarecenter.utils import ExecutionTime
43 from softwarecenter.version import *
44
45 import softwarecenter.log
46@@ -79,7 +78,6 @@
47 parser.add_option("--dummy-backend", action="store_true",
48 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",
49 default=False)
50-
51 (options, args) = parser.parse_args()
52
53 # statup time measure implies "performance" in debug filters
54@@ -134,6 +132,8 @@
55
56 # create the app
57 from softwarecenter.ui.gtk.app import SoftwareCenterApp
58+ from softwarecenter.utils import ExecutionTime
59+
60 with ExecutionTime("create SoftwareCenterApp"):
61 app = SoftwareCenterApp(datadir, xapian_base_path, options, args)
62
63@@ -154,4 +154,3 @@
64
65 # run it normally
66 app.run(args)
67-
68
69=== modified file 'software-center-gtk3'
70--- software-center-gtk3 2011-08-15 11:25:29 +0000
71+++ software-center-gtk3 2011-08-19 10:38:33 +0000
72@@ -81,6 +81,9 @@
73 parser.add_option("--dummy-backend", action="store_true",
74 help="run with a dummy backend, this will not actually install or remove anything and is useful for testing",
75 default=False)
76+ parser.add_option("--packagekit-backend", action="store_true",
77+ help="use PackageKit backend (experimental)",
78+ default=False)
79
80 (options, args) = parser.parse_args()
81
82@@ -97,6 +100,13 @@
83 softwarecenter.log.root.setLevel(level=logging.DEBUG)
84 else:
85 softwarecenter.log.root.setLevel(level=logging.INFO)
86+
87+ # packagekit
88+ if options.packagekit_backend:
89+ softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True
90+ logging.info("Using PackageKit backend")
91+ else:
92+ softwarecenter.enums.USE_PACKAGEKIT_BACKEND = False
93
94 # dummy backend
95 if options.dummy_backend:
96
97=== modified file 'softwarecenter/backend/channel.py'
98--- softwarecenter/backend/channel.py 2011-08-09 08:47:43 +0000
99+++ softwarecenter/backend/channel.py 2011-08-19 10:38:33 +0000
100@@ -26,23 +26,100 @@
101
102 from softwarecenter.enums import (SortMethods,
103 Icons,
104+ ViewPages,
105 )
106
107 LOG = logging.getLogger(__name__)
108
109 class ChannelsManager(object):
110+ def __init__(self, db, **kwargs):
111+ self.distro = get_distro()
112+ self.db = db
113+
114 @property
115 def channels(self):
116- return []
117+ return self._get_channels_from_db()
118
119 @property
120 def channels_installed_only(self):
121- return []
122+ return self._get_channels_from_db(True)
123
124- @staticmethod
125- def channel_available(channelname):
126+ @classmethod
127+ def channel_available(kls, channelname):
128 pass
129
130+ def _get_channels_from_db(self, installed_only=False):
131+ """
132+ (internal) implements 'channels()' and 'channels_installed_only()' properties
133+ """
134+ distro_channel_name = self.distro.get_distro_channel_name()
135+
136+ # gather the set of software channels and order them
137+ other_channel_list = []
138+ cached_origins = []
139+ for channel_iter in self.db.xapiandb.allterms("XOL"):
140+ if len(channel_iter.term) == 3:
141+ continue
142+ channel_name = channel_iter.term[3:]
143+ channel_origin = ""
144+
145+ # get origin information for this channel
146+ m = self.db.xapiandb.postlist_begin(channel_iter.term)
147+ doc = self.db.xapiandb.get_document(m.get_docid())
148+ for term_iter in doc.termlist():
149+ if term_iter.term.startswith("XOO") and len(term_iter.term) > 3:
150+ channel_origin = term_iter.term[3:]
151+ break
152+ LOG.debug("channel_name: %s" % channel_name)
153+ LOG.debug("channel_origin: %s" % channel_origin)
154+ if channel_origin not in cached_origins:
155+ other_channel_list.append((channel_name, channel_origin))
156+ cached_origins.append(channel_origin)
157+
158+ dist_channel = None
159+ other_channels = []
160+ unknown_channel = []
161+ local_channel = None
162+
163+ for (channel_name, channel_origin) in other_channel_list:
164+ if not channel_name:
165+ unknown_channel.append(SoftwareChannel(channel_name,
166+ channel_origin,
167+ None,
168+ installed_only=installed_only))
169+ elif channel_name == distro_channel_name:
170+ dist_channel = (SoftwareChannel(distro_channel_name,
171+ channel_origin,
172+ None,
173+ installed_only=installed_only))
174+ elif channel_name == "notdownloadable":
175+ if installed_only:
176+ local_channel = SoftwareChannel(channel_name,
177+ None,
178+ None,
179+ installed_only=installed_only)
180+ else:
181+ other_channels.append(SoftwareChannel(channel_name,
182+ channel_origin,
183+ None,
184+ installed_only=installed_only))
185+
186+ # set them in order
187+ channels = []
188+ if dist_channel is not None:
189+ channels.append(dist_channel)
190+ channels.extend(other_channels)
191+ channels.extend(unknown_channel)
192+ if local_channel is not None:
193+ channels.append(local_channel)
194+
195+ for channel in channels:
196+ if installed_only:
197+ channel._channel_view_id = ViewPages.INSTALLED
198+ else:
199+ channel._channel_view_id = ViewPages.AVAILABLE
200+ return channels
201+
202 class SoftwareChannel(object):
203 """
204 class to represent a software channel
205@@ -239,8 +316,12 @@
206 def get_channels_manager(db):
207 global channels_manager
208 if channels_manager is None:
209- from softwarecenter.backend.aptchannels import AptChannelsManager
210- channels_manager = AptChannelsManager(db)
211+ from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
212+ if not USE_PACKAGEKIT_BACKEND:
213+ from softwarecenter.backend.aptchannels import AptChannelsManager
214+ channels_manager = AptChannelsManager(db)
215+ else:
216+ channels_manager = ChannelsManager(db)
217 return channels_manager
218
219 def is_channel_available(channelname):
220
221=== modified file 'softwarecenter/backend/installbackend.py'
222--- softwarecenter/backend/installbackend.py 2011-07-05 07:26:22 +0000
223+++ softwarecenter/backend/installbackend.py 2011-08-19 10:38:33 +0000
224@@ -19,6 +19,10 @@
225 from softwarecenter.utils import UnimplementedError
226
227 class InstallBackend(object):
228+ def __init__(self):
229+ self.pending_transactions = {}
230+ self.pending_purchases = []
231+
232 def upgrade(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):
233 pass
234 def remove(self, pkgname, appname, iconname, addons_install=[], addons_remove=[], metadata=None):
235@@ -56,8 +60,13 @@
236 def get_install_backend():
237 global install_backend
238 if install_backend is None:
239- from softwarecenter.backend.aptd import AptdaemonBackend
240- install_backend = AptdaemonBackend()
241+ from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
242+ if not USE_PACKAGEKIT_BACKEND:
243+ from softwarecenter.backend.aptd import AptdaemonBackend
244+ install_backend = AptdaemonBackend()
245+ else:
246+ from softwarecenter.backend.packagekitd import PackagekitBackend
247+ install_backend = PackagekitBackend()
248 return install_backend
249
250
251
252=== added file 'softwarecenter/backend/packagekitd.py'
253--- softwarecenter/backend/packagekitd.py 1970-01-01 00:00:00 +0000
254+++ softwarecenter/backend/packagekitd.py 2011-08-19 10:38:33 +0000
255@@ -0,0 +1,377 @@
256+# Copyright (C) 2009-2010 Canonical
257+#
258+# Authors:
259+# Alex Eftimie
260+#
261+# This program is free software; you can redistribute it and/or modify it under
262+# the terms of the GNU General Public License as published by the Free Software
263+# Foundation; version 3.
264+#
265+# This program is distributed in the hope that it will be useful, but WITHOUT
266+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
267+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
268+# details.
269+#
270+# You should have received a copy of the GNU General Public License along with
271+# this program; if not, write to the Free Software Foundation, Inc.,
272+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
273+
274+import logging
275+import dbus
276+import dbus.mainloop.glib
277+
278+from gi.repository import GObject
279+from gi.repository import PackageKitGlib as packagekit
280+
281+from softwarecenter.enums import TransactionTypes
282+from softwarecenter.backend.transactionswatcher import (BaseTransactionsWatcher,
283+ BaseTransaction,
284+ TransactionFinishedResult,
285+ TransactionProgress)
286+from softwarecenter.backend.installbackend import InstallBackend
287+
288+# temporary, must think of better solution
289+from softwarecenter.db.pkginfo import get_pkg_info
290+
291+LOG = logging.getLogger("softwarecenter.backend.packagekit")
292+
293+class PackagekitTransaction(BaseTransaction):
294+ _meta_data = {}
295+
296+ def __init__(self, trans):
297+ """ trans -- a PkProgress object """
298+ GObject.GObject.__init__(self)
299+ self._trans = trans
300+ self._setup_signals()
301+
302+ def _setup_signals(self):
303+ """ Connect signals to the PkProgress from libpackagekitlib,
304+ because PK DBus exposes only a generic Changed, without
305+ specifying the property changed
306+ """
307+ self._trans.connect('notify::role', self._emit, 'role-changed', 'role')
308+ self._trans.connect('notify::status', self._emit, 'status-changed', 'status')
309+ self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
310+ #self._trans.connect('notify::subpercentage', self._emit, 'progress-changed', 'subpercentage') # SC UI does not support subprogress
311+ self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
312+ self._trans.connect('notify::allow-cancel', self._emit, 'cancellable-changed', 'allow-cancel')
313+
314+ # connect the delete:
315+ proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
316+ trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
317+ trans.connect_to_signal("Destroy", self._remove)
318+
319+ def _emit(self, *args):
320+ prop, what = args[-1], args[-2]
321+ self.emit(what, self._trans.get_property(prop))
322+
323+ @property
324+ def tid(self):
325+ return self._trans.get_property('transaction-id')
326+ @property
327+ def status_details(self):
328+ return self.get_status_description() # FIXME
329+ @property
330+ def meta_data(self):
331+ return self._meta_data
332+ @property
333+ def cancellable(self):
334+ return self._trans.get_property('allow-cancel')
335+ @property
336+ def progress(self):
337+ return self._trans.get_property('percentage')
338+
339+ def get_role_description(self, role=None):
340+ role = role if role is not None else self._trans.get_property('role')
341+ return self.meta_data.get('sc_appname', packagekit.role_enum_to_string(role))
342+
343+ def get_status_description(self, status=None):
344+ status = status if status is not None else self._trans.get_property('status')
345+ return packagekit.status_enum_to_string(status)
346+
347+ def is_waiting(self):
348+ """ return true if a time consuming task is taking place """
349+ #LOG.debug('is_waiting ' + str(self._trans.get_property('status')))
350+ status = self._trans.get_property('status')
351+ return status == packagekit.StatusEnum.WAIT or \
352+ status == packagekit.StatusEnum.LOADING_CACHE or \
353+ status == packagekit.StatusEnum.SETUP
354+
355+ def is_downloading(self):
356+ #LOG.debug('is_downloading ' + str(self._trans.get_property('status')))
357+ status = self._trans.get_property('status')
358+ return status == packagekit.StatusEnum.DOWNLOAD or \
359+ (status >= packagekit.StatusEnum.DOWNLOAD_REPOSITORY and \
360+ status <= packagekit.StatusEnum.DOWNLOAD_UPDATEINFO)
361+
362+ def cancel(self):
363+ proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
364+ trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
365+ trans.Cancel()
366+
367+ def _remove(self):
368+ """ delete transaction from _tlist """
369+ # also notify pk install backend, so that this transaction gets removed
370+ # from pending_transactions
371+ self.emit('deleted')
372+ if self.tid in PackagekitTransactionsWatcher._tlist.keys():
373+ del PackagekitTransactionsWatcher._tlist[self.tid]
374+ LOG.debug("Delete transaction %s" % self.tid)
375+
376+class PackagekitTransactionsWatcher(BaseTransactionsWatcher):
377+ _tlist = {}
378+
379+ def __init__(self):
380+ super(PackagekitTransactionsWatcher, self).__init__()
381+ self.client = packagekit.Client()
382+
383+ bus = dbus.SystemBus()
384+ proxy = bus.get_object('org.freedesktop.PackageKit', '/org/freedesktop/PackageKit')
385+ daemon = dbus.Interface(proxy, 'org.freedesktop.PackageKit')
386+ daemon.connect_to_signal("TransactionListChanged",
387+ self._on_transactions_changed)
388+ queued = daemon.GetTransactionList()
389+ self._on_transactions_changed(queued)
390+
391+ def _on_transactions_changed(self, queued):
392+ if len(queued) > 0:
393+ current = queued[0]
394+ queued = queued[1:] if len(queued) > 1 else []
395+ else:
396+ current = None
397+ self.emit("lowlevel-transactions-changed", current, queued)
398+
399+ def add_transaction(self, tid, trans):
400+ """ return a tuple, (transaction, is_new) """
401+ if tid not in PackagekitTransactionsWatcher._tlist.keys():
402+ LOG.debug("Trying to setup %s" % tid)
403+ if not trans:
404+ trans = self.client.get_progress(tid, None)
405+ trans = PackagekitTransaction(trans)
406+ LOG.debug("Add return new transaction %s %s" % (tid, trans))
407+ PackagekitTransactionsWatcher._tlist[tid] = trans
408+ return (trans, True)
409+ return (PackagekitTransactionsWatcher._tlist[tid], False)
410+
411+ def get_transaction(self, tid):
412+ if tid not in PackagekitTransactionsWatcher._tlist.keys():
413+ trans, new = self.add_transaction(tid, None)
414+ return trans
415+ return PackagekitTransactionsWatcher._tlist[tid]
416+
417+class PackagekitBackend(GObject.GObject, InstallBackend):
418+
419+ __gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST,
420+ GObject.TYPE_NONE,
421+ (str,str,str,str)),
422+ # emits a TransactionFinished object
423+ 'transaction-finished':(GObject.SIGNAL_RUN_FIRST,
424+ GObject.TYPE_NONE,
425+ (GObject.TYPE_PYOBJECT, )),
426+ 'transaction-stopped':(GObject.SIGNAL_RUN_FIRST,
427+ GObject.TYPE_NONE,
428+ (GObject.TYPE_PYOBJECT,)),
429+ 'transactions-changed':(GObject.SIGNAL_RUN_FIRST,
430+ GObject.TYPE_NONE,
431+ (GObject.TYPE_PYOBJECT, )),
432+ 'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST,
433+ GObject.TYPE_NONE,
434+ (str,int,)),
435+ # the number/names of the available channels changed
436+ # FIXME: not emitted.
437+ 'channels-changed':(GObject.SIGNAL_RUN_FIRST,
438+ GObject.TYPE_NONE,
439+ (bool,)),
440+ }
441+
442+ def __init__(self):
443+ GObject.GObject.__init__(self)
444+ InstallBackend.__init__(self)
445+
446+ # transaction details for setting as meta
447+ self.new_pkgname, self.new_appname, self.new_iconname = '', '', ''
448+
449+ # this is public exposed
450+ self.pending_transactions = {}
451+
452+ self.client = packagekit.Client()
453+ self.pkginfo = get_pkg_info()
454+ self.pkginfo.open()
455+
456+ self._transactions_watcher = PackagekitTransactionsWatcher()
457+ self._transactions_watcher.connect('lowlevel-transactions-changed',
458+ self._on_lowlevel_transactions_changed)
459+
460+ def upgrade(self, pkgname, appname, iconname, addons_install=[],
461+ addons_remove=[], metadata=None):
462+ pass # FIXME implement it
463+ def remove(self, pkgname, appname, iconname, addons_install=[],
464+ addons_remove=[], metadata=None):
465+ self.remove_multiple((pkgname,), (appname,), (iconname,),
466+ addons_install, addons_remove, metadata
467+ )
468+
469+ def remove_multiple(self, pkgnames, appnames, iconnames,
470+ addons_install=[], addons_remove=[], metadatas=None):
471+
472+ # keep track of pkg, app and icon for setting them as meta
473+ self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
474+
475+ # temporary hack
476+ pkgnames = self._fix_pkgnames(pkgnames)
477+
478+ self.client.remove_packages_async(pkgnames,
479+ False, # allow deps
480+ False, # autoremove
481+ None, # cancellable
482+ self._on_progress_changed,
483+ None, # progress data
484+ self._on_remove_ready, # callback ready
485+ None # callback data
486+ )
487+ self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.REMOVE)
488+
489+ def install(self, pkgname, appname, iconname, filename=None,
490+ addons_install=[], addons_remove=[], metadata=None):
491+ if filename is not None:
492+ LOG.error("Filename not implemented") # FIXME
493+ else:
494+ self.install_multiple((pkgname,), (appname,), (iconname,),
495+ addons_install, addons_remove, metadata
496+ )
497+
498+ def install_multiple(self, pkgnames, appnames, iconnames,
499+ addons_install=[], addons_remove=[], metadatas=None):
500+
501+ # keep track of pkg, app and icon for setting them as meta
502+ self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
503+
504+ # temporary hack
505+ pkgnames = self._fix_pkgnames(pkgnames)
506+
507+ self.client.install_packages_async(False, # only trusted
508+ pkgnames,
509+ None, # cancellable
510+ self._on_progress_changed,
511+ None, # progress data
512+ self._on_install_ready, # GAsyncReadyCallback
513+ None # ready data
514+ )
515+ self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.INSTALL)
516+
517+ def apply_changes(self, pkgname, appname, iconname,
518+ addons_install=[], addons_remove=[], metadata=None):
519+ pass
520+ def reload(self, sources_list=None, metadata=None):
521+ """ reload package list """
522+ pass
523+
524+ def _on_transaction_deleted(self, trans):
525+ name = trans.meta_data.get('sc_pkgname', '')
526+ if name in self.pending_transactions:
527+ del self.pending_transactions[name]
528+ LOG.debug("Deleted transaction " + name)
529+ else:
530+ LOG.error("Could not delete: " + name + str(trans))
531+ # this is needed too
532+ self.emit('transactions-changed', self.pending_transactions)
533+ # also hack PackagekitInfo cache so that it emits a cache-ready signal
534+ if hasattr(self.pkginfo, '_reset_cache'):
535+ self.pkginfo._reset_cache(name)
536+
537+ def _on_progress_changed(self, progress, ptype, data=None):
538+ """ de facto callback on transaction's progress change """
539+ tid = progress.get_property('transaction-id')
540+ status = progress.get_property('status')
541+ if not tid:
542+ LOG.debug("Progress without transaction")
543+ return
544+
545+ trans, new = self._transactions_watcher.add_transaction(tid, progress)
546+ if new:
547+ trans.connect('deleted', self._on_transaction_deleted)
548+ LOG.debug("new transaction" + str(trans))
549+ # should add it to pending_transactions, but
550+ # i cannot get the pkgname here
551+ trans.meta_data['sc_appname'] = self.new_appname
552+ trans.meta_data['sc_pkgname'] = self.new_pkgname
553+ trans.meta_data['sc_iconname'] = self.new_iconname
554+ if self.new_pkgname not in self.pending_transactions:
555+ self.pending_transactions[self.new_pkgname] = trans
556+
557+ #LOG.debug("Progress update %s %s %s %s" % (status, ptype, progress.get_property('transaction-id'),progress.get_property('status')))
558+
559+ if status == packagekit.StatusEnum.FINISHED:
560+ LOG.debug("Transaction finished %s" % tid)
561+ self.emit("transaction-finished", TransactionFinishedResult(trans, True))
562+
563+ if status == packagekit.StatusEnum.CANCEL:
564+ LOG.debug("Transaction canceled %s" % tid)
565+ self.emit("transaction-stopped", TransactionFinishedResult(trans, True))
566+
567+ if ptype == packagekit.ProgressType.PACKAGE:
568+ # this should be done better
569+ # mvo: why getting package here at all?
570+ #package = progress.get_property('package')
571+ # fool sc ui about the name change
572+ trans.emit('role-changed', packagekit.RoleEnum.LAST)
573+
574+ if ptype == packagekit.ProgressType.PERCENTAGE:
575+ pkgname = trans.meta_data.get('sc_pkgname', '')
576+ prog = progress.get_property('percentage')
577+ if prog >= 0:
578+ self.emit("transaction-progress-changed", pkgname, prog)
579+ else:
580+ self.emit("transaction-progress-changed", pkgname, 0)
581+
582+ def _on_lowlevel_transactions_changed(self, watcher, current, pending):
583+ # update self.pending_transactions
584+ self.pending_transactions.clear()
585+
586+ for tid in [current] + pending:
587+ if not tid:
588+ continue
589+ trans = self._transactions_watcher.get_transaction(tid)
590+ trans_progress = TransactionProgress(trans)
591+ try:
592+ self.pending_transactions[trans_progress.pkgname] = trans_progress
593+ except:
594+ self.pending_transactions[trans.tid] = trans_progress
595+
596+ self.emit('transactions-changed', self.pending_transactions)
597+
598+ def _on_install_ready(self, source, result, data=None):
599+ LOG.debug("install done %s %s", source, result)
600+
601+ def _on_remove_ready(self, source, result, data=None):
602+ LOG.debug("remove done %s %s", source, result)
603+
604+ def _fix_pkgnames(self, pkgnames):
605+ is_pk_id = lambda a: ';' in a
606+ res = []
607+ for p in pkgnames:
608+ if not is_pk_id(p):
609+ version = self.pkginfo[p].candidate.version
610+ p = '{name};{version};{arch};{source}'.format(name=p,
611+ version=version, arch='', source=''
612+ )
613+ res.append(p)
614+ return res
615+
616+if __name__ == "__main__":
617+ package = 'firefox'
618+
619+ loop = dbus.mainloop.glib.DBusGMainLoop()
620+ dbus.set_default_main_loop(loop)
621+
622+ backend = PackagekitBackend()
623+ pkginfo = get_pkg_info()
624+ if pkginfo[package].is_installed:
625+ backend.remove(package, package, '')
626+ backend.install(package, package, '')
627+ else:
628+ backend.install(package, package, '')
629+ backend.remove(package, package, '')
630+ import gtk;gtk.main()
631+ #print backend._fix_pkgnames(('cheese',))
632+
633
634=== modified file 'softwarecenter/backend/reviews.py'
635--- softwarecenter/backend/reviews.py 2011-08-19 09:08:47 +0000
636+++ softwarecenter/backend/reviews.py 2011-08-19 10:38:33 +0000
637@@ -57,7 +57,6 @@
638 from StringIO import StringIO
639 from urllib import quote_plus
640
641-
642 from softwarecenter.backend.piston.rnrclient import RatingsAndReviewsAPI
643 from softwarecenter.backend.piston.rnrclient_pristine import ReviewDetails
644 from softwarecenter.db.categories import CategoriesParser
645
646=== modified file 'softwarecenter/backend/transactionswatcher.py'
647--- softwarecenter/backend/transactionswatcher.py 2011-08-17 12:24:39 +0000
648+++ softwarecenter/backend/transactionswatcher.py 2011-08-19 10:38:33 +0000
649@@ -28,6 +28,26 @@
650 """
651 wrapper class for install backend dbus Transaction objects
652 """
653+ __gsignals__ = {'progress-details-changed':(GObject.SIGNAL_RUN_FIRST,
654+ GObject.TYPE_NONE,
655+ (int, int, int, int, int, int)),
656+ 'progress-changed':(GObject.SIGNAL_RUN_FIRST,
657+ GObject.TYPE_NONE,
658+ (GObject.TYPE_PYOBJECT, )),
659+ 'status-changed':(GObject.SIGNAL_RUN_FIRST,
660+ GObject.TYPE_NONE,
661+ (GObject.TYPE_PYOBJECT, )),
662+ 'cancellable-changed':(GObject.SIGNAL_RUN_FIRST,
663+ GObject.TYPE_NONE,
664+ (GObject.TYPE_PYOBJECT, )),
665+ 'role-changed':(GObject.SIGNAL_RUN_FIRST,
666+ GObject.TYPE_NONE,
667+ (GObject.TYPE_PYOBJECT, )),
668+ 'deleted':(GObject.SIGNAL_RUN_FIRST,
669+ GObject.TYPE_NONE,
670+ []),
671+ }
672+
673 @property
674 def tid(self):
675 pass
676@@ -99,6 +119,11 @@
677 def get_transactions_watcher():
678 global _tw
679 if _tw is None:
680- from aptd import AptdaemonTransactionsWatcher
681- _tw = AptdaemonTransactionsWatcher()
682+ from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
683+ if not USE_PACKAGEKIT_BACKEND:
684+ from aptd import AptdaemonTransactionsWatcher
685+ _tw = AptdaemonTransactionsWatcher()
686+ else:
687+ from softwarecenter.backend.packagekitd import PackagekitTransactionsWatcher
688+ _tw = PackagekitTransactionsWatcher()
689 return _tw
690
691=== modified file 'softwarecenter/db/appfilter.py'
692--- softwarecenter/db/appfilter.py 2011-08-07 07:55:40 +0000
693+++ softwarecenter/db/appfilter.py 2011-08-19 10:38:33 +0000
694@@ -63,8 +63,12 @@
695 return False
696 if self.installed_only:
697 # use the lowlevel cache here, twice as fast
698- lowlevel_cache = self.cache._cache._cache
699- if (not pkgname in lowlevel_cache or
700+ try:
701+ lowlevel_cache = self.cache._cache._cache
702+ except AttributeError:
703+ lowlevel_cache = None
704+ if lowlevel_cache and \
705+ (not pkgname in lowlevel_cache or
706 not lowlevel_cache[pkgname].current_ver):
707 return False
708 if self.not_installed_only:
709
710=== modified file 'softwarecenter/db/history.py'
711--- softwarecenter/db/history.py 2011-05-28 09:28:30 +0000
712+++ softwarecenter/db/history.py 2011-08-19 10:38:33 +0000
713@@ -105,6 +105,10 @@
714 """ get the global PackageHistory() singleton object """
715 global pkg_history
716 if pkg_history is None:
717- from history_impl.apthistory import AptHistory
718- pkg_history = AptHistory()
719+ from softwarecenter.enums import USE_APT_HISTORY
720+ if USE_APT_HISTORY:
721+ from history_impl.apthistory import AptHistory
722+ pkg_history = AptHistory()
723+ else:
724+ pkg_history = PackageHistory()
725 return pkg_history
726
727=== modified file 'softwarecenter/db/history_impl/apthistory.py'
728--- softwarecenter/db/history_impl/apthistory.py 2011-08-19 08:46:09 +0000
729+++ softwarecenter/db/history_impl/apthistory.py 2011-08-19 10:38:33 +0000
730@@ -44,7 +44,6 @@
731 except ImportError:
732 import pickle
733
734-
735 LOG = logging.getLogger(__name__)
736
737 from softwarecenter.paths import SOFTWARE_CENTER_CACHE_DIR
738
739=== modified file 'softwarecenter/db/pkginfo.py'
740--- softwarecenter/db/pkginfo.py 2011-08-17 12:24:39 +0000
741+++ softwarecenter/db/pkginfo.py 2011-08-19 10:38:33 +0000
742@@ -138,7 +138,8 @@
743 return -1
744 def get_origins(self, pkgname):
745 return []
746- def get_addons(self, pkgname, ignore_installed):
747+ def get_addons(self, pkgname, ignore_installed=False):
748+ """ :return: a tuple of pkgnames (recommends, suggests) """
749 return ([], [])
750
751 def get_packages_removed_on_remove(self, pkg):
752@@ -173,6 +174,11 @@
753 def get_pkg_info():
754 global pkginfo
755 if pkginfo is None:
756- from softwarecenter.db.pkginfo_impl.aptcache import AptCache
757- pkginfo = AptCache()
758+ from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
759+ if not USE_PACKAGEKIT_BACKEND:
760+ from softwarecenter.db.pkginfo_impl.aptcache import AptCache
761+ pkginfo = AptCache()
762+ else:
763+ from softwarecenter.db.pkginfo_impl.packagekit import PackagekitInfo
764+ pkginfo = PackagekitInfo()
765 return pkginfo
766
767=== added file 'softwarecenter/db/pkginfo_impl/packagekit.py'
768--- softwarecenter/db/pkginfo_impl/packagekit.py 1970-01-01 00:00:00 +0000
769+++ softwarecenter/db/pkginfo_impl/packagekit.py 2011-08-19 10:38:33 +0000
770@@ -0,0 +1,257 @@
771+# Copyright (C) 2011 Canonical
772+#
773+# Authors:
774+# Alex Eftimie
775+#
776+# This program is free software; you can redistribute it and/or modify it under
777+# the terms of the GNU General Public License as published by the Free Software
778+# Foundation; version 3.
779+#
780+# This program is distributed in the hope that it will be useful, but WITHOUT
781+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
782+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
783+# details.
784+#
785+# You should have received a copy of the GNU General Public License along with
786+# this program; if not, write to the Free Software Foundation, Inc.,
787+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
788+
789+from gi.repository import PackageKitGlib as packagekit
790+import logging
791+
792+from softwarecenter.db.pkginfo import PackageInfo, _Version
793+from softwarecenter.distro import get_distro
794+
795+LOG = logging.getLogger('softwarecenter.db.packagekit')
796+
797+class FakeOrigin:
798+ def __init__(self, name, label = None):
799+ self.origin = name
800+ self.trusted = True
801+ self.component = 'unknown-component'
802+ self.site = ''
803+ self.label = name.capitalize() if not label else label
804+ self.archive = name
805+
806+class PackagekitVersion(_Version):
807+ def __init__(self, package, pkginfo):
808+ self.package = package
809+ self.pkginfo = pkginfo
810+
811+ @property
812+ def description(self):
813+ pkgid = self.package.get_id()
814+ return self.pkginfo.get_description(pkgid)
815+
816+ @property
817+ def downloadable(self):
818+ return True #FIXME: check for an equivalent
819+ @property
820+ def summary(self):
821+ return self.package.get_property('summary')
822+ @property
823+ def size(self):
824+ return self.pkginfo.get_size(self.package.get_name())
825+ @property
826+ def installed_size(self):
827+ """ In packagekit, installed_size can be fetched only for installed packages,
828+ and is stored in the same 'size' property as the package size """
829+ return self.pkginfo.get_installed_size(self.package.get_name())
830+ @property
831+ def version(self):
832+ return self.package.get_version()
833+ @property
834+ def origins(self):
835+ return self.pkginfo.get_origins(self.package.get_name())
836+
837+
838+class PackagekitInfo(PackageInfo):
839+ USE_CACHE = True
840+
841+ def __init__(self):
842+ super(PackagekitInfo, self).__init__()
843+ self.client = packagekit.Client()
844+ self._cache = {} # temporary hack for decent testing
845+ self._notfound_cache = []
846+ self.distro = get_distro()
847+
848+ def __contains__(self, pkgname):
849+ # setting it like this for now
850+ return pkgname not in self._notfound_cache
851+
852+ def is_installed(self, pkgname):
853+ p = self._get_one_package(pkgname)
854+ if not p:
855+ return False
856+ return p.get_info() == packagekit.InfoEnum.INSTALLED
857+
858+ def is_available(self, pkgname):
859+ # FIXME: i don't think this is being used
860+ return True
861+
862+ def get_installed(self, pkgname):
863+ p = self._get_one_package(pkgname)
864+ if p.get_info() == packagekit.InfoEnum.INSTALLED:
865+ return PackagekitVersion(p, self) if p else None
866+
867+ def get_candidate(self, pkgname):
868+ p = self._get_one_package(pkgname, pfilter=packagekit.FilterEnum.NEWEST)
869+ return PackagekitVersion(p, self) if p else None
870+
871+ def get_versions(self, pkgname):
872+ return [PackagekitVersion(p, self) for p in self._get_packages(pkgname)]
873+
874+ def get_section(self, pkgname):
875+ # FIXME: things are fuzzy here - group-section association
876+ p = self._get_one_package(pkgname)
877+ if p:
878+ return packagekit.group_enum_to_string(p.get_property('group'))
879+
880+ def get_summary(self, pkgname):
881+ p = self._get_one_package(pkgname)
882+ return p.get_property('summary') if p else ''
883+
884+ def get_description(self, packageid):
885+ p = self._get_package_details(packageid)
886+ return p.get_property('description') if p else ''
887+
888+ def get_website(self, pkgname):
889+ p = self._get_one_package(pkgname)
890+ if not p:
891+ return ''
892+ p = self._get_package_details(p.get_id())
893+ return p.get_property('url') if p else ''
894+
895+ def get_installed_files(self, pkgname):
896+ p = self._get_one_package(pkgname)
897+ if not p:
898+ return []
899+ res = self.client.get_files((p.get_id(),), None, self._on_progress_changed, None)
900+ files = res.get_files_array()
901+ if not files:
902+ return []
903+ return files[0].get_property('files')
904+
905+ def get_size(self, pkgname):
906+ p = self._get_one_package(pkgname)
907+ if not p:
908+ return -1
909+ p = self._get_package_details(p.get_id())
910+ return p.get_property('size') if p else -1
911+
912+ def get_installed_size(self, pkgname):
913+ return self.get_size(pkgname)
914+
915+ def get_origins(self, pkgname):
916+ return [FakeOrigin(self.distro.get_distro_channel_name(), self.distro.get_distro_channel_description())]
917+
918+ def component_available(self, distro_codename, component):
919+ # FIXME stub
920+ return True
921+
922+ def get_addons(self, pkgname, ignore_installed=True):
923+ # FIXME implement it
924+ return ([], [])
925+
926+ def get_packages_removed_on_remove(self, pkg):
927+ """ Returns a package names list of reverse dependencies
928+ which will be removed if the package is removed."""
929+ p = self._get_one_package(pkg.name)
930+ if not p:
931+ return []
932+ autoremove = False
933+ res = self.client.simulate_remove_packages((p.get_id(),),
934+ autoremove, None,
935+ self._on_progress_changed, None,
936+ )
937+ if not res:
938+ return []
939+ return [p.get_name() for p in res.get_package_array() if p.get_name() != pkg.name]
940+
941+ def get_packages_removed_on_install(self, pkg):
942+ """ Returns a package names list of dependencies
943+ which will be removed if the package is installed."""
944+ p = self._get_one_package(pkg.name)
945+ if not p:
946+ return []
947+ res = self.client.simulate_install_packages((p.get_id(),),
948+ None,
949+ self._on_progress_changed, None,
950+ )
951+ if not res:
952+ return []
953+ return [p.get_name() for p in res.get_package_array() if (p.get_name() != pkg.name) and p.get_info() == packagekit.InfoEnum.INSTALLED]
954+
955+ def get_total_size_on_install(self, pkgname, addons_install=None,
956+ addons_remove=None):
957+ """ Returns a tuple (download_size, installed_size)
958+ with disk size in KB calculated for pkgname installation
959+ plus addons change.
960+ """
961+ # FIXME implement it
962+ return (0, 0)
963+
964+ @property
965+ def ready(self):
966+ """ No PK equivalent, simply returning True """
967+ return True
968+
969+ """ private methods """
970+ def _get_package_details(self, packageid, cache=USE_CACHE):
971+ LOG.debug("package_details %s", packageid) #, self._cache.keys()
972+ if (packageid in self._cache.keys()) and cache:
973+ return self._cache[packageid]
974+
975+ result = self.client.get_details((packageid,), None, self._on_progress_changed, None)
976+ pkgs = result.get_details_array()
977+ if not pkgs:
978+ return None
979+ packageid = pkgs[0].get_property('package-id')
980+ self._cache[packageid] = pkgs[0]
981+ return pkgs[0]
982+
983+ def _get_one_package(self, pkgname, pfilter=packagekit.FilterEnum.NONE, cache=USE_CACHE):
984+ LOG.debug("package_one %s", pkgname) #, self._cache.keys()
985+ if (pkgname in self._cache.keys()) and cache:
986+ return self._cache[pkgname]
987+ ps = self._get_packages(pkgname, pfilter)
988+ if not ps:
989+ # also keep it in not found, to prevent further calls of resolve
990+ if pkgname not in self._notfound_cache:
991+ LOG.debug("blacklisted %s", pkgname)
992+ self._notfound_cache.append(pkgname)
993+ return None
994+ self._cache[pkgname] = ps[0]
995+ return ps[0]
996+
997+ def _get_packages(self, pkgname, pfilter=packagekit.FilterEnum.NONE):
998+ """ resolve a package name into a PkPackage object or return None """
999+ pfilter = 1 << pfilter
1000+ result = self.client.resolve(pfilter,
1001+ (pkgname,),
1002+ None,
1003+ self._on_progress_changed, None
1004+ )
1005+ pkgs = result.get_package_array()
1006+ return pkgs
1007+
1008+ def _reset_cache(self, name=None):
1009+ # Clean resolved packages cache
1010+ # This is used after finishing a transaction, so that we always
1011+ # have the latest package information
1012+ LOG.debug("[reset_cache] here: %s name: %s", self._cache.keys(), name)
1013+ if name and (name in self._cache.keys()):
1014+ del self._cache[name]
1015+ else:
1016+ # delete all
1017+ self._cache = {}
1018+ # appdetails gets refreshed:
1019+ self.emit('cache-ready')
1020+
1021+ def _on_progress_changed(self, progress, ptype, data=None):
1022+ pass
1023+
1024+if __name__=="__main__":
1025+ pi = PackagekitInfo()
1026+
1027+ print "Firefox, installed ", pi.is_installed('firefox')
1028
1029=== modified file 'softwarecenter/db/update.py'
1030--- softwarecenter/db/update.py 2011-08-19 08:21:58 +0000
1031+++ softwarecenter/db/update.py 2011-08-19 10:38:33 +0000
1032@@ -231,6 +231,12 @@
1033 return self._parse_with_lists(key)
1034 else:
1035 return self._parse_value(key)
1036+ def get_desktop_categories(self):
1037+ return self._get_desktop_list("Categories", split_str=',')
1038+ def get_desktop_mimetypes(self):
1039+ if not self.has_option_desktop("MimeType"):
1040+ return []
1041+ return self._get_desktop_list("MimeType", split_str=',')
1042 def _parse_value(self, key):
1043 for child in self.appinfo_xml.iter(key):
1044 # FIXME: deal with the i18n
1045@@ -530,7 +536,7 @@
1046 icondata = ""
1047 # write it if we have data
1048 if icondata:
1049- # the iconcache gets mightly confused if there is a "." in the name
1050+ # the iconcache gets mightly confused if there is a "." in the name
1051 iconname = "sc-agent-%s" % entry.package_name.replace(".", "__")
1052 open(os.path.join(
1053 softwarecenter.paths.SOFTWARE_CENTER_ICON_CACHE_DIR,
1054
1055=== added file 'softwarecenter/distro/SUSELINUX.py'
1056--- softwarecenter/distro/SUSELINUX.py 1970-01-01 00:00:00 +0000
1057+++ softwarecenter/distro/SUSELINUX.py 2011-08-19 10:38:33 +0000
1058@@ -0,0 +1,85 @@
1059+# Copyright (C) 2011 Canonical
1060+#
1061+# Authors:
1062+# Alex Eftimie
1063+#
1064+# This program is free software; you can redistribute it and/or modify it under
1065+# the terms of the GNU General Public License as published by the Free Software
1066+# Foundation; version 3.
1067+#
1068+# This program is distributed in the hope that it will be useful, but WITHOUT
1069+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1070+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1071+# details.
1072+#
1073+# You should have received a copy of the GNU General Public License along with
1074+# this program; if not, write to the Free Software Foundation, Inc.,
1075+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1076+
1077+import os
1078+from gettext import gettext as _
1079+from softwarecenter.distro import Distro
1080+
1081+class SUSELINUX(Distro):
1082+ # see __init__.py description
1083+ DISTROSERIES = ["11.4",
1084+ ]
1085+
1086+ # screenshot handling
1087+ SCREENSHOT_THUMB_URL = "http://screenshots.ubuntu.com/thumbnail-with-version/%(pkgname)s/%(version)s"
1088+ SCREENSHOT_LARGE_URL = "http://screenshots.ubuntu.com/screenshot-with-version/%(pkgname)s/%(version)s"
1089+
1090+ # reviews
1091+ REVIEWS_SERVER = os.environ.get("SOFTWARE_CENTER_REVIEWS_HOST") or "http://reviews.ubuntu.com/reviews/api/1.0"
1092+ REVIEWS_URL = REVIEWS_SERVER + "/reviews/filter/%(language)s/%(origin)s/%(distroseries)s/%(version)s/%(pkgname)s%(appname)s/"
1093+
1094+ REVIEW_STATS_URL = REVIEWS_SERVER+"/review-stats"
1095+
1096+ def get_app_name(self):
1097+ return _("Software Center")
1098+
1099+ def get_app_description(self):
1100+ return _("Lets you choose from thousands of applications available.")
1101+
1102+ def get_distro_channel_name(self):
1103+ """ The name in the Release file """
1104+ return "openSUSE"
1105+
1106+ def get_distro_channel_description(self):
1107+ """ The description of the main distro channel """
1108+ return _("Provided by openSUSE")
1109+
1110+ def get_removal_warning_text(self, cache, pkg, appname, depends):
1111+ primary = _("To remove %s, these items must be removed "
1112+ "as well:") % appname
1113+ button_text = _("Remove All")
1114+
1115+ return (primary, button_text)
1116+
1117+ def get_license_text(self, component):
1118+ if component in ("main", "universe", "independent"):
1119+ return _("Open source")
1120+ elif component in ("restricted", "commercial"):
1121+ return _("Proprietary")
1122+
1123+ def is_supported(self, cache, doc, pkgname):
1124+ # FIXME
1125+ return False
1126+
1127+ def get_supported_query(self):
1128+ # FIXME
1129+ import xapian
1130+ query1 = xapian.Query("XOL"+"Ubuntu")
1131+ query2a = xapian.Query("XOC"+"main")
1132+ query2b = xapian.Query("XOC"+"restricted")
1133+ query2 = xapian.Query(xapian.Query.OP_OR, query2a, query2b)
1134+ return xapian.Query(xapian.Query.OP_AND, query1, query2)
1135+
1136+ def get_maintenance_status(self, cache, appname, pkgname, component, channelname):
1137+ # FIXME
1138+ return
1139+
1140+ def get_downloadable_icon_url(self, full_archive_url, icon_filename):
1141+ # FIXME
1142+ return None
1143+
1144
1145=== modified file 'softwarecenter/distro/Ubuntu.py'
1146--- softwarecenter/distro/Ubuntu.py 2011-07-29 15:24:17 +0000
1147+++ softwarecenter/distro/Ubuntu.py 2011-08-19 10:38:33 +0000
1148@@ -129,6 +129,8 @@
1149 # (to exclude stuff in ubuntu-updates for the support time
1150 # calculation because the "Release" file time for that gets
1151 # updated regularly)
1152+ if not hasattr(cache, '_cache') or not hasattr(pkgname, '_pkg'):
1153+ return
1154 releasef = get_release_filename_for_pkg(cache._cache, pkgname,
1155 "Ubuntu",
1156 self.get_codename())
1157
1158=== modified file 'softwarecenter/distro/__init__.py'
1159--- softwarecenter/distro/__init__.py 2011-08-09 08:47:43 +0000
1160+++ softwarecenter/distro/__init__.py 2011-08-19 10:38:33 +0000
1161@@ -145,7 +145,8 @@
1162
1163 def _get_distro():
1164 distro_id = subprocess.Popen(["lsb_release","-i","-s"],
1165- stdout=subprocess.PIPE).communicate()[0].strip()
1166+ stdout=subprocess.PIPE).communicate()[0]
1167+ distro_id = distro_id.strip().replace(' ', '')
1168 logging.getLogger("softwarecenter.distro").debug("get_distro: '%s'" % distro_id)
1169 # start with a import, this gives us only a softwarecenter module
1170 module = __import__(distro_id, globals(), locals(), [], -1)
1171
1172=== modified file 'softwarecenter/enums.py'
1173--- softwarecenter/enums.py 2011-08-18 16:36:07 +0000
1174+++ softwarecenter/enums.py 2011-08-19 10:38:33 +0000
1175@@ -202,5 +202,9 @@
1176 DISTRO,
1177 RELEASE,
1178 CODENAME)
1179-
1180-
1181+
1182+# global backend switch
1183+USE_PACKAGEKIT_BACKEND = False
1184+
1185+# history switch (useful on non apt based distros)
1186+USE_APT_HISTORY = True
1187
1188=== modified file 'softwarecenter/ui/gtk/appdetailsview_gtk.py'
1189--- softwarecenter/ui/gtk/appdetailsview_gtk.py 2011-08-19 08:46:09 +0000
1190+++ softwarecenter/ui/gtk/appdetailsview_gtk.py 2011-08-19 10:38:33 +0000
1191@@ -1525,7 +1525,7 @@
1192 self.app.pkgname and
1193 self.app.appname == app.appname and
1194 self.app.pkgname == app.pkgname)
1195- #print 'SameApp:', same_app
1196+ #print 'SameApp:', same_app, "force", force
1197
1198 # init data
1199 self.app = app
1200@@ -1823,15 +1823,25 @@
1201 import softwarecenter.distro
1202 distro = softwarecenter.distro.get_distro()
1203
1204+ def handle_action(view, app, addons_install, addons_remove, action):
1205+ logging.debug('[action here] %s %s' % (action, app))
1206+ # action_func is one of: "install", "remove", "upgrade", "apply_changes"
1207+ action_func = getattr(view.backend, action)
1208+ if callable(action_func):
1209+ action_func(app.pkgname, app.appname, '', addons_install=addons_install, addons_remove=addons_remove)
1210+ else:
1211+ LOG.error("Not a valid action in backend: '%s'" % action)
1212+
1213 # gui
1214 win = gtk.Window()
1215 scroll = gtk.ScrolledWindow()
1216 view = AppDetailsViewGtk(db, distro, icons, cache, datadir, win)
1217+ view.connect("application-request-action", handle_action)
1218 #view.show_app(Application("Pay App Example", "pay-app"))
1219 #view.show_app(Application("3D Chess", "3dchess"))
1220 #view.show_app(Application("Movie Player", "totem"))
1221 #view.show_app(Application("ACE", "unace"))
1222- view.show_app(Application("", "apt"))
1223+ view.show_app(Application("", "firefox"))
1224
1225 #view.show_app("AMOR")
1226 #view.show_app("Configuration Editor")
1227@@ -1849,6 +1859,17 @@
1228 # keep it spinning to test for re-draw issues and memleaks
1229 #GObject.timeout_add_seconds(2, _show_app, view)
1230
1231+ # also show pending view
1232+ from softwarecenter.ui.gtk.pendingview import PendingView
1233+ view2 = PendingView(icons)
1234+ scroll2 = gtk.ScrolledWindow()
1235+ scroll2.add(view2)
1236+ win2 = gtk.Window()
1237+ win2.add(scroll2)
1238+ view2.grab_focus()
1239+ win2.set_size_request(500,200)
1240+ win2.connect('delete-event', gtk.main_quit)
1241+ win2.show_all()
1242
1243 # run it
1244 gtk.main()
1245
1246=== modified file 'softwarecenter/ui/gtk/historypane.py'
1247--- softwarecenter/ui/gtk/historypane.py 2011-08-17 11:44:31 +0000
1248+++ softwarecenter/ui/gtk/historypane.py 2011-08-19 10:38:33 +0000
1249@@ -117,7 +117,7 @@
1250 removals_action.set_group(all_action)
1251 removals_button = removals_action.create_tool_item()
1252 self.toolbar.insert(removals_button, 3)
1253-
1254+
1255 self._actions_list = all_action.get_group()
1256 self._set_actions_sensitive(False)
1257
1258@@ -129,7 +129,7 @@
1259 gtk.POLICY_AUTOMATIC)
1260 self.history_view.show()
1261 self.history_view.add(self.view)
1262-
1263+
1264 # make a spinner to display while history is loading
1265 self.spinner_view = SpinnerView(_('Loading history'))
1266 self.spinner_notebook = gtk.Notebook()
1267@@ -147,7 +147,7 @@
1268 self.view.set_model(self.store_filter)
1269 all_action.set_active(True)
1270 self.last = None
1271-
1272+
1273 # to save (a lot of) time at startup we load history later, only when
1274 # it is selected to be viewed
1275 self.history = None
1276
1277=== modified file 'softwarecenter/ui/gtk/pendingview.py'
1278--- softwarecenter/ui/gtk/pendingview.py 2011-08-17 11:44:31 +0000
1279+++ softwarecenter/ui/gtk/pendingview.py 2011-08-19 10:38:33 +0000
1280@@ -174,6 +174,13 @@
1281 status = _("Downloaded %sB of %sB") % \
1282 (current_bytes_str, total_bytes_str)
1283 row[self.COL_STATUS] = self._render_status_text(name, status)
1284+ elif trans.is_waiting():
1285+ name = row[self.COL_NAME]
1286+ if eta != 0:
1287+ status = _("ETA: %s") % eta
1288+ row[self.COL_STATUS] = self._render_status_text(name, status)
1289+ else:
1290+ logging.debug("neither downloading or waiting")
1291
1292 def _on_progress_changed(self, trans, progress):
1293 # print "_on_progress_changed: ", trans, progress
1294@@ -280,6 +287,9 @@
1295 if __name__ == "__main__":
1296 logging.basicConfig(level=logging.DEBUG)
1297
1298+ import dbus.mainloop.glib
1299+ dbus.set_default_main_loop(dbus.mainloop.glib.DBusGMainLoop())
1300+
1301 icons = gtk.icon_theme_get_default()
1302 view = PendingView(icons)
1303
1304@@ -291,6 +301,14 @@
1305 win.add(scroll)
1306 view.grab_focus()
1307 win.set_size_request(500,200)
1308+ win.connect('delete-event', gtk.main_quit)
1309 win.show_all()
1310
1311+ backend = view.tv.get_model().backend
1312+ #packages = ('cheese', 'firefox')
1313+ packages = ('firefox',)
1314+ if backend.pkginfo['firefox'].is_installed:
1315+ backend.remove_multiple(packages, packages, ('', ''))
1316+ else:
1317+ backend.install_multiple(packages, packages, ('', ''))
1318 gtk.main()
1319
1320=== modified file 'softwarecenter/ui/gtk/viewswitcher.py'
1321--- softwarecenter/ui/gtk/viewswitcher.py 2011-08-17 11:44:31 +0000
1322+++ softwarecenter/ui/gtk/viewswitcher.py 2011-08-19 10:38:33 +0000
1323@@ -415,8 +415,13 @@
1324 if __name__ == "__main__":
1325 logging.basicConfig(level=logging.DEBUG)
1326 import sys
1327- import apt
1328-
1329+
1330+ import dbus
1331+ import dbus.mainloop.glib
1332+
1333+ loop = dbus.mainloop.glib.DBusGMainLoop()
1334+ dbus.set_default_main_loop(loop)
1335+
1336 if len(sys.argv) > 1:
1337 datadir = sys.argv[1]
1338 elif os.path.exists("./data"):
1339@@ -429,7 +434,10 @@
1340
1341 xapian_base_path = XAPIAN_BASE_PATH
1342 pathname = os.path.join(xapian_base_path, "xapian")
1343- cache = apt.Cache(apt.progress.text.OpProgress())
1344+
1345+ # cache
1346+ from softwarecenter.db.pkginfo import get_pkg_info
1347+ cache = get_pkg_info()
1348 db = StoreDatabase(pathname, cache)
1349 db.open()
1350
1351@@ -447,4 +455,5 @@
1352 win.set_size_request(400,400)
1353 win.show_all()
1354
1355+ view.backend.install_multiple(['cheese'],['cheese'],[''])
1356 gtk.main()
1357
1358=== modified file 'softwarecenter/ui/gtk3/app.py'
1359--- softwarecenter/ui/gtk3/app.py 2011-08-18 17:42:33 +0000
1360+++ softwarecenter/ui/gtk3/app.py 2011-08-19 10:38:33 +0000
1361@@ -65,7 +65,7 @@
1362 try:
1363 from aptd_gtk3 import InstallBackendUI
1364 InstallBackendUI # pyflakes
1365-except ImportError:
1366+except:
1367 from softwarecenter.backend.installbackend import InstallBackendUI
1368
1369 # ui imports
1370
1371=== added file 'softwarecenter/ui/gtk3/panes/installedpane.py'
1372--- softwarecenter/ui/gtk3/panes/installedpane.py 1970-01-01 00:00:00 +0000
1373+++ softwarecenter/ui/gtk3/panes/installedpane.py 2011-08-18 17:42:33 +0000
1374@@ -0,0 +1,426 @@
1375+# Copyright (C) 2009 Canonical
1376+#
1377+# Authors:
1378+# Michael Vogt
1379+#
1380+# This program is free software; you can redistribute it and/or modify it under
1381+# the terms of the GNU General Public License as published by the Free Software
1382+# Foundation; version 3.
1383+#
1384+# This program is distributed in the hope that it will be useful, but WITHOUT
1385+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1386+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1387+# details.
1388+#
1389+# You should have received a copy of the GNU General Public License along with
1390+# this program; if not, write to the Free Software Foundation, Inc.,
1391+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1392+
1393+from gi.repository import Gtk
1394+import logging
1395+import xapian
1396+from gi.repository import GObject
1397+
1398+from gettext import gettext as _
1399+
1400+from softwarecenter.enums import (NonAppVisibility,
1401+ SortMethods)
1402+from softwarecenter.utils import wait_for_apt_cache_ready
1403+from softwarecenter.db.categories import (CategoriesParser,
1404+ categories_sorted_by_name)
1405+from softwarecenter.ui.gtk3.models.appstore2 import (
1406+ AppTreeStore, CategoryRowReference)
1407+from softwarepane import SoftwarePane
1408+from softwarecenter.db.appfilter import AppFilter
1409+
1410+LOG=logging.getLogger(__name__)
1411+
1412+def interrupt_build_and_wait(f):
1413+ """ decorator that ensures that a build of the categorised installed apps
1414+ is interrupted before a new build commences.
1415+ expects self._build_in_progress and self._halt_build as properties
1416+ """
1417+ def wrapper(*args, **kwargs):
1418+ self = args[0]
1419+ if self._build_in_progress:
1420+ LOG.debug('Waiting for build to exit...')
1421+ self._halt_build = True
1422+ GObject.timeout_add(200, lambda: wrapper(*args, **kwargs))
1423+ return False
1424+ # ready now
1425+ self._halt_build = False
1426+ f(*args, **kwargs)
1427+ return False
1428+ return wrapper
1429+
1430+
1431+class InstalledPane(SoftwarePane, CategoriesParser):
1432+ """Widget that represents the installed panel in software-center
1433+ It contains a search entry and navigation buttons
1434+ """
1435+
1436+ class Pages:
1437+ # page names, useful for debuggin
1438+ NAMES = ('list', 'details')
1439+ # the actual page id's
1440+ (LIST,
1441+ DETAILS) = range(2)
1442+ # the default page
1443+ HOME = LIST
1444+
1445+ __gsignals__ = {'installed-pane-created':(GObject.SignalFlags.RUN_FIRST,
1446+ None,
1447+ ())}
1448+
1449+ def __init__(self, cache, db, distro, icons, datadir):
1450+
1451+ # parent
1452+ SoftwarePane.__init__(self, cache, db, distro, icons, datadir, show_ratings=False)
1453+ CategoriesParser.__init__(self, db)
1454+
1455+ self.current_appview_selection = None
1456+ self.loaded = False
1457+ self.pane_name = _("Installed Software")
1458+
1459+ self.installed_apps = 0
1460+
1461+ # switches to terminate build in progress
1462+ self._build_in_progress = False
1463+ self._halt_build = False
1464+
1465+ self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE
1466+
1467+ def init_view(self):
1468+ if self.view_initialized: return
1469+
1470+ SoftwarePane.init_view(self)
1471+
1472+ self.search_aid.set_no_show_all(True)
1473+ self.notebook.append_page(self.box_app_list, Gtk.Label(label="installed"))
1474+
1475+ # details
1476+ self.notebook.append_page(self.scroll_details, Gtk.Label(label="details"))
1477+ # initial refresh
1478+ self.state.search_term = ""
1479+
1480+ # build models and filters
1481+ self.base_model = AppTreeStore(self.db, self.cache, self.icons)
1482+
1483+ self.treefilter = self.base_model.filter_new(None)
1484+ self.treefilter.set_visible_func(self._row_visibility_func,
1485+ AppTreeStore.COL_ROW_DATA)
1486+ self.app_view.set_model(self.treefilter)
1487+ self.app_view.tree_view.connect("row-collapsed", self._on_row_collapsed)
1488+
1489+ self.visible_docids = None
1490+
1491+ self._all_cats = self.parse_applications_menu('/usr/share/app-install')
1492+ self._all_cats = categories_sorted_by_name(self._all_cats)
1493+
1494+ # now we are initialized
1495+ self.emit("installed-pane-created")
1496+ self.show_all()
1497+
1498+ # hacky, hide the header
1499+ self.app_view.header_hbox.hide()
1500+
1501+ self.view_initialized = True
1502+ return False
1503+
1504+ def _on_row_collapsed(self, view, it, path):
1505+ return
1506+
1507+ def _row_visibility_func(self, model, it, col):
1508+
1509+ if self.visible_docids is None:
1510+
1511+ row = model.get_value(it, col)
1512+ if isinstance(row, CategoryRowReference):
1513+ row.vis_count = row.pkg_count
1514+ return True
1515+
1516+ row = model.get_value(it, col)
1517+ if isinstance(row, CategoryRowReference):
1518+ visible = row.untranslated_name in self.visible_cats.keys()
1519+
1520+ if visible:
1521+ row.vis_count = self.visible_cats[row.untranslated_name]
1522+
1523+ # process one event
1524+ while Gtk.events_pending():
1525+ Gtk.main_iteration()
1526+
1527+ return visible
1528+
1529+ if row is None: return False
1530+
1531+ return row.get_docid() in self.visible_docids
1532+
1533+ def _use_category(self, cat):
1534+ # System cat is large and slow to search, filter it in default mode
1535+
1536+ if ('carousel-only' in cat.flags or
1537+ ((self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE)
1538+ and cat.untranslated_name == 'System')): return False
1539+
1540+ return True
1541+
1542+ #~ @interrupt_build_and_wait
1543+ def _build_categorised_view(self):
1544+ LOG.debug('Rebuilding categorised installedview...')
1545+ self.cat_docid_map = {}
1546+ enq = self.enquirer
1547+ model = self.base_model # base model not treefilter
1548+ model.clear()
1549+
1550+ i = 0
1551+
1552+ xfilter = AppFilter(self.db, self.cache)
1553+ xfilter.set_installed_only(True)
1554+ for cat in self._all_cats:
1555+ # for each category do category query and append as a new
1556+ # node to tree_view
1557+ if not self._use_category(cat): continue
1558+ query = self.get_query_for_cat(cat)
1559+ LOG.debug("filter.instaleld_only: %s" % xfilter.installed_only)
1560+ enq.set_query(query,
1561+ sortmode=SortMethods.BY_ALPHABET,
1562+ nonapps_visible=self.nonapps_visible,
1563+ filter=xfilter,
1564+ nonblocking_load=False,
1565+ persistent_duplicate_filter=(i>0))
1566+
1567+ L = len(self.enquirer.matches)
1568+ if L:
1569+ i += L
1570+ docs = enq.get_documents()
1571+ self.cat_docid_map[cat.untranslated_name] = \
1572+ [doc.get_docid() for doc in docs]
1573+ model.set_category_documents(cat, docs)
1574+
1575+ # check for uncategorised pkgs
1576+ enq.set_query(self.state.channel.query,
1577+ sortmode=SortMethods.BY_ALPHABET,
1578+ nonapps_visible=NonAppVisibility.MAYBE_VISIBLE,
1579+ filter=xfilter,
1580+ nonblocking_load=False,
1581+ persistent_duplicate_filter=(i>0))
1582+
1583+ L = len(enq.matches)
1584+ if L:
1585+ # some foo for channels
1586+ # if no categorised results but in channel, then use
1587+ # the channel name for the category
1588+ channel_name = None
1589+ if not i and self.state.channel:
1590+ channel_name = self.state.channel.display_name
1591+ model.set_nocategory_documents(enq.get_documents(),
1592+ display_name=channel_name)
1593+ i += L
1594+
1595+ if i:
1596+ self.app_view.tree_view.set_cursor(Gtk.TreePath(),
1597+ None, False)
1598+ if i <= 10:
1599+ self.app_view.tree_view.expand_all()
1600+
1601+ # cache the installed app count
1602+ self.installed_count = i
1603+
1604+ self.app_view._append_appcount(self.installed_count, installed=True)
1605+
1606+ self.emit("app-list-changed", i)
1607+ return
1608+
1609+ def _check_expand(self):
1610+ it = self.treefilter.get_iter_first()
1611+ while it:
1612+ path = self.treefilter.get_path(it)
1613+ if self.state.search_term:# or path in self._user_expanded_paths:
1614+ self.app_view.tree_view.expand_row(path, False)
1615+ else:
1616+ self.app_view.tree_view.collapse_row(path)
1617+
1618+ it = self.treefilter.iter_next(it)
1619+ return
1620+
1621+ def _search(self, terms=None):
1622+ if not terms:
1623+ self.visible_docids = None
1624+ self.state.search_term = ""
1625+ self._clear_search()
1626+
1627+ elif self.state.search_term != terms:
1628+ self.state.search_term = terms
1629+ self.enquirer.set_query(self.get_query(),
1630+ nonapps_visible=self.nonapps_visible,
1631+ filter=self.apps_filter,
1632+ nonblocking_load=True)
1633+
1634+ self.visible_docids = self.enquirer.get_docids()
1635+ self.visible_cats = self._get_vis_cats(self.visible_docids)
1636+
1637+ self.treefilter.refilter()
1638+ if terms:
1639+ self.app_view.tree_view.expand_all()
1640+ else:
1641+ self._check_expand()
1642+ return
1643+
1644+ def get_query(self):
1645+ # search terms
1646+ return self.db.get_query_list_from_search_entry(
1647+ self.state.search_term)
1648+
1649+ def get_query_for_cat(self, cat):
1650+ LOG.debug("self.state.channel: %s" % self.state.channel)
1651+ if self.state.channel and self.state.channel.query:
1652+ query = xapian.Query(xapian.Query.OP_AND,
1653+ cat.query,
1654+ self.state.channel.query)
1655+ return query
1656+ return cat.query
1657+
1658+ def update_show_hide_nonapps(self, length=-1):
1659+ # override SoftwarePane.update_show_hide_nonapps
1660+ """
1661+ update the state of the show/hide non-applications control
1662+ in the action_bar
1663+ """
1664+ #~ appstore = self.app_view.get_model()
1665+ #~ if not appstore:
1666+ #~ self.action_bar.unset_label()
1667+ #~ return
1668+
1669+ # first figure out if we are only showing installed
1670+ enquirer = self.enquirer
1671+ enquirer.filter = self.state.filter
1672+
1673+ self.action_bar.unset_label()
1674+
1675+ if self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE:
1676+ label = _("_Hide technical software_")
1677+ self.action_bar.set_label(label, self._hide_nonapp_pkgs)
1678+ else:
1679+ label = _("_Display technical software_")
1680+ self.action_bar.set_label(label, self._show_nonapp_pkgs)
1681+ return
1682+
1683+ @wait_for_apt_cache_ready
1684+ def refresh_apps(self, *args, **kwargs):
1685+ """refresh the applist and update the navigation bar """
1686+ logging.debug("installedpane refresh_apps")
1687+ return
1688+
1689+ def _clear_search(self):
1690+ # remove the details and clear the search
1691+ self.searchentry.clear_with_no_signal()
1692+
1693+ def on_search_terms_changed(self, searchentry, terms):
1694+ """callback when the search entry widget changes"""
1695+ logging.debug("on_search_terms_changed: '%s'" % terms)
1696+
1697+ self._search(terms.strip())
1698+ self.state.search_term = terms
1699+ self.notebook.set_current_page(InstalledPane.Pages.LIST)
1700+ return
1701+
1702+ def _get_vis_cats(self, visids):
1703+ vis_cats = {}
1704+ appcount = 0
1705+ for cat_uname, docids in self.cat_docid_map.iteritems():
1706+ children = len(set(docids) & set(visids))
1707+ if children:
1708+ appcount += children
1709+ vis_cats[cat_uname] = children
1710+ self.app_view._append_appcount(appcount, installed=True)
1711+ return vis_cats
1712+
1713+ def on_db_reopen(self, db):
1714+ self.refresh_apps(rebuild=True)
1715+ self.app_details_view.refresh_app()
1716+
1717+ def on_application_selected(self, appview, app):
1718+ """callback when an app is selected"""
1719+ logging.debug("on_application_selected: '%s'" % app)
1720+ self.current_appview_selection = app
1721+
1722+ def get_callback_for_page(self, page, state):
1723+ if page == InstalledPane.Pages.LIST:
1724+ return self.display_overview_page
1725+ return self.display_details_page
1726+
1727+ def display_search(self):
1728+ model = self.app_view.get_model()
1729+ if model:
1730+ self.emit("app-list-changed", len(model))
1731+ self.searchentry.show()
1732+
1733+ def display_overview_page(self, page, view_state):
1734+ LOG.debug("view_state: %s" % view_state)
1735+ self._build_categorised_view()
1736+
1737+ if self.state.search_term:
1738+ self._search(self.state.search_term)
1739+
1740+ self.update_show_hide_nonapps()
1741+ return True
1742+
1743+ def get_current_app(self):
1744+ """return the current active application object applicable
1745+ to the context"""
1746+ return self.current_appview_selection
1747+
1748+ def is_category_view_showing(self):
1749+ # there is no category view in the installed pane
1750+ return False
1751+
1752+ def is_applist_view_showing(self):
1753+ """Return True if we are in the applist view """
1754+ return (self.notebook.get_current_page() ==
1755+ InstalledPane.Pages.LIST)
1756+
1757+ def is_app_details_view_showing(self):
1758+ """Return True if we are in the app_details view """
1759+ return self.notebook.get_current_page() == InstalledPane.Pages.DETAILS
1760+
1761+
1762+def get_test_window():
1763+ from softwarecenter.testutils import (get_test_db,
1764+ get_test_datadir,
1765+ get_test_gtk3_viewmanager,
1766+ get_test_pkg_info,
1767+ get_test_gtk3_icon_cache,
1768+ )
1769+ # needed because available pane will try to get it
1770+ vm = get_test_gtk3_viewmanager()
1771+ vm # make pyflakes happy
1772+ db = get_test_db()
1773+ cache = get_test_pkg_info()
1774+ datadir = get_test_datadir()
1775+ icons = get_test_gtk3_icon_cache()
1776+
1777+ w = InstalledPane(cache, db, 'Ubuntu', icons, datadir)
1778+ w.show()
1779+
1780+ win = Gtk.Window()
1781+ win.set_data("pane", w)
1782+ win.add(w)
1783+ win.set_size_request(400, 600)
1784+ win.connect("destroy", lambda x: Gtk.main_quit())
1785+
1786+ # init the view
1787+ w.init_view()
1788+
1789+ from softwarecenter.backend.channel import AllInstalledChannel
1790+ w.state.channel = AllInstalledChannel()
1791+ w.display_overview_page(None, None)
1792+
1793+ win.show_all()
1794+ return win
1795+
1796+
1797+if __name__ == "__main__":
1798+ win = get_test_window()
1799+ Gtk.main()
1800+
1801
1802=== modified file 'softwarecenter/ui/gtk3/utils.py'
1803--- softwarecenter/ui/gtk3/utils.py 2011-08-12 13:24:31 +0000
1804+++ softwarecenter/ui/gtk3/utils.py 2011-08-19 10:38:33 +0000
1805@@ -17,7 +17,7 @@
1806 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1807
1808 import gi
1809-gi.require_version("Gtk", "3.0")
1810+#gi.require_version("Gtk", "3.0")
1811 from gi.repository import Gtk
1812
1813 from softwarecenter.paths import ICON_PATH, SOFTWARE_CENTER_ICON_CACHE_DIR
1814
1815=== modified file 'test/test_package_info.py'
1816--- test/test_package_info.py 2011-07-08 07:37:41 +0000
1817+++ test/test_package_info.py 2011-08-19 10:38:33 +0000
1818@@ -6,16 +6,21 @@
1819 import logging
1820 import unittest
1821
1822-from softwarecenter.db.pkginfo import get_pkg_info, _Package, _Version
1823-
1824-class TestPkgInfo(unittest.TestCase):
1825+from softwarecenter.db.pkginfo import _Package, _Version
1826+from softwarecenter.db.pkginfo_impl.aptcache import AptCache
1827+from softwarecenter.db.pkginfo_impl.packagekit import PackagekitInfo
1828+
1829+class TestPkgInfoAptCache(unittest.TestCase):
1830+
1831+ # the backend that we want to test
1832+ klass = AptCache
1833
1834 def setUp(self):
1835- pass
1836+ self.pkginfo = self.klass()
1837+ self.pkginfo.open()
1838
1839 def test_pkg_version(self):
1840- pkginfo = get_pkg_info()
1841- pkginfo.open()
1842+ pkginfo = self.pkginfo
1843
1844 pkg = pkginfo['coreutils']
1845 self.assertTrue(isinstance(pkg, _Package))
1846@@ -33,30 +38,50 @@
1847 self.assertTrue(isinstance(v, _Version))
1848
1849 def test_pkg_info(self):
1850- pkginfo = get_pkg_info()
1851- pkginfo.open()
1852+ pkginfo = self.pkginfo
1853 self.assertTrue(pkginfo.is_installed("coreutils"))
1854 self.assertTrue(pkginfo.is_available("bash"))
1855- self.assertTrue(len(pkginfo.get_addons("firefox")) > 0)
1856- self.assertEqual(pkginfo.get_section('bash'), 'shells')
1857 self.assertTrue('GNU Bourne Again' in pkginfo.get_summary('bash'))
1858 self.assertTrue(pkginfo.get_description('bash') != '')
1859- self.assertTrue(len(pkginfo.get_origins("firefox")) > 0)
1860 self.assertTrue(pkginfo.get_installed("coreutils") is not None)
1861 self.assertTrue(pkginfo.get_candidate("coreutils") is not None)
1862 self.assertTrue(len(pkginfo.get_versions("coreutils")) != 0)
1863
1864 self.assertTrue('coreutils' in pkginfo)
1865-
1866+
1867+ # test getitem
1868 pkg = pkginfo['coreutils']
1869- self.assertTrue(len(pkginfo.get_packages_removed_on_remove(pkg)) != 0)
1870- self.assertTrue(len(pkginfo.get_packages_removed_on_install(pkg)) == 0)
1871 self.assertTrue(pkg is not None)
1872 self.assertTrue(pkg.is_installed)
1873 self.assertTrue(len(pkg.versions) != 0)
1874- self.assertEqual(pkg.section, "utils")
1875 self.assertEqual(pkg.website, 'http://gnu.org/software/coreutils')
1876
1877+ def test_section(self):
1878+ self.assertEqual(self.pkginfo.get_section('bash'), 'shells')
1879+
1880+ def test_origins(self):
1881+ self.assertTrue(len(self.pkginfo.get_origins("firefox")) > 0)
1882+
1883+ def test_addons(self):
1884+ pkginfo = self.pkginfo
1885+ self.assertTrue(len(pkginfo.get_addons("firefox")) > 0)
1886+ pkg = pkginfo['firefox']
1887+ self.assertTrue(len(pkginfo.get_packages_removed_on_install(pkg)) == 0)
1888+ self.assertTrue(len(pkginfo.get_packages_removed_on_remove(pkg)) != 0)
1889+
1890+ def test_installed_files(self):
1891+ pkg = self.pkginfo['coreutils']
1892+ files = pkg.installed_files
1893+ self.assertTrue('/usr/bin/whoami' in files)
1894+
1895+class TestPkgInfoPackagekit(TestPkgInfoAptCache):
1896+ klass = PackagekitInfo
1897+
1898+ def test_addons(self):
1899+ pass
1900+ def test_section(self):
1901+ pass
1902+
1903 if __name__ == "__main__":
1904 logging.basicConfig(level=logging.DEBUG)
1905 unittest.main()
1906
1907=== modified file 'utils/update-software-center'
1908--- utils/update-software-center 2011-08-10 11:23:23 +0000
1909+++ utils/update-software-center 2011-08-19 10:38:33 +0000
1910@@ -17,6 +17,11 @@
1911 # this program; if not, write to the Free Software Foundation, Inc.,
1912 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1913
1914+# We have to import Gio before everything, for dynamic PK to work.
1915+try:
1916+ from gi.repository import Gio
1917+except: pass
1918+
1919 import locale
1920 import gettext
1921 import logging
1922@@ -31,7 +36,6 @@
1923 from softwarecenter.enums import *
1924 from softwarecenter.paths import XAPIAN_BASE_PATH
1925 from softwarecenter.db.update import rebuild_database
1926-
1927 import softwarecenter.paths
1928
1929 # dbus may not be available during a upgrade so we
1930@@ -91,6 +95,9 @@
1931 help="set different appstream xml rootdir")
1932 parser.add_option("--debug", "", action="store_true", default=False,
1933 help="show debug output")
1934+ parser.add_option("--use-packagekit", action="store_true",
1935+ help="use PackageKit backend (experimental)",
1936+ default=False)
1937 (options, args) = parser.parse_args()
1938
1939 #logging.basicConfig(level=logging.INFO)
1940@@ -112,6 +119,10 @@
1941 if options.appstream_xml_path:
1942 softwarecenter.paths.APPSTREAM_XML_PATH = options.appstream_xml_path
1943
1944+ if options.use_packagekit:
1945+ LOG.info("using the PackageKit backend")
1946+ softwarecenter.enums.USE_PACKAGEKIT_BACKEND = True
1947+
1948 # check if we are dpkg triggered because of langpack change
1949 # and avoid unneeded database rebuilds by checking the timestamp
1950 # of the app-install-data mo file
1951@@ -168,4 +179,3 @@
1952 context = GObject.main_context_default()
1953 while context.pending():
1954 context.iteration()
1955-