Working on failed download retry

This commit is contained in:
Sumner Evans
2020-05-31 01:57:34 -06:00
parent 61d337659a
commit 112f83cf3b
4 changed files with 86 additions and 55 deletions

View File

@@ -180,7 +180,7 @@ class DownloadProgress:
@property
def progress_fraction(self) -> Optional[float]:
if not self.current_bytes or not self.total_bytes:
if self.current_bytes is None or self.total_bytes is None:
return None
return self.current_bytes / self.total_bytes
@@ -405,6 +405,15 @@ class AdapterManager:
before_download()
expected_size_exists = expected_size is not None
if expected_size_exists:
AdapterManager._instance.song_download_progress(
id,
DownloadProgress(
DownloadProgress.Type.PROGRESS,
total_bytes=expected_size,
current_bytes=0,
),
)
# TODO (#122): figure out how to retry if the other request failed.
if resource_downloading:
@@ -456,7 +465,7 @@ class AdapterManager:
if i % 100 == 0:
# Only delay (if configured) and update the progress UI
# every 10 KiB.
# every 100 KiB.
if DOWNLOAD_BLOCK_DELAY is not None:
sleep(DOWNLOAD_BLOCK_DELAY)

View File

@@ -32,8 +32,9 @@
margin: 10px;
}
#current-downloads-list-pending-count {
margin: 10px;
#current-downloads-list-pending-count,
#current-downloads-list-failed-count {
margin: 0 5px;
}
#current-downloads-cover-art-image {

View File

