Merge lp:~mvo/software-center/size-calculation-via-aptdaemon into lp:software-center

Proposed by Michael Vogt
Status: Merged
Merged at revision: 3114
Proposed branch: lp:~mvo/software-center/size-calculation-via-aptdaemon
Merge into: lp:software-center
Diff against target: 391 lines (+127/-105)
6 files modified
softwarecenter/db/pkginfo.py (+10/-3)
softwarecenter/db/pkginfo_impl/aptcache.py (+52/-77)
softwarecenter/db/pkginfo_impl/packagekit.py (+8/-4)
softwarecenter/ui/gtk3/views/appdetailsview.py (+18/-14)
tests/test_pkginfo.py (+36/-4)
tests/utils.py (+3/-3)
To merge this branch: bzr merge lp:~mvo/software-center/size-calculation-via-aptdaemon
Reviewer Review Type Date Requested Status
James Westby (community) Approve
software-store-developers Pending
Review via email: mp+120359@code.launchpad.net

Description of the change

This moves the size calculation code outside of software-center into aptdaemon. This improves
responsiveness and fixes the issues described in bug #988854.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwarecenter/db/pkginfo.py'
2--- softwarecenter/db/pkginfo.py 2012-03-15 03:52:56 +0000
3+++ softwarecenter/db/pkginfo.py 2012-08-20 10:09:26 +0000
4@@ -121,6 +121,10 @@
5 'cache-broken': (GObject.SIGNAL_RUN_FIRST,
6 GObject.TYPE_NONE,
7 ()),
8+ 'query-total-size-on-install-done': (
9+ GObject.SIGNAL_RUN_FIRST,
10+ GObject.TYPE_NONE,
11+ (int, int)),
12 }
13
14 def __getitem__(self, k):
15@@ -201,15 +205,18 @@
16 which will be removed if the package is installed."""
17 return []
18
19- def get_total_size_on_install(self, pkgname,
20+ def query_total_size_on_install(self, pkgname,
21 addons_install=None, addons_remove=None,
22 archive_suite=None):
23- """ Returns a tuple (download_size, installed_size)
24+ """ Query for download and installed size
25 with disk size in KB calculated for pkgname installation
26 plus addons change and a (optional) archive_suite that the
27 package comes from
28+
29+ This will emit a "query-total-size-on-install-done" signal
30+ with the parameters (download_size, required_space_on_disk)
31 """
32- return (0, 0)
33+ self.emit("query-total-size-on-install-done", 0, 0)
34
35 def open(self):
36 """
37
38=== modified file 'softwarecenter/db/pkginfo_impl/aptcache.py'
39--- softwarecenter/db/pkginfo_impl/aptcache.py 2012-08-07 13:15:08 +0000
40+++ softwarecenter/db/pkginfo_impl/aptcache.py 2012-08-20 10:09:26 +0000
41@@ -26,6 +26,9 @@
42 from gi.repository import GObject
43 from gi.repository import Gio
44
45+# we need this to get the size calculation done asyncronously and reliable
46+from aptdaemon.client import AptClient
47+
48 from softwarecenter.db.pkginfo import PackageInfo, _Version
49 from softwarecenter.enums import PkgStates
50 from softwarecenter.utils import ExecutionTime
51@@ -135,6 +138,9 @@
52 "changed", self._on_apt_finished_stamp_changed)
53 # this is fast, so ok
54 self._language_packages = self._read_language_pkgs()
55+ # query the totalize on install using aptdaemon
56+ self.aptd_client = AptClient()
57+ self._aptd_trans = None
58
59 @staticmethod
60 def version_compare(a, b):
61@@ -569,84 +575,53 @@
62 pkg._pkg, version._cand, archive_suite)
63 return res
64
65- def get_total_size_on_install(self, pkgname,
66- addons_install=None, addons_remove=None,
67- archive_suite=None):
68- pkgs_to_install = []
69- pkgs_to_remove = []
70- total_download_size = 0 # in kB
71- total_install_size = 0 # in kB
72-
73+ # space calulcation stuff
74+ def _on_total_size_calculation_done(self, trans, space):
75+ self.emit(
76+ "query-total-size-on-install-done", trans.download, trans.space)
77+
78+ def _on_trans_simulate_error(self, error):
79+ LOG.exception("simulate failed")
80+
81+ def _on_trans_commit_packages_ready(self, trans):
82+ trans.connect("space-changed", self._on_total_size_calculation_done)
83+ try:
84+ trans.simulate(reply_handler=lambda: True,
85+ error_handler=self._on_trans_simulate_error)
86+ except:
87+ LOG.exception("simulate failed")
88+
89+ def query_total_size_on_install(self, pkgname,
90+ addons_install=[], addons_remove=[],
91+ archive_suite=""):
92 if not pkgname in self._cache:
93- return (0, 0)
94-
95- pkg = self._cache[pkgname]
96- version = pkg.installed
97-
98- all_install = []
99- if addons_install is not None:
100- all_install += addons_install
101-
102- if version is None:
103- # its important that its the first pkg as the depcache will
104- # get cleared for each pkg and that will means that the
105- # set_candidate_release is lost again
106- all_install.append(pkgname)
107-
108- for p in all_install:
109- # ensure that the archive_suite is set if needed, this needs to
110- # be in the loop as the cache is cleared in each loop iteration
111- if archive_suite:
112- self._set_candidate_release(pkg, archive_suite)
113- # now get the right version
114- version = self._cache[p].candidate
115- # this can happen on e.g. deb packages that are not in the cache
116- # testcase: software-center google-chrome-stable_current_amd64.deb
117- if not version:
118- continue
119- pkgs_to_install.append(version)
120- # now do it
121- deps_inst = self._try_install_and_get_all_deps_installed(
122- self._cache[p])
123- for dep in deps_inst:
124- if self._cache[dep].installed is None:
125- dep_version = self._cache[dep].candidate
126- pkgs_to_install.append(dep_version)
127- deps_remove = self._try_install_and_get_all_deps_removed(
128- self._cache[p])
129- for dep in deps_remove:
130- if self._cache[dep].is_installed:
131- dep_version = self._cache[dep].installed
132- pkgs_to_remove.append(dep_version)
133-
134- all_remove = [] if addons_remove is None else addons_remove
135- for p in all_remove:
136- version = self._cache[p].installed
137- pkgs_to_remove.append(version)
138- deps_inst = self._try_install_and_get_all_deps_installed(
139- self._cache[p])
140- for dep in deps_inst:
141- if self._cache[dep].installed is None:
142- version = self._cache[dep].candidate
143- pkgs_to_install.append(version)
144- deps_remove = self._try_install_and_get_all_deps_removed(
145- self._cache[p])
146- for dep in deps_remove:
147- if self._cache[dep].installed is not None:
148- version = self._cache[dep].installed
149- pkgs_to_remove.append(version)
150-
151- pkgs_to_install = list(set(pkgs_to_install))
152- pkgs_to_remove = list(set(pkgs_to_remove))
153-
154- for pkg in pkgs_to_install:
155- if not pkg_downloaded(pkg) and not pkg.package.installed:
156- total_download_size += pkg.size
157- total_install_size += pkg.installed_size
158- for pkg in pkgs_to_remove:
159- total_install_size -= pkg.installed_size
160-
161- return (total_download_size, total_install_size)
162+ self.emit("query-total-size-on-install-done", 0, 0)
163+
164+ # ensure the syntax is right
165+ if archive_suite:
166+ pkgname = pkgname + "/" + archive_suite
167+
168+ # and simulate the install/remove via aptdaemon
169+ install = [pkgname] + addons_install
170+ remove = addons_remove
171+ reinstall = purge = upgrade = downgrade = []
172+
173+ if self._aptd_trans:
174+ self._aptd_trans.cancel()
175+ self._aptd_trans = None
176+
177+ # do this async
178+ try:
179+ self.aptd_client.commit_packages(
180+ install, reinstall, remove, purge, upgrade, downgrade,
181+ # wait
182+ False,
183+ # reply and error handlers
184+ self._on_trans_commit_packages_ready,
185+ self._on_trans_simulate_error)
186+ except:
187+ LOG.exception(
188+ "getting commit_packages trans failed for '%s'" % pkgname)
189
190 def get_all_deps_upgrading(self, pkg):
191 # note: this seems not to be used anywhere
192
193=== modified file 'softwarecenter/db/pkginfo_impl/packagekit.py'
194--- softwarecenter/db/pkginfo_impl/packagekit.py 2012-03-07 20:08:53 +0000
195+++ softwarecenter/db/pkginfo_impl/packagekit.py 2012-08-20 10:09:26 +0000
196@@ -264,20 +264,24 @@
197 if (p.get_name() != pkg.name)
198 and p.get_info() == packagekit.InfoEnum.INSTALLED]
199
200- def get_total_size_on_install(self, pkgname,
201+ def query_total_size_on_install(self, pkgname,
202 addons_install=None, addons_remove=None,
203 archive_suite=None):
204- """ Returns a tuple (download_size, installed_size)
205+ """ emit query-total-size-on-install-done signal
206 with disk size in KB calculated for pkgname installation
207 plus addons change.
208 """
209 # FIXME: support archive_suite here too
210
211 # FIXME: PackageKit reports only one size at a time
212+ download_size = 0
213+ install_size = 0
214 if self.is_installed(pkgname):
215- return (0, self.get_size(pkgname))
216+ install_size = self.get_size(pkgname)
217 else:
218- return (self.get_size(pkgname), 0)
219+ download_size = self.get_size(pkgname)
220+ self.emit(
221+ "query-total-size-on-install-done", download_size, install_size)
222
223 @property
224 def ready(self):
225
226=== modified file 'softwarecenter/ui/gtk3/views/appdetailsview.py'
227--- softwarecenter/ui/gtk3/views/appdetailsview.py 2012-08-17 08:56:26 +0000
228+++ softwarecenter/ui/gtk3/views/appdetailsview.py 2012-08-20 10:09:26 +0000
229@@ -762,8 +762,7 @@
230 if addon in self.addons_to_install:
231 self.addons_to_install.remove(addon)
232 self.status_bar.configure()
233- GObject.idle_add(self.view.update_totalsize,
234- priority=GObject.PRIORITY_LOW)
235+ self.view.update_totalsize()
236
237 def configure(self, pkgname, update_addons=True):
238 self.addons_to_install = []
239@@ -780,8 +779,7 @@
240 self.addons_to_install = []
241 self.addons_to_remove = []
242 self.configure(self.view.app.pkgname)
243- GObject.idle_add(self.view.update_totalsize,
244- priority=GObject.PRIORITY_LOW)
245+ self.view.update_totalsize()
246
247
248 _asset_cache = {}
249@@ -814,6 +812,8 @@
250 self.distro = distro
251 self.icons = icons
252 self.cache = cache
253+ self.cache.connect("query-total-size-on-install-done",
254+ self._on_query_total_size_on_install_done)
255 self.backend = get_install_backend()
256 self.cache.connect("cache-ready", self._on_cache_ready)
257 self.connect("destroy", self._on_destroy)
258@@ -1496,8 +1496,7 @@
259 self.addons_manager.configure(app_details.pkgname)
260
261 # Update total size label
262- self.totalsize_info.set_value(_("Calculating..."))
263- GObject.timeout_add(500, self.update_totalsize)
264+ self.update_totalsize()
265
266 # Update addons state bar
267 self.addons_statusbar.configure()
268@@ -1545,9 +1544,7 @@
269 self.addon_view.hide()
270 if self.addon_view.get_parent():
271 self.info_vb.remove(self.addon_view)
272- self.totalsize_info.set_value(_("Calculating..."))
273- GObject.idle_add(self.update_totalsize,
274- priority=GObject.PRIORITY_LOW)
275+ self.update_totalsize()
276 self._update_recommendations(app_details.pkgname)
277 self._update_reviews(app_details)
278
279@@ -2004,18 +2001,25 @@
280 if not self.totalsize_info.get_property('visible'):
281 return False
282
283+ self.totalsize_info.set_value(_("Calculating..."))
284+
285 while Gtk.events_pending():
286 Gtk.main_iteration()
287
288- label_string = ""
289-
290- res = self.cache.get_total_size_on_install(
291+ self.cache.query_total_size_on_install(
292 self.app_details.pkgname,
293 self.addons_manager.addons_to_install,
294 self.addons_manager.addons_to_remove,
295 self.app.archive_suite)
296- total_download_size, total_install_size = res
297- if res == (0, 0) and isinstance(self.app, DebFileApplication):
298+
299+ def _on_query_total_size_on_install_done(self,
300+ pkginfo,
301+ total_download_size,
302+ total_install_size):
303+ label_string = ""
304+ if (total_download_size == 0 and
305+ total_install_size == 0 and
306+ isinstance(self.app, DebFileApplication)):
307 total_install_size = self.app_details.installed_size
308 if total_download_size > 0:
309 download_size = GLib.format_size(total_download_size)
310
311=== modified file 'tests/test_pkginfo.py'
312--- tests/test_pkginfo.py 2012-05-30 18:39:55 +0000
313+++ tests/test_pkginfo.py 2012-08-20 10:09:26 +0000
314@@ -6,6 +6,7 @@
315 from mock import patch
316
317 from tests.utils import (
318+ get_test_pkg_info,
319 setup_test_env,
320 )
321 setup_test_env()
322@@ -35,7 +36,36 @@
323 self.cache = apt.Cache(memonly=True)
324
325 def test_get_total_size(self):
326- # get a cache
327+ def _on_query_total_size_on_install_done(pkginfo, download, space):
328+ self.need_download = download
329+ self.need_space = space
330+ loop.quit()
331+ TEST_PKG = "casper"
332+ ADDONS_TO_INSTALL = [ "lupin-casper" ]
333+ ADDONS_TO_REMOVE = []
334+ loop = GObject.MainLoop(GObject.main_context_default())
335+ cache = get_test_pkg_info()
336+ cache.connect(
337+ "query-total-size-on-install-done",
338+ _on_query_total_size_on_install_done)
339+ cache.query_total_size_on_install(
340+ TEST_PKG, ADDONS_TO_INSTALL, ADDONS_TO_REMOVE)
341+ loop.run()
342+ # work out the numbers that we at least need to get (this will
343+ # not include dependencies so it is probably lower)
344+ need_at_least_download = (
345+ cache[TEST_PKG].candidate.size +
346+ sum([cache[pkg].candidate.size for pkg in ADDONS_TO_INSTALL]))
347+ need_at_least_installed = (
348+ cache[TEST_PKG].candidate.installed_size +
349+ sum([cache[pkg].candidate.installed_size for pkg in ADDONS_TO_INSTALL]))
350+ self.assertTrue(self.need_download >= need_at_least_download)
351+ self.assertTrue(self.need_space >= need_at_least_installed)
352+ del self.need_download
353+ del self.need_space
354+
355+ def test_get_total_size_with_mock(self):
356+ # get a cache
357 cache = get_pkg_info()
358 cache.open()
359 # pick first uninstalled pkg
360@@ -45,11 +75,13 @@
361 # prepare args
362 addons_to_install = addons_to_remove = []
363 archive_suite = "foo"
364- with patch.object(cache, "_set_candidate_release") as f_mock:
365- cache.get_total_size_on_install(
366+ with patch.object(cache.aptd_client, "commit_packages") as f_mock:
367+ cache.query_total_size_on_install(
368 pkg.name, addons_to_install, addons_to_remove, archive_suite)
369 # ensure it got called with the right arguments
370- f_mock.assert_called_with(pkg, archive_suite)
371+ args, kwargs = f_mock.call_args
372+ to_install = args[0]
373+ self.assertTrue(to_install[0].endswith("/%s" % archive_suite))
374
375
376 if __name__ == "__main__":
377
378=== modified file 'tests/utils.py'
379--- tests/utils.py 2012-05-31 12:50:01 +0000
380+++ tests/utils.py 2012-08-20 10:09:26 +0000
381@@ -395,7 +395,7 @@
382 """Return (recommended, suggested) addons for 'pkgname'."""
383 return ([], [])
384
385- def get_total_size_on_install(self, pkgname, addons_to_install,
386+ def query_total_size_on_install(self, pkgname, addons_to_install,
387 addons_to_remove, archive_suite):
388- """Return a fake (total_download_size, total_install_size) result."""
389- return (0, 0)
390+ """Emit a fake signal "query-total-size-on-install-done" """
391+ self.emit("query-total-size-on-install-done", (0, 0))

Subscribers

People subscribed via source and target branches