From 325a752b891d4328d0fb6020badf26873e9caf6e Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Mon, 28 Jan 2008 21:32:56 +0000 Subject: [PATCH] Moved background updates and icon downloading to tasks system. --- zeroinstall/0launch-gui/iface_browser.py | 19 +++++++- zeroinstall/injector/background.py | 18 +++---- zeroinstall/injector/cli.py | 7 ++- zeroinstall/injector/iface_cache.py | 5 ++ zeroinstall/injector/policy.py | 84 +++++++++++++++++--------------- 5 files changed, 80 insertions(+), 53 deletions(-) diff --git a/zeroinstall/0launch-gui/iface_browser.py b/zeroinstall/0launch-gui/iface_browser.py index 1b87575..ab18728 100644 --- a/zeroinstall/0launch-gui/iface_browser.py +++ b/zeroinstall/0launch-gui/iface_browser.py @@ -1,6 +1,6 @@ import gtk, gobject -from zeroinstall.support import basedir +from zeroinstall.support import basedir, tasks from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import model import properties @@ -222,7 +222,7 @@ class InterfaceBrowser: try: return self.cached_icon[iface.uri] except KeyError: - path = policy.get_icon_path(iface) + path = iface_cache.get_icon_path(iface) if path: try: loader = gtk.gdk.PixbufLoader('png') @@ -231,6 +231,7 @@ class InterfaceBrowser: finally: loader.close() icon = loader.get_pixbuf() + assert icon, "Failed to load cached PNG icon data" except Exception, ex: warn("Failed to load cached PNG icon: %s", ex) return None @@ -242,6 +243,20 @@ class InterfaceBrowser: gtk.gdk.INTERP_BILINEAR) self.cached_icon[iface.uri] = icon return icon + else: + # Try to download the icon + fetcher = policy.download_icon(iface) + if fetcher: + def update_display(): + yield fetcher.finished + try: + tasks.check(fetcher.finished) + self.build_tree() + except Exception, ex: + import traceback + traceback.print_exc() + policy.handler.report_error(ex) + tasks.Task(update_display(), "fetch_icon_and_refresh_display") return None diff --git a/zeroinstall/injector/background.py b/zeroinstall/injector/background.py index d42a337..73b35ab 100644 --- a/zeroinstall/injector/background.py +++ b/zeroinstall/injector/background.py @@ -8,6 +8,7 @@ This avoids the need to annoy people with a 'checking for updates' box when they import sys, os from logging import info +from zeroinstall.support import tasks from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import handler @@ -117,9 +118,10 @@ def _check_for_updates(policy, verbose): notify("Zero Install", "Checking for updates to '%s'..." % root_iface, timeout = 1) policy.handler = BackgroundHandler(root_iface) - policy.freshness = 0 # Don't bother trying to refresh when getting the interface - policy.refresh_all() # (causes confusing log messages) - policy.handler.wait_for_downloads() + policy.freshness = 0 # Don't bother trying to refresh when getting the interface + refresh = policy.refresh_all() # (causes confusing log messages) + policy.handler.wait_for_blocker(refresh.finished) + # We could even download the archives here, but for now just # update the interfaces. @@ -132,19 +134,17 @@ def _check_for_updates(policy, verbose): notify("Zero Install", "Updates ready to download for '%s'." % root_iface) sys.exit(0) - import gobject - ctx = gobject.main_context_default() - loop = gobject.MainLoop(ctx) + notification_closed = tasks.Blocker("wait for notification response") def _NotificationClosed(nid, *unused): if nid != our_question: return - loop.quit() + notification_closed.trigger() def _ActionInvoked(nid, action): if nid != our_question: return if action == 'download': _exec_gui(policy.root) - loop.quit() + notification_closed.trigger() notification_service.connect_to_signal('NotificationClosed', _NotificationClosed) notification_service.connect_to_signal('ActionInvoked', _ActionInvoked) @@ -152,7 +152,7 @@ def _check_for_updates(policy, verbose): our_question = notify("Zero Install", "Updates ready to download for '%s'." % root_iface, actions = ['download', 'Download']) - loop.run() + policy.handler.wait_for_blocker(notification_closed) def spawn_background_update(policy, verbose): # Mark all feeds as being updated. Do this before forking, so that if someone is diff --git a/zeroinstall/injector/cli.py b/zeroinstall/injector/cli.py index 255f6ef..2c36df8 100755 --- a/zeroinstall/injector/cli.py +++ b/zeroinstall/injector/cli.py @@ -158,11 +158,14 @@ def _normal_mode(options, args): else: can_run_immediately = (not policy.need_download()) and policy.ready - if options.download_only and policy.stale_feeds: + from zeroinstall.injector.iface_cache import iface_cache + stale_feeds = [feed for feed in policy.solver.feeds_used if policy.is_stale(iface_cache.get_feed(feed))] + + if options.download_only and stale_feeds: can_run_immediately = False if can_run_immediately: - if policy.stale_feeds: + if stale_feeds: if policy.network_use == model.network_offline: logging.debug("No doing background update because we are in off-line mode.") else: diff --git a/zeroinstall/injector/iface_cache.py b/zeroinstall/injector/iface_cache.py index 6c7d201..8aea746 100644 --- a/zeroinstall/injector/iface_cache.py +++ b/zeroinstall/injector/iface_cache.py @@ -347,6 +347,11 @@ class IfaceCache(object): reader.update_from_cache(interface) + def get_feed(self, url): + # TODO: This isn't a good implementation + iface = self.get_interface(url) + return iface._main_feed + def get_interface(self, uri): """Get the interface for uri, creating a new one if required. New interfaces are initialised from the disk cache, but not from diff --git a/zeroinstall/injector/policy.py b/zeroinstall/injector/policy.py index 13d98d6..e4ce3f7 100644 --- a/zeroinstall/injector/policy.py +++ b/zeroinstall/injector/policy.py @@ -220,6 +220,25 @@ class Policy(object): else: debug("Skipping '%s'; unsupported architecture %s-%s", f, f.os, f.machine) + + def is_stale(self, feed): + """Check whether feed needs updating, based on the configured L{freshness}. + @return: true if feed is stale or missing.""" + if feed.last_modified is None: + return True # Don't even have it yet + now = time.time() + staleness = now - (feed.last_checked or 0) + debug("Staleness for %s is %.2f hours", feed, staleness / 3600.0) + + if self.freshness == 0 or staleness < self.freshness: + return False # Fresh enough for us + + last_check_attempt = iface_cache.get_last_check_attempt(feed.url) + if last_check_attempt and last_check_attempt > now - FAILED_CHECK_DELAY: + debug("Stale, but tried to check recently (%s) so not rechecking now.", time.ctime(last_check_attempt)) + return False + + return True def get_interface(self, uri): """Get an interface from the L{iface_cache}. If it is missing start a new download. @@ -249,18 +268,9 @@ class Policy(object): warn("Nothing known about interface '%s', but we are in off-line mode " "(so not fetching).", uri) self._warned_offline = True - else: - now = time.time() - staleness = now - (iface.last_checked or 0) - debug("Staleness for %s is %.2f hours", iface, staleness / 3600.0) - - if self.freshness > 0 and staleness > self.freshness: - last_check_attempt = iface_cache.get_last_check_attempt(iface.uri) - if last_check_attempt and last_check_attempt > now - FAILED_CHECK_DELAY: - debug("Stale, but tried to check recently (%s) so not rechecking now.", time.ctime(last_check_attempt)) - else: - debug("Adding %s to stale set", iface) - self.stale_feeds.add(iface) + elif self.is_stale(iface): + debug("Adding %s to stale set", iface) + self.stale_feeds.add(iface) #else: debug("Local interface, so not checking staleness.") return iface @@ -336,10 +346,16 @@ class Policy(object): dl.expected_size = download_source.size + (download_source.start_offset or 0) return (dl.downloaded, dl.tempfile) - def begin_icon_download(self, interface, force = False): - """Start downloading an icon for this interface. On success, add it to the - icon cache. If the interface has no icon, do nothing.""" - debug("begin_icon_download %s (force = %d)", interface, force) + def download_icon(self, interface, force = False): + """Download an icon for this interface and add it to the + icon cache. If the interface has no icon or we are offline, do nothing. + @return: the task doing the import, or None + @rtype: L{tasks.Task}""" + debug("download_icon %s (force = %d)", interface, force) + + if self.network_use == network_offline: + info("No icon present for %s, but off-line so not downloading", interface) + return # Find a suitable icon to download for icon in interface.get_metadata(XMLNS_IFACE, 'icon'): @@ -356,13 +372,18 @@ class Policy(object): return dl = self.handler.get_download(source, force = force) - if dl.on_success: - # Possibly we should handle this better, but it's unlikely anyone will need - # to use an icon as an interface or implementation as well, and some of the code - # may assume it's OK keep asking for the same icon to be downloaded. - info("Already have a handler for %s; not adding another", source) - return - dl.on_success.append(lambda stream: self.store_icon(interface, stream)) + + def add_icon(): + stream = dl.tempfile + yield dl.downloaded + try: + tasks.check(dl.downloaded) + stream.seek(0) + self.store_icon(interface, stream) + except Exception, ex: + self.handler.report_error(ex) + + return tasks.Task(add_icon(), "download_and_import_icon " + source) def store_icon(self, interface, stream): """Called when an icon has been successfully downloaded. @@ -462,23 +483,6 @@ class Policy(object): warn("Warning: unknown interface '%s'" % feed_iface_uri) return [iface_cache.get_interface(uri) for uri in feed_targets] - def get_icon_path(self, iface): - """Get an icon for this interface. If the icon is in the cache, use that. - If not, start a download. If we already started a download (successful or - not) do nothing. - @return: The cached icon's path, or None if no icon is currently available. - @rtype: str""" - path = iface_cache.get_icon_path(iface) - if path: - return path - - if self.network_use == network_offline: - info("No icon present for %s, but off-line so not downloading", iface) - return None - - self.begin_icon_download(iface) - return None - def get_best_source(self, impl): """Return the best download source for this implementation. @rtype: L{model.RetrievalMethod}""" -- 2.11.4.GIT