Working on propagating song cancellation

This commit is contained in:
Sumner Evans
2020-05-30 23:50:44 -06:00
parent da694387df
commit 61d337659a
4 changed files with 158 additions and 36 deletions

View File

@@ -454,9 +454,9 @@ class AdapterManager:
total_consumed += len(data)
f.write(data)
if i % 5 == 0:
if i % 100 == 0:
# Only delay (if configured) and update the progress UI
# every 5 KiB.
# every 10 KiB.
if DOWNLOAD_BLOCK_DELAY is not None:
sleep(DOWNLOAD_BLOCK_DELAY)
@@ -482,6 +482,8 @@ class AdapterManager:
id,
DownloadProgress(DownloadProgress.Type.ERROR, exception=e),
)
# Re-raise the exception so that we can actually handle it.
raise
finally:
# Always release the download set lock, even if there's an error.
with AdapterManager.download_set_lock:
@@ -954,7 +956,6 @@ class AdapterManager:
AdapterManager._instance.song_download_progress(
song_id, DownloadProgress(DownloadProgress.Type.DONE),
)
song = AdapterManager.get_song_details(song_id).result()
except CacheMissError:
# The song is not already cached.
if before_download:
@@ -963,7 +964,7 @@ class AdapterManager:
song = AdapterManager.get_song_details(song_id).result()
# Download the song.
# TODO (#64) handle download errors?
# TODO figure out how to cancel
song_tmp_filename = AdapterManager._create_download_fn(
AdapterManager._instance.ground_truth_adapter.get_song_uri(
song_id, AdapterManager._get_scheme()
@@ -978,9 +979,6 @@ class AdapterManager:
(None, song_tmp_filename, None),
)
on_song_download_complete(song_id)
# Download the corresponding cover art.
AdapterManager.get_cover_art_filename(song.cover_art).result()
finally:
# Release the semaphore lock. This will allow the next song in the queue
# to be downloaded. I'm doing this in the finally block so that it
@@ -997,7 +995,7 @@ class AdapterManager:
):
return
# Alert the UI that the downloads are queue.
# Alert the UI that the downloads are queued.
for song_id in song_ids:
# Everything succeeded.
AdapterManager._instance.song_download_progress(
@@ -1034,6 +1032,10 @@ class AdapterManager:
return Result(do_batch_download_songs, is_download=True, on_cancel=on_cancel)
@staticmethod
def cancel_download_songs(song_ids: Sequence[str]):
print("cancel songs!!!!!", song_ids)
@staticmethod
def batch_permanently_cache_songs(
song_ids: Sequence[str],

View File

@@ -865,7 +865,7 @@ class SublimeMusicApp(Gtk.Application):
def on_song_download_progress(self, song_id: str, progress: DownloadProgress):
assert self.window
self.window.update_song_download_progress(song_id, progress)
GLib.idle_add(self.window.update_song_download_progress, song_id, progress)
def on_app_shutdown(self, app: "SublimeMusicApp"):
self.exiting = True

View File

@@ -32,6 +32,14 @@
margin: 10px;
}
#current-downloads-list-pending-count {
margin: 10px;
}
#current-downloads-cover-art-image {
margin: 3px 5px;
}
.menu-label {
margin-right: 15px;
}

View File

