Refactor ProgressIndicator.

Add on_ticks() and max_ticks to simplify code using progress indicators.
And support final ticks to set progress to 100% before a final long
operation.

Change interval from 100ms to 250ms, to allow more time to be spent on
the operation.
This commit is contained in:
auouymous 2023-01-03 05:58:42 -07:00
parent 733cdd2169
commit 36dbad9c53
5 changed files with 78 additions and 63 deletions

View File

@ -628,6 +628,7 @@ class gPodderCli(object):
show_guid = '--guid' in args show_guid = '--guid' in args
common.find_partial_downloads(self._model.get_podcasts(), common.find_partial_downloads(self._model.get_podcasts(),
noop,
noop, noop,
noop, noop,
on_finish) on_finish)
@ -687,6 +688,7 @@ class gPodderCli(object):
self._download_episodes(episodes) self._download_episodes(episodes)
common.find_partial_downloads(self._model.get_podcasts(), common.find_partial_downloads(self._model.get_podcasts(),
noop,
noop, noop,
noop, noop,
on_finish) on_finish)

View File

@ -62,26 +62,20 @@ class gPodderExtension:
number_of_episodes = len(episodes) number_of_episodes = len(episodes)
progress_indicator = ProgressIndicator( progress_indicator = ProgressIndicator(
_('Renaming all downloaded episodes'), _('Renaming all downloaded episodes'),
'', True, self.gpodder.get_dialog_parent()) '', True, self.gpodder.get_dialog_parent(), number_of_episodes)
progress_indicator.on_message('0 / %d' % number_of_episodes)
renamed_count = 0
for episode in episodes: for episode in episodes:
self.on_episode_downloaded(episode) self.on_episode_downloaded(episode)
renamed_count += 1 if not progress_indicator.on_tick():
progress_indicator.on_message('%d / %d' % (renamed_count, number_of_episodes)) break
progress_indicator.on_progress(renamed_count / number_of_episodes) renamed_count = progress_indicator.tick_counter
if time.time() >= progress_indicator.next_update:
progress_indicator.update_gui()
self.gpodder.force_ui_update()
if not progress_indicator.cancellable:
break
progress_indicator.on_finished() progress_indicator.on_finished()
self.gpodder.show_message(_('Renamed %(count)d downloaded episodes') % {'count': number_of_episodes}, if renamed_count > 0:
_('Renaming finished'), important=True) 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): def make_filename(self, current_filename, title, sortdate, podcast_title):
dirname = os.path.dirname(current_filename) dirname = os.path.dirname(current_filename)

View File

