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
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)

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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