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:
parent
733cdd2169
commit
36dbad9c53
2
bin/gpo
2
bin/gpo
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue