Merge lp:~software-updates-spec/update-manager/group-by-applications into lp:update-manager
- group-by-applications
- Merge into main
Status: | Merged |
---|---|
Merged at revision: | 2582 |
Proposed branch: | lp:~software-updates-spec/update-manager/group-by-applications |
Merge into: | lp:update-manager |
Diff against target: |
2166 lines (+1173/-366) 17 files modified
UpdateManager/Core/MyCache.py (+2/-55) UpdateManager/Core/UpdateList.py (+303/-67) UpdateManager/Core/utils.py (+67/-28) UpdateManager/UpdatesAvailable.py (+417/-156) data/com.ubuntu.update-manager.gschema.xml.in (+2/-2) data/gtkbuilder/UpdateManager.ui (+3/-7) tests/aptroot-grouping-test/etc/apt/sources.list (+2/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release (+9/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release.gpg (+7/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_main_binary-amd64_Packages (+10/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release (+9/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release.gpg (+7/-0) tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages (+80/-0) tests/aptroot-grouping-test/var/lib/dpkg/status (+82/-0) tests/test_update_list.py (+89/-9) tests/test_update_origin.py (+14/-23) tests/test_utils.py (+70/-19) |
To merge this branch: | bzr merge lp:~software-updates-spec/update-manager/group-by-applications |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matthew Paul Thomas (community) | design | Approve | |
Michael Vogt (community) | Approve | ||
Review via email: mp+144362@code.launchpad.net |
Commit message
Description of the change
This is a continuation of https:/
This implements the 'Available Updates' description pane in the Software Updates spec:
https:/
Notably, it replaces the current list of packages with groups of packages and highlights the description of the package rather than the debian package name.
It also no longer categorizes the updates by source. The only categorization it does is "security or not".
It's a big diff, I know. Sorry. Mostly visual changes. And I added tests for the non-visual bits.
One big unfinished part of the spec is that when two packages have the same description (e.g. "Transitional package") we don't append the debian package name (e.g. "Transitional package (unity-2d)"). This is technically difficult. Talking to mpt, he indicated that just showing the label without the parenthetical would be an acceptable workaround until we can implement that bit. Ideally packages wouldn't have such duplicate descriptions.
Test with: ./update-manager --data-dir=./data
- 2486. By Dylan McCall
-
Fixed application crashing when running with XDG_DATA_DIRS or XDG_CURRENT_DESKTOP unset.
Michael Vogt (mvo) wrote : | # |
Michael Vogt (mvo) wrote : | # |
I just got the chance to play with it too, looks very nice indeed.
- 2487. By Michael Terry
-
merge from trunk
- 2488. By Michael Terry
-
add myself to UpdateList authors and update copyright in UpdateList and UpdatesAvailable
- 2489. By Michael Terry
-
fix nits from review
- 2490. By Michael Terry
-
sort entries case insensitively
Michael Terry (mterry) wrote : | # |
OK, fixed the nits and added one more fix -- sorting entries case insensitively. I wanted mpt to review changes too just to make sure I didn't screw his vision up. He said he'd get to it on Friday likely.
Matthew Paul Thomas (mpt) wrote : | # |
This looks excellent to me. The behavior of the tri-state checkboxes isn't perfect, but that can certainly wait.
Preview Diff
1 | === modified file 'UpdateManager/Core/MyCache.py' | |||
2 | --- UpdateManager/Core/MyCache.py 2012-06-28 00:10:23 +0000 | |||
3 | +++ UpdateManager/Core/MyCache.py 2013-01-23 15:48:20 +0000 | |||
4 | @@ -43,7 +43,6 @@ | |||
5 | 43 | import re | 43 | import re |
6 | 44 | import DistUpgrade.DistUpgradeCache | 44 | import DistUpgrade.DistUpgradeCache |
7 | 45 | from gettext import gettext as _ | 45 | from gettext import gettext as _ |
8 | 46 | from .UpdateList import UpdateOrigin | ||
9 | 47 | 46 | ||
10 | 48 | SYNAPTIC_PINFILE = "/var/lib/synaptic/preferences" | 47 | SYNAPTIC_PINFILE = "/var/lib/synaptic/preferences" |
11 | 49 | CHANGELOGS_POOL = "http://changelogs.ubuntu.com/changelogs/pool/" | 48 | CHANGELOGS_POOL = "http://changelogs.ubuntu.com/changelogs/pool/" |
12 | @@ -133,59 +132,6 @@ | |||
13 | 133 | self._depcache.upgrade() | 132 | self._depcache.upgrade() |
14 | 134 | return wouldDelete | 133 | return wouldDelete |
15 | 135 | 134 | ||
16 | 136 | def match_package_origin(self, pkg, matcher): | ||
17 | 137 | """ match 'pkg' origin against 'matcher', take versions between | ||
18 | 138 | installed.version and candidate.version into account too | ||
19 | 139 | Useful if installed pkg A v1.0 is available in both | ||
20 | 140 | -updates (as v1.2) and -security (v1.1). we want to display | ||
21 | 141 | it as a security update then | ||
22 | 142 | """ | ||
23 | 143 | inst_ver = pkg._pkg.current_ver | ||
24 | 144 | cand_ver = self._depcache.get_candidate_ver(pkg._pkg) | ||
25 | 145 | # init matcher with candidate.version | ||
26 | 146 | update_origin = matcher[(None, None)] | ||
27 | 147 | verFileIter = None | ||
28 | 148 | for (verFileIter, index) in cand_ver.file_list: | ||
29 | 149 | if (verFileIter.archive, verFileIter.origin) in matcher: | ||
30 | 150 | indexfile = pkg._pcache._list.find_index(verFileIter) | ||
31 | 151 | if indexfile: # and indexfile.IsTrusted: | ||
32 | 152 | match = matcher[verFileIter.archive, verFileIter.origin] | ||
33 | 153 | update_origin = match | ||
34 | 154 | break | ||
35 | 155 | else: | ||
36 | 156 | # add a node for each origin/archive combination | ||
37 | 157 | if verFileIter and verFileIter.origin and verFileIter.archive: | ||
38 | 158 | matcher[verFileIter.archive, verFileIter.origin] = \ | ||
39 | 159 | UpdateOrigin(_("Other updates (%s)") % verFileIter.origin, | ||
40 | 160 | 0) | ||
41 | 161 | update_origin = matcher[verFileIter.archive, | ||
42 | 162 | verFileIter.origin] | ||
43 | 163 | # if the candidate comes from a unknown source (e.g. a PPA) skip | ||
44 | 164 | # skip the shadow logic below as it would put e.g. a PPA package | ||
45 | 165 | # in "Recommended updates" when the version in the PPA | ||
46 | 166 | # is higher than the one in %s-updates | ||
47 | 167 | if update_origin.importance <= 0: | ||
48 | 168 | return update_origin | ||
49 | 169 | # for known packages, check if we have higher versions that | ||
50 | 170 | # "shadow" this one | ||
51 | 171 | for ver in pkg._pkg.version_list: | ||
52 | 172 | # discard is < than installed ver | ||
53 | 173 | if (inst_ver and | ||
54 | 174 | apt_pkg.version_compare(ver.ver_str, | ||
55 | 175 | inst_ver.ver_str) <= 0): | ||
56 | 176 | #print("skipping '%s' " % ver.ver_str) | ||
57 | 177 | continue | ||
58 | 178 | # check if we have a match | ||
59 | 179 | for (verFileIter, index) in ver.file_list: | ||
60 | 180 | if (verFileIter.archive, verFileIter.origin) in matcher: | ||
61 | 181 | indexfile = pkg._pcache._list.find_index(verFileIter) | ||
62 | 182 | if indexfile: # and indexfile.IsTrusted: | ||
63 | 183 | match = matcher[verFileIter.archive, | ||
64 | 184 | verFileIter.origin] | ||
65 | 185 | if match.importance > update_origin.importance: | ||
66 | 186 | update_origin = match | ||
67 | 187 | return update_origin | ||
68 | 188 | |||
69 | 189 | def _strip_epoch(self, verstr): | 135 | def _strip_epoch(self, verstr): |
70 | 190 | " strip of the epoch " | 136 | " strip of the epoch " |
71 | 191 | l = verstr.split(":") | 137 | l = verstr.split(":") |
72 | @@ -356,9 +302,10 @@ | |||
73 | 356 | def get_changelog(self, name): | 302 | def get_changelog(self, name): |
74 | 357 | " get the changelog file from the changelog location " | 303 | " get the changelog file from the changelog location " |
75 | 358 | origins = self[name].candidate.origins | 304 | origins = self[name].candidate.origins |
77 | 359 | self.all_changes[name] = _("Changes for the versions:\n" | 305 | self.all_changes[name] = _("Changes for %s versions:\n" |
78 | 360 | "Installed version: %s\n" | 306 | "Installed version: %s\n" |
79 | 361 | "Available version: %s\n\n") % ( | 307 | "Available version: %s\n\n") % ( |
80 | 308 | name, | ||
81 | 362 | getattr(self[name].installed, "version", | 309 | getattr(self[name].installed, "version", |
82 | 363 | None), | 310 | None), |
83 | 364 | self[name].candidate.version) | 311 | self[name].candidate.version) |
84 | 365 | 312 | ||
85 | === modified file 'UpdateManager/Core/UpdateList.py' | |||
86 | --- UpdateManager/Core/UpdateList.py 2013-01-22 17:59:35 +0000 | |||
87 | +++ UpdateManager/Core/UpdateList.py 2013-01-23 15:48:20 +0000 | |||
88 | @@ -1,9 +1,11 @@ | |||
89 | 1 | # UpdateList.py | 1 | # UpdateList.py |
90 | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- |
91 | 3 | # | 3 | # |
93 | 4 | # Copyright (c) 2004-2012 Canonical | 4 | # Copyright (c) 2004-2013 Canonical |
94 | 5 | # | 5 | # |
95 | 6 | # Author: Michael Vogt <mvo@debian.org> | 6 | # Author: Michael Vogt <mvo@debian.org> |
96 | 7 | # Dylan McCall <dylanmccall@ubuntu.com> | ||
97 | 8 | # Michael Terry <michael.terry@canonical.com> | ||
98 | 7 | # | 9 | # |
99 | 8 | # This program is free software; you can redistribute it and/or | 10 | # This program is free software; you can redistribute it and/or |
100 | 9 | # modify it under the terms of the GNU General Public License as | 11 | # modify it under the terms of the GNU General Public License as |
101 | @@ -22,36 +24,131 @@ | |||
102 | 22 | 24 | ||
103 | 23 | from __future__ import print_function | 25 | from __future__ import print_function |
104 | 24 | 26 | ||
105 | 27 | import warnings | ||
106 | 28 | warnings.filterwarnings("ignore", "Accessed deprecated property", | ||
107 | 29 | DeprecationWarning) | ||
108 | 30 | |||
109 | 25 | from gettext import gettext as _ | 31 | from gettext import gettext as _ |
110 | 26 | |||
111 | 27 | import apt | 32 | import apt |
112 | 28 | import logging | 33 | import logging |
113 | 29 | import operator | 34 | import operator |
114 | 35 | import itertools | ||
115 | 36 | import platform | ||
116 | 37 | import subprocess | ||
117 | 38 | import os | ||
118 | 30 | import random | 39 | import random |
123 | 31 | import subprocess | 40 | import glob |
124 | 32 | 41 | ||
125 | 33 | 42 | from gi.repository import Gio | |
126 | 34 | class UpdateOrigin(object): | 43 | |
127 | 44 | from UpdateManager.Core import utils | ||
128 | 45 | |||
129 | 46 | |||
130 | 47 | class UpdateItem(): | ||
131 | 48 | def __init__(self, pkg, name, icon): | ||
132 | 49 | self.icon = icon | ||
133 | 50 | self.name = name | ||
134 | 51 | self.pkg = pkg | ||
135 | 52 | |||
136 | 53 | |||
137 | 54 | class UpdateGroup(UpdateItem): | ||
138 | 55 | def __init__(self, pkg, name, icon): | ||
139 | 56 | UpdateItem.__init__(self, pkg, name, icon) | ||
140 | 57 | self._items = set() | ||
141 | 58 | self.core_item = None | ||
142 | 59 | if pkg is not None: | ||
143 | 60 | self.core_item = UpdateItem(pkg, name, icon) | ||
144 | 61 | self._items.add(self.core_item) | ||
145 | 62 | |||
146 | 63 | @property | ||
147 | 64 | def items(self): | ||
148 | 65 | all_items = [] | ||
149 | 66 | all_items.extend(self._items) | ||
150 | 67 | return sorted(all_items, key=lambda a: a.name.lower()) | ||
151 | 68 | |||
152 | 69 | def add(self, pkg): | ||
153 | 70 | name = utils.get_package_label(pkg) | ||
154 | 71 | icon = Gio.ThemedIcon.new("package") | ||
155 | 72 | self._items.add(UpdateItem(pkg, name, icon)) | ||
156 | 73 | |||
157 | 74 | def contains(self, item): | ||
158 | 75 | return item in self._items | ||
159 | 76 | |||
160 | 77 | def _is_dependency_helper(self, cache, pkg, dep, seen=set()): | ||
161 | 78 | if pkg is None or pkg.candidate is None or pkg in seen: | ||
162 | 79 | return False | ||
163 | 80 | elif pkg.name == dep.name: | ||
164 | 81 | return True | ||
165 | 82 | seen.add(pkg) | ||
166 | 83 | candidate = pkg.candidate | ||
167 | 84 | dependencies = candidate.get_dependencies('Depends', 'Recommends') | ||
168 | 85 | for dependency_pkg in itertools.chain.from_iterable(dependencies): | ||
169 | 86 | if dependency_pkg.name in cache and \ | ||
170 | 87 | self._is_dependency_helper(cache, cache[dependency_pkg.name], | ||
171 | 88 | dep, seen): | ||
172 | 89 | return True | ||
173 | 90 | return False | ||
174 | 91 | |||
175 | 92 | def is_dependency(self, cache, maybe_dep): | ||
176 | 93 | # This is a recursive dependency check. TODO: We do this many times | ||
177 | 94 | # when grouping packages, and it could be made more efficient. | ||
178 | 95 | seen = set() | ||
179 | 96 | for item in self._items: | ||
180 | 97 | if self._is_dependency_helper(cache, item.pkg, maybe_dep, | ||
181 | 98 | seen=seen): | ||
182 | 99 | return True | ||
183 | 100 | return False | ||
184 | 101 | |||
185 | 102 | def packages_are_selected(self): | ||
186 | 103 | for item in self.items: | ||
187 | 104 | if item.pkg.marked_install or item.pkg.marked_upgrade: | ||
188 | 105 | return True | ||
189 | 106 | return False | ||
190 | 107 | |||
191 | 108 | def selection_is_inconsistent(self): | ||
192 | 109 | pkgs_installing = [item for item in self.items | ||
193 | 110 | if item.pkg.marked_install or item.pkg.marked_upgrade] | ||
194 | 111 | return (len(pkgs_installing) > 0 and | ||
195 | 112 | len(pkgs_installing) < len(self.items)) | ||
196 | 113 | |||
197 | 114 | def get_total_size(self): | ||
198 | 115 | size = 0 | ||
199 | 116 | for item in self.items: | ||
200 | 117 | size += getattr(item.pkg.candidate, "size", 0) | ||
201 | 118 | return size | ||
202 | 119 | |||
203 | 120 | |||
204 | 121 | class UpdateApplicationGroup(UpdateGroup): | ||
205 | 122 | def __init__(self, pkg, application): | ||
206 | 123 | name = application.get_display_name() | ||
207 | 124 | icon = application.get_icon() | ||
208 | 125 | super(UpdateApplicationGroup, self).__init__(pkg, name, icon) | ||
209 | 126 | |||
210 | 127 | |||
211 | 128 | class UpdatePackageGroup(UpdateGroup): | ||
212 | 129 | def __init__(self, pkg): | ||
213 | 130 | name = utils.get_package_label(pkg) | ||
214 | 131 | icon = Gio.ThemedIcon.new("package") | ||
215 | 132 | super(UpdatePackageGroup, self).__init__(pkg, name, icon) | ||
216 | 133 | |||
217 | 134 | |||
218 | 135 | class UpdateSystemGroup(UpdateGroup): | ||
219 | 136 | def __init__(self, cache): | ||
220 | 137 | # Translators: the %s is a distro name, like 'Ubuntu' and 'base' as in | ||
221 | 138 | # the core components and packages. | ||
222 | 139 | name = _("%s base") % utils.get_ubuntu_flavor_name(cache=cache) | ||
223 | 140 | icon = Gio.ThemedIcon.new("distributor-logo") | ||
224 | 141 | super(UpdateSystemGroup, self).__init__(None, name, icon) | ||
225 | 142 | |||
226 | 143 | |||
227 | 144 | class UpdateOrigin(): | ||
228 | 35 | def __init__(self, desc, importance): | 145 | def __init__(self, desc, importance): |
229 | 36 | self.packages = [] | 146 | self.packages = [] |
230 | 37 | self.importance = importance | 147 | self.importance = importance |
231 | 38 | self.description = desc | 148 | self.description = desc |
232 | 39 | 149 | ||
233 | 40 | 150 | ||
248 | 41 | class OriginsImportance: | 151 | class UpdateList(): |
235 | 42 | # filled in by us | ||
236 | 43 | SECURITY = 10 | ||
237 | 44 | UPDATES = 9 | ||
238 | 45 | PROPOSED = 8 | ||
239 | 46 | BACKPORTS = 7 | ||
240 | 47 | ARCHIVE = 6 | ||
241 | 48 | # this is filled in by MyCache | ||
242 | 49 | OTHER = 0 | ||
243 | 50 | # this is used by us | ||
244 | 51 | OTHER_UNKNOWN = -1 | ||
245 | 52 | |||
246 | 53 | |||
247 | 54 | class UpdateList(object): | ||
249 | 55 | """ | 152 | """ |
250 | 56 | class that contains the list of available updates in | 153 | class that contains the list of available updates in |
251 | 57 | self.pkgs[origin] where origin is the user readable string | 154 | self.pkgs[origin] where origin is the user readable string |
252 | @@ -64,6 +161,8 @@ | |||
253 | 64 | # the file that contains the uniq machine id | 161 | # the file that contains the uniq machine id |
254 | 65 | UNIQ_MACHINE_ID_FILE = "/var/lib/dbus/machine-id" | 162 | UNIQ_MACHINE_ID_FILE = "/var/lib/dbus/machine-id" |
255 | 66 | 163 | ||
256 | 164 | APP_INSTALL_PATH = "/usr/share/app-install/desktop" | ||
257 | 165 | |||
258 | 67 | # the configuration key to turn phased-updates always on | 166 | # the configuration key to turn phased-updates always on |
259 | 68 | ALWAYS_INCLUDE_PHASED_UPDATES = ( | 167 | ALWAYS_INCLUDE_PHASED_UPDATES = ( |
260 | 69 | "Update-Manager::Always-Include-Phased-Updates") | 168 | "Update-Manager::Always-Include-Phased-Updates") |
261 | @@ -71,47 +170,111 @@ | |||
262 | 71 | NEVER_INCLUDE_PHASED_UPDATES = ( | 170 | NEVER_INCLUDE_PHASED_UPDATES = ( |
263 | 72 | "Update-Manager::Never-Include-Phased-Updates") | 171 | "Update-Manager::Never-Include-Phased-Updates") |
264 | 73 | 172 | ||
273 | 74 | def __init__(self, parent): | 173 | def __init__(self, parent, dist=None): |
274 | 75 | # a map of packages under their origin | 174 | self.dist = dist if dist else platform.dist()[2] |
267 | 76 | try: | ||
268 | 77 | dist = subprocess.check_output( | ||
269 | 78 | ["lsb_release", "-c", "-s"], universal_newlines=True).strip() | ||
270 | 79 | except subprocess.CalledProcessError as e: | ||
271 | 80 | print("Error in lsb_release: %s" % e) | ||
272 | 81 | dist = None | ||
275 | 82 | self.distUpgradeWouldDelete = 0 | 175 | self.distUpgradeWouldDelete = 0 |
277 | 83 | self.pkgs = {} | 176 | self.update_groups = [] |
278 | 177 | self.security_groups = [] | ||
279 | 84 | self.num_updates = 0 | 178 | self.num_updates = 0 |
280 | 85 | self.matcher = self.initMatcher(dist) | ||
281 | 86 | self.random = random.Random() | 179 | self.random = random.Random() |
282 | 87 | # a stable machine uniq id | 180 | # a stable machine uniq id |
283 | 88 | with open(self.UNIQ_MACHINE_ID_FILE) as f: | 181 | with open(self.UNIQ_MACHINE_ID_FILE) as f: |
284 | 89 | self.machine_uniq_id = f.read() | 182 | self.machine_uniq_id = f.read() |
285 | 90 | 183 | ||
310 | 91 | def initMatcher(self, dist): | 184 | if 'XDG_DATA_DIRS' in os.environ and os.environ['XDG_DATA_DIRS']: |
311 | 92 | # (origin, archive, description, importance) | 185 | data_dirs = os.environ['XDG_DATA_DIRS'] |
312 | 93 | matcher_templates = [ | 186 | else: |
313 | 94 | (None, None, _("Other updates"), OriginsImportance.OTHER_UNKNOWN) | 187 | data_dirs= '/usr/local/share/:/usr/share/' |
314 | 95 | ] | 188 | self.application_dirs = [os.path.join(base, 'applications') |
315 | 96 | if dist: | 189 | for base in data_dirs.split(':')] |
316 | 97 | matcher_templates += [ | 190 | |
317 | 98 | ("%s-security" % dist, "Ubuntu", | 191 | if 'XDG_CURRENT_DESKTOP' in os.environ: |
318 | 99 | _("Important security updates"), OriginsImportance.SECURITY), | 192 | self.current_desktop = os.environ.get('XDG_CURRENT_DESKTOP') |
319 | 100 | ("%s-updates" % dist, "Ubuntu", | 193 | else: |
320 | 101 | _("Recommended updates"), OriginsImportance.UPDATES), | 194 | self.current_desktop = '' |
321 | 102 | ("%s-proposed" % dist, "Ubuntu", | 195 | |
322 | 103 | _("Proposed updates"), OriginsImportance.PROPOSED), | 196 | def _file_is_application(self, file_path): |
323 | 104 | ("%s-backports" % dist, "Ubuntu", | 197 | file_path = os.path.abspath(file_path) |
324 | 105 | _("Backports"), OriginsImportance.BACKPORTS), | 198 | is_application = False |
325 | 106 | (dist, "Ubuntu", | 199 | for app_dir in self.application_dirs: |
326 | 107 | _("Distribution updates"), OriginsImportance.ARCHIVE), | 200 | is_application = is_application or file_path.startswith(app_dir) |
327 | 108 | ] | 201 | extension = os.path.splitext(file_path)[1] |
328 | 109 | matcher = {} | 202 | is_application = is_application and (extension == '.desktop') |
329 | 110 | for (origin, archive, desc, importance) in matcher_templates: | 203 | return is_application |
330 | 111 | matcher[(origin, archive)] = UpdateOrigin(desc, importance) | 204 | |
331 | 112 | return matcher | 205 | def _rate_application_for_package(self, application, pkg): |
332 | 113 | 206 | score = 0 | |
333 | 114 | def is_ignored_phased_update(self, pkg): | 207 | desktop_file = os.path.basename(application.get_filename()) |
334 | 208 | application_id = os.path.splitext(desktop_file)[0] | ||
335 | 209 | |||
336 | 210 | if application.should_show(): | ||
337 | 211 | score += 1 | ||
338 | 212 | |||
339 | 213 | if application_id == pkg.name: | ||
340 | 214 | score += 5 | ||
341 | 215 | |||
342 | 216 | return score | ||
343 | 217 | |||
344 | 218 | def _get_application_for_package(self, pkg): | ||
345 | 219 | desktop_files = [] | ||
346 | 220 | rated_applications = [] | ||
347 | 221 | |||
348 | 222 | for installed_file in pkg.installed_files: | ||
349 | 223 | if self._file_is_application(installed_file): | ||
350 | 224 | desktop_files.append(installed_file) | ||
351 | 225 | |||
352 | 226 | app_install_pattern = os.path.join(self.APP_INSTALL_PATH, | ||
353 | 227 | '%s:*' % pkg.name) | ||
354 | 228 | for desktop_file in glob.glob(app_install_pattern): | ||
355 | 229 | desktop_files.append(desktop_file) | ||
356 | 230 | |||
357 | 231 | for desktop_file in desktop_files: | ||
358 | 232 | try: | ||
359 | 233 | application = Gio.DesktopAppInfo.new_from_filename( | ||
360 | 234 | desktop_file) | ||
361 | 235 | application.set_desktop_env(self.current_desktop) | ||
362 | 236 | except Exception as e: | ||
363 | 237 | print("Error loading .desktop file %s: %s" % | ||
364 | 238 | (installed_file, e)) | ||
365 | 239 | continue | ||
366 | 240 | score = self._rate_application_for_package(application, pkg) | ||
367 | 241 | if score > 0: | ||
368 | 242 | rated_applications.append((score, application)) | ||
369 | 243 | |||
370 | 244 | rated_applications.sort(key=lambda app: app[0], reverse=True) | ||
371 | 245 | if len(rated_applications) > 0: | ||
372 | 246 | return rated_applications[0][1] | ||
373 | 247 | else: | ||
374 | 248 | return None | ||
375 | 249 | |||
376 | 250 | def _is_security_update(self, pkg): | ||
377 | 251 | """ This will test if the pkg is a security update. | ||
378 | 252 | This includes if there is a newer version in -updates, but also | ||
379 | 253 | an older update available in -security. For example, if | ||
380 | 254 | installed pkg A v1.0 is available in both -updates (as v1.2) and | ||
381 | 255 | -security (v1.1). we want to display it as a security update. | ||
382 | 256 | |||
383 | 257 | :return: True if the update comes from the security pocket | ||
384 | 258 | """ | ||
385 | 259 | if not self.dist: | ||
386 | 260 | return False | ||
387 | 261 | inst_ver = pkg._pkg.current_ver | ||
388 | 262 | for ver in pkg._pkg.version_list: | ||
389 | 263 | # discard is < than installed ver | ||
390 | 264 | if (inst_ver and | ||
391 | 265 | apt.apt_pkg.version_compare(ver.ver_str, | ||
392 | 266 | inst_ver.ver_str) <= 0): | ||
393 | 267 | continue | ||
394 | 268 | # check if we have a match | ||
395 | 269 | for (verFileIter, index) in ver.file_list: | ||
396 | 270 | if verFileIter.archive == "%s-security" % self.dist and \ | ||
397 | 271 | verFileIter.origin == "Ubuntu": | ||
398 | 272 | indexfile = pkg._pcache._list.find_index(verFileIter) | ||
399 | 273 | if indexfile: # and indexfile.IsTrusted: | ||
400 | 274 | return True | ||
401 | 275 | return False | ||
402 | 276 | |||
403 | 277 | def _is_ignored_phased_update(self, pkg): | ||
404 | 115 | """ This will test if the pkg is a phased update and if | 278 | """ This will test if the pkg is a phased update and if |
405 | 116 | it needs to get installed or ignored. | 279 | it needs to get installed or ignored. |
406 | 117 | 280 | ||
407 | @@ -142,15 +305,88 @@ | |||
408 | 142 | return True | 305 | return True |
409 | 143 | return False | 306 | return False |
410 | 144 | 307 | ||
411 | 308 | def _get_linux_packages(self): | ||
412 | 309 | "Return all binary packages made by the linux-meta source package" | ||
413 | 310 | # Hard code this rather than generate from source info in cache because | ||
414 | 311 | # that might only be available if we have deb-src lines. I think we | ||
415 | 312 | # could also generate it by iterating over all the binary package info | ||
416 | 313 | # we have, but that is costly. These don't change often. | ||
417 | 314 | return ['linux', 'linux-image', 'linux-headers-generic', | ||
418 | 315 | 'linux-image-generic', 'linux-generic', | ||
419 | 316 | 'linux-headers-generic-pae', 'linux-image-generic-pae', | ||
420 | 317 | 'linux-generic-pae', 'linux-headers-omap', 'linux-image-omap', | ||
421 | 318 | 'linux-omap', 'linux-headers-server', 'linux-image-server', | ||
422 | 319 | 'linux-server', 'linux-signed-image-generic', | ||
423 | 320 | 'linux-signed-generic', 'linux-headers-virtual', | ||
424 | 321 | 'linux-image-virtual', 'linux-virtual', | ||
425 | 322 | 'linux-image-extra-virtual'] | ||
426 | 323 | |||
427 | 324 | def _make_groups(self, cache, pkgs): | ||
428 | 325 | pkgs_by_source = {} | ||
429 | 326 | ungrouped_pkgs = [] | ||
430 | 327 | app_groups = [] | ||
431 | 328 | pkg_groups = [] | ||
432 | 329 | |||
433 | 330 | # Index packages by source package name | ||
434 | 331 | for pkg in pkgs: | ||
435 | 332 | srcpkg = pkg.candidate.source_name | ||
436 | 333 | pkgs_by_source.setdefault(srcpkg, []).append(pkg) | ||
437 | 334 | |||
438 | 335 | for srcpkg, pkgs in pkgs_by_source.items(): | ||
439 | 336 | for pkg in pkgs: | ||
440 | 337 | app = self._get_application_for_package(pkg) | ||
441 | 338 | if app is not None: | ||
442 | 339 | app_group = UpdateApplicationGroup(pkg, app) | ||
443 | 340 | app_groups.append(app_group) | ||
444 | 341 | else: | ||
445 | 342 | ungrouped_pkgs.append(pkg) | ||
446 | 343 | |||
447 | 344 | # Stick together applications and their immediate dependencies | ||
448 | 345 | for pkg in list(ungrouped_pkgs): | ||
449 | 346 | dep_groups = [] | ||
450 | 347 | for group in app_groups: | ||
451 | 348 | if group.is_dependency(cache, pkg): | ||
452 | 349 | dep_groups.append(group) | ||
453 | 350 | if len(dep_groups) > 1: | ||
454 | 351 | break | ||
455 | 352 | if len(dep_groups) == 1: | ||
456 | 353 | dep_groups[0].add(pkg) | ||
457 | 354 | ungrouped_pkgs.remove(pkg) | ||
458 | 355 | |||
459 | 356 | # Separate out system base packages | ||
460 | 357 | system_group = None | ||
461 | 358 | meta_group = UpdateGroup(None, None, None) | ||
462 | 359 | flavor_package = utils.get_ubuntu_flavor_package(cache=cache) | ||
463 | 360 | meta_pkgs = [flavor_package, "ubuntu-standard", "ubuntu-minimal"] | ||
464 | 361 | meta_pkgs.extend(self._get_linux_packages()) | ||
465 | 362 | for pkg in meta_pkgs: | ||
466 | 363 | if pkg in cache: | ||
467 | 364 | meta_group.add(cache[pkg]) | ||
468 | 365 | for pkg in ungrouped_pkgs: | ||
469 | 366 | if meta_group.contains(pkg) or meta_group.is_dependency(cache, pkg): | ||
470 | 367 | if system_group is None: | ||
471 | 368 | system_group = UpdateSystemGroup(cache) | ||
472 | 369 | system_group.add(pkg) | ||
473 | 370 | else: | ||
474 | 371 | pkg_groups.append(UpdatePackageGroup(pkg)) | ||
475 | 372 | |||
476 | 373 | app_groups.sort(key=lambda a: a.name.lower()) | ||
477 | 374 | pkg_groups.sort(key=lambda a: a.name.lower()) | ||
478 | 375 | if system_group: | ||
479 | 376 | pkg_groups.append(system_group) | ||
480 | 377 | |||
481 | 378 | return app_groups + pkg_groups | ||
482 | 379 | |||
483 | 145 | def update(self, cache): | 380 | def update(self, cache): |
484 | 146 | self.held_back = [] | 381 | self.held_back = [] |
485 | 147 | 382 | ||
486 | 148 | # do the upgrade | 383 | # do the upgrade |
487 | 149 | self.distUpgradeWouldDelete = cache.saveDistUpgrade() | 384 | self.distUpgradeWouldDelete = cache.saveDistUpgrade() |
488 | 150 | 385 | ||
490 | 151 | #dselect_upgrade_origin = UpdateOrigin(_("Previous selected"), 1) | 386 | security_pkgs = [] |
491 | 387 | upgrade_pkgs = [] | ||
492 | 152 | 388 | ||
494 | 153 | # sort by origin | 389 | # Find all upgradable packages |
495 | 154 | for pkg in cache: | 390 | for pkg in cache: |
496 | 155 | if pkg.is_upgradable or pkg.marked_install: | 391 | if pkg.is_upgradable or pkg.marked_install: |
497 | 156 | if getattr(pkg.candidate, "origins", None) is None: | 392 | if getattr(pkg.candidate, "origins", None) is None: |
498 | @@ -159,22 +395,22 @@ | |||
499 | 159 | print("WARNING: upgradable but no candidate.origins?!?: ", | 395 | print("WARNING: upgradable but no candidate.origins?!?: ", |
500 | 160 | pkg.name) | 396 | pkg.name) |
501 | 161 | continue | 397 | continue |
502 | 162 | # check where the package belongs | ||
503 | 163 | origin_node = cache.match_package_origin(pkg, self.matcher) | ||
504 | 164 | 398 | ||
505 | 165 | # see if its a phased update and *not* a security update | 399 | # see if its a phased update and *not* a security update |
509 | 166 | # or shadowing a security update | 400 | is_security_update = self._is_security_update(pkg) |
510 | 167 | if (origin_node.importance != OriginsImportance.SECURITY and | 401 | if (not is_security_update and |
511 | 168 | self.is_ignored_phased_update(pkg)): | 402 | self._is_ignored_phased_update(pkg)): |
512 | 169 | continue | 403 | continue |
513 | 170 | 404 | ||
517 | 171 | if origin_node not in self.pkgs: | 405 | if is_security_update: |
518 | 172 | self.pkgs[origin_node] = [] | 406 | security_pkgs.append(pkg) |
519 | 173 | self.pkgs[origin_node].append(pkg) | 407 | else: |
520 | 408 | upgrade_pkgs.append(pkg) | ||
521 | 174 | self.num_updates = self.num_updates + 1 | 409 | self.num_updates = self.num_updates + 1 |
522 | 410 | |||
523 | 175 | if pkg.is_upgradable and not (pkg.marked_upgrade or | 411 | if pkg.is_upgradable and not (pkg.marked_upgrade or |
524 | 176 | pkg.marked_install): | 412 | pkg.marked_install): |
525 | 177 | self.held_back.append(pkg.name) | 413 | self.held_back.append(pkg.name) |
529 | 178 | for l in self.pkgs.keys(): | 414 | |
530 | 179 | self.pkgs[l].sort(key=operator.attrgetter("name")) | 415 | self.update_groups = self._make_groups(cache, upgrade_pkgs) |
531 | 180 | self.keepcount = cache._depcache.keep_count | 416 | self.security_groups = self._make_groups(cache, security_pkgs) |
532 | 181 | 417 | ||
533 | === modified file 'UpdateManager/Core/utils.py' | |||
534 | --- UpdateManager/Core/utils.py 2012-12-14 23:03:19 +0000 | |||
535 | +++ UpdateManager/Core/utils.py 2013-01-23 15:48:20 +0000 | |||
536 | @@ -1,9 +1,10 @@ | |||
537 | 1 | # utils.py | 1 | # utils.py |
538 | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- |
539 | 3 | # | 3 | # |
541 | 4 | # Copyright (c) 2004-2008 Canonical | 4 | # Copyright (c) 2004-2013 Canonical |
542 | 5 | # | 5 | # |
544 | 6 | # Author: Michael Vogt <mvo@debian.org> | 6 | # Authors: Michael Vogt <mvo@debian.org> |
545 | 7 | # Michael Terry <michael.terry@canonical.com> | ||
546 | 7 | # | 8 | # |
547 | 8 | # This program is free software; you can redistribute it and/or | 9 | # This program is free software; you can redistribute it and/or |
548 | 9 | # modify it under the terms of the GNU General Public License as | 10 | # modify it under the terms of the GNU General Public License as |
549 | @@ -27,6 +28,7 @@ | |||
550 | 27 | from stat import (S_IMODE, ST_MODE, S_IXUSR) | 28 | from stat import (S_IMODE, ST_MODE, S_IXUSR) |
551 | 28 | from math import ceil | 29 | from math import ceil |
552 | 29 | 30 | ||
553 | 31 | import apt | ||
554 | 30 | import apt_pkg | 32 | import apt_pkg |
555 | 31 | apt_pkg.init_config() | 33 | apt_pkg.init_config() |
556 | 32 | 34 | ||
557 | @@ -58,6 +60,7 @@ | |||
558 | 58 | from urlparse import urlsplit | 60 | from urlparse import urlsplit |
559 | 59 | 61 | ||
560 | 60 | from copy import copy | 62 | from copy import copy |
561 | 63 | from gi.repository import GLib | ||
562 | 61 | 64 | ||
563 | 62 | 65 | ||
564 | 63 | class ExecutionTime(object): | 66 | class ExecutionTime(object): |
565 | @@ -402,36 +405,56 @@ | |||
566 | 402 | return None | 405 | return None |
567 | 403 | 406 | ||
568 | 404 | 407 | ||
570 | 405 | def get_ubuntu_flavor(): | 408 | def get_ubuntu_flavor(cache=None): |
571 | 406 | """ try to guess the flavor based on the running desktop """ | 409 | """ try to guess the flavor based on the running desktop """ |
572 | 407 | # this will (of course) not work in a server environment, | 410 | # this will (of course) not work in a server environment, |
573 | 408 | # but the main use case for this is to show the right | 411 | # but the main use case for this is to show the right |
598 | 409 | # release notes | 412 | # release notes. |
599 | 410 | # TODO: actually examine which meta packages are installed, like | 413 | pkg = get_ubuntu_flavor_package(cache=cache) |
600 | 411 | # DistUpgrade/DistUpgradeCache.py does and use that to choose a flavor. | 414 | return pkg.split('-', 1)[0] |
601 | 412 | denv = os.environ.get("DESKTOP_SESSION", "") | 415 | |
602 | 413 | if "gnome" in denv: | 416 | |
603 | 414 | return "ubuntu" | 417 | def _load_meta_pkg_list(): |
604 | 415 | elif "kde" in denv: | 418 | # This could potentially introduce a circular dependency, but the config |
605 | 416 | return "kubuntu" | 419 | # parser logic is simple, and doesn't rely on any UpdateManager code. |
606 | 417 | elif "xfce" in denv or "xubuntu" in denv: | 420 | from DistUpgrade.DistUpgradeConfigParser import DistUpgradeConfig |
607 | 418 | return "xubuntu" | 421 | parser = DistUpgradeConfig('/usr/share/ubuntu-release-upgrader') |
608 | 419 | elif "LXDE" in denv or "Lubuntu" in denv: | 422 | return parser.getlist('Distro', 'MetaPkgs') |
609 | 420 | return "lubuntu" | 423 | |
610 | 421 | # default to ubuntu if nothing more specific is found | 424 | |
611 | 422 | return "ubuntu" | 425 | def get_ubuntu_flavor_package(cache=None): |
612 | 423 | 426 | """ try to guess the flavor metapackage based on the running desktop """ | |
613 | 424 | 427 | # From spec, first if ubuntu-desktop is installed, use that. | |
614 | 425 | def get_ubuntu_flavor_name(): | 428 | # Second, grab first installed one from DistUpgrade.cfg. |
615 | 426 | flavor = get_ubuntu_flavor() | 429 | # Lastly, fallback to ubuntu-desktop again. |
616 | 427 | if flavor == "kubuntu": | 430 | meta_pkgs = ['ubuntu-desktop'] |
617 | 428 | return "Kubuntu" | 431 | |
618 | 429 | elif flavor == "xubuntu": | 432 | try: |
619 | 430 | return "Xubuntu" | 433 | meta_pkgs.extend(sorted(_load_meta_pkg_list())) |
620 | 431 | elif flavor == "lubuntu": | 434 | except Exception as e: |
621 | 432 | return "Lubuntu" | 435 | print('Could not load list of meta packages:', e) |
622 | 436 | |||
623 | 437 | if cache is None: | ||
624 | 438 | cache = apt.Cache() | ||
625 | 439 | for meta_pkg in meta_pkgs: | ||
626 | 440 | cache_pkg = cache[meta_pkg] if meta_pkg in cache else None | ||
627 | 441 | if cache_pkg and cache_pkg.is_installed: | ||
628 | 442 | return meta_pkg | ||
629 | 443 | return 'ubuntu-desktop' | ||
630 | 444 | |||
631 | 445 | |||
632 | 446 | def get_ubuntu_flavor_name(cache=None): | ||
633 | 447 | """ try to guess the flavor name based on the running desktop """ | ||
634 | 448 | pkg = get_ubuntu_flavor_package(cache=cache) | ||
635 | 449 | lookup = {'ubuntustudio-desktop': 'Ubuntu Studio'} | ||
636 | 450 | if pkg in lookup: | ||
637 | 451 | return lookup[pkg] | ||
638 | 452 | elif pkg.endswith('-desktop'): | ||
639 | 453 | return capitalize_first_word(pkg.rsplit('-desktop', 1)[0]) | ||
640 | 454 | elif pkg.endswith('-netbook'): | ||
641 | 455 | return capitalize_first_word(pkg.rsplit('-netbook', 1)[0]) | ||
642 | 433 | else: | 456 | else: |
644 | 434 | return "Ubuntu" | 457 | return 'Ubuntu' |
645 | 435 | 458 | ||
646 | 436 | 459 | ||
647 | 437 | # Unused by update-manager, but still used by ubuntu-release-upgrader | 460 | # Unused by update-manager, but still used by ubuntu-release-upgrader |
648 | @@ -523,6 +546,22 @@ | |||
649 | 523 | return True | 546 | return True |
650 | 524 | 547 | ||
651 | 525 | 548 | ||
652 | 549 | def capitalize_first_word(string): | ||
653 | 550 | """ this uppercases the first word's first letter | ||
654 | 551 | """ | ||
655 | 552 | if len(string) > 1 and string[0].isalpha() and not string[0].isupper(): | ||
656 | 553 | return string[0].capitalize() + string[1:] | ||
657 | 554 | return string | ||
658 | 555 | |||
659 | 556 | |||
660 | 557 | def get_package_label(pkg): | ||
661 | 558 | """ this takes a package synopsis and uppercases the first word's | ||
662 | 559 | first letter | ||
663 | 560 | """ | ||
664 | 561 | name = GLib.markup_escape_text(getattr(pkg.candidate, "summary", "")) | ||
665 | 562 | return capitalize_first_word(name) | ||
666 | 563 | |||
667 | 564 | |||
668 | 526 | if __name__ == "__main__": | 565 | if __name__ == "__main__": |
669 | 527 | #print(mirror_from_sources_list()) | 566 | #print(mirror_from_sources_list()) |
670 | 528 | #print(on_battery()) | 567 | #print(on_battery()) |
671 | 529 | 568 | ||
672 | === modified file 'UpdateManager/UpdatesAvailable.py' | |||
673 | --- UpdateManager/UpdatesAvailable.py 2012-12-13 20:39:37 +0000 | |||
674 | +++ UpdateManager/UpdatesAvailable.py 2013-01-23 15:48:20 +0000 | |||
675 | @@ -1,7 +1,7 @@ | |||
676 | 1 | # UpdatesAvailable.py | 1 | # UpdatesAvailable.py |
677 | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- | 2 | # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- |
678 | 3 | # | 3 | # |
680 | 4 | # Copyright (c) 2004-2012 Canonical | 4 | # Copyright (c) 2004-2013 Canonical |
681 | 5 | # 2004 Michiel Sikkes | 5 | # 2004 Michiel Sikkes |
682 | 6 | # 2005 Martin Willemoes Hansen | 6 | # 2005 Martin Willemoes Hansen |
683 | 7 | # 2010 Mohamed Amine IL Idrissi | 7 | # 2010 Mohamed Amine IL Idrissi |
684 | @@ -12,6 +12,7 @@ | |||
685 | 12 | # Mohamed Amine IL Idrissi <ilidrissiamine@gmail.com> | 12 | # Mohamed Amine IL Idrissi <ilidrissiamine@gmail.com> |
686 | 13 | # Alex Launi <alex.launi@canonical.com> | 13 | # Alex Launi <alex.launi@canonical.com> |
687 | 14 | # Michael Terry <michael.terry@canonical.com> | 14 | # Michael Terry <michael.terry@canonical.com> |
688 | 15 | # Dylan McCall <dylanmccall@ubuntu.com> | ||
689 | 15 | # | 16 | # |
690 | 16 | # This program is free software; you can redistribute it and/or | 17 | # This program is free software; you can redistribute it and/or |
691 | 17 | # modify it under the terms of the GNU General Public License as | 18 | # modify it under the terms of the GNU General Public License as |
692 | @@ -30,6 +31,7 @@ | |||
693 | 30 | 31 | ||
694 | 31 | from __future__ import absolute_import, print_function | 32 | from __future__ import absolute_import, print_function |
695 | 32 | 33 | ||
696 | 34 | from gi.repository import GLib | ||
697 | 33 | from gi.repository import Gtk | 35 | from gi.repository import Gtk |
698 | 34 | from gi.repository import Gdk | 36 | from gi.repository import Gdk |
699 | 35 | from gi.repository import GObject | 37 | from gi.repository import GObject |
700 | @@ -46,18 +48,17 @@ | |||
701 | 46 | import os | 48 | import os |
702 | 47 | import re | 49 | import re |
703 | 48 | import logging | 50 | import logging |
704 | 49 | import operator | ||
705 | 50 | import subprocess | 51 | import subprocess |
706 | 51 | import time | 52 | import time |
707 | 52 | import threading | 53 | import threading |
708 | 53 | import xml.sax.saxutils | ||
709 | 54 | 54 | ||
710 | 55 | from gettext import gettext as _ | 55 | from gettext import gettext as _ |
711 | 56 | from gettext import ngettext | 56 | from gettext import ngettext |
712 | 57 | 57 | ||
713 | 58 | 58 | ||
715 | 59 | from .Core.utils import humanize_size | 59 | from .Core.utils import (get_package_label, humanize_size) |
716 | 60 | from .Core.AlertWatcher import AlertWatcher | 60 | from .Core.AlertWatcher import AlertWatcher |
717 | 61 | from .Core.UpdateList import UpdateSystemGroup | ||
718 | 61 | 62 | ||
719 | 62 | from DistUpgrade.DistUpgradeCache import NotEnoughFreeSpaceError | 63 | from DistUpgrade.DistUpgradeCache import NotEnoughFreeSpaceError |
720 | 63 | 64 | ||
721 | @@ -70,15 +71,126 @@ | |||
722 | 70 | 71 | ||
723 | 71 | # FIXME: | 72 | # FIXME: |
724 | 72 | # - kill "all_changes" and move the changes into the "Update" class | 73 | # - kill "all_changes" and move the changes into the "Update" class |
725 | 74 | # - screen reader does not read update toggle state | ||
726 | 75 | # - screen reader does not say "Downloaded" for downloaded updates | ||
727 | 73 | 76 | ||
728 | 74 | # list constants | 77 | # list constants |
731 | 75 | (LIST_CONTENTS, LIST_NAME, LIST_PKG, | 78 | (LIST_NAME, LIST_UPDATE_DATA, LIST_SIZE, LIST_TOGGLE_ACTIVE) = range(4) |
730 | 76 | LIST_ORIGIN, LIST_TOGGLE_CHECKED) = range(5) | ||
732 | 77 | 79 | ||
733 | 78 | # NetworkManager enums | 80 | # NetworkManager enums |
734 | 79 | from .Core.roam import NetworkManagerHelper | 81 | from .Core.roam import NetworkManagerHelper |
735 | 80 | 82 | ||
736 | 81 | 83 | ||
737 | 84 | class UpdateData(): | ||
738 | 85 | def __init__(self, groups, group, item): | ||
739 | 86 | self.groups = groups if groups else [] | ||
740 | 87 | self.group = group | ||
741 | 88 | self.item = item | ||
742 | 89 | |||
743 | 90 | |||
744 | 91 | class CellAreaPackage(Gtk.CellAreaBox): | ||
745 | 92 | """This CellArea lays our package cells side by side, without allocating | ||
746 | 93 | width for a cell if it isn't present (like icons for header labels). | ||
747 | 94 | """ | ||
748 | 95 | |||
749 | 96 | def __init__(self, indent_toplevel=False): | ||
750 | 97 | Gtk.CellAreaBox.__init__(self) | ||
751 | 98 | self.indent_toplevel = indent_toplevel | ||
752 | 99 | self.column = None | ||
753 | 100 | self.toggle_size = None | ||
754 | 101 | self.pixbuf_size = None | ||
755 | 102 | self.cached_cell_start = {} | ||
756 | 103 | |||
757 | 104 | def do_foreach_alloc(self, context, widget, cell_area_in, bg_area_in, | ||
758 | 105 | callback): | ||
759 | 106 | # First, gather cell renderers and make sure they are what we expect | ||
760 | 107 | cells = [] | ||
761 | 108 | def gather(cell, data): | ||
762 | 109 | cells.append(cell) | ||
763 | 110 | self.foreach(gather, None) | ||
764 | 111 | if (len(cells) != 3 or | ||
765 | 112 | not isinstance(cells[0], Gtk.CellRendererToggle) or | ||
766 | 113 | not isinstance(cells[1], Gtk.CellRendererPixbuf) or | ||
767 | 114 | not isinstance(cells[2], Gtk.CellRendererText)): | ||
768 | 115 | return | ||
769 | 116 | toggle = cells[0] | ||
770 | 117 | pixbuf = cells[1] | ||
771 | 118 | text = cells[2] | ||
772 | 119 | |||
773 | 120 | # Now just grab some size info | ||
774 | 121 | cell_area = cell_area_in.copy() | ||
775 | 122 | bg_area = bg_area_in.copy() | ||
776 | 123 | spacing = self.get_property("spacing") | ||
777 | 124 | gicon = pixbuf.get_property("gicon") | ||
778 | 125 | cell_start = self.get_cell_start(widget) | ||
779 | 126 | orig_end = cell_area.width + cell_area.x | ||
780 | 127 | if self.toggle_size is None: | ||
781 | 128 | toggle_min, self.toggle_size = toggle.get_preferred_width(widget) | ||
782 | 129 | if gicon and self.pixbuf_size is None: | ||
783 | 130 | pixbuf_min, self.pixbuf_size = pixbuf.get_preferred_width(widget) | ||
784 | 131 | |||
785 | 132 | # And finally, start handling each cell | ||
786 | 133 | |||
787 | 134 | cur_path = self.get_current_path_string() | ||
788 | 135 | depth = Gtk.TreePath.new_from_string(cur_path).get_depth() | ||
789 | 136 | if gicon is not None and self.indent_toplevel: | ||
790 | 137 | # if not a header, align with header rows | ||
791 | 138 | depth = depth + 1 | ||
792 | 139 | if depth == 1: | ||
793 | 140 | cell_area.x = cell_start | ||
794 | 141 | elif depth == 2: | ||
795 | 142 | cell_area.x = cell_start + self.toggle_size + spacing | ||
796 | 143 | elif depth == 3: | ||
797 | 144 | # Oddly, cells don't line up if we simply use spacing * 2 | ||
798 | 145 | cell_area.x = cell_start + self.toggle_size * 2 + spacing + 1 | ||
799 | 146 | cell_area.width = self.toggle_size | ||
800 | 147 | if callback(cells[0], cell_area.copy(), bg_area.copy()): | ||
801 | 148 | return | ||
802 | 149 | |||
803 | 150 | cell_area.x = cell_area.x + cell_area.width + spacing | ||
804 | 151 | if gicon is None: | ||
805 | 152 | cell_area.width = 0 | ||
806 | 153 | else: | ||
807 | 154 | cell_area.width = self.pixbuf_size | ||
808 | 155 | if callback(cells[1], cell_area.copy(), bg_area.copy()): | ||
809 | 156 | return | ||
810 | 157 | |||
811 | 158 | if gicon is not None: | ||
812 | 159 | cell_area.x = cell_area.x + cell_area.width + spacing | ||
813 | 160 | cell_area.width = orig_end - cell_area.x | ||
814 | 161 | if callback(cells[2], cell_area.copy(), bg_area.copy()): | ||
815 | 162 | return | ||
816 | 163 | |||
817 | 164 | def do_event(self, context, widget, event, cell_area, flags): | ||
818 | 165 | # This override is just to trick our parent implementation into | ||
819 | 166 | # allowing clicks on toggle cells when they are where the expanders | ||
820 | 167 | # usually are. It doesn't expect that, so we expand the cell_area | ||
821 | 168 | # here to be equivalent to bg_area. | ||
822 | 169 | cell_start = self.get_cell_start(widget) | ||
823 | 170 | cell_area.width = cell_area.width + cell_area.x - cell_start | ||
824 | 171 | cell_area.x = cell_start | ||
825 | 172 | return Gtk.CellAreaBox.do_event(self, context, widget, event, | ||
826 | 173 | cell_area, flags) | ||
827 | 174 | |||
828 | 175 | def get_cell_start(self, widget): | ||
829 | 176 | if not self.column: | ||
830 | 177 | return 0 | ||
831 | 178 | else: | ||
832 | 179 | val = GObject.Value() | ||
833 | 180 | val.init(int) | ||
834 | 181 | widget.style_get_property("horizontal-separator", val) | ||
835 | 182 | h_sep = val.get_int() | ||
836 | 183 | widget.style_get_property("grid-line-width", val) | ||
837 | 184 | line_width = val.get_int() | ||
838 | 185 | cell_start = self.column.get_x_offset() - h_sep - line_width | ||
839 | 186 | if not self.indent_toplevel: # i.e. if no headers | ||
840 | 187 | widget.style_get_property("expander-size", val) | ||
841 | 188 | spacing = self.get_property("spacing") | ||
842 | 189 | # Hardcode 4 because GTK+ hardcodes 4 internally | ||
843 | 190 | cell_start = cell_start + val.get_int() + 4 + spacing | ||
844 | 191 | return cell_start | ||
845 | 192 | |||
846 | 193 | |||
847 | 82 | class UpdatesAvailable(SimpleGtkbuilderApp): | 194 | class UpdatesAvailable(SimpleGtkbuilderApp): |
848 | 83 | 195 | ||
849 | 84 | def __init__(self, app, header=None, desc=None): | 196 | def __init__(self, app, header=None, desc=None): |
850 | @@ -134,38 +246,71 @@ | |||
851 | 134 | self.update_close_button() | 246 | self.update_close_button() |
852 | 135 | 247 | ||
853 | 136 | # the treeview (move into it's own code!) | 248 | # the treeview (move into it's own code!) |
856 | 137 | self.store = Gtk.ListStore(str, str, GObject.TYPE_PYOBJECT, | 249 | self.store = Gtk.TreeStore(str, GObject.TYPE_PYOBJECT, str, bool) |
855 | 138 | GObject.TYPE_PYOBJECT, bool) | ||
857 | 139 | self.treeview_update.set_model(self.store) | 250 | self.treeview_update.set_model(self.store) |
859 | 140 | self.treeview_update.set_headers_clickable(True) | 251 | |
860 | 252 | restart_icon_renderer = Gtk.CellRendererPixbuf() | ||
861 | 253 | restart_icon_renderer.set_property("xpad", 4) | ||
862 | 254 | restart_icon_renderer.set_property("ypad", 2) | ||
863 | 255 | restart_icon_renderer.set_property("stock-size", Gtk.IconSize.MENU) | ||
864 | 256 | restart_icon_renderer.set_property("follow-state", True) | ||
865 | 257 | restart_column = Gtk.TreeViewColumn(None, restart_icon_renderer) | ||
866 | 258 | restart_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) | ||
867 | 259 | restart_column.set_fixed_width(20) | ||
868 | 260 | self.treeview_update.append_column(restart_column) | ||
869 | 261 | restart_column.set_cell_data_func(restart_icon_renderer, | ||
870 | 262 | self.restart_icon_renderer_data_func) | ||
871 | 263 | |||
872 | 264 | pkg_area = CellAreaPackage(bool(self.list.security_groups)) | ||
873 | 265 | pkg_column = Gtk.TreeViewColumn.new_with_area(pkg_area) | ||
874 | 266 | pkg_area.column = pkg_column | ||
875 | 267 | pkg_column.set_title(_("Install")) | ||
876 | 268 | pkg_column.set_property("spacing", 4) | ||
877 | 269 | pkg_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) | ||
878 | 270 | pkg_column.set_expand(True) | ||
879 | 271 | self.treeview_update.append_column(pkg_column) | ||
880 | 272 | |||
881 | 273 | pkg_toggle_renderer = Gtk.CellRendererToggle() | ||
882 | 274 | pkg_toggle_renderer.set_property("ypad", 2) | ||
883 | 275 | pkg_toggle_renderer.connect("toggled", self.on_update_toggled) | ||
884 | 276 | pkg_column.pack_start(pkg_toggle_renderer, False) | ||
885 | 277 | pkg_column.add_attribute(pkg_toggle_renderer, | ||
886 | 278 | 'active', LIST_TOGGLE_ACTIVE) | ||
887 | 279 | pkg_column.set_cell_data_func(pkg_toggle_renderer, | ||
888 | 280 | self.pkg_toggle_renderer_data_func) | ||
889 | 281 | |||
890 | 282 | pkg_icon_renderer = Gtk.CellRendererPixbuf() | ||
891 | 283 | pkg_icon_renderer.set_property("ypad", 2) | ||
892 | 284 | pkg_icon_renderer.set_property("stock-size", Gtk.IconSize.MENU) | ||
893 | 285 | pkg_column.pack_start(pkg_icon_renderer, False) | ||
894 | 286 | pkg_column.set_cell_data_func(pkg_icon_renderer, | ||
895 | 287 | self.pkg_icon_renderer_data_func) | ||
896 | 288 | |||
897 | 289 | pkg_label_renderer = Gtk.CellRendererText() | ||
898 | 290 | pkg_label_renderer.set_property("ypad", 2) | ||
899 | 291 | pkg_column.pack_start(pkg_label_renderer, True) | ||
900 | 292 | pkg_column.set_cell_data_func(pkg_label_renderer, | ||
901 | 293 | self.pkg_label_renderer_data_func) | ||
902 | 294 | |||
903 | 295 | size_renderer = Gtk.CellRendererText() | ||
904 | 296 | size_renderer.set_property("xpad", 6) | ||
905 | 297 | size_renderer.set_property("ypad", 0) | ||
906 | 298 | size_renderer.set_property("xalign", 1) | ||
907 | 299 | # 1.0/1.2 == PANGO.Scale.SMALL. Constant is not (yet) introspected. | ||
908 | 300 | size_renderer.set_property("scale", 1.0 / 1.2) | ||
909 | 301 | size_column = Gtk.TreeViewColumn(_("Download"), size_renderer, | ||
910 | 302 | text=LIST_SIZE) | ||
911 | 303 | size_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) | ||
912 | 304 | self.treeview_update.append_column(size_column) | ||
913 | 305 | |||
914 | 306 | self.treeview_update.set_headers_visible(True) | ||
915 | 307 | self.treeview_update.set_headers_clickable(False) | ||
916 | 141 | self.treeview_update.set_direction(Gtk.TextDirection.LTR) | 308 | self.treeview_update.set_direction(Gtk.TextDirection.LTR) |
917 | 142 | |||
918 | 143 | tr = Gtk.CellRendererText() | ||
919 | 144 | tr.set_property("xpad", 6) | ||
920 | 145 | tr.set_property("ypad", 6) | ||
921 | 146 | cr = Gtk.CellRendererToggle() | ||
922 | 147 | cr.set_property("activatable", True) | ||
923 | 148 | cr.set_property("xpad", 6) | ||
924 | 149 | cr.connect("toggled", self.toggled) | ||
925 | 150 | |||
926 | 151 | column_install = Gtk.TreeViewColumn(_("Install"), cr, | ||
927 | 152 | active=LIST_TOGGLE_CHECKED) | ||
928 | 153 | column_install.set_cell_data_func(cr, self.install_column_view_func) | ||
929 | 154 | column = Gtk.TreeViewColumn(_("Name"), tr, markup=LIST_CONTENTS) | ||
930 | 155 | column.set_resizable(True) | ||
931 | 156 | |||
932 | 157 | column_install.set_sizing(Gtk.TreeViewColumnSizing.FIXED) | ||
933 | 158 | column_install.set_fixed_width(30) | ||
934 | 159 | column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) | ||
935 | 160 | column.set_fixed_width(100) | ||
936 | 161 | self.treeview_update.set_fixed_height_mode(False) | 309 | self.treeview_update.set_fixed_height_mode(False) |
941 | 162 | 310 | self.treeview_update.set_expander_column(pkg_column) | |
938 | 163 | self.treeview_update.append_column(column_install) | ||
939 | 164 | column_install.set_visible(True) | ||
940 | 165 | self.treeview_update.append_column(column) | ||
942 | 166 | self.treeview_update.set_search_column(LIST_NAME) | 311 | self.treeview_update.set_search_column(LIST_NAME) |
943 | 167 | self.treeview_update.connect("button-press-event", | 312 | self.treeview_update.connect("button-press-event", |
945 | 168 | self.show_context_menu) | 313 | self.on_treeview_button_press) |
946 | 169 | 314 | ||
947 | 170 | # setup the help viewer and disable the help button if there | 315 | # setup the help viewer and disable the help button if there |
948 | 171 | # is no viewer available | 316 | # is no viewer available |
949 | @@ -210,26 +355,97 @@ | |||
950 | 210 | self.button_close.set_use_underline(False) | 355 | self.button_close.set_use_underline(False) |
951 | 211 | 356 | ||
952 | 212 | def install_all_updates(self, menu, menuitem, data): | 357 | def install_all_updates(self, menu, menuitem, data): |
954 | 213 | self.select_all_updgrades(None) | 358 | self.select_all_upgrades(None) |
955 | 214 | self.on_button_install_clicked(None) | 359 | self.on_button_install_clicked(None) |
956 | 215 | 360 | ||
972 | 216 | def install_column_view_func(self, cell_layout, renderer, model, iter, | 361 | def restart_icon_renderer_data_func(self, cell_layout, renderer, model, |
973 | 217 | data): | 362 | iter, data): |
974 | 218 | pkg = model.get_value(iter, LIST_PKG) | 363 | def pkg_requires_restart(pkg): |
975 | 219 | if pkg is None: | 364 | restart_condition = pkg.candidate.record.get('XB-Restart-Required') |
976 | 220 | renderer.set_property("activatable", True) | 365 | return restart_condition == 'system' |
977 | 221 | return | 366 | |
978 | 222 | current_state = renderer.get_property("active") | 367 | data = model.get_value(iter, LIST_UPDATE_DATA) |
979 | 223 | to_install = pkg.marked_install or pkg.marked_upgrade | 368 | path = model.get_path(iter) |
980 | 224 | renderer.set_property("active", to_install) | 369 | |
981 | 225 | # we need to update the store as well to ensure orca knowns | 370 | requires_restart = False |
982 | 226 | # about state changes (it will not read view_func changes) | 371 | if data.item: |
983 | 227 | if to_install != current_state: | 372 | requires_restart = pkg_requires_restart(data.item.pkg) |
984 | 228 | self.store[iter][LIST_TOGGLE_CHECKED] = to_install | 373 | elif data.group: |
985 | 229 | if pkg.name in self.list.held_back: | 374 | if not self.treeview_update.row_expanded(path): |
986 | 230 | renderer.set_property("activatable", False) | 375 | # A package in the group requires restart |
987 | 376 | for group_item in data.group.items: | ||
988 | 377 | if pkg_requires_restart(group_item.pkg): | ||
989 | 378 | requires_restart = True | ||
990 | 379 | break | ||
991 | 380 | |||
992 | 381 | # FIXME: Non-standard, incorrect icon name (from app category). | ||
993 | 382 | # Theme support for what we want seems to be lacking. | ||
994 | 383 | if requires_restart: | ||
995 | 384 | restart_icon_names = ['view-refresh-symbolic', | ||
996 | 385 | 'system-restart', | ||
997 | 386 | 'system-reboot'] | ||
998 | 387 | gicon = Gio.ThemedIcon.new_from_names(restart_icon_names) | ||
999 | 231 | else: | 388 | else: |
1001 | 232 | renderer.set_property("activatable", True) | 389 | gicon = None |
1002 | 390 | renderer.set_property("gicon", gicon) | ||
1003 | 391 | |||
1004 | 392 | def pkg_toggle_renderer_data_func(self, cell_layout, renderer, model, iter, | ||
1005 | 393 | data): | ||
1006 | 394 | data = model.get_value(iter, LIST_UPDATE_DATA) | ||
1007 | 395 | |||
1008 | 396 | activatable = False | ||
1009 | 397 | inconsistent = False | ||
1010 | 398 | if data.item: | ||
1011 | 399 | activatable = data.item.name not in self.list.held_back | ||
1012 | 400 | inconsistent = False | ||
1013 | 401 | elif data.group: | ||
1014 | 402 | activatable = True | ||
1015 | 403 | inconsistent = data.group.selection_is_inconsistent() | ||
1016 | 404 | elif data.groups: | ||
1017 | 405 | activatable = True | ||
1018 | 406 | inconsistent = False | ||
1019 | 407 | saw_install = None | ||
1020 | 408 | for group in data.groups: | ||
1021 | 409 | for item in group.items: | ||
1022 | 410 | pkg = item.pkg | ||
1023 | 411 | this_install = pkg.marked_install or pkg.marked_upgrade | ||
1024 | 412 | if saw_install is not None and saw_install != this_install: | ||
1025 | 413 | inconsistent = True | ||
1026 | 414 | break | ||
1027 | 415 | saw_install = this_install | ||
1028 | 416 | if inconsistent: | ||
1029 | 417 | break | ||
1030 | 418 | |||
1031 | 419 | # The "active" attribute is already set via LIST_TOGGLE_ACTIVE in the | ||
1032 | 420 | # tree model, so we don't set it here. | ||
1033 | 421 | renderer.set_property("activatable", activatable) | ||
1034 | 422 | renderer.set_property("inconsistent", inconsistent) | ||
1035 | 423 | |||
1036 | 424 | def pkg_icon_renderer_data_func(self, cell_layout, renderer, model, iter, | ||
1037 | 425 | data): | ||
1038 | 426 | data = model.get_value(iter, LIST_UPDATE_DATA) | ||
1039 | 427 | |||
1040 | 428 | gicon = None | ||
1041 | 429 | if data.group: | ||
1042 | 430 | gicon = data.group.icon | ||
1043 | 431 | elif data.item: | ||
1044 | 432 | gicon = data.item.icon | ||
1045 | 433 | |||
1046 | 434 | renderer.set_property("gicon", gicon) | ||
1047 | 435 | |||
1048 | 436 | def pkg_label_renderer_data_func(self, cell_layout, renderer, model, iter, | ||
1049 | 437 | data): | ||
1050 | 438 | data = model.get_value(iter, LIST_UPDATE_DATA) | ||
1051 | 439 | name = model.get_value(iter, LIST_NAME) | ||
1052 | 440 | |||
1053 | 441 | if data.group: | ||
1054 | 442 | markup = name | ||
1055 | 443 | elif data.item: | ||
1056 | 444 | markup = name | ||
1057 | 445 | else: # header | ||
1058 | 446 | markup = "<b>%s</b>" % name | ||
1059 | 447 | |||
1060 | 448 | renderer.set_property("markup", markup) | ||
1061 | 233 | 449 | ||
1062 | 234 | def set_changes_buffer(self, changes_buffer, text, name, srcpkg): | 450 | def set_changes_buffer(self, changes_buffer, text, name, srcpkg): |
1063 | 235 | changes_buffer.set_text("") | 451 | changes_buffer.set_text("") |
1064 | @@ -264,16 +480,21 @@ | |||
1065 | 264 | iter = model.get_iter(path) | 480 | iter = model.get_iter(path) |
1066 | 265 | 481 | ||
1067 | 266 | # set descr | 482 | # set descr |
1071 | 267 | pkg = model.get_value(iter, LIST_PKG) | 483 | data = model.get_value(iter, LIST_UPDATE_DATA) |
1072 | 268 | if (pkg is None or pkg.candidate is None or | 484 | item = data.item |
1073 | 269 | pkg.candidate.description is None): | 485 | if (item is None and data.group is not None and |
1074 | 486 | data.group.core_item is not None): | ||
1075 | 487 | item = data.group.core_item | ||
1076 | 488 | if (item is None or item.pkg is None or | ||
1077 | 489 | item.pkg.candidate is None or | ||
1078 | 490 | item.pkg.candidate.description is None): | ||
1079 | 270 | changes_buffer = self.textview_changes.get_buffer() | 491 | changes_buffer = self.textview_changes.get_buffer() |
1080 | 271 | changes_buffer.set_text("") | 492 | changes_buffer.set_text("") |
1081 | 272 | desc_buffer = self.textview_descr.get_buffer() | 493 | desc_buffer = self.textview_descr.get_buffer() |
1082 | 273 | desc_buffer.set_text("") | 494 | desc_buffer.set_text("") |
1083 | 274 | self.notebook_details.set_sensitive(False) | 495 | self.notebook_details.set_sensitive(False) |
1084 | 275 | return | 496 | return |
1086 | 276 | long_desc = pkg.candidate.description | 497 | long_desc = item.pkg.candidate.description |
1087 | 277 | self.notebook_details.set_sensitive(True) | 498 | self.notebook_details.set_sensitive(True) |
1088 | 278 | # do some regular expression magic on the description | 499 | # do some regular expression magic on the description |
1089 | 279 | # Add a newline before each bullet | 500 | # Add a newline before each bullet |
1090 | @@ -290,7 +511,7 @@ | |||
1091 | 290 | desc_buffer.set_text(long_desc) | 511 | desc_buffer.set_text(long_desc) |
1092 | 291 | 512 | ||
1093 | 292 | # now do the changelog | 513 | # now do the changelog |
1095 | 293 | name = model.get_value(iter, LIST_NAME) | 514 | name = item.pkg.name |
1096 | 294 | if name is None: | 515 | if name is None: |
1097 | 295 | return | 516 | return |
1098 | 296 | 517 | ||
1099 | @@ -334,11 +555,10 @@ | |||
1100 | 334 | button.disconnect(id) | 555 | button.disconnect(id) |
1101 | 335 | # check if we still are in the right pkg (the download may have taken | 556 | # check if we still are in the right pkg (the download may have taken |
1102 | 336 | # some time and the user may have clicked on a new pkg) | 557 | # some time and the user may have clicked on a new pkg) |
1105 | 337 | path = widget.get_cursor()[0] | 558 | now_path = widget.get_cursor()[0] |
1106 | 338 | if path is None: | 559 | if now_path is None: |
1107 | 339 | return | 560 | return |
1110 | 340 | now_name = widget.get_model()[path][LIST_NAME] | 561 | if path != now_path: |
1109 | 341 | if name != now_name: | ||
1111 | 342 | return | 562 | return |
1112 | 343 | # display NEWS.Debian first, then the changelog | 563 | # display NEWS.Debian first, then the changelog |
1113 | 344 | changes = "" | 564 | changes = "" |
1114 | @@ -350,7 +570,7 @@ | |||
1115 | 350 | if changes: | 570 | if changes: |
1116 | 351 | self.set_changes_buffer(changes_buffer, changes, name, srcpkg) | 571 | self.set_changes_buffer(changes_buffer, changes, name, srcpkg) |
1117 | 352 | 572 | ||
1119 | 353 | def show_context_menu(self, widget, event): | 573 | def on_treeview_button_press(self, widget, event): |
1120 | 354 | """ | 574 | """ |
1121 | 355 | Show a context menu if a right click was performed on an update entry | 575 | Show a context menu if a right click was performed on an update entry |
1122 | 356 | """ | 576 | """ |
1123 | @@ -361,10 +581,13 @@ | |||
1124 | 361 | self.menu = menu = Gtk.Menu() | 581 | self.menu = menu = Gtk.Menu() |
1125 | 362 | item_select_none = \ | 582 | item_select_none = \ |
1126 | 363 | Gtk.MenuItem.new_with_mnemonic(_("_Deselect All")) | 583 | Gtk.MenuItem.new_with_mnemonic(_("_Deselect All")) |
1128 | 364 | item_select_none.connect("activate", self.select_none_updgrades) | 584 | item_select_none.connect("activate", self.select_none_upgrades) |
1129 | 365 | menu.append(item_select_none) | 585 | menu.append(item_select_none) |
1130 | 586 | num_updates = self.cache.install_count | ||
1131 | 587 | if num_updates == 0: | ||
1132 | 588 | item_select_none.set_property("sensitive", False) | ||
1133 | 366 | item_select_all = Gtk.MenuItem.new_with_mnemonic(_("Select _All")) | 589 | item_select_all = Gtk.MenuItem.new_with_mnemonic(_("Select _All")) |
1135 | 367 | item_select_all.connect("activate", self.select_all_updgrades) | 590 | item_select_all.connect("activate", self.select_all_upgrades) |
1136 | 368 | menu.append(item_select_all) | 591 | menu.append(item_select_all) |
1137 | 369 | menu.show_all() | 592 | menu.show_all() |
1138 | 370 | menu.popup_for_device( | 593 | menu.popup_for_device( |
1139 | @@ -373,35 +596,36 @@ | |||
1140 | 373 | return True | 596 | return True |
1141 | 374 | 597 | ||
1142 | 375 | # we need this for select all/unselect all | 598 | # we need this for select all/unselect all |
1145 | 376 | def _toggle_origin_headers(self, new_selection_value): | 599 | def _toggle_group_headers(self, new_selection_value): |
1146 | 377 | """ small helper that will set/unset the origin headers | 600 | """ small helper that will set/unset the group headers |
1147 | 378 | """ | 601 | """ |
1148 | 379 | model = self.treeview_update.get_model() | 602 | model = self.treeview_update.get_model() |
1149 | 380 | for row in model: | 603 | for row in model: |
1152 | 381 | if not model.get_value(row.iter, LIST_PKG): | 604 | data = model.get_value(row.iter, LIST_UPDATE_DATA) |
1153 | 382 | model.set_value(row.iter, LIST_TOGGLE_CHECKED, | 605 | if data.groups is not None or data.group is not None: |
1154 | 606 | model.set_value(row.iter, LIST_TOGGLE_ACTIVE, | ||
1155 | 383 | new_selection_value) | 607 | new_selection_value) |
1156 | 384 | 608 | ||
1158 | 385 | def select_all_updgrades(self, widget): | 609 | def select_all_upgrades(self, widget): |
1159 | 386 | """ | 610 | """ |
1160 | 387 | Select all updates | 611 | Select all updates |
1161 | 388 | """ | 612 | """ |
1162 | 389 | self.setBusy(True) | 613 | self.setBusy(True) |
1163 | 390 | self.cache.saveDistUpgrade() | 614 | self.cache.saveDistUpgrade() |
1165 | 391 | self._toggle_origin_headers(True) | 615 | self._toggle_group_headers(True) |
1166 | 392 | self.treeview_update.queue_draw() | 616 | self.treeview_update.queue_draw() |
1168 | 393 | self.refresh_updates_count() | 617 | self.updates_changed() |
1169 | 394 | self.setBusy(False) | 618 | self.setBusy(False) |
1170 | 395 | 619 | ||
1172 | 396 | def select_none_updgrades(self, widget): | 620 | def select_none_upgrades(self, widget): |
1173 | 397 | """ | 621 | """ |
1174 | 398 | Select none updates | 622 | Select none updates |
1175 | 399 | """ | 623 | """ |
1176 | 400 | self.setBusy(True) | 624 | self.setBusy(True) |
1177 | 401 | self.cache.clear() | 625 | self.cache.clear() |
1179 | 402 | self._toggle_origin_headers(False) | 626 | self._toggle_group_headers(False) |
1180 | 403 | self.treeview_update.queue_draw() | 627 | self.treeview_update.queue_draw() |
1182 | 404 | self.refresh_updates_count() | 628 | self.updates_changed() |
1183 | 405 | self.setBusy(False) | 629 | self.setBusy(False) |
1184 | 406 | 630 | ||
1185 | 407 | def setBusy(self, flag): | 631 | def setBusy(self, flag): |
1186 | @@ -417,7 +641,21 @@ | |||
1187 | 417 | while Gtk.events_pending(): | 641 | while Gtk.events_pending(): |
1188 | 418 | Gtk.main_iteration() | 642 | Gtk.main_iteration() |
1189 | 419 | 643 | ||
1191 | 420 | def refresh_updates_count(self): | 644 | def _mark_selected_updates(self): |
1192 | 645 | def foreach_cb(model, path, iter, data): | ||
1193 | 646 | data = model.get_value(iter, LIST_UPDATE_DATA) | ||
1194 | 647 | active = False | ||
1195 | 648 | if data.item: | ||
1196 | 649 | active = data.item.pkg.marked_install or \ | ||
1197 | 650 | data.item.pkg.marked_upgrade | ||
1198 | 651 | elif data.group: | ||
1199 | 652 | active = data.group.packages_are_selected() | ||
1200 | 653 | elif data.groups: | ||
1201 | 654 | active = any([g.packages_are_selected() for g in data.groups]) | ||
1202 | 655 | model.set_value(iter, LIST_TOGGLE_ACTIVE, active) | ||
1203 | 656 | self.store.foreach(foreach_cb, None) | ||
1204 | 657 | |||
1205 | 658 | def _refresh_updates_count(self): | ||
1206 | 421 | self.button_install.set_sensitive(self.cache.install_count) | 659 | self.button_install.set_sensitive(self.cache.install_count) |
1207 | 422 | try: | 660 | try: |
1208 | 423 | inst_count = self.cache.install_count | 661 | inst_count = self.cache.install_count |
1209 | @@ -458,10 +696,14 @@ | |||
1210 | 458 | self.hbox_downsize.show() | 696 | self.hbox_downsize.show() |
1211 | 459 | self.vbox_alerts.show() | 697 | self.vbox_alerts.show() |
1212 | 460 | 698 | ||
1213 | 699 | def updates_changed(self): | ||
1214 | 700 | self._mark_selected_updates() | ||
1215 | 701 | self._refresh_updates_count() | ||
1216 | 702 | |||
1217 | 461 | def update_count(self): | 703 | def update_count(self): |
1218 | 462 | """activate or disable widgets and show dialog texts correspoding to | 704 | """activate or disable widgets and show dialog texts correspoding to |
1219 | 463 | the number of available updates""" | 705 | the number of available updates""" |
1221 | 464 | self.refresh_updates_count() | 706 | self.updates_changed() |
1222 | 465 | 707 | ||
1223 | 466 | text_header = None | 708 | text_header = None |
1224 | 467 | text_desc = None | 709 | text_desc = None |
1225 | @@ -486,8 +728,6 @@ | |||
1226 | 486 | self.notebook_details.set_sensitive(True) | 728 | self.notebook_details.set_sensitive(True) |
1227 | 487 | self.treeview_update.set_sensitive(True) | 729 | self.treeview_update.set_sensitive(True) |
1228 | 488 | self.button_install.grab_default() | 730 | self.button_install.grab_default() |
1229 | 489 | self.treeview_update.set_cursor(Gtk.TreePath.new_from_string("1"), | ||
1230 | 490 | None, False) | ||
1231 | 491 | self.label_header.set_label(text_header) | 731 | self.label_header.set_label(text_header) |
1232 | 492 | 732 | ||
1233 | 493 | if text_desc is not None: | 733 | if text_desc is not None: |
1234 | @@ -573,14 +813,14 @@ | |||
1235 | 573 | # can deal with dialup connections properly | 813 | # can deal with dialup connections properly |
1236 | 574 | if state in NetworkManagerHelper.NM_STATE_CONNECTING_LIST: | 814 | if state in NetworkManagerHelper.NM_STATE_CONNECTING_LIST: |
1237 | 575 | self.label_offline.set_text(_("Connecting...")) | 815 | self.label_offline.set_text(_("Connecting...")) |
1239 | 576 | self.refresh_updates_count() | 816 | self.updates_changed() |
1240 | 577 | self.hbox_offline.show() | 817 | self.hbox_offline.show() |
1241 | 578 | self.vbox_alerts.show() | 818 | self.vbox_alerts.show() |
1242 | 579 | self.connected = False | 819 | self.connected = False |
1243 | 580 | # in doubt (STATE_UNKNOWN), assume connected | 820 | # in doubt (STATE_UNKNOWN), assume connected |
1244 | 581 | elif (state in NetworkManagerHelper.NM_STATE_CONNECTED_LIST or | 821 | elif (state in NetworkManagerHelper.NM_STATE_CONNECTED_LIST or |
1245 | 582 | state == NetworkManagerHelper.NM_STATE_UNKNOWN): | 822 | state == NetworkManagerHelper.NM_STATE_UNKNOWN): |
1247 | 583 | self.refresh_updates_count() | 823 | self.updates_changed() |
1248 | 584 | self.hbox_offline.hide() | 824 | self.hbox_offline.hide() |
1249 | 585 | self.connected = True | 825 | self.connected = True |
1250 | 586 | # trigger re-showing the current app to get changelog info (if | 826 | # trigger re-showing the current app to get changelog info (if |
1251 | @@ -590,7 +830,7 @@ | |||
1252 | 590 | self.connected = False | 830 | self.connected = False |
1253 | 591 | self.label_offline.set_text(_("You may not be able to check for " | 831 | self.label_offline.set_text(_("You may not be able to check for " |
1254 | 592 | "updates or download new updates.")) | 832 | "updates or download new updates.")) |
1256 | 593 | self.refresh_updates_count() | 833 | self.updates_changed() |
1257 | 594 | self.hbox_offline.show() | 834 | self.hbox_offline.show() |
1258 | 595 | self.vbox_alerts.show() | 835 | self.vbox_alerts.show() |
1259 | 596 | 836 | ||
1260 | @@ -613,63 +853,37 @@ | |||
1261 | 613 | self.hbox_on_3g.hide() | 853 | self.hbox_on_3g.hide() |
1262 | 614 | self.hbox_roaming.hide() | 854 | self.hbox_roaming.hide() |
1263 | 615 | 855 | ||
1293 | 616 | def row_activated(self, treeview, path, column): | 856 | def on_update_toggled(self, renderer, path): |
1265 | 617 | iter = self.store.get_iter(path) | ||
1266 | 618 | |||
1267 | 619 | pkg = self.store.get_value(iter, LIST_PKG) | ||
1268 | 620 | origin = self.store.get_value(iter, LIST_ORIGIN) | ||
1269 | 621 | if pkg is not None: | ||
1270 | 622 | return | ||
1271 | 623 | self.toggle_from_origin(pkg, origin, True) | ||
1272 | 624 | |||
1273 | 625 | def toggle_from_origin(self, pkg, origin, select_all=True): | ||
1274 | 626 | self.setBusy(True) | ||
1275 | 627 | actiongroup = apt_pkg.ActionGroup(self.cache._depcache) | ||
1276 | 628 | for pkg in self.list.pkgs[origin]: | ||
1277 | 629 | if pkg.marked_install or pkg.marked_upgrade: | ||
1278 | 630 | #print("marking keep: ", pkg.name) | ||
1279 | 631 | pkg.mark_keep() | ||
1280 | 632 | elif not (pkg.name in self.list.held_back): | ||
1281 | 633 | #print("marking install: ", pkg.name) | ||
1282 | 634 | pkg.mark_install(auto_fix=False, auto_inst=False) | ||
1283 | 635 | # check if we left breakage | ||
1284 | 636 | if self.cache._depcache.broken_count: | ||
1285 | 637 | Fix = apt_pkg.ProblemResolver(self.cache._depcache) | ||
1286 | 638 | Fix.resolve_by_keep() | ||
1287 | 639 | self.refresh_updates_count() | ||
1288 | 640 | self.treeview_update.queue_draw() | ||
1289 | 641 | del actiongroup | ||
1290 | 642 | self.setBusy(False) | ||
1291 | 643 | |||
1292 | 644 | def toggled(self, renderer, path): | ||
1294 | 645 | """ a toggle button in the listview was toggled """ | 857 | """ a toggle button in the listview was toggled """ |
1295 | 646 | iter = self.store.get_iter(path) | 858 | iter = self.store.get_iter(path) |
1298 | 647 | pkg = self.store.get_value(iter, LIST_PKG) | 859 | data = self.store.get_value(iter, LIST_UPDATE_DATA) |
1297 | 648 | origin = self.store.get_value(iter, LIST_ORIGIN) | ||
1299 | 649 | # make sure that we don't allow to toggle deactivated updates | 860 | # make sure that we don't allow to toggle deactivated updates |
1300 | 650 | # this is needed for the call by the row activation callback | 861 | # this is needed for the call by the row activation callback |
1305 | 651 | if pkg is None: | 862 | if data.groups or data.group: |
1306 | 652 | toggled_value = not self.store.get_value(iter, LIST_TOGGLE_CHECKED) | 863 | if data.groups: |
1307 | 653 | self.toggle_from_origin(pkg, origin, toggled_value) | 864 | self.toggle_from_groups(data.groups) |
1308 | 654 | self.store.set_value(iter, LIST_TOGGLE_CHECKED, toggled_value) | 865 | elif data.group: |
1309 | 866 | self.toggle_from_groups([data.group]) | ||
1310 | 867 | self.updates_changed() | ||
1311 | 655 | self.treeview_update.queue_draw() | 868 | self.treeview_update.queue_draw() |
1312 | 656 | return | 869 | return |
1314 | 657 | if pkg is None or pkg.name in self.list.held_back: | 870 | if (data.item is None or data.item.pkg is None or |
1315 | 871 | data.item.pkg.name in self.list.held_back): | ||
1316 | 658 | return False | 872 | return False |
1317 | 659 | self.setBusy(True) | 873 | self.setBusy(True) |
1318 | 660 | # update the cache | 874 | # update the cache |
1321 | 661 | if pkg.marked_install or pkg.marked_upgrade: | 875 | if data.item.pkg.marked_install or data.item.pkg.marked_upgrade: |
1322 | 662 | pkg.mark_keep() | 876 | data.item.pkg.mark_keep() |
1323 | 663 | if self.cache._depcache.broken_count: | 877 | if self.cache._depcache.broken_count: |
1324 | 664 | Fix = apt_pkg.ProblemResolver(self.cache._depcache) | 878 | Fix = apt_pkg.ProblemResolver(self.cache._depcache) |
1325 | 665 | Fix.resolve_by_keep() | 879 | Fix.resolve_by_keep() |
1326 | 666 | else: | 880 | else: |
1327 | 667 | try: | 881 | try: |
1329 | 668 | pkg.mark_install() | 882 | data.item.pkg.mark_install() |
1330 | 669 | except SystemError: | 883 | except SystemError: |
1331 | 670 | pass | 884 | pass |
1332 | 885 | self.updates_changed() | ||
1333 | 671 | self.treeview_update.queue_draw() | 886 | self.treeview_update.queue_draw() |
1334 | 672 | self.refresh_updates_count() | ||
1335 | 673 | self.setBusy(False) | 887 | self.setBusy(False) |
1336 | 674 | 888 | ||
1337 | 675 | def on_treeview_update_row_activated(self, treeview, path, column, *args): | 889 | def on_treeview_update_row_activated(self, treeview, path, column, *args): |
1338 | @@ -677,7 +891,29 @@ | |||
1339 | 677 | If an update row was activated (by pressing space), toggle the | 891 | If an update row was activated (by pressing space), toggle the |
1340 | 678 | install check box | 892 | install check box |
1341 | 679 | """ | 893 | """ |
1343 | 680 | self.toggled(None, path) | 894 | self.on_update_toggled(None, path) |
1344 | 895 | |||
1345 | 896 | def toggle_from_groups(self, groups): | ||
1346 | 897 | self.setBusy(True) | ||
1347 | 898 | actiongroup = apt_pkg.ActionGroup(self.cache._depcache) | ||
1348 | 899 | |||
1349 | 900 | # Deselect all updates if any in group are selected | ||
1350 | 901 | keep_packages = any([g.packages_are_selected() for g in groups]) | ||
1351 | 902 | for group in groups: | ||
1352 | 903 | for item in group.items: | ||
1353 | 904 | if keep_packages: | ||
1354 | 905 | item.pkg.mark_keep() | ||
1355 | 906 | elif not item.pkg.name in self.list.held_back: | ||
1356 | 907 | item.pkg.mark_install(auto_fix=False, auto_inst=False) | ||
1357 | 908 | |||
1358 | 909 | # check if we left breakage | ||
1359 | 910 | if self.cache._depcache.broken_count: | ||
1360 | 911 | Fix = apt_pkg.ProblemResolver(self.cache._depcache) | ||
1361 | 912 | Fix.resolve_by_keep() | ||
1362 | 913 | self.updates_changed() | ||
1363 | 914 | self.treeview_update.queue_draw() | ||
1364 | 915 | del actiongroup | ||
1365 | 916 | self.setBusy(False) | ||
1366 | 681 | 917 | ||
1367 | 682 | def save_state(self): | 918 | def save_state(self): |
1368 | 683 | """ save the state (window-size for now) """ | 919 | """ save the state (window-size for now) """ |
1369 | @@ -693,9 +929,58 @@ | |||
1370 | 693 | expanded = self.expander_details.get_expanded() | 929 | expanded = self.expander_details.get_expanded() |
1371 | 694 | self.window_main.set_resizable(expanded) | 930 | self.window_main.set_resizable(expanded) |
1372 | 695 | if w > 0 and h > 0 and expanded: | 931 | if w > 0 and h > 0 and expanded: |
1374 | 696 | self.window_main.resize(w, h) | 932 | # There is a race here. If we immediately resize, it often doesn't |
1375 | 933 | # take. Using idle_add helps, but we *still* occasionally don't | ||
1376 | 934 | # restore the size correctly. Help needed to track this down! | ||
1377 | 935 | GLib.idle_add(lambda: self.window_main.resize(w, h)) | ||
1378 | 697 | return False | 936 | return False |
1379 | 698 | 937 | ||
1380 | 938 | def _add_header(self, name, groups): | ||
1381 | 939 | total_size = 0 | ||
1382 | 940 | for group in groups: | ||
1383 | 941 | total_size = total_size + group.get_total_size() | ||
1384 | 942 | header_row = [ | ||
1385 | 943 | name, | ||
1386 | 944 | UpdateData(groups, None, None), | ||
1387 | 945 | humanize_size(total_size), | ||
1388 | 946 | True | ||
1389 | 947 | ] | ||
1390 | 948 | return self.store.append(None, header_row) | ||
1391 | 949 | |||
1392 | 950 | def _add_groups(self, groups): | ||
1393 | 951 | # Each row contains: | ||
1394 | 952 | # row label (for screen reader), | ||
1395 | 953 | # update data tuple (is_toplevel, group object, package object), | ||
1396 | 954 | # update size, | ||
1397 | 955 | # update selection state | ||
1398 | 956 | for group in groups: | ||
1399 | 957 | if not group.items: | ||
1400 | 958 | continue | ||
1401 | 959 | |||
1402 | 960 | group_is_item = None | ||
1403 | 961 | if not isinstance(group, UpdateSystemGroup) and \ | ||
1404 | 962 | len(group.items) == 1: | ||
1405 | 963 | group_is_item = group.items[0] | ||
1406 | 964 | |||
1407 | 965 | group_row = [ | ||
1408 | 966 | group.name, | ||
1409 | 967 | UpdateData(None, group, group_is_item), | ||
1410 | 968 | humanize_size(group.get_total_size()), | ||
1411 | 969 | True | ||
1412 | 970 | ] | ||
1413 | 971 | group_iter = self.store.append(None, group_row) | ||
1414 | 972 | |||
1415 | 973 | if group_is_item: | ||
1416 | 974 | continue | ||
1417 | 975 | for item in group.items: | ||
1418 | 976 | item_row = [ | ||
1419 | 977 | item.name, | ||
1420 | 978 | UpdateData(None, None, item), | ||
1421 | 979 | humanize_size(getattr(item.pkg.candidate, "size", 0)), | ||
1422 | 980 | True | ||
1423 | 981 | ] | ||
1424 | 982 | self.store.append(group_iter, item_row) | ||
1425 | 983 | |||
1426 | 699 | def fillstore(self): | 984 | def fillstore(self): |
1427 | 700 | # use the watch cursor | 985 | # use the watch cursor |
1428 | 701 | self.setBusy(True) | 986 | self.setBusy(True) |
1429 | @@ -706,46 +991,22 @@ | |||
1430 | 706 | self.dl_size = 0 | 991 | self.dl_size = 0 |
1431 | 707 | 992 | ||
1432 | 708 | self.scrolledwindow_update.show() | 993 | self.scrolledwindow_update.show() |
1467 | 709 | origin_list = sorted( | 994 | |
1468 | 710 | self.list.pkgs, key=operator.attrgetter("importance"), | 995 | # add security and update groups to self.store |
1469 | 711 | reverse=True) | 996 | if self.list.security_groups: |
1470 | 712 | for origin in origin_list: | 997 | self._add_header(_("Security updates"), self.list.security_groups) |
1471 | 713 | self.store.append(['<b><big>%s</big></b>' % origin.description, | 998 | self._add_groups(self.list.security_groups) |
1472 | 714 | origin.description, None, origin, True]) | 999 | if self.list.security_groups and self.list.update_groups: |
1473 | 715 | for pkg in self.list.pkgs[origin]: | 1000 | self._add_header(_("Other updates"), self.list.update_groups) |
1474 | 716 | name = xml.sax.saxutils.escape(pkg.name) | 1001 | if self.list.update_groups: |
1475 | 717 | if not pkg.is_installed: | 1002 | self._add_groups(self.list.update_groups) |
1476 | 718 | name += _(" (New install)") | 1003 | |
1443 | 719 | summary = xml.sax.saxutils.escape(getattr(pkg.candidate, | ||
1444 | 720 | "summary", "")) | ||
1445 | 721 | if self.summary_before_name: | ||
1446 | 722 | contents = "%s\n<small>%s</small>" % (summary, name) | ||
1447 | 723 | else: | ||
1448 | 724 | contents = "<b>%s</b>\n<small>%s</small>" % (name, summary) | ||
1449 | 725 | #TRANSLATORS: the b stands for Bytes | ||
1450 | 726 | size = _("(Size: %s)") % humanize_size(getattr(pkg.candidate, | ||
1451 | 727 | "size", 0)) | ||
1452 | 728 | installed_version = getattr(pkg.installed, "version", None) | ||
1453 | 729 | candidate_version = getattr(pkg.candidate, "version", None) | ||
1454 | 730 | if installed_version is not None: | ||
1455 | 731 | version = _("From version %(old_version)s " | ||
1456 | 732 | "to %(new_version)s") % { | ||
1457 | 733 | "old_version": installed_version, | ||
1458 | 734 | "new_version": candidate_version} | ||
1459 | 735 | else: | ||
1460 | 736 | version = _("Version %s") % candidate_version | ||
1461 | 737 | if self.show_versions: | ||
1462 | 738 | contents = "%s\n<small>%s %s</small>" % (contents, version, | ||
1463 | 739 | size) | ||
1464 | 740 | else: | ||
1465 | 741 | contents = "%s <small>%s</small>" % (contents, size) | ||
1466 | 742 | self.store.append([contents, pkg.name, pkg, None, True]) | ||
1477 | 743 | self.treeview_update.set_model(self.store) | 1004 | self.treeview_update.set_model(self.store) |
1478 | 744 | self.update_count() | 1005 | self.update_count() |
1479 | 745 | self.setBusy(False) | 1006 | self.setBusy(False) |
1480 | 746 | while Gtk.events_pending(): | 1007 | while Gtk.events_pending(): |
1481 | 747 | Gtk.main_iteration() | 1008 | Gtk.main_iteration() |
1483 | 748 | self.refresh_updates_count() | 1009 | self.updates_changed() |
1484 | 749 | return False | 1010 | return False |
1485 | 750 | 1011 | ||
1486 | 751 | def main(self): | 1012 | def main(self): |
1487 | 752 | 1013 | ||
1488 | === modified file 'data/com.ubuntu.update-manager.gschema.xml.in' | |||
1489 | --- data/com.ubuntu.update-manager.gschema.xml.in 2012-05-31 22:28:41 +0000 | |||
1490 | +++ data/com.ubuntu.update-manager.gschema.xml.in 2013-01-23 15:48:20 +0000 | |||
1491 | @@ -6,12 +6,12 @@ | |||
1492 | 6 | <description>Stores the state of the expander that contains the list of changes and the description</description> | 6 | <description>Stores the state of the expander that contains the list of changes and the description</description> |
1493 | 7 | </key> | 7 | </key> |
1494 | 8 | <key name="window-width" type="i"> | 8 | <key name="window-width" type="i"> |
1496 | 9 | <default>0</default> | 9 | <default>1</default> |
1497 | 10 | <summary>The window width</summary> | 10 | <summary>The window width</summary> |
1498 | 11 | <description>Stores the width of the update-manager dialog</description> | 11 | <description>Stores the width of the update-manager dialog</description> |
1499 | 12 | </key> | 12 | </key> |
1500 | 13 | <key name="window-height" type="i"> | 13 | <key name="window-height" type="i"> |
1502 | 14 | <default>0</default> | 14 | <default>400</default> |
1503 | 15 | <summary>The window height</summary> | 15 | <summary>The window height</summary> |
1504 | 16 | <description>Stores the height of the update-manager dialog</description> | 16 | <description>Stores the height of the update-manager dialog</description> |
1505 | 17 | </key> | 17 | </key> |
1506 | 18 | 18 | ||
1507 | === modified file 'data/gtkbuilder/UpdateManager.ui' | |||
1508 | --- data/gtkbuilder/UpdateManager.ui 2012-12-14 22:11:25 +0000 | |||
1509 | +++ data/gtkbuilder/UpdateManager.ui 2013-01-23 15:48:20 +0000 | |||
1510 | @@ -112,11 +112,13 @@ | |||
1511 | 112 | <property name="visible">True</property> | 112 | <property name="visible">True</property> |
1512 | 113 | <property name="can_focus">True</property> | 113 | <property name="can_focus">True</property> |
1513 | 114 | <property name="shadow_type">in</property> | 114 | <property name="shadow_type">in</property> |
1514 | 115 | <property name="min_content_height">100</property> | ||
1515 | 115 | <child> | 116 | <child> |
1516 | 116 | <object class="GtkTreeView" id="treeview_update"> | 117 | <object class="GtkTreeView" id="treeview_update"> |
1517 | 117 | <property name="visible">True</property> | 118 | <property name="visible">True</property> |
1518 | 118 | <property name="can_focus">True</property> | 119 | <property name="can_focus">True</property> |
1519 | 119 | <property name="headers_visible">False</property> | 120 | <property name="headers_visible">False</property> |
1520 | 121 | <property name="headers_clickable">False</property> | ||
1521 | 120 | <property name="rules_hint">True</property> | 122 | <property name="rules_hint">True</property> |
1522 | 121 | <child internal-child="accessible"> | 123 | <child internal-child="accessible"> |
1523 | 122 | <object class="AtkObject" id="treeview_update-atkobject"> | 124 | <object class="AtkObject" id="treeview_update-atkobject"> |
1524 | @@ -470,11 +472,9 @@ | |||
1525 | 470 | <child> | 472 | <child> |
1526 | 471 | <object class="GtkButton" id="button_settings"> | 473 | <object class="GtkButton" id="button_settings"> |
1527 | 472 | <property name="label" translatable="yes">_Settings...</property> | 474 | <property name="label" translatable="yes">_Settings...</property> |
1528 | 473 | <property name="use_action_appearance">False</property> | ||
1529 | 474 | <property name="visible">True</property> | 475 | <property name="visible">True</property> |
1530 | 475 | <property name="can_focus">True</property> | 476 | <property name="can_focus">True</property> |
1531 | 476 | <property name="receives_default">True</property> | 477 | <property name="receives_default">True</property> |
1532 | 477 | <property name="use_action_appearance">False</property> | ||
1533 | 478 | <property name="use_underline">True</property> | 478 | <property name="use_underline">True</property> |
1534 | 479 | <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/> | 479 | <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/> |
1535 | 480 | </object> | 480 | </object> |
1536 | @@ -488,15 +488,13 @@ | |||
1537 | 488 | <child> | 488 | <child> |
1538 | 489 | <object class="GtkButton" id="button_close"> | 489 | <object class="GtkButton" id="button_close"> |
1539 | 490 | <property name="label">gtk-cancel</property> | 490 | <property name="label">gtk-cancel</property> |
1540 | 491 | <property name="use_action_appearance">False</property> | ||
1541 | 492 | <property name="visible">True</property> | 491 | <property name="visible">True</property> |
1542 | 493 | <property name="can_focus">True</property> | 492 | <property name="can_focus">True</property> |
1543 | 494 | <property name="can_default">True</property> | 493 | <property name="can_default">True</property> |
1544 | 495 | <property name="receives_default">True</property> | 494 | <property name="receives_default">True</property> |
1545 | 496 | <property name="use_action_appearance">False</property> | ||
1546 | 497 | <property name="use_stock">True</property> | 495 | <property name="use_stock">True</property> |
1547 | 496 | <accelerator key="W" signal="clicked" modifiers="GDK_CONTROL_MASK"/> | ||
1548 | 498 | <accelerator key="Q" signal="clicked" modifiers="GDK_CONTROL_MASK"/> | 497 | <accelerator key="Q" signal="clicked" modifiers="GDK_CONTROL_MASK"/> |
1549 | 499 | <accelerator key="W" signal="clicked" modifiers="GDK_CONTROL_MASK"/> | ||
1550 | 500 | </object> | 498 | </object> |
1551 | 501 | <packing> | 499 | <packing> |
1552 | 502 | <property name="expand">False</property> | 500 | <property name="expand">False</property> |
1553 | @@ -507,13 +505,11 @@ | |||
1554 | 507 | <child> | 505 | <child> |
1555 | 508 | <object class="GtkButton" id="button_install"> | 506 | <object class="GtkButton" id="button_install"> |
1556 | 509 | <property name="label" translatable="yes">_Install Now</property> | 507 | <property name="label" translatable="yes">_Install Now</property> |
1557 | 510 | <property name="use_action_appearance">False</property> | ||
1558 | 511 | <property name="visible">True</property> | 508 | <property name="visible">True</property> |
1559 | 512 | <property name="can_focus">True</property> | 509 | <property name="can_focus">True</property> |
1560 | 513 | <property name="can_default">True</property> | 510 | <property name="can_default">True</property> |
1561 | 514 | <property name="has_default">True</property> | 511 | <property name="has_default">True</property> |
1562 | 515 | <property name="receives_default">True</property> | 512 | <property name="receives_default">True</property> |
1563 | 516 | <property name="use_action_appearance">False</property> | ||
1564 | 517 | <property name="image">image-apply</property> | 513 | <property name="image">image-apply</property> |
1565 | 518 | <property name="use_underline">True</property> | 514 | <property name="use_underline">True</property> |
1566 | 519 | <signal name="clicked" handler="on_button_install_clicked" swapped="no"/> | 515 | <signal name="clicked" handler="on_button_install_clicked" swapped="no"/> |
1567 | 520 | 516 | ||
1568 | === added directory 'tests/aptroot-grouping-test' | |||
1569 | === added directory 'tests/aptroot-grouping-test/etc' | |||
1570 | === added directory 'tests/aptroot-grouping-test/etc/apt' | |||
1571 | === added file 'tests/aptroot-grouping-test/etc/apt/sources.list' | |||
1572 | --- tests/aptroot-grouping-test/etc/apt/sources.list 1970-01-01 00:00:00 +0000 | |||
1573 | +++ tests/aptroot-grouping-test/etc/apt/sources.list 2013-01-23 15:48:20 +0000 | |||
1574 | @@ -0,0 +1,2 @@ | |||
1575 | 1 | deb http://archive.ubuntu.com/ubuntu lucid main | ||
1576 | 2 | deb http://archive.ubuntu.com/ubuntu lucid-security main | ||
1577 | 0 | 3 | ||
1578 | === added symlink 'tests/aptroot-grouping-test/etc/apt/trusted.gpg' | |||
1579 | === target is u'/etc/apt/trusted.gpg' | |||
1580 | === added directory 'tests/aptroot-grouping-test/var' | |||
1581 | === added directory 'tests/aptroot-grouping-test/var/cache' | |||
1582 | === added directory 'tests/aptroot-grouping-test/var/lib' | |||
1583 | === added directory 'tests/aptroot-grouping-test/var/lib/apt' | |||
1584 | === added directory 'tests/aptroot-grouping-test/var/lib/apt/lists' | |||
1585 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release' | |||
1586 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release 1970-01-01 00:00:00 +0000 | |||
1587 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release 2013-01-23 15:48:20 +0000 | |||
1588 | @@ -0,0 +1,9 @@ | |||
1589 | 1 | Origin: Ubuntu | ||
1590 | 2 | Label: Ubuntu | ||
1591 | 3 | Suite: lucid-security | ||
1592 | 4 | Version: 10.04 | ||
1593 | 5 | Codename: lucid | ||
1594 | 6 | Date: Thu, 29 Apr 2010 17:24:55 UTC | ||
1595 | 7 | Architectures: amd64 armel i386 ia64 powerpc sparc | ||
1596 | 8 | Components: main restricted universe multiverse | ||
1597 | 9 | Description: Ubuntu Lucid 10.04 | ||
1598 | 0 | 10 | ||
1599 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release.gpg' | |||
1600 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release.gpg 1970-01-01 00:00:00 +0000 | |||
1601 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_Release.gpg 2013-01-23 15:48:20 +0000 | |||
1602 | @@ -0,0 +1,7 @@ | |||
1603 | 1 | -----BEGIN PGP SIGNATURE----- | ||
1604 | 2 | Version: GnuPG v1.4.6 (GNU/Linux) | ||
1605 | 3 | |||
1606 | 4 | iD8DBQBL2cDzQJdur0N9BbURAmk2AJ9ungOjKn0ektAH87KhRIHht+1cDQCfck7P | ||
1607 | 5 | ZoIb2P0v2PEqa4Az8KnIIW4= | ||
1608 | 6 | =b/mY | ||
1609 | 7 | -----END PGP SIGNATURE----- | ||
1610 | 0 | 8 | ||
1611 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_main_binary-amd64_Packages' | |||
1612 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_main_binary-amd64_Packages 1970-01-01 00:00:00 +0000 | |||
1613 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid-security_main_binary-amd64_Packages 2013-01-23 15:48:20 +0000 | |||
1614 | @@ -0,0 +1,10 @@ | |||
1615 | 1 | Package: base-pkg | ||
1616 | 2 | Priority: optional | ||
1617 | 3 | Section: admin | ||
1618 | 4 | Installed-Size: 1 | ||
1619 | 5 | Maintainer: Foo <foo@bar.com> | ||
1620 | 6 | Architecture: all | ||
1621 | 7 | Version: 2 | ||
1622 | 8 | Size: 1 | ||
1623 | 9 | Description: a base package | ||
1624 | 10 | Origin: Ubuntu | ||
1625 | 0 | 11 | ||
1626 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release' | |||
1627 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release 1970-01-01 00:00:00 +0000 | |||
1628 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release 2013-01-23 15:48:20 +0000 | |||
1629 | @@ -0,0 +1,9 @@ | |||
1630 | 1 | Origin: Ubuntu | ||
1631 | 2 | Label: Ubuntu | ||
1632 | 3 | Suite: lucid | ||
1633 | 4 | Version: 10.04 | ||
1634 | 5 | Codename: lucid | ||
1635 | 6 | Date: Thu, 29 Apr 2010 17:24:55 UTC | ||
1636 | 7 | Architectures: amd64 armel i386 ia64 powerpc sparc | ||
1637 | 8 | Components: main restricted universe multiverse | ||
1638 | 9 | Description: Ubuntu Lucid 10.04 | ||
1639 | 0 | 10 | ||
1640 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release.gpg' | |||
1641 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release.gpg 1970-01-01 00:00:00 +0000 | |||
1642 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_Release.gpg 2013-01-23 15:48:20 +0000 | |||
1643 | @@ -0,0 +1,7 @@ | |||
1644 | 1 | -----BEGIN PGP SIGNATURE----- | ||
1645 | 2 | Version: GnuPG v1.4.6 (GNU/Linux) | ||
1646 | 3 | |||
1647 | 4 | iD8DBQBL2cDzQJdur0N9BbURAmk2AJ9ungOjKn0ektAH87KhRIHht+1cDQCfck7P | ||
1648 | 5 | ZoIb2P0v2PEqa4Az8KnIIW4= | ||
1649 | 6 | =b/mY | ||
1650 | 7 | -----END PGP SIGNATURE----- | ||
1651 | 0 | 8 | ||
1652 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages' | |||
1653 | --- tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages 1970-01-01 00:00:00 +0000 | |||
1654 | +++ tests/aptroot-grouping-test/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages 2013-01-23 15:48:20 +0000 | |||
1655 | @@ -0,0 +1,80 @@ | |||
1656 | 1 | Package: installed-app | ||
1657 | 2 | Priority: optional | ||
1658 | 3 | Section: admin | ||
1659 | 4 | Installed-Size: 1 | ||
1660 | 5 | Maintainer: Foo <foo@bar.com> | ||
1661 | 6 | Architecture: all | ||
1662 | 7 | Version: 2 | ||
1663 | 8 | Size: 1 | ||
1664 | 9 | Depends: installed-pkg-multiple-deps | ||
1665 | 10 | Suggests: installed-pkg-single-dep | ||
1666 | 11 | Description: just an app | ||
1667 | 12 | Origin: Ubuntu | ||
1668 | 13 | |||
1669 | 14 | Package: installed-app-with-subitems | ||
1670 | 15 | Priority: optional | ||
1671 | 16 | Section: admin | ||
1672 | 17 | Installed-Size: 1 | ||
1673 | 18 | Maintainer: Foo <foo@bar.com> | ||
1674 | 19 | Architecture: all | ||
1675 | 20 | Version: 2 | ||
1676 | 21 | Size: 1 | ||
1677 | 22 | Recommends: intermediate, installed-pkg-multiple-deps | ||
1678 | 23 | Description: app with subitems | ||
1679 | 24 | Origin: Ubuntu | ||
1680 | 25 | |||
1681 | 26 | Package: intermediate | ||
1682 | 27 | Priority: optional | ||
1683 | 28 | Section: admin | ||
1684 | 29 | Installed-Size: 1 | ||
1685 | 30 | Maintainer: Foo <foo@bar.com> | ||
1686 | 31 | Architecture: all | ||
1687 | 32 | Version: 0 | ||
1688 | 33 | Size: 1 | ||
1689 | 34 | Description: intermediate pkg | ||
1690 | 35 | Origin: Ubuntu | ||
1691 | 36 | |||
1692 | 37 | Package: installed-pkg | ||
1693 | 38 | Priority: optional | ||
1694 | 39 | Section: admin | ||
1695 | 40 | Installed-Size: 1 | ||
1696 | 41 | Maintainer: Foo <foo@bar.com> | ||
1697 | 42 | Architecture: all | ||
1698 | 43 | Version: 2 | ||
1699 | 44 | Size: 1 | ||
1700 | 45 | Description: just a pkg | ||
1701 | 46 | Origin: Ubuntu | ||
1702 | 47 | |||
1703 | 48 | Package: installed-pkg-multiple-deps | ||
1704 | 49 | Priority: optional | ||
1705 | 50 | Section: admin | ||
1706 | 51 | Installed-Size: 1 | ||
1707 | 52 | Maintainer: Foo <foo@bar.com> | ||
1708 | 53 | Architecture: all | ||
1709 | 54 | Version: 2 | ||
1710 | 55 | Size: 1 | ||
1711 | 56 | Description: pkg with multiple deps | ||
1712 | 57 | Origin: Ubuntu | ||
1713 | 58 | |||
1714 | 59 | Package: installed-pkg-single-dep | ||
1715 | 60 | Priority: optional | ||
1716 | 61 | Section: admin | ||
1717 | 62 | Installed-Size: 1 | ||
1718 | 63 | Maintainer: Foo <foo@bar.com> | ||
1719 | 64 | Architecture: all | ||
1720 | 65 | Version: 2 | ||
1721 | 66 | Size: 1 | ||
1722 | 67 | Description: pkg with single dep | ||
1723 | 68 | Origin: Ubuntu | ||
1724 | 69 | |||
1725 | 70 | Package: ubuntu-minimal | ||
1726 | 71 | Priority: optional | ||
1727 | 72 | Section: admin | ||
1728 | 73 | Installed-Size: 1 | ||
1729 | 74 | Maintainer: Foo <foo@bar.com> | ||
1730 | 75 | Architecture: all | ||
1731 | 76 | Version: 2 | ||
1732 | 77 | Size: 1 | ||
1733 | 78 | Recommends: base-pkg | ||
1734 | 79 | Description: meta package | ||
1735 | 80 | Origin: Ubuntu | ||
1736 | 0 | 81 | ||
1737 | === added file 'tests/aptroot-grouping-test/var/lib/apt/lists/lock' | |||
1738 | === added directory 'tests/aptroot-grouping-test/var/lib/apt/lists/partial' | |||
1739 | === added directory 'tests/aptroot-grouping-test/var/lib/dpkg' | |||
1740 | === added file 'tests/aptroot-grouping-test/var/lib/dpkg/status' | |||
1741 | --- tests/aptroot-grouping-test/var/lib/dpkg/status 1970-01-01 00:00:00 +0000 | |||
1742 | +++ tests/aptroot-grouping-test/var/lib/dpkg/status 2013-01-23 15:48:20 +0000 | |||
1743 | @@ -0,0 +1,82 @@ | |||
1744 | 1 | Package: base-pkg | ||
1745 | 2 | Status: install ok installed | ||
1746 | 3 | Priority: optional | ||
1747 | 4 | Section: admin | ||
1748 | 5 | Installed-Size: 1 | ||
1749 | 6 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1750 | 7 | Architecture: all | ||
1751 | 8 | Version: 0.1 | ||
1752 | 9 | Description: a base package | ||
1753 | 10 | |||
1754 | 11 | Package: installed-app | ||
1755 | 12 | Status: install ok installed | ||
1756 | 13 | Priority: optional | ||
1757 | 14 | Section: admin | ||
1758 | 15 | Installed-Size: 1 | ||
1759 | 16 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1760 | 17 | Architecture: all | ||
1761 | 18 | Version: 0.1 | ||
1762 | 19 | Description: an installed app | ||
1763 | 20 | |||
1764 | 21 | Package: installed-app-with-subitems | ||
1765 | 22 | Status: install ok installed | ||
1766 | 23 | Priority: optional | ||
1767 | 24 | Section: admin | ||
1768 | 25 | Installed-Size: 1 | ||
1769 | 26 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1770 | 27 | Architecture: all | ||
1771 | 28 | Version: 0.1 | ||
1772 | 29 | Description: an installed app with subitems | ||
1773 | 30 | |||
1774 | 31 | Package: intermediate | ||
1775 | 32 | Status: install ok installed | ||
1776 | 33 | Priority: optional | ||
1777 | 34 | Section: admin | ||
1778 | 35 | Installed-Size: 1 | ||
1779 | 36 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1780 | 37 | Architecture: all | ||
1781 | 38 | Version: 0.1 | ||
1782 | 39 | Depends: installed-pkg-single-dep | ||
1783 | 40 | Description: an intermediate pkg | ||
1784 | 41 | |||
1785 | 42 | Package: installed-pkg | ||
1786 | 43 | Status: install ok installed | ||
1787 | 44 | Priority: optional | ||
1788 | 45 | Section: admin | ||
1789 | 46 | Installed-Size: 1 | ||
1790 | 47 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1791 | 48 | Architecture: all | ||
1792 | 49 | Version: 0.1 | ||
1793 | 50 | Description: an installed package | ||
1794 | 51 | |||
1795 | 52 | Package: not-updatable | ||
1796 | 53 | Status: install ok installed | ||
1797 | 54 | Priority: optional | ||
1798 | 55 | Section: admin | ||
1799 | 56 | Installed-Size: 1 | ||
1800 | 57 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1801 | 58 | Architecture: all | ||
1802 | 59 | Version: 0.1 | ||
1803 | 60 | Depends: installed-pkg-single-dep | ||
1804 | 61 | Description: extra pkg | ||
1805 | 62 | to confirm we only look at updatable pkgs when calculating groupings | ||
1806 | 63 | |||
1807 | 64 | Package: installed-pkg-multiple-deps | ||
1808 | 65 | Status: install ok installed | ||
1809 | 66 | Priority: optional | ||
1810 | 67 | Section: admin | ||
1811 | 68 | Installed-Size: 1 | ||
1812 | 69 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1813 | 70 | Architecture: all | ||
1814 | 71 | Version: 0.1 | ||
1815 | 72 | Description: an installed package that multiple apps depend on | ||
1816 | 73 | |||
1817 | 74 | Package: installed-pkg-single-dep | ||
1818 | 75 | Status: install ok installed | ||
1819 | 76 | Priority: optional | ||
1820 | 77 | Section: admin | ||
1821 | 78 | Installed-Size: 1 | ||
1822 | 79 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | ||
1823 | 80 | Architecture: all | ||
1824 | 81 | Version: 0.1 | ||
1825 | 82 | Description: an installed package that only one app depends on | ||
1826 | 0 | 83 | ||
1827 | === added directory 'tests/aptroot-grouping-test/var/lib/dpkg/updates' | |||
1828 | === modified file 'tests/test_update_list.py' | |||
1829 | --- tests/test_update_list.py 2013-01-16 12:45:38 +0000 | |||
1830 | +++ tests/test_update_list.py 2013-01-23 15:48:20 +0000 | |||
1831 | @@ -6,15 +6,16 @@ | |||
1832 | 6 | import apt | 6 | import apt |
1833 | 7 | import unittest | 7 | import unittest |
1834 | 8 | 8 | ||
1836 | 9 | from UpdateManager.Core.UpdateList import UpdateList | 9 | from UpdateManager.Core import UpdateList |
1837 | 10 | from UpdateManager.Core.MyCache import MyCache | 10 | from UpdateManager.Core.MyCache import MyCache |
1838 | 11 | 11 | ||
1840 | 12 | from mock import patch | 12 | from gi.repository import Gio |
1841 | 13 | from mock import patch, PropertyMock, MagicMock | ||
1842 | 13 | 14 | ||
1843 | 14 | CURDIR = os.path.dirname(os.path.abspath(__file__)) | 15 | CURDIR = os.path.dirname(os.path.abspath(__file__)) |
1844 | 15 | 16 | ||
1845 | 16 | 17 | ||
1847 | 17 | class UpdateListTestCase(unittest.TestCase): | 18 | class PhasedTestCase(unittest.TestCase): |
1848 | 18 | 19 | ||
1849 | 19 | def setUp(self): | 20 | def setUp(self): |
1850 | 20 | # mangle the arch | 21 | # mangle the arch |
1851 | @@ -30,11 +31,10 @@ | |||
1852 | 30 | self.cache._listsLock = 0 | 31 | self.cache._listsLock = 0 |
1853 | 31 | self.cache.update() | 32 | self.cache.update() |
1854 | 32 | self.cache.open() | 33 | self.cache.open() |
1856 | 33 | self.updates_list = UpdateList(parent=None) | 34 | self.updates_list = UpdateList.UpdateList(parent=None) |
1857 | 34 | 35 | ||
1858 | 35 | def assertUpdatesListLen(self, nr): | 36 | def assertUpdatesListLen(self, nr): |
1861 | 36 | origin = list(self.updates_list.pkgs.keys())[0] | 37 | self.assertEqual(self.updates_list.num_updates, nr) |
1860 | 37 | self.assertEqual(len(self.updates_list.pkgs[origin]), nr) | ||
1862 | 38 | 38 | ||
1863 | 39 | def test_phased_percentage_not_included(self): | 39 | def test_phased_percentage_not_included(self): |
1864 | 40 | """ Test that updates above the threshold are not included""" | 40 | """ Test that updates above the threshold are not included""" |
1865 | @@ -76,16 +76,96 @@ | |||
1866 | 76 | self.updates_list.update(self.cache) | 76 | self.updates_list.update(self.cache) |
1867 | 77 | self.assertUpdatesListLen(1) | 77 | self.assertUpdatesListLen(1) |
1868 | 78 | 78 | ||
1871 | 79 | @patch('UpdateManager.Core.UpdateList.OriginsImportance') | 79 | @patch('UpdateManager.Core.UpdateList.UpdateList._is_security_update') |
1872 | 80 | def test_phased_percentage_from_security(self, mock_origin_importance): | 80 | def test_phased_percentage_from_security(self, mock_security): |
1873 | 81 | """ Test that updates from the security node go in""" | 81 | """ Test that updates from the security node go in""" |
1874 | 82 | # pretend all updates come from security for the sake of this test | 82 | # pretend all updates come from security for the sake of this test |
1876 | 83 | mock_origin_importance.SECURITY = 0 | 83 | mock_security.return_value = True |
1877 | 84 | with patch.object(self.updates_list.random, "randint") as mock_randint: | 84 | with patch.object(self.updates_list.random, "randint") as mock_randint: |
1878 | 85 | mock_randint.return_value = 100 | 85 | mock_randint.return_value = 100 |
1879 | 86 | self.updates_list.update(self.cache) | 86 | self.updates_list.update(self.cache) |
1880 | 87 | self.assertUpdatesListLen(2) | 87 | self.assertUpdatesListLen(2) |
1881 | 88 | 88 | ||
1882 | 89 | class GroupingTestCase(unittest.TestCase): | ||
1883 | 90 | # installed_files does not respect aptroot, so we have to patch it | ||
1884 | 91 | @patch('apt.package.Package.installed_files', new_callable=PropertyMock) | ||
1885 | 92 | @patch('gi.repository.Gio.DesktopAppInfo.new_from_filename') | ||
1886 | 93 | def setUp(self, mock_desktop, mock_installed): | ||
1887 | 94 | # mangle the arch | ||
1888 | 95 | real_arch = apt.apt_pkg.config.find("APT::Architecture") | ||
1889 | 96 | apt.apt_pkg.config.set("APT::Architecture", "amd64") | ||
1890 | 97 | self.addCleanup( | ||
1891 | 98 | lambda: apt.apt_pkg.config.set("APT::Architecture", real_arch)) | ||
1892 | 99 | self.aptroot = os.path.join(CURDIR, | ||
1893 | 100 | "aptroot-grouping-test") | ||
1894 | 101 | self.cache = MyCache(apt.progress.base.OpProgress(), | ||
1895 | 102 | rootdir=self.aptroot) | ||
1896 | 103 | mock_installed.__get__ = self.fake_installed_files | ||
1897 | 104 | mock_desktop.side_effect = self.fake_desktop | ||
1898 | 105 | self.updates_list = UpdateList.UpdateList(parent=None, dist='lucid') | ||
1899 | 106 | self.updates_list.update(self.cache) | ||
1900 | 107 | |||
1901 | 108 | def fake_installed_files(self, mock_prop, pkg, pkg_class): | ||
1902 | 109 | if pkg.name == 'installed-app': | ||
1903 | 110 | return ['/usr/share/applications/installed-app.desktop'] | ||
1904 | 111 | elif pkg.name == 'installed-app-with-subitems': | ||
1905 | 112 | return ['/usr/share/applications/installed-app2.desktop'] | ||
1906 | 113 | else: | ||
1907 | 114 | return [] | ||
1908 | 115 | |||
1909 | 116 | def fake_desktop(self, path): | ||
1910 | 117 | # These can all be the same for our purposes | ||
1911 | 118 | app = MagicMock() | ||
1912 | 119 | app.get_filename.return_value = path | ||
1913 | 120 | app.get_display_name.return_value = 'App ' + os.path.basename(path) | ||
1914 | 121 | app.get_icon.return_value = Gio.ThemedIcon.new("package") | ||
1915 | 122 | return app | ||
1916 | 123 | |||
1917 | 124 | def test_app(self): | ||
1918 | 125 | self.assertGreater(len(self.updates_list.update_groups), 0) | ||
1919 | 126 | group = self.updates_list.update_groups[0] | ||
1920 | 127 | self.assertIsInstance(group, UpdateList.UpdateApplicationGroup) | ||
1921 | 128 | self.assertIsNotNone(group.core_item) | ||
1922 | 129 | self.assertEqual(group.core_item.pkg.name, 'installed-app') | ||
1923 | 130 | self.assertListEqual([x.pkg.name for x in group.items], | ||
1924 | 131 | ['installed-app']) | ||
1925 | 132 | |||
1926 | 133 | def test_app_with_subitems(self): | ||
1927 | 134 | self.assertGreater(len(self.updates_list.update_groups), 1) | ||
1928 | 135 | group = self.updates_list.update_groups[1] | ||
1929 | 136 | self.assertIsInstance(group, UpdateList.UpdateApplicationGroup) | ||
1930 | 137 | self.assertIsNotNone(group.core_item) | ||
1931 | 138 | self.assertEqual(group.core_item.pkg.name, | ||
1932 | 139 | 'installed-app-with-subitems') | ||
1933 | 140 | self.assertListEqual([x.pkg.name for x in group.items], | ||
1934 | 141 | ['installed-app-with-subitems', | ||
1935 | 142 | 'installed-pkg-single-dep']) | ||
1936 | 143 | |||
1937 | 144 | def test_pkg(self): | ||
1938 | 145 | self.assertGreater(len(self.updates_list.update_groups), 2) | ||
1939 | 146 | group = self.updates_list.update_groups[2] | ||
1940 | 147 | self.assertIsInstance(group, UpdateList.UpdatePackageGroup) | ||
1941 | 148 | self.assertIsNotNone(group.core_item) | ||
1942 | 149 | self.assertEqual(group.core_item.pkg.name, 'installed-pkg') | ||
1943 | 150 | self.assertListEqual([x.pkg.name for x in group.items], | ||
1944 | 151 | ['installed-pkg']) | ||
1945 | 152 | |||
1946 | 153 | def test_pkg_multiple_deps(self): | ||
1947 | 154 | self.assertEqual(len(self.updates_list.update_groups), 4) | ||
1948 | 155 | group = self.updates_list.update_groups[3] | ||
1949 | 156 | self.assertIsInstance(group, UpdateList.UpdatePackageGroup) | ||
1950 | 157 | self.assertIsNotNone(group.core_item) | ||
1951 | 158 | self.assertEqual(group.core_item.pkg.name, | ||
1952 | 159 | 'installed-pkg-multiple-deps') | ||
1953 | 160 | self.assertListEqual([x.pkg.name for x in group.items], | ||
1954 | 161 | ['installed-pkg-multiple-deps']) | ||
1955 | 162 | |||
1956 | 163 | def test_security(self): | ||
1957 | 164 | self.assertEqual(len(self.updates_list.security_groups), 1) | ||
1958 | 165 | group = self.updates_list.security_groups[0] | ||
1959 | 166 | self.assertIsInstance(group, UpdateList.UpdateSystemGroup) | ||
1960 | 167 | self.assertIsNone(group.core_item) | ||
1961 | 168 | self.assertListEqual([x.pkg.name for x in group.items], ['base-pkg']) | ||
1962 | 89 | 169 | ||
1963 | 90 | if __name__ == "__main__": | 170 | if __name__ == "__main__": |
1964 | 91 | unittest.main() | 171 | unittest.main() |
1965 | 92 | 172 | ||
1966 | === modified file 'tests/test_update_origin.py' | |||
1967 | --- tests/test_update_origin.py 2012-11-19 16:20:30 +0000 | |||
1968 | +++ tests/test_update_origin.py 2013-01-23 15:48:20 +0000 | |||
1969 | @@ -48,15 +48,11 @@ | |||
1970 | 48 | if l.archive == "lucid-security"]: | 48 | if l.archive == "lucid-security"]: |
1971 | 49 | test_pkgs.add(pkg.name) | 49 | test_pkgs.add(pkg.name) |
1972 | 50 | self.assertTrue(len(test_pkgs) > 0) | 50 | self.assertTrue(len(test_pkgs) > 0) |
1975 | 51 | ul = UpdateList(None) | 51 | ul = UpdateList(None, dist="lucid") |
1974 | 52 | matcher = ul.initMatcher("lucid") | ||
1976 | 53 | for pkgname in test_pkgs: | 52 | for pkgname in test_pkgs: |
1977 | 54 | pkg = self.cache[pkgname] | 53 | pkg = self.cache[pkgname] |
1983 | 55 | origin = self.cache.match_package_origin(pkg, matcher) | 54 | self.assertTrue(ul._is_security_update(pkg), |
1984 | 56 | self.assertEqual(self.cache.match_package_origin(pkg, matcher), | 55 | "pkg '%s' is not in lucid-security" % pkg.name) |
1980 | 57 | matcher[("lucid-security", "Ubuntu")], | ||
1981 | 58 | "pkg '%s' is not in lucid-security but in '%s' " | ||
1982 | 59 | "instead" % (pkg.name, origin.description)) | ||
1985 | 60 | 56 | ||
1986 | 61 | def testOriginMatcherWithVersionInUpdatesAndSecurity(self): | 57 | def testOriginMatcherWithVersionInUpdatesAndSecurity(self): |
1987 | 62 | # empty dpkg status | 58 | # empty dpkg status |
1988 | @@ -90,15 +86,13 @@ | |||
1989 | 90 | "newer") | 86 | "newer") |
1990 | 91 | 87 | ||
1991 | 92 | # now test if versions in -security are detected | 88 | # now test if versions in -security are detected |
1994 | 93 | ul = UpdateList(None) | 89 | ul = UpdateList(None, dist="lucid") |
1993 | 94 | matcher = ul.initMatcher("lucid") | ||
1995 | 95 | for pkgname in test_pkgs: | 90 | for pkgname in test_pkgs: |
1996 | 96 | pkg = self.cache[pkgname] | 91 | pkg = self.cache[pkgname] |
2002 | 97 | self.assertEqual(self.cache.match_package_origin(pkg, matcher), | 92 | self.assertTrue(ul._is_security_update(pkg), |
2003 | 98 | matcher[("lucid-security", "Ubuntu")], | 93 | "package '%s' from lucid-updates contains also a " |
2004 | 99 | "package '%s' from lucid-updates contains also a " | 94 | "(not yet installed) security updates, but it is " |
2005 | 100 | "(not yet installed) security updates, but it is " | 95 | "not labeled as such" % pkg.name) |
2001 | 101 | "not labeled as such" % pkg.name) | ||
2006 | 102 | 96 | ||
2007 | 103 | # now check if it marks the version with -update if the -security | 97 | # now check if it marks the version with -update if the -security |
2008 | 104 | # version is installed | 98 | # version is installed |
2009 | @@ -118,17 +112,14 @@ | |||
2010 | 118 | self.cache.open() | 112 | self.cache.open() |
2011 | 119 | for pkgname in test_pkgs: | 113 | for pkgname in test_pkgs: |
2012 | 120 | pkg = self.cache[pkgname] | 114 | pkg = self.cache[pkgname] |
2015 | 121 | self.assertNotEqual(None, pkg._pkg.current_ver, | 115 | self.assertIsNotNone(pkg._pkg.current_ver, |
2016 | 122 | "no package '%s' installed" % pkg.name) | 116 | "no package '%s' installed" % pkg.name) |
2017 | 123 | candidate_version = getattr(pkg.candidate, "version", None) | 117 | candidate_version = getattr(pkg.candidate, "version", None) |
2021 | 124 | origin = self.cache.match_package_origin(pkg, matcher) | 118 | self.assertFalse(ul._is_security_update(pkg), |
2019 | 125 | self.assertEqual(self.cache.match_package_origin(pkg, matcher), | ||
2020 | 126 | matcher[("lucid-updates", "Ubuntu")], | ||
2022 | 127 | "package '%s' (%s) from lucid-updates is " | 119 | "package '%s' (%s) from lucid-updates is " |
2027 | 128 | "labelled '%s' even though we have marked this " | 120 | "labelled as a security update even though we " |
2028 | 129 | "version as installed already" % | 121 | "have marked this version as installed already" % |
2029 | 130 | (pkg.name, candidate_version, | 122 | (pkg.name, candidate_version)) |
2026 | 131 | origin.description)) | ||
2030 | 132 | 123 | ||
2031 | 133 | 124 | ||
2032 | 134 | if __name__ == "__main__": | 125 | if __name__ == "__main__": |
2033 | 135 | 126 | ||
2034 | === modified file 'tests/test_utils.py' | |||
2035 | --- tests/test_utils.py 2012-08-17 21:11:57 +0000 | |||
2036 | +++ tests/test_utils.py 2013-01-23 15:48:20 +0000 | |||
2037 | @@ -3,14 +3,11 @@ | |||
2038 | 3 | 3 | ||
2039 | 4 | import logging | 4 | import logging |
2040 | 5 | import glob | 5 | import glob |
2041 | 6 | import mock | ||
2042 | 6 | import sys | 7 | import sys |
2043 | 7 | import unittest | 8 | import unittest |
2044 | 8 | 9 | ||
2050 | 9 | from UpdateManager.Core.utils import ( | 10 | from UpdateManager.Core import utils |
2046 | 10 | is_child_of_process_name, | ||
2047 | 11 | get_string_with_no_auth_from_source_entry, | ||
2048 | 12 | humanize_size, | ||
2049 | 13 | estimate_kernel_size_in_boot) | ||
2051 | 14 | 11 | ||
2052 | 15 | 12 | ||
2053 | 16 | class TestUtils(unittest.TestCase): | 13 | class TestUtils(unittest.TestCase): |
2054 | @@ -18,27 +15,27 @@ | |||
2055 | 18 | def test_humanize_size(self): | 15 | def test_humanize_size(self): |
2056 | 19 | # humanize size is a bit funny, it rounds up to kB as the meaningful | 16 | # humanize size is a bit funny, it rounds up to kB as the meaningful |
2057 | 20 | # unit for users | 17 | # unit for users |
2061 | 21 | self.assertEqual(humanize_size(1000), "1 kB") | 18 | self.assertEqual(utils.humanize_size(1000), "1 kB") |
2062 | 22 | self.assertEqual(humanize_size(10), "1 kB") | 19 | self.assertEqual(utils.humanize_size(10), "1 kB") |
2063 | 23 | self.assertEqual(humanize_size(1200), "2 kB") | 20 | self.assertEqual(utils.humanize_size(1200), "2 kB") |
2064 | 24 | # but not for MB as well | 21 | # but not for MB as well |
2067 | 25 | self.assertEqual(humanize_size(1200 * 1000), "1.2 MB") | 22 | self.assertEqual(utils.humanize_size(1200 * 1000), "1.2 MB") |
2068 | 26 | self.assertEqual(humanize_size(1478 * 1000), "1.5 MB") | 23 | self.assertEqual(utils.humanize_size(1478 * 1000), "1.5 MB") |
2069 | 27 | # and we don't go to Gb just yet (as its not really needed | 24 | # and we don't go to Gb just yet (as its not really needed |
2070 | 28 | # in a upgrade context most of the time | 25 | # in a upgrade context most of the time |
2072 | 29 | self.assertEqual(humanize_size(1000 * 1000 * 1000), "1000.0 MB") | 26 | self.assertEqual(utils.humanize_size(1000 * 1000 * 1000), "1000.0 MB") |
2073 | 30 | 27 | ||
2074 | 31 | @unittest.skipIf(not glob.glob("/boot/*"), "inside chroot") | 28 | @unittest.skipIf(not glob.glob("/boot/*"), "inside chroot") |
2075 | 32 | def test_estimate_kernel_size(self): | 29 | def test_estimate_kernel_size(self): |
2077 | 33 | estimate = estimate_kernel_size_in_boot() | 30 | estimate = utils.estimate_kernel_size_in_boot() |
2078 | 34 | self.assertTrue(estimate > 0) | 31 | self.assertTrue(estimate > 0) |
2079 | 35 | 32 | ||
2080 | 36 | def test_is_child_of_process_name(self): | 33 | def test_is_child_of_process_name(self): |
2083 | 37 | self.assertTrue(is_child_of_process_name("init")) | 34 | self.assertTrue(utils.is_child_of_process_name("init")) |
2084 | 38 | self.assertFalse(is_child_of_process_name("mvo")) | 35 | self.assertFalse(utils.is_child_of_process_name("mvo")) |
2085 | 39 | for e in glob.glob("/proc/[0-9]*"): | 36 | for e in glob.glob("/proc/[0-9]*"): |
2086 | 40 | pid = int(e[6:]) | 37 | pid = int(e[6:]) |
2088 | 41 | is_child_of_process_name("gdm", pid) | 38 | utils.is_child_of_process_name("gdm", pid) |
2089 | 42 | 39 | ||
2090 | 43 | def test_is_port_listening(self): | 40 | def test_is_port_listening(self): |
2091 | 44 | from UpdateManager.Core.utils import is_port_already_listening | 41 | from UpdateManager.Core.utils import is_port_already_listening |
2092 | @@ -49,16 +46,70 @@ | |||
2093 | 49 | # entry with PW | 46 | # entry with PW |
2094 | 50 | s = SourceEntry("deb http://user:pass@some-ppa/ ubuntu main") | 47 | s = SourceEntry("deb http://user:pass@some-ppa/ ubuntu main") |
2095 | 51 | self.assertTrue( | 48 | self.assertTrue( |
2097 | 52 | not "user" in get_string_with_no_auth_from_source_entry(s)) | 49 | not "user" in utils.get_string_with_no_auth_from_source_entry(s)) |
2098 | 53 | self.assertTrue( | 50 | self.assertTrue( |
2101 | 54 | not "pass" in get_string_with_no_auth_from_source_entry(s)) | 51 | not "pass" in utils.get_string_with_no_auth_from_source_entry(s)) |
2102 | 55 | self.assertEqual(get_string_with_no_auth_from_source_entry(s), | 52 | self.assertEqual(utils.get_string_with_no_auth_from_source_entry(s), |
2103 | 56 | "deb http://hidden-u:hidden-p@some-ppa/ ubuntu main") | 53 | "deb http://hidden-u:hidden-p@some-ppa/ ubuntu main") |
2104 | 57 | # no pw | 54 | # no pw |
2105 | 58 | s = SourceEntry("deb http://some-ppa/ ubuntu main") | 55 | s = SourceEntry("deb http://some-ppa/ ubuntu main") |
2107 | 59 | self.assertEqual(get_string_with_no_auth_from_source_entry(s), | 56 | self.assertEqual(utils.get_string_with_no_auth_from_source_entry(s), |
2108 | 60 | "deb http://some-ppa/ ubuntu main") | 57 | "deb http://some-ppa/ ubuntu main") |
2109 | 61 | 58 | ||
2110 | 59 | @mock.patch('UpdateManager.Core.utils._load_meta_pkg_list') | ||
2111 | 60 | def test_flavor_package_ubuntu_first(self, mock_load): | ||
2112 | 61 | cache = {'ubuntu-desktop': mock.MagicMock(), | ||
2113 | 62 | 'other-desktop': mock.MagicMock()} | ||
2114 | 63 | cache['ubuntu-desktop'].is_installed = True | ||
2115 | 64 | cache['other-desktop'].is_installed = True | ||
2116 | 65 | mock_load.return_value = ['other-desktop'] | ||
2117 | 66 | self.assertEqual(utils.get_ubuntu_flavor_package(cache=cache), | ||
2118 | 67 | 'ubuntu-desktop') | ||
2119 | 68 | |||
2120 | 69 | @mock.patch('UpdateManager.Core.utils._load_meta_pkg_list') | ||
2121 | 70 | def test_flavor_package_match(self, mock_load): | ||
2122 | 71 | cache = {'a': mock.MagicMock(), | ||
2123 | 72 | 'b': mock.MagicMock(), | ||
2124 | 73 | 'c': mock.MagicMock()} | ||
2125 | 74 | cache['a'].is_installed = True | ||
2126 | 75 | cache['b'].is_installed = True | ||
2127 | 76 | cache['c'].is_installed = True | ||
2128 | 77 | mock_load.return_value = ['c', 'a', 'b'] | ||
2129 | 78 | # Must pick alphabetically first | ||
2130 | 79 | self.assertEqual(utils.get_ubuntu_flavor_package(cache=cache), 'a') | ||
2131 | 80 | |||
2132 | 81 | def test_flavor_package_default(self): | ||
2133 | 82 | self.assertEqual(utils.get_ubuntu_flavor_package(cache={}), | ||
2134 | 83 | 'ubuntu-desktop') | ||
2135 | 84 | |||
2136 | 85 | def test_flavor_default(self): | ||
2137 | 86 | self.assertEqual(utils.get_ubuntu_flavor(cache={}), 'ubuntu') | ||
2138 | 87 | |||
2139 | 88 | @mock.patch('UpdateManager.Core.utils.get_ubuntu_flavor_package') | ||
2140 | 89 | def test_flavor_simple(self, mock_package): | ||
2141 | 90 | mock_package.return_value = 'd' | ||
2142 | 91 | self.assertEqual(utils.get_ubuntu_flavor(), 'd') | ||
2143 | 92 | |||
2144 | 93 | @mock.patch('UpdateManager.Core.utils.get_ubuntu_flavor_package') | ||
2145 | 94 | def test_flavor_chop(self, mock_package): | ||
2146 | 95 | mock_package.return_value = 'd-pkg' | ||
2147 | 96 | self.assertEqual(utils.get_ubuntu_flavor(), 'd') | ||
2148 | 97 | |||
2149 | 98 | @mock.patch('UpdateManager.Core.utils.get_ubuntu_flavor_package') | ||
2150 | 99 | def test_flavor_name_desktop(self, mock_package): | ||
2151 | 100 | mock_package.return_value = 'something-desktop' | ||
2152 | 101 | self.assertEqual(utils.get_ubuntu_flavor_name(), 'Something') | ||
2153 | 102 | |||
2154 | 103 | @mock.patch('UpdateManager.Core.utils.get_ubuntu_flavor_package') | ||
2155 | 104 | def test_flavor_name_netbook(self, mock_package): | ||
2156 | 105 | mock_package.return_value = 'something-netbook' | ||
2157 | 106 | self.assertEqual(utils.get_ubuntu_flavor_name(), 'Something') | ||
2158 | 107 | |||
2159 | 108 | @mock.patch('UpdateManager.Core.utils.get_ubuntu_flavor_package') | ||
2160 | 109 | def test_flavor_name_studio(self, mock_package): | ||
2161 | 110 | mock_package.return_value = 'ubuntustudio-desktop' | ||
2162 | 111 | self.assertEqual(utils.get_ubuntu_flavor_name(), 'Ubuntu Studio') | ||
2163 | 112 | |||
2164 | 62 | 113 | ||
2165 | 63 | if __name__ == '__main__': | 114 | if __name__ == '__main__': |
2166 | 64 | if len(sys.argv) > 1 and sys.argv[1] == "-v": | 115 | if len(sys.argv) > 1 and sys.argv[1] == "-v": |
On Tue, Jan 22, 2013 at 06:25:25PM -0000, Michael Terry wrote: /code.launchpad .net/~software- updates- spec/update- manager/ group-by- applications/ +merge/ 144362 /code.launchpad .net/~dylanmcca ll/update- manager/ group-by- applications/ +merge/ 112678 /wiki.ubuntu. com/SoftwareUpd ates#Expanded_ presentation_ of_updates
> Michael Terry has proposed merging lp:~software-updates-spec/update-manager/group-by-applications into lp:update-manager.
>
> Requested reviews:
> Ubuntu Core Development Team (ubuntu-core-dev)
>
> For more details, see:
> https:/
>
> This is a continuation of https:/
>
> This implements the 'Available Updates' description pane in the Software Updates spec:
> https:/
Thanks for this branch! I haven't really had a chance to test-run it group-testing" !
yet, my bandwidth right now is not very good. From looking at the diff
it looks good, I like the new tests and the new
"aptroot-
Some things I noticed during reading the diff, nothing that really
needs fixing, just tiny nit-picking.
[..] check_output( newlines= True).strip( ) CalledProcessEr ror as e:
> + def __init__(self, parent, dist=None):
> + self.dist = dist
> + if self.dist is None:
> + try:
> + self.dist = subprocess.
> + ["lsb_release", "-c", "-s"],
> + universal_
> + except subprocess.
> + print("Error in lsb_release: %s" % e)
[..]
As a tiny nit-pick, this above could be written also via:
import platform
self.dist = platform.dist()[2]
[..] label(pkg) : saxutils. escape( getattr( pkg.candidate, "summary", first_word( name)
> +import xml.sax.saxutils
..
> +def get_package_
> + """ this takes a package synopsis and uppercases the first word's
> + first letter
> + """
> + import xml.sax.saxutils
> + name = xml.sax.
> ""))
> + return capitalize_
Looks like one of the imports can be skipped. Or (probably better) we use escape_ text() instead.
GLib.markup_
> + if len(cells) != 3 or \ cells[0] , Gtk.CellRendere rToggle) or \ cells[1] , Gtk.CellRendere rPixbuf) or \ cells[2] , Gtk.CellRendere rText):
> + not isinstance(
> + not isinstance(
> + not isinstance(
> + return
Most of the other python code seems to put "(" around ")" the multi
line if statements, either is fine with me, just wanted to mention
it.
Cheers,
Michael