@@ -35,8 +35,10 @@ class MainWindow(Gtk.ApplicationWindow):
_updating_settings: bool = False
_pending_downloads: Set[str] = set()
_failed_downloads: Set[str] = set()
_current_download_boxes: Dict[str, Gtk.Box] = {}
_pending_downloads_placeholder: Optional[Gtk.Label] = None
_failed_downloads_box: Optional[Gtk.Label] = None
_pending_downloads_label: Optional[Gtk.Label] = None
_current_downloads_placeholder: Optional[Gtk.Label] = None
def __init__(self, *args, **kwargs):
@@ -195,7 +197,11 @@ class MainWindow(Gtk.ApplicationWindow):
def update_song_download_progress(self, song_id: str, progress: DownloadProgress):
if progress.type == DownloadProgress.Type.QUEUED:
self._pending_downloads.add(song_id)
if (
song_id not in self._failed_downloads
and song_id not in self._current_download_boxes.keys()
):
self._pending_downloads.add(song_id)
elif progress.type in (
DownloadProgress.Type.DONE,
DownloadProgress.Type.CANCELLED,
@@ -209,7 +215,9 @@ class MainWindow(Gtk.ApplicationWindow):
if song_id in self._pending_downloads:
self._pending_downloads.remove(song_id)
elif progress.type == DownloadProgress.Type.ERROR:
self._current_download_boxes[song_id].set_error(progress.exception)
self._failed_downloads.add(song_id)
self.current_downloads_box.remove(self._current_download_boxes[song_id])
del self._current_download_boxes[song_id]
elif progress.type == DownloadProgress.Type.PROGRESS:
if song_id not in self._current_download_boxes:
# Create and add the box to show the progress.
@@ -217,40 +225,78 @@ class MainWindow(Gtk.ApplicationWindow):
self._current_download_boxes[song_id].connect(
"cancel-clicked", self._on_download_box_cancel_click
)
self._current_download_boxes[song_id].connect(
"retry-clicked", self._on_download_box_retry_click
)
self.current_downloads_box.add(self._current_download_boxes[song_id])
if song_id in self._pending_downloads:
self._pending_downloads.remove(song_id)
if song_id in self._failed_downloads:
self._failed_downloads.remove(song_id)
self._current_download_boxes[song_id].update_progress(
progress.progress_fraction
)
# Show or hide the "failed count" indicator.
failed_download_count = len(self._failed_downloads)
if failed_download_count > 0:
if not self._failed_downloads_box:
self._failed_downloads_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL
)
self._failed_downloads_label = Gtk.Label(
label="",
halign=Gtk.Align.START,
name="current-downloads-list-failed-count",
)
self._failed_downloads_box.add(self._failed_downloads_label)
retry_all_button = IconButton(
"view-refresh-symbolic", tooltip_text="Retry all failed downloads."
)
retry_all_button.connect("clicked", self._on_retry_all_clicked)
self._failed_downloads_box.pack_end(retry_all_button, False, False, 0)
self.current_downloads_box.pack_start(
self._failed_downloads_box, False, False, 5
)
songs = util.pluralize("song", failed_download_count)
self._failed_downloads_label.set_text(
f"{failed_download_count} {songs} failed to download"
)
else:
if self._failed_downloads_box:
self.current_downloads_box.remove(self._failed_downloads_box)
self._failed_downloads_box = None
# Show or hide the "pending count" indicator.
pending_download_count = len(self._pending_downloads)
if pending_download_count > 0:
if not self._pending_downloads_placeholder:
self._pending_downloads_placeholder = Gtk.Label(
if not self._pending_downloads_label:
self._pending_downloads_label = Gtk.Label(
label="",
halign=Gtk.Align.START,
name="current-downloads-list-pending-count",
)
self.current_downloads_box.pack_end(
self._pending_downloads_placeholder, True, True, 0
self._pending_downloads_label, False, False, 5
)
songs = util.pluralize("song", pending_download_count)
self._pending_downloads_placeholder.set_text(
self._pending_downloads_label.set_text(
f"+{pending_download_count} pending {songs}"
)
else:
if self._pending_downloads_placeholder:
self.current_downloads_box.remove(self._pending_downloads_placeholder)
self._pending_downloads_placeholder = None
if self._pending_downloads_label:
self.current_downloads_box.remove(self._pending_downloads_label)
self._pending_downloads_label = None
# Show or hide the placeholder depending on whether or not there's anything to
# show.
current_downloads = len(self._current_download_boxes)
current_downloads = (
len(self._current_download_boxes)
+ pending_download_count
+ failed_download_count
)
if current_downloads == 0:
if not self._current_downloads_placeholder:
self._current_downloads_placeholder = Gtk.Label(
@@ -266,9 +312,7 @@ class MainWindow(Gtk.ApplicationWindow):
self.current_downloads_box.show_all()
self.cancel_all_button.set_sensitive(
pending_download_count + current_downloads > 0
)
self.cancel_all_button.set_sensitive(current_downloads > 0)
def _on_cancel_all_clicked(self, _):
AdapterManager.cancel_download_songs(
@@ -278,8 +322,10 @@ class MainWindow(Gtk.ApplicationWindow):
def _on_download_box_cancel_click(self, _, song_id: str):
AdapterManager.cancel_download_songs([song_id])
def _on_download_box_retry_click(self, _, song_id: str):
AdapterManager.batch_download_songs([song_id], lambda _: None, lambda _: None)
def _on_retry_all_clicked(self, _):
AdapterManager.batch_download_songs(
self._failed_downloads, lambda _: None, lambda _: None,
)
def _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
stack = Gtk.Stack()
@@ -446,6 +492,8 @@ class MainWindow(Gtk.ApplicationWindow):
self.current_downloads_box.add(self._current_downloads_placeholder)
vbox.add(self.current_downloads_box)
vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
clear_cache = self._create_model_button("Clear Cache", menu_name="clear-cache")
vbox.add(clear_cache)
menu.add(vbox)
@@ -969,8 +1017,8 @@ class DownloadStatusBox(Gtk.Box):
)
self.add(image)
song_stats = util.dot_join(self.song.artist.name if self.song.artist else None)
label_text = util.dot_join(f"<b>{util.esc(self.song.title)}</b>", song_stats)
artist = util.esc(self.song.artist.name if self.song.artist else None)
label_text = util.dot_join(f"<b>{util.esc(self.song.title)}</b>", artist)
self.song_label = Gtk.Label(
label=label_text,
ellipsize=Pango.EllipsizeMode.END,
@@ -981,8 +1029,8 @@ class DownloadStatusBox(Gtk.Box):
)
self.pack_start(self.song_label, True, True, 5)
self.progress_retry_container = Gtk.Box()
self.add(self.progress_retry_container)
self.download_progress = Gtk.ProgressBar(show_text=True)
self.add(self.download_progress)
self.cancel_button = IconButton(
"process-stop-symbolic", tooltip_text="Cancel download"
@@ -992,9 +1040,6 @@ class DownloadStatusBox(Gtk.Box):
)
self.add(self.cancel_button)
self.has_error = False
self.is_downloading = False
def image_callback(f: Result):
image.set_loading(False)
image.set_from_file(f.result())
@@ -1002,30 +1047,5 @@ class DownloadStatusBox(Gtk.Box):
artwork_future = AdapterManager.get_cover_art_filename(self.song.cover_art)
artwork_future.add_done_callback(lambda f: GLib.idle_add(image_callback, f))
def _replace_progress_retry_container(self, element: Any):
for c in self.progress_retry_container.get_children():
self.progress_retry_container.remove(c)
self.progress_retry_container.add(element)
self.progress_retry_container.show_all()
def update_progress(self, progress_fraction: float):
if not self.is_downloading:
self.download_progress = Gtk.ProgressBar(show_text=True)
self._replace_progress_retry_container(self.download_progress)
self.download_progress.set_fraction(progress_fraction)
self.is_downloading = True
self.has_error = False
def set_error(self, exception: Exception):
if not self.has_error:
retry_button = IconButton(
"view-refresh-symbolic", tooltip_text="Retry download"
)
retry_button.connect(
"clicked", lambda *a: self.emit("retry-clicked", self.song.id)
)
self._replace_progress_retry_container(retry_button)
self.is_downloading = False
self.has_error = True

View File

@@ -202,6 +202,7 @@ def show_song_popover(
)
def on_remove_downloads_click(_: Any):
AdapterManager.cancel_download_songs(song_ids)
AdapterManager.batch_delete_cached_songs(
song_ids, on_song_delete=on_download_state_change,
)