@ -47,7 +47,7 @@ def clean_up_downloads(delete_partial=False):
util.delete_file(tempfile) 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 """Find partial downloads and match them with episodes
channels - A list of all model.PodcastChannel objects 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: if not candidates:
break break
final_progress_callback()
for f in partial_files: for f in partial_files:
logger.warning('Partial file without episode: %s', f) logger.warning('Partial file without episode: %s', f)
util.delete_file(f) util.delete_file(f)

View File

@ -22,6 +22,7 @@ import time
from gi.repository import GLib, Gtk, Pango from gi.repository import GLib, Gtk, Pango
import gpodder import gpodder
from gpodder import util
from gpodder.gtkui.widgets import SpinningProgressIndicator from gpodder.gtkui.widgets import SpinningProgressIndicator
_ = gpodder.gettext _ = gpodder.gettext
@ -32,14 +33,15 @@ class ProgressIndicator(object):
DELAY = 500 DELAY = 500
# Time between GUI updates after window creation # 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.title = title
self.subtitle = subtitle self.subtitle = subtitle
self.cancellable = True if cancellable else False self.cancellable = True if cancellable else False
self.cancel_callback = cancellable self.cancel_callback = cancellable
self.cancel_id = 0 self.cancel_id = 0
self.cancelled = False
self.next_update = time.time() + (self.DELAY / 1000) self.next_update = time.time() + (self.DELAY / 1000)
self.parent = parent self.parent = parent
self.dialog = None self.dialog = None
@ -50,10 +52,19 @@ class ProgressIndicator(object):
self._progress_set = False self._progress_set = False
self.source_id = GLib.timeout_add(self.DELAY, self._create_progress) 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): def _on_delete_event(self, window, event):
if self.cancellable: if self.cancellable:
self.dialog.response(Gtk.ResponseType.CANCEL) self.dialog.response(Gtk.ResponseType.CANCEL)
self.cancellable = False self.cancellable = False
self.cancelled = True
return True return True
def _create_progress(self): def _create_progress(self):
@ -64,6 +75,7 @@ class ProgressIndicator(object):
if self.cancellable: if self.cancellable:
def cancel_callback(dialog, response): def cancel_callback(dialog, response):
self.cancellable = False self.cancellable = False
self.cancelled = True
self.dialog.set_deletable(False) self.dialog.set_deletable(False)
self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False) self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False)
if callable(self.cancel_callback): if callable(self.cancel_callback):
@ -78,8 +90,7 @@ class ProgressIndicator(object):
if isinstance(label, Gtk.Label): if isinstance(label, Gtk.Label):
label.set_selectable(False) label.set_selectable(False)
self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, self.cancellable)
self.cancellable)
self.progressbar = Gtk.ProgressBar() self.progressbar = Gtk.ProgressBar()
self.progressbar.set_show_text(True) self.progressbar.set_show_text(True)
@ -111,13 +122,6 @@ class ProgressIndicator(object):
self.next_update = time.time() + (self.INTERVAL / 1000) self.next_update = time.time() + (self.INTERVAL / 1000)
return True 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): def on_message(self, message):
if self.progressbar: if self.progressbar:
self.progressbar.set_text(message) self.progressbar.set_text(message)
@ -131,6 +135,39 @@ class ProgressIndicator(object):
else: else:
self._initial_progress = progress 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): def on_finished(self):
if self.dialog is not None: if self.dialog is not None:
if self.cancel_id: if self.cancel_id:

View File

@ -429,9 +429,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
def progress_callback(title, progress): def progress_callback(title, progress):
self.partial_downloads_indicator.on_message(title) self.partial_downloads_indicator.on_message(title)
self.partial_downloads_indicator.on_progress(progress) self.partial_downloads_indicator.on_progress(progress)
if time.time() >= self.partial_downloads_indicator.next_update: self.partial_downloads_indicator.on_tick() # not cancellable
self.partial_downloads_indicator.update_gui()
self.force_ui_update() def final_progress_callback():
self.partial_downloads_indicator.on_tick(final=_('Finishing...'))
def finish_progress_callback(resumable_episodes): def finish_progress_callback(resumable_episodes):
def offer_resuming(): def offer_resuming():
@ -452,6 +453,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
common.find_partial_downloads(self.channels, common.find_partial_downloads(self.channels,
start_progress_callback, start_progress_callback,
progress_callback, progress_callback,
final_progress_callback,
finish_progress_callback) finish_progress_callback)
def episode_object_by_uri(self, uri): def episode_object_by_uri(self, uri):
@ -1624,40 +1626,22 @@ class gPodder(BuilderWidget, dbus.service.Object):
else: else:
self.download_queue_manager.queue_task(task) 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): def _for_each_task_set_status(self, tasks, status, force_start=False):
count = len(tasks) count = len(tasks)
if count: if count:
progress_indicator = ProgressIndicator( progress_indicator = ProgressIndicator(
_('Queueing') if status == download.DownloadTask.QUEUED else _('Queueing') if status == download.DownloadTask.QUEUED else
_('Removing') if status is None else download.DownloadTask.STATUS_MESSAGE[status], _('Removing') if status is None else download.DownloadTask.STATUS_MESSAGE[status],
'', True, self.get_dialog_parent()) '', True, self.get_dialog_parent(), count)
progress_indicator.on_message('0 / %d' % count)
else: else:
progress_indicator = None progress_indicator = None
def progress_callback(title, progress): self.__for_each_task_set_status(tasks, status, force_start=force_start, progress_indicator=progress_indicator)
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)
if progress_indicator: if progress_indicator:
progress_indicator.on_finished() progress_indicator.on_finished()
def __for_each_task_set_status(self, tasks, status, force_start=False, progress_callback=None): def __for_each_task_set_status(self, tasks, status, force_start=False, progress_indicator=None):
count = len(tasks)
n = 0
episode_urls = set() episode_urls = set()
model = self.treeDownloads.get_model() model = self.treeDownloads.get_model()
has_queued_tasks = False has_queued_tasks = False
@ -1707,10 +1691,11 @@ class gPodder(BuilderWidget, dbus.service.Object):
else: else:
# We can (hopefully) simply set the task status here # We can (hopefully) simply set the task status here
task.status = status task.status = status
if progress_callback: if progress_indicator:
n += 1 if not progress_indicator.on_tick():
if not progress_callback('%d / %d' % (n, count), n / count):
break break
if progress_indicator:
progress_indicator.on_tick(final=_('Finishing...'))
# Update the tab title and downloads list # Update the tab title and downloads list
if has_queued_tasks: 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 download_episode_list(self, episodes, add_paused=False, force_start=False, downloader=None, hide_progress=False):
def queue_tasks(tasks, queued_existing_task): def queue_tasks(tasks, queued_existing_task):
n = 0
count = len(tasks) count = len(tasks)
if count and not hide_progress: if count and not hide_progress:
progress_indicator = ProgressIndicator( progress_indicator = ProgressIndicator(
_('Queueing'), _('Queueing'),
'', True, self.get_dialog_parent()) '', True, self.get_dialog_parent(), count)
progress_indicator.on_message('0 / %d' % count)
else: else:
progress_indicator = None progress_indicator = None
@ -3263,14 +3246,11 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.mygpo_client.on_download([task.episode]) self.mygpo_client.on_download([task.episode])
self.queue_task(task, force_start) self.queue_task(task, force_start)
if progress_indicator: if progress_indicator:
n += 1 if not progress_indicator.on_tick():
progress_indicator.on_message('%d / %d' % (n, count)) break
progress_indicator.on_progress(n / count) if progress_indicator:
if time.time() >= progress_indicator.next_update: progress_indicator.on_tick(final=_('Finishing...'))
progress_indicator.update_gui()
self.force_ui_update()
if not progress_indicator.cancellable:
break
if tasks or queued_existing_task: if tasks or queued_existing_task:
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF) self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
# Flush updated episode status # Flush updated episode status