@@ -34,7 +34,10 @@ class MainWindow(Gtk.ApplicationWindow):
}
_updating_settings: bool = False
_pending_downloads: Set[str] = set()
_current_download_boxes: Dict[str, Gtk.Box] = {}
_pending_downloads_placeholder: Optional[Gtk.Label] = None
_current_downloads_placeholder: Optional[Gtk.Label] = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -192,36 +195,91 @@ class MainWindow(Gtk.ApplicationWindow):
def update_song_download_progress(self, song_id: str, progress: DownloadProgress):
if progress.type == DownloadProgress.Type.QUEUED:
# Create and add the box to show the progress.
self._current_download_boxes[song_id] = DownloadStatusBox(song_id)
self.current_downloads_box.add(self._current_download_boxes[song_id])
self._pending_downloads.add(song_id)
elif progress.type in (
DownloadProgress.Type.DONE,
DownloadProgress.Type.CANCELLED,
):
# Remove and delet the box for the download.
# Remove and delete the box for the download if it exists.
if song_id in self._current_download_boxes:
self.current_downloads_box.remove(self._current_download_boxes[song_id])
del self._current_download_boxes[song_id]
# The download is no longer pending.
if song_id in self._pending_downloads:
self._pending_downloads.remove(song_id)
elif progress.type == DownloadProgress.Type.ERROR:
GLib.idle_add(
self._current_download_boxes[song_id].set_error, progress.exception
)
self._current_download_boxes[song_id].set_error(progress.exception)
elif progress.type == DownloadProgress.Type.PROGRESS:
GLib.idle_add(
self._current_download_boxes[song_id].update_progress,
progress.progress_fraction,
if song_id not in self._current_download_boxes:
# Create and add the box to show the progress.
self._current_download_boxes[song_id] = DownloadStatusBox(song_id)
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])
self._pending_downloads.remove(song_id)
self._current_download_boxes[song_id].update_progress(
progress.progress_fraction
)
if len(self._current_download_boxes) == 0:
self.current_downloads_placeholder.show()
# 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(
label="",
halign=Gtk.Align.START,
name="current-downloads-list-pending-count",
)
self.current_downloads_box.pack_end(
self._pending_downloads_placeholder, True, True, 0
)
songs = util.pluralize("song", pending_download_count)
self._pending_downloads_placeholder.set_text(
f"+{pending_download_count} pending {songs}"
)
else:
self.current_downloads_placeholder.hide()
if self._pending_downloads_placeholder:
self.current_downloads_box.remove(self._pending_downloads_placeholder)
self._pending_downloads_placeholder = None
def _create_download_status_box(self, song_id: str) -> Gtk.Box:
status_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
# Show or hide the placeholder depending on whether or not there's anything to
# show.
current_downloads = len(self._current_download_boxes)
if current_downloads == 0:
if not self._current_downloads_placeholder:
self._current_downloads_placeholder = Gtk.Label(
label="<i>No current downloads</i>",
use_markup=True,
name="current-downloads-list-placeholder",
)
self.current_downloads_box.add(self._current_downloads_placeholder)
else:
if self._current_downloads_placeholder:
self.current_downloads_box.remove(self._current_downloads_placeholder)
self._current_downloads_placeholder = None
return status_box
self.current_downloads_box.show_all()
self.cancel_all_button.set_sensitive(
pending_download_count + current_downloads > 0
)
def _on_cancel_all_clicked(self, _):
AdapterManager.cancel_download_songs(
{*self._pending_downloads, *self._current_download_boxes.keys()}
)
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 _create_stack(self, **kwargs: Gtk.Widget) -> Gtk.Stack:
stack = Gtk.Stack()
@@ -370,21 +428,22 @@ class MainWindow(Gtk.ApplicationWindow):
)
)
current_downloads_label.get_style_context().add_class("menu-label")
cancel_all_button = IconButton(
self.cancel_all_button = IconButton(
"process-stop-symbolic", "Cancel all downloads", sensitive=False
)
current_downloads_header.pack_end(cancel_all_button, False, False, 0)
self.cancel_all_button.connect("clicked", self._on_cancel_all_clicked)
current_downloads_header.pack_end(self.cancel_all_button, False, False, 0)
vbox.add(current_downloads_header)
self.current_downloads_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, name="current-downloads-list"
)
self.current_downloads_placeholder = Gtk.Label(
self._current_downloads_placeholder = Gtk.Label(
label="<i>No current downloads</i>",
use_markup=True,
name="current-downloads-list-placeholder",
)
self.current_downloads_box.add(self.current_downloads_placeholder)
self.current_downloads_box.add(self._current_downloads_placeholder)
vbox.add(self.current_downloads_box)
clear_cache = self._create_model_button("Clear Cache", menu_name="clear-cache")
@@ -895,25 +954,78 @@ class MainWindow(Gtk.ApplicationWindow):
class DownloadStatusBox(Gtk.Box):
__gsignals__ = {
"cancel-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,),),
"retry-clicked": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str,),),
}
def __init__(self, song_id: str):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
song = AdapterManager.get_song_details(song_id).result()
self.song = AdapterManager.get_song_details(song_id).result()
self.song_label = Gtk.Label(label=song.title, halign=Gtk.Align.START)
image = SpinnerImage(
image_size=30, image_name="current-downloads-cover-art-image"
)
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)
self.song_label = Gtk.Label(
label=label_text,
ellipsize=Pango.EllipsizeMode.END,
max_width_chars=30,
name="currently-downloading-song-title",
use_markup=True,
halign=Gtk.Align.START,
)
self.pack_start(self.song_label, True, True, 5)
self.download_progress = Gtk.ProgressBar(show_text=True)
self.download_progress.set_text(song.title)
self.add(self.download_progress)
self.progress_retry_container = Gtk.Box()
self.add(self.progress_retry_container)
self.cancel_button = IconButton(
"process-stop-symbolic", tooltip_text="Cancel download", relief=True
"process-stop-symbolic", tooltip_text="Cancel download"
)
self.cancel_button.connect(
"clicked", lambda *a: self.emit("cancel-clicked", self.song.id)
)
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())
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):
print(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