diff --git a/bin/gpo b/bin/gpo index cb438be0..2850783d 100755 --- a/bin/gpo +++ b/bin/gpo @@ -628,6 +628,7 @@ class gPodderCli(object): show_guid = '--guid' in args common.find_partial_downloads(self._model.get_podcasts(), + noop, noop, noop, on_finish) @@ -687,6 +688,7 @@ class gPodderCli(object): self._download_episodes(episodes) common.find_partial_downloads(self._model.get_podcasts(), + noop, noop, noop, on_finish) diff --git a/share/gpodder/extensions/rename_download.py b/share/gpodder/extensions/rename_download.py index 3db19d71..b783934e 100644 --- a/share/gpodder/extensions/rename_download.py +++ b/share/gpodder/extensions/rename_download.py @@ -62,26 +62,20 @@ class gPodderExtension: number_of_episodes = len(episodes) progress_indicator = ProgressIndicator( _('Renaming all downloaded episodes'), - '', True, self.gpodder.get_dialog_parent()) - progress_indicator.on_message('0 / %d' % number_of_episodes) + '', True, self.gpodder.get_dialog_parent(), number_of_episodes) - renamed_count = 0 for episode in episodes: self.on_episode_downloaded(episode) - renamed_count += 1 - progress_indicator.on_message('%d / %d' % (renamed_count, number_of_episodes)) - progress_indicator.on_progress(renamed_count / number_of_episodes) - if time.time() >= progress_indicator.next_update: - progress_indicator.update_gui() - self.gpodder.force_ui_update() - if not progress_indicator.cancellable: - break + if not progress_indicator.on_tick(): + break + renamed_count = progress_indicator.tick_counter progress_indicator.on_finished() - self.gpodder.show_message(_('Renamed %(count)d downloaded episodes') % {'count': number_of_episodes}, - _('Renaming finished'), important=True) + if renamed_count > 0: + self.gpodder.show_message(_('Renamed %(count)d downloaded episodes') % {'count': renamed_count}, + _('Renaming finished'), important=True) def make_filename(self, current_filename, title, sortdate, podcast_title): dirname = os.path.dirname(current_filename) diff --git a/src/gpodder/common.py b/src/gpodder/common.py index dc94bbc5..a69d6539 100644 --- a/src/gpodder/common.py +++ b/src/gpodder/common.py @@ -47,7 +47,7 @@ def clean_up_downloads(delete_partial=False): util.delete_file(tempfile) -def find_partial_downloads(channels, start_progress_callback, progress_callback, finish_progress_callback): +def find_partial_downloads(channels, start_progress_callback, progress_callback, final_progress_callback, finish_progress_callback): """Find partial downloads and match them with episodes channels - A list of all model.PodcastChannel objects @@ -86,6 +86,8 @@ def find_partial_downloads(channels, start_progress_callback, progress_callback, if not candidates: break + final_progress_callback() + for f in partial_files: logger.warning('Partial file without episode: %s', f) util.delete_file(f) diff --git a/src/gpodder/gtkui/interface/progress.py b/src/gpodder/gtkui/interface/progress.py index 8edb4f30..917fcec4 100644 --- a/src/gpodder/gtkui/interface/progress.py +++ b/src/gpodder/gtkui/interface/progress.py @@ -22,6 +22,7 @@ import time from gi.repository import GLib, Gtk, Pango import gpodder +from gpodder import util from gpodder.gtkui.widgets import SpinningProgressIndicator _ = gpodder.gettext @@ -32,14 +33,15 @@ class ProgressIndicator(object): DELAY = 500 # Time between GUI updates after window creation - INTERVAL = 100 + INTERVAL = 250 - def __init__(self, title, subtitle=None, cancellable=False, parent=None): + def __init__(self, title, subtitle=None, cancellable=False, parent=None, max_ticks=None): self.title = title self.subtitle = subtitle self.cancellable = True if cancellable else False self.cancel_callback = cancellable self.cancel_id = 0 + self.cancelled = False self.next_update = time.time() + (self.DELAY / 1000) self.parent = parent self.dialog = None @@ -50,10 +52,19 @@ class ProgressIndicator(object): self._progress_set = False self.source_id = GLib.timeout_add(self.DELAY, self._create_progress) + self.set_max_ticks(max_ticks) + + def set_max_ticks(self, max_ticks): + self.max_ticks = max_ticks + self.tick_counter = 0 + if max_ticks is not None: + self.on_message('0 / %d' % max_ticks) + def _on_delete_event(self, window, event): if self.cancellable: self.dialog.response(Gtk.ResponseType.CANCEL) self.cancellable = False + self.cancelled = True return True def _create_progress(self): @@ -64,6 +75,7 @@ class ProgressIndicator(object): if self.cancellable: def cancel_callback(dialog, response): self.cancellable = False + self.cancelled = True self.dialog.set_deletable(False) self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False) if callable(self.cancel_callback): @@ -78,8 +90,7 @@ class ProgressIndicator(object): if isinstance(label, Gtk.Label): label.set_selectable(False) - self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, - self.cancellable) + self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, self.cancellable) self.progressbar = Gtk.ProgressBar() self.progressbar.set_show_text(True) @@ -111,13 +122,6 @@ class ProgressIndicator(object): self.next_update = time.time() + (self.INTERVAL / 1000) return True - def update_gui(self): - if self.dialog: - if self.source_id: - GLib.source_remove(self.source_id) - self.source_id = 0 - self._update_gui() - def on_message(self, message): if self.progressbar: self.progressbar.set_text(message) @@ -131,6 +135,39 @@ class ProgressIndicator(object): else: self._initial_progress = progress + def on_tick(self, final=False): + if final: + # Dialog is no longer cancellable + self.cancellable = False + if self.dialog is not None: + self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False) + self.dialog.set_deletable(False) + elif 2 * (time.time() - (self.next_update - (self.DELAY / 1000))) > (self.DELAY / 1000): + # Assume final operation will take as long as all ticks and open dialog + if self.source_id: + GLib.source_remove(self.source_id) + self._create_progress() + + if self.max_ticks is not None and not final: + self.tick_counter += 1 + + if time.time() >= self.next_update or (final and self.dialog): + if type(final) == str: + self.on_message(final) + self.on_progress(1.0) + elif self.max_ticks is not None: + self.on_message('%d / %d' % (self.tick_counter, self.max_ticks)) + self.on_progress(self.tick_counter / self.max_ticks) + + # Allow UI to redraw. + util.idle_add(Gtk.main_quit) + # self._create_progress() or self._update_gui() is called by a timer to update the dialog + Gtk.main() + + if self.cancelled: + return False + return True + def on_finished(self): if self.dialog is not None: if self.cancel_id: diff --git a/src/gpodder/gtkui/main.py b/src/gpodder/gtkui/main.py index e1c13fc9..8aa3f3c5 100644 --- a/src/gpodder/gtkui/main.py +++ b/src/gpodder/gtkui/main.py @@ -429,9 +429,10 @@ class gPodder(BuilderWidget, dbus.service.Object): def progress_callback(title, progress): self.partial_downloads_indicator.on_message(title) self.partial_downloads_indicator.on_progress(progress) - if time.time() >= self.partial_downloads_indicator.next_update: - self.partial_downloads_indicator.update_gui() - self.force_ui_update() + self.partial_downloads_indicator.on_tick() # not cancellable + + def final_progress_callback(): + self.partial_downloads_indicator.on_tick(final=_('Finishing...')) def finish_progress_callback(resumable_episodes): def offer_resuming(): @@ -452,6 +453,7 @@ class gPodder(BuilderWidget, dbus.service.Object): common.find_partial_downloads(self.channels, start_progress_callback, progress_callback, + final_progress_callback, finish_progress_callback) def episode_object_by_uri(self, uri): @@ -1624,40 +1626,22 @@ class gPodder(BuilderWidget, dbus.service.Object): else: self.download_queue_manager.queue_task(task) - def force_ui_update(self): - def callback(): - Gtk.main_quit() - GLib.timeout_add(1, callback) - Gtk.main() - def _for_each_task_set_status(self, tasks, status, force_start=False): count = len(tasks) if count: progress_indicator = ProgressIndicator( _('Queueing') if status == download.DownloadTask.QUEUED else _('Removing') if status is None else download.DownloadTask.STATUS_MESSAGE[status], - '', True, self.get_dialog_parent()) - progress_indicator.on_message('0 / %d' % count) + '', True, self.get_dialog_parent(), count) else: progress_indicator = None - def progress_callback(title, progress): - progress_indicator.on_message(title) - progress_indicator.on_progress(progress) - if time.time() >= progress_indicator.next_update: - progress_indicator.update_gui() - self.force_ui_update() - if not progress_indicator.cancellable: - return False - return True - self.__for_each_task_set_status(tasks, status, force_start=force_start, progress_callback=progress_callback) + self.__for_each_task_set_status(tasks, status, force_start=force_start, progress_indicator=progress_indicator) if progress_indicator: progress_indicator.on_finished() - def __for_each_task_set_status(self, tasks, status, force_start=False, progress_callback=None): - count = len(tasks) - n = 0 + def __for_each_task_set_status(self, tasks, status, force_start=False, progress_indicator=None): episode_urls = set() model = self.treeDownloads.get_model() has_queued_tasks = False @@ -1707,10 +1691,11 @@ class gPodder(BuilderWidget, dbus.service.Object): else: # We can (hopefully) simply set the task status here task.status = status - if progress_callback: - n += 1 - if not progress_callback('%d / %d' % (n, count), n / count): + if progress_indicator: + if not progress_indicator.on_tick(): break + if progress_indicator: + progress_indicator.on_tick(final=_('Finishing...')) # Update the tab title and downloads list if has_queued_tasks: @@ -3245,13 +3230,11 @@ class gPodder(BuilderWidget, dbus.service.Object): def download_episode_list(self, episodes, add_paused=False, force_start=False, downloader=None, hide_progress=False): def queue_tasks(tasks, queued_existing_task): - n = 0 count = len(tasks) if count and not hide_progress: progress_indicator = ProgressIndicator( _('Queueing'), - '', True, self.get_dialog_parent()) - progress_indicator.on_message('0 / %d' % count) + '', True, self.get_dialog_parent(), count) else: progress_indicator = None @@ -3263,14 +3246,11 @@ class gPodder(BuilderWidget, dbus.service.Object): self.mygpo_client.on_download([task.episode]) self.queue_task(task, force_start) if progress_indicator: - n += 1 - progress_indicator.on_message('%d / %d' % (n, count)) - progress_indicator.on_progress(n / count) - if time.time() >= progress_indicator.next_update: - progress_indicator.update_gui() - self.force_ui_update() - if not progress_indicator.cancellable: - break + if not progress_indicator.on_tick(): + break + if progress_indicator: + progress_indicator.on_tick(final=_('Finishing...')) + if tasks or queued_existing_task: self.set_download_list_state(gPodderSyncUI.DL_ONEOFF) # Flush updated episode status