From 6475a8d7d54d093ef5d6a4c0380f8a653bdcbc28 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sat, 2 Feb 2008 17:40:19 +0000 Subject: [PATCH] Improve progress bar display when downloading. If there are multiple files being downloaded or we know the total size, show the fraction done not just a pulse. --- zeroinstall/0launch-gui/gui.py | 37 ++++++++++++++++++++++++++++++++++--- zeroinstall/injector/download.py | 18 ++++++++++++++---- zeroinstall/injector/handler.py | 23 ++++++++++++++++++----- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/zeroinstall/0launch-gui/gui.py b/zeroinstall/0launch-gui/gui.py index e98240d..2cdb73a 100644 --- a/zeroinstall/0launch-gui/gui.py +++ b/zeroinstall/0launch-gui/gui.py @@ -6,7 +6,7 @@ from zeroinstall.injector.policy import Policy from zeroinstall.injector import download, handler from zeroinstall.injector.model import SafeException from zeroinstall.injector.reader import InvalidInterface -from zeroinstall.support import tasks +from zeroinstall.support import tasks, pretty_size import dialog from checking import CheckingBox @@ -43,13 +43,44 @@ class GUIHandler(handler.Handler): if self.monitored_downloads and self.pulse is None: def pulse(): if self.policy.checking: - self.policy.checking.progress.pulse() + progress = self.policy.checking.progress else: - self.policy.window.progress.pulse() + progress = self.policy.window.progress + + any_known = False + done = total = self.total_bytes_downloaded # Completed downloads + n_downloads = self.n_completed_downloads + # Now add downloads in progress... + for x in self.monitored_downloads.values(): + if x.status != download.download_fetching: continue + n_downloads += 1 + if x.expected_size: + any_known = True + so_far = x.get_bytes_downloaded_so_far() + total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons + done += so_far + + progress_text = '%s / %s' % (pretty_size(done), pretty_size(total)) + if n_downloads == 1: + progress.set_text('Downloading one file (%s)' % progress_text) + else: + progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text)) + + if total == 0 or (n_downloads < 2 and not any_known): + progress.pulse() + else: + progress.set_fraction(float(done) / total) + return True + pulse() self.pulse = gobject.timeout_add(50, pulse) self.policy.window.progress.show() elif len(self.monitored_downloads) == 0: + # Reset counters + self.n_completed_downloads = 0 + self.total_bytes_downloaded = 0 + + # Stop animation if self.pulse: gobject.source_remove(self.pulse) self.policy.window.progress.hide() diff --git a/zeroinstall/injector/download.py b/zeroinstall/injector/download.py index 4721271..c8cc519 100644 --- a/zeroinstall/injector/download.py +++ b/zeroinstall/injector/download.py @@ -19,7 +19,6 @@ from logging import info download_starting = "starting" # Waiting for UI to start it download_fetching = "fetching" # In progress -download_checking = "checking" # Checking GPG sig (possibly interactive) download_complete = "complete" # Downloaded and cached OK download_failed = "failed" @@ -28,7 +27,7 @@ class DownloadError(SafeException): class Download(object): __slots__ = ['url', 'tempfile', 'status', 'errors', 'expected_size', 'downloaded', - 'expected_size', 'child_pid', 'child_stderr'] + 'expected_size', 'child_pid', 'child_stderr', '_final_total_size'] def __init__(self, url): "Initial status is starting." @@ -40,6 +39,7 @@ class Download(object): self.downloaded = None self.expected_size = None # Final size (excluding skipped bytes) + self._final_total_size = None # Set when download is finished self.child_pid = None self.child_stderr = None @@ -102,6 +102,8 @@ class Download(object): errors = 'Download process exited with error status ' \ 'code ' + hex(status) + self._final_total_size = self.get_bytes_downloaded_so_far() + stream = self.tempfile self.tempfile = None @@ -122,7 +124,7 @@ class Download(object): _, ex, tb = sys.exc_info() self.downloaded.trigger(exception = (ex, tb)) else: - self.status = download_checking + self.status = download_complete self.downloaded.trigger() def download_as_child(self): @@ -167,8 +169,16 @@ class Download(object): return 1 if self.expected_size is None: return None # Unknown - current_size = os.fstat(self.tempfile.fileno()).st_size + current_size = self.get_bytes_downloaded_so_far() return float(current_size) / self.expected_size + def get_bytes_downloaded_so_far(self): + if self.status is download_starting: + return 0 + elif self.status is download_fetching: + return os.fstat(self.tempfile.fileno()).st_size + else: + return self._final_total_size + def __str__(self): return "" % self.url diff --git a/zeroinstall/injector/handler.py b/zeroinstall/injector/handler.py index 90eb263..a05a1f9 100644 --- a/zeroinstall/injector/handler.py +++ b/zeroinstall/injector/handler.py @@ -23,15 +23,21 @@ class Handler(object): This implementation of the handler interface uses the GLib mainloop. @ivar monitored_downloads: dict of downloads in progress - @type monitored_downloads: {URL: (error_stream, L{download.Download})} + @type monitored_downloads: {URL: L{download.Download}} + @ivar n_completed_downloads: number of downloads which have finished for GUIs, etc (can be reset as desired). + @type n_completed_downloads: int + @ivar total_bytes_downloaded: informational counter for GUIs, etc (can be reset as desired). Updated when download finishes. + @type total_bytes_downloaded: int """ - __slots__ = ['monitored_downloads', '_loop', 'dry_run'] + __slots__ = ['monitored_downloads', '_loop', 'dry_run', 'total_bytes_downloaded', 'n_completed_downloads'] def __init__(self, mainloop = None, dry_run = False): self.monitored_downloads = {} self._loop = None self.dry_run = dry_run + self.n_completed_downloads = 0 + self.total_bytes_downloaded = 0 def monitor_download(self, dl): """Called when a new L{download} is started. @@ -40,11 +46,18 @@ class Handler(object): self.monitored_downloads[dl.url] = dl self.downloads_changed() + @tasks.async def download_done(): yield dl.downloaded - del self.monitored_downloads[dl.url] - self.downloads_changed() - monitor = tasks.Task(download_done(), "download monitor") + # NB: we don't check for exceptions here; someone else should be doing that + try: + self.n_completed_downloads += 1 + self.total_bytes_downloaded += dl.get_bytes_downloaded_so_far() + del self.monitored_downloads[dl.url] + self.downloads_changed() + except Exception, ex: + self.report_error(ex) + download_done() def downloads_changed(self): # This is just for the GUI to override -- 2.11.4.GIT