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