Update the UI more efficiently, make it much faster
Remove all unnecessary full episode list reloads and reduce the number of UI updates while downloading to make the UI feel (and be) more responsive and also not need to reset the scroll position because of a full channel/episode list reload. That's good stuff!
This commit is contained in:
parent
e1c7c3a381
commit
c2ac54ff11
|
@ -93,6 +93,10 @@ class DownloadThread(threading.Thread):
|
|||
threading.Thread.__init__( self)
|
||||
self.setDaemon( True)
|
||||
|
||||
if gpodder.interface == gpodder.MAEMO:
|
||||
# Only update status every 3 seconds on Maemo
|
||||
self.MAX_UPDATES_PER_SEC = 1./3.
|
||||
|
||||
self.channel = channel
|
||||
self.episode = episode
|
||||
|
||||
|
|
|
@ -547,6 +547,14 @@ class gPodder(GladeWidget):
|
|||
elif gl.config.minimize_to_tray:
|
||||
self.tray_icon.set_visible(False)
|
||||
|
||||
# a dictionary that maps episode URLs to the current
|
||||
# treeAvailable row numbers to generate tree paths
|
||||
self.url_path_mapping = {}
|
||||
|
||||
# a dictionary that maps channel URLs to the current
|
||||
# treeChannels row numbers to generate tree paths
|
||||
self.channel_url_path_mapping = {}
|
||||
|
||||
services.download_status_manager.register( 'list-changed', self.download_status_updated)
|
||||
services.download_status_manager.register( 'progress-changed', self.download_progress_updated)
|
||||
services.cover_downloader.register('cover-available', self.cover_download_finished)
|
||||
|
@ -565,6 +573,7 @@ class gPodder(GladeWidget):
|
|||
# Subscribed channels
|
||||
self.active_channel = None
|
||||
self.channels = load_channels()
|
||||
self.channel_list_changed = True
|
||||
self.update_podcasts_tab()
|
||||
|
||||
# load list of user applications for audio playback
|
||||
|
@ -1112,11 +1121,22 @@ class gPodder(GladeWidget):
|
|||
if count == 0:
|
||||
if len(gl.config.cmd_all_downloads_complete) > 0:
|
||||
Thread(target=gl.ext_command_thread, args=(self.notification,gl.config.cmd_all_downloads_complete)).start()
|
||||
|
||||
def update_selected_episode_list_icons(self):
|
||||
"""
|
||||
Updates the status icons in the episode list
|
||||
"""
|
||||
selection = self.treeAvailable.get_selection()
|
||||
(model, paths) = selection.get_selected_rows()
|
||||
for path in paths:
|
||||
iter = model.get_iter(path)
|
||||
self.active_channel.iter_set_downloading_columns(model, iter)
|
||||
|
||||
def playback_episode(self, episode, stream=False):
|
||||
(success, application) = gl.playback_episode(episode, stream)
|
||||
if not success:
|
||||
self.show_message( _('The selected player application cannot be found. Please check your media player settings in the preferences dialog.'), _('Error opening player: %s') % ( saxutils.escape( application), ))
|
||||
self.update_selected_episode_list_icons()
|
||||
self.updateComboBox(only_selected_channel=True)
|
||||
|
||||
def treeAvailable_search_equal( self, model, column, key, iter, data = None):
|
||||
|
@ -1241,14 +1261,20 @@ class gPodder(GladeWidget):
|
|||
|
||||
return (can_play, can_download, can_transfer, can_cancel, can_delete, open_instead_of_play)
|
||||
|
||||
def download_status_updated( self):
|
||||
def download_status_updated(self, episode_urls, channel_urls):
|
||||
count = services.download_status_manager.count()
|
||||
if count:
|
||||
self.labelDownloads.set_text( _('Downloads (%d)') % count)
|
||||
else:
|
||||
self.labelDownloads.set_text( _('Downloads'))
|
||||
|
||||
self.updateComboBox()
|
||||
model = self.treeAvailable.get_model()
|
||||
for url in episode_urls:
|
||||
if url in self.url_path_mapping:
|
||||
path = (self.url_path_mapping[url],)
|
||||
self.active_channel.iter_set_downloading_columns(model, model.get_iter(path))
|
||||
|
||||
self.updateComboBox(only_these_urls=channel_urls)
|
||||
|
||||
def on_cbMaxDownloads_toggled(self, widget, *args):
|
||||
self.spinMaxDownloads.set_sensitive(self.cbMaxDownloads.get_active())
|
||||
|
@ -1260,31 +1286,60 @@ class gPodder(GladeWidget):
|
|||
self.updateComboBox()
|
||||
self.updateTreeView()
|
||||
|
||||
def updateComboBox(self, selected_url=None, only_selected_channel=False):
|
||||
(model, iter) = self.treeChannels.get_selection().get_selected()
|
||||
def updateComboBox(self, selected_url=None, only_selected_channel=False, only_these_urls=None):
|
||||
selection = self.treeChannels.get_selection()
|
||||
(model, iter) = selection.get_selected()
|
||||
|
||||
if only_selected_channel:
|
||||
# very cheap! only update selected channel
|
||||
if iter and self.active_channel is not None:
|
||||
update_channel_model_by_iter( self.treeChannels.get_model(),
|
||||
iter, self.active_channel, self.channel_colors,
|
||||
self.cover_cache, *(gl.config.podcast_list_icon_size,)*2 )
|
||||
update_channel_model_by_iter(model, iter,
|
||||
self.active_channel, self.channel_colors,
|
||||
self.cover_cache,
|
||||
gl.config.podcast_list_icon_size,
|
||||
gl.config.podcast_list_icon_size)
|
||||
elif not self.channel_list_changed:
|
||||
# we can keep the model, but have to update some
|
||||
if only_these_urls is None:
|
||||
# still cheaper than reloading the whole list
|
||||
iter = model.get_iter_first()
|
||||
while iter is not None:
|
||||
(index,) = model.get_path(iter)
|
||||
update_channel_model_by_iter(model, iter,
|
||||
self.channels[index], self.channel_colors,
|
||||
self.cover_cache,
|
||||
gl.config.podcast_list_icon_size,
|
||||
gl.config.podcast_list_icon_size)
|
||||
iter = model.iter_next(iter)
|
||||
else:
|
||||
# ok, we got a bunch of urls to update
|
||||
for url in only_these_urls:
|
||||
index = self.channel_url_path_mapping[url]
|
||||
path = (index,)
|
||||
iter = model.get_iter(path)
|
||||
update_channel_model_by_iter(model, iter,
|
||||
self.channels[index], self.channel_colors,
|
||||
self.cover_cache,
|
||||
gl.config.podcast_list_icon_size,
|
||||
gl.config.podcast_list_icon_size)
|
||||
else:
|
||||
if model and iter and selected_url is None:
|
||||
# Get the URL of the currently-selected podcast
|
||||
selected_url = model.get_value(iter, 0)
|
||||
|
||||
rect = self.treeChannels.get_visible_rect()
|
||||
self.treeChannels.set_model( channels_to_model( self.channels,
|
||||
self.channel_colors, self.cover_cache,
|
||||
*(gl.config.podcast_list_icon_size,)*2 ))
|
||||
util.idle_add(self.treeChannels.scroll_to_point, rect.x, rect.y)
|
||||
(model, urls) = channels_to_model(self.channels,
|
||||
self.channel_colors, self.cover_cache,
|
||||
gl.config.podcast_list_icon_size,
|
||||
gl.config.podcast_list_icon_size)
|
||||
|
||||
self.channel_url_path_mapping = dict(zip(urls, range(len(urls))))
|
||||
self.treeChannels.set_model(model)
|
||||
|
||||
try:
|
||||
selected_path = (0,)
|
||||
# Find the previously-selected URL in the new
|
||||
# model if we have an URL (else select first)
|
||||
if selected_url is not None:
|
||||
model = self.treeChannels.get_model()
|
||||
pos = model.get_iter_first()
|
||||
while pos is not None:
|
||||
url = model.get_value(pos, 0)
|
||||
|
@ -1296,19 +1351,20 @@ class gPodder(GladeWidget):
|
|||
self.treeChannels.get_selection().select_path(selected_path)
|
||||
except:
|
||||
log( 'Cannot set selection on treeChannels', sender = self)
|
||||
self.on_treeChannels_cursor_changed( self.treeChannels)
|
||||
self.on_treeChannels_cursor_changed( self.treeChannels)
|
||||
self.channel_list_changed = False
|
||||
|
||||
def updateTreeView(self, retain_position=True):
|
||||
def updateTreeView(self):
|
||||
if self.channels and self.active_channel is not None:
|
||||
rect = self.treeAvailable.get_visible_rect()
|
||||
self.treeAvailable.set_model(self.active_channel.tree_model)
|
||||
if retain_position:
|
||||
util.idle_add(self.treeAvailable.scroll_to_point, rect.x, rect.y)
|
||||
(model, urls) = self.active_channel.get_tree_model()
|
||||
self.treeAvailable.set_model(model)
|
||||
self.url_path_mapping = dict(zip(urls, range(len(urls))))
|
||||
self.treeAvailable.columns_autosize()
|
||||
self.play_or_download()
|
||||
else:
|
||||
if self.treeAvailable.get_model():
|
||||
self.treeAvailable.get_model().clear()
|
||||
model = self.treeAvailable.get_model()
|
||||
if model is not None:
|
||||
model.clear()
|
||||
|
||||
def drag_data_received(self, widget, context, x, y, sel, ttype, time):
|
||||
(path, column, rx, ry) = self.treeChannels.get_path_at_pos( x, y) or (None,)*4
|
||||
|
@ -1393,6 +1449,7 @@ class gPodder(GladeWidget):
|
|||
def add_new_channel_finish( self, channel, url, error, ask_download_new, quiet, waitdlg):
|
||||
if channel is not None:
|
||||
self.channels.append( channel)
|
||||
self.channel_list_changed = True
|
||||
save_channels( self.channels)
|
||||
if not quiet:
|
||||
# download changed channels and select the new episode in the UI afterwards
|
||||
|
@ -1408,6 +1465,7 @@ class gPodder(GladeWidget):
|
|||
# data won't show up in the channel editor.
|
||||
# TODO: Only updated the newly added feed to save some cpu cycles
|
||||
self.channels = load_channels()
|
||||
self.channel_list_changed = True
|
||||
|
||||
if ask_download_new:
|
||||
new_episodes = channel.get_new_episodes()
|
||||
|
@ -1499,6 +1557,7 @@ class gPodder(GladeWidget):
|
|||
|
||||
# open the episodes selection dialog
|
||||
self.channels = load_channels()
|
||||
self.channel_list_changed = True
|
||||
self.updateComboBox()
|
||||
if not self.feed_cache_update_cancelled:
|
||||
self.download_all_new(channels=channels)
|
||||
|
@ -1546,6 +1605,7 @@ class gPodder(GladeWidget):
|
|||
|
||||
if not force_update:
|
||||
self.channels = load_channels()
|
||||
self.channel_list_changed = True
|
||||
self.updateComboBox()
|
||||
return
|
||||
|
||||
|
@ -1677,6 +1737,7 @@ class gPodder(GladeWidget):
|
|||
except Exception, e:
|
||||
log( 'Warning: Error in for_each_selected_episode_url for URL %s: %s', url, e, sender = self)
|
||||
|
||||
self.update_selected_episode_list_icons()
|
||||
self.updateComboBox(only_selected_channel=True)
|
||||
|
||||
def delete_episode_list( self, episodes, confirm = True):
|
||||
|
@ -1752,7 +1813,6 @@ class gPodder(GladeWidget):
|
|||
self.for_each_selected_episode_url(callback)
|
||||
|
||||
def on_channel_toggle_lock_activate(self, widget, toggle=True, new_value=False):
|
||||
|
||||
self.active_channel.channel_is_locked = not self.active_channel.channel_is_locked
|
||||
db.update_channel_lock(self.active_channel)
|
||||
|
||||
|
@ -1763,8 +1823,8 @@ class gPodder(GladeWidget):
|
|||
|
||||
for episode in self.active_channel.get_all_episodes():
|
||||
db.mark_episode(episode.url, is_locked=self.active_channel.channel_is_locked)
|
||||
self.updateComboBox()
|
||||
|
||||
self.updateComboBox(only_selected_channel=True)
|
||||
|
||||
def on_item_email_subscriptions_activate(self, widget):
|
||||
if not self.channels:
|
||||
|
@ -1828,13 +1888,13 @@ class gPodder(GladeWidget):
|
|||
else:
|
||||
gPodderWelcome(center_on_widget=self.gPodder, show_example_podcasts_callback=self.on_itemImportChannels_activate, setup_my_gpodder_callback=self.on_download_from_mygpo)
|
||||
|
||||
def download_episode_list( self, episodes):
|
||||
def download_episode_list(self, episodes):
|
||||
services.download_status_manager.start_batch_mode()
|
||||
for episode in episodes:
|
||||
log('Downloading episode: %s', episode.title, sender = self)
|
||||
filename = episode.local_filename()
|
||||
if not episode.was_downloaded(and_exists=True) and not services.download_status_manager.is_download_in_progress( episode.url):
|
||||
download.DownloadThread( episode.channel, episode, self.notification).start()
|
||||
download.DownloadThread(episode.channel, episode, self.notification).start()
|
||||
services.download_status_manager.end_batch_mode()
|
||||
|
||||
def new_episodes_show(self, episodes):
|
||||
|
@ -2279,6 +2339,7 @@ class gPodder(GladeWidget):
|
|||
# Remove the channel
|
||||
self.active_channel.delete()
|
||||
self.channels.remove(self.active_channel)
|
||||
self.channel_list_changed = True
|
||||
save_channels(self.channels)
|
||||
|
||||
# Re-load the channels and select the desired new channel
|
||||
|
@ -2397,10 +2458,14 @@ class gPodder(GladeWidget):
|
|||
def on_treeChannels_cursor_changed(self, widget, *args):
|
||||
( model, iter ) = self.treeChannels.get_selection().get_selected()
|
||||
|
||||
if model is not None and iter != None:
|
||||
id = model.get_path( iter)[0]
|
||||
if model is not None and iter is not None:
|
||||
old_active_channel = self.active_channel
|
||||
(id,) = model.get_path(iter)
|
||||
self.active_channel = self.channels[id]
|
||||
|
||||
if self.active_channel == old_active_channel:
|
||||
return
|
||||
|
||||
if gpodder.interface == gpodder.MAEMO:
|
||||
self.set_title(self.active_channel.title)
|
||||
self.itemEditChannel.show_all()
|
||||
|
@ -2417,7 +2482,7 @@ class gPodder(GladeWidget):
|
|||
self.itemRemoveChannel.hide_all()
|
||||
self.channel_toggle_lock.hide_all()
|
||||
|
||||
self.updateTreeView(False)
|
||||
self.updateTreeView()
|
||||
|
||||
def on_entryAddChannel_changed(self, widget, *args):
|
||||
active = self.entryAddChannel.get_text() not in ('', self.ENTER_URL_TEXT)
|
||||
|
@ -2473,10 +2538,13 @@ class gPodder(GladeWidget):
|
|||
self.playback_episode(episode, stream=True)
|
||||
elif do_epdialog:
|
||||
play_callback = lambda: self.playback_episode(episode)
|
||||
download_callback = lambda: self.download_episode_list([episode])
|
||||
def download_callback():
|
||||
self.download_episode_list([episode])
|
||||
self.play_or_download()
|
||||
gPodderEpisode(episode=episode, download_callback=download_callback, play_callback=play_callback)
|
||||
else:
|
||||
self.download_episode_list(episodes)
|
||||
self.play_or_download()
|
||||
except:
|
||||
log('Error in on_treeAvailable_row_activated', traceback=True, sender=self)
|
||||
|
||||
|
@ -2523,6 +2591,7 @@ class gPodder(GladeWidget):
|
|||
for url in cancel_urls:
|
||||
services.download_status_manager.cancel_by_url( url)
|
||||
services.download_status_manager.end_batch_mode()
|
||||
self.play_or_download()
|
||||
|
||||
def on_btnCancelDownloadStatus_clicked(self, widget, *args):
|
||||
self.on_treeDownloads_row_activated( widget, None)
|
||||
|
|
|
@ -344,21 +344,6 @@ class podcastChannel(object):
|
|||
def get_all_episodes(self):
|
||||
return db.load_episodes(self, factory = lambda d: podcastItem.create_from_dict(d, self))
|
||||
|
||||
# not used anymore
|
||||
def update_model( self):
|
||||
self.update_save_dir_size()
|
||||
model = self.tree_model
|
||||
|
||||
iter = model.get_iter_first()
|
||||
while iter is not None:
|
||||
self.iter_set_downloading_columns(model, iter)
|
||||
iter = model.iter_next( iter)
|
||||
|
||||
@property
|
||||
def tree_model( self):
|
||||
log('Returning TreeModel for %s', self.url, sender = self)
|
||||
return self.items_liststore()
|
||||
|
||||
def iter_set_downloading_columns( self, model, iter, episode=None):
|
||||
global ICON_AUDIO_FILE, ICON_VIDEO_FILE
|
||||
global ICON_DOWNLOADING, ICON_DELETED, ICON_NEW
|
||||
|
@ -403,7 +388,7 @@ class podcastChannel(object):
|
|||
|
||||
model.set( iter, 4, status_icon)
|
||||
|
||||
def items_liststore( self):
|
||||
def get_tree_model(self):
|
||||
"""
|
||||
Return a gtk.ListStore containing episodes for this channel
|
||||
"""
|
||||
|
@ -411,6 +396,8 @@ class podcastChannel(object):
|
|||
gobject.TYPE_BOOLEAN, gtk.gdk.Pixbuf, gobject.TYPE_STRING, gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING )
|
||||
|
||||
log('Returning TreeModel for %s', self.url, sender = self)
|
||||
urls = []
|
||||
for item in self.get_all_episodes():
|
||||
description = item.title_and_description
|
||||
|
||||
|
@ -423,9 +410,10 @@ class podcastChannel(object):
|
|||
True, None, item.cute_pubdate(), description, util.remove_html_tags(item.description),
|
||||
item.local_filename(), item.extension()))
|
||||
self.iter_set_downloading_columns( new_model, new_iter, episode=item)
|
||||
urls.append(item.url)
|
||||
|
||||
self.update_save_dir_size()
|
||||
return new_model
|
||||
return (new_model, urls)
|
||||
|
||||
def find_episode( self, url):
|
||||
return db.load_episode(url, factory=lambda x: podcastItem.create_from_dict(x, self))
|
||||
|
@ -734,16 +722,17 @@ class podcastItem(object):
|
|||
|
||||
|
||||
def update_channel_model_by_iter( model, iter, channel, color_dict,
|
||||
cover_cache=None, max_width=0, max_height=0 ):
|
||||
cover_cache=None, max_width=0, max_height=0, initialize_all=False):
|
||||
|
||||
count_downloaded = channel.stat(state=db.STATE_DOWNLOADED)
|
||||
count_new = channel.stat(state=db.STATE_NORMAL, is_played=False)
|
||||
count_unplayed = channel.stat(state=db.STATE_DOWNLOADED, is_played=False)
|
||||
|
||||
channel.iter = iter
|
||||
model.set(iter, 0, channel.url)
|
||||
model.set(iter, 1, channel.title)
|
||||
if initialize_all:
|
||||
model.set(iter, 0, channel.url)
|
||||
|
||||
model.set(iter, 1, channel.title)
|
||||
title_markup = saxutils.escape(channel.title)
|
||||
description_markup = saxutils.escape(util.get_first_line(channel.description) or _('No description available'))
|
||||
d = []
|
||||
|
@ -773,23 +762,26 @@ def update_channel_model_by_iter( model, iter, channel, color_dict,
|
|||
else:
|
||||
model.set(iter, 7, False)
|
||||
|
||||
# Load the cover if we have it, but don't download
|
||||
# it if it's not available (to avoid blocking here)
|
||||
pixbuf = services.cover_downloader.get_cover(channel, avoid_downloading=True)
|
||||
new_pixbuf = None
|
||||
if pixbuf is not None:
|
||||
new_pixbuf = util.resize_pixbuf_keep_ratio(pixbuf, max_width, max_height, channel.url, cover_cache)
|
||||
model.set(iter, 5, new_pixbuf or pixbuf)
|
||||
if initialize_all:
|
||||
# Load the cover if we have it, but don't download
|
||||
# it if it's not available (to avoid blocking here)
|
||||
pixbuf = services.cover_downloader.get_cover(channel, avoid_downloading=True)
|
||||
new_pixbuf = None
|
||||
if pixbuf is not None:
|
||||
new_pixbuf = util.resize_pixbuf_keep_ratio(pixbuf, max_width, max_height, channel.url, cover_cache)
|
||||
model.set(iter, 5, new_pixbuf or pixbuf)
|
||||
|
||||
def channels_to_model(channels, color_dict, cover_cache=None, max_width=0, max_height=0):
|
||||
new_model = gtk.ListStore( str, str, str, gtk.gdk.Pixbuf, int,
|
||||
gtk.gdk.Pixbuf, str, bool, str )
|
||||
|
||||
urls = []
|
||||
for channel in channels:
|
||||
update_channel_model_by_iter( new_model, new_model.append(), channel,
|
||||
color_dict, cover_cache, max_width, max_height )
|
||||
update_channel_model_by_iter(new_model, new_model.append(), channel,
|
||||
color_dict, cover_cache, max_width, max_height, True)
|
||||
urls.append(channel.url)
|
||||
|
||||
return new_model
|
||||
return (new_model, urls)
|
||||
|
||||
|
||||
def load_channels():
|
||||
|
|
|
@ -283,7 +283,7 @@ cover_downloader = CoverDownloader()
|
|||
class DownloadStatusManager(ObservableService):
|
||||
COLUMN_NAMES = { 0: 'episode', 1: 'speed', 2: 'progress', 3: 'url' }
|
||||
COLUMN_TYPES = ( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_FLOAT, gobject.TYPE_STRING )
|
||||
PROGRESS_HOLDDOWN_TIMEOUT = 1
|
||||
PROGRESS_HOLDDOWN_TIMEOUT = 5
|
||||
|
||||
def __init__( self):
|
||||
self.status_list = {}
|
||||
|
@ -303,8 +303,9 @@ class DownloadStatusManager(ObservableService):
|
|||
|
||||
# batch add in progress?
|
||||
self.batch_mode_enabled = False
|
||||
# we set this flag if we would notify inside batch mode
|
||||
self.batch_mode_notify_flag = False
|
||||
# remember which episodes and channels changed during batch mode
|
||||
self.batch_mode_changed_episode_urls = set()
|
||||
self.batch_mode_changed_channel_urls = set()
|
||||
|
||||
# Used to notify all threads that they should
|
||||
# re-check if they can acquire the lock
|
||||
|
@ -333,9 +334,10 @@ class DownloadStatusManager(ObservableService):
|
|||
This sends out a notification that the list has changed.
|
||||
"""
|
||||
self.batch_mode_enabled = False
|
||||
if self.batch_mode_notify_flag:
|
||||
self.notify('list-changed')
|
||||
self.batch_mode_notify_flag = False
|
||||
if len(self.batch_mode_changed_episode_urls) + len(self.batch_mode_changed_channel_urls) > 0:
|
||||
self.notify('list-changed', self.batch_mode_changed_episode_urls, self.batch_mode_changed_channel_urls)
|
||||
self.batch_mode_changed_episode_urls = set()
|
||||
self.batch_mode_changed_channel_urls = set()
|
||||
|
||||
def notify_progress(self, force=False):
|
||||
now = (self.count(), self.average_progress())
|
||||
|
@ -404,9 +406,10 @@ class DownloadStatusManager(ObservableService):
|
|||
self.tree_model_lock.acquire()
|
||||
self.status_list[id] = { 'iter': self.tree_model.append(), 'thread': thread, 'progress': 0.0, 'speed': _('Queued'), }
|
||||
if self.batch_mode_enabled:
|
||||
self.batch_mode_notify_flag = True
|
||||
self.batch_mode_changed_episode_urls.add(thread.episode.url)
|
||||
self.batch_mode_changed_channel_urls.add(thread.channel.url)
|
||||
else:
|
||||
self.notify('list-changed')
|
||||
self.notify('list-changed', [thread.episode.url], [thread.channel.url])
|
||||
self.tree_model_lock.release()
|
||||
|
||||
def remove_download_id( self, id):
|
||||
|
@ -418,15 +421,22 @@ class DownloadStatusManager(ObservableService):
|
|||
util.idle_add(self.remove_iter, iter)
|
||||
self.tree_model_lock.release()
|
||||
self.status_list[id]['iter'] = None
|
||||
episode_url = self.status_list[id]['thread'].episode.url
|
||||
channel_url = self.status_list[id]['thread'].channel.url
|
||||
self.status_list[id]['thread'].cancel()
|
||||
del self.status_list[id]
|
||||
if not self.has_items():
|
||||
# Reset the counter now
|
||||
self.downloads_done_bytes = 0
|
||||
if self.batch_mode_enabled:
|
||||
self.batch_mode_notify_flag = True
|
||||
else:
|
||||
self.notify('list-changed')
|
||||
episode_url = None
|
||||
channel_url = None
|
||||
|
||||
if self.batch_mode_enabled:
|
||||
self.batch_mode_changed_episode_urls.add(episode_url)
|
||||
self.batch_mode_changed_channel_urls.add(channel_url)
|
||||
else:
|
||||
self.notify('list-changed', [episode_url], [channel_url])
|
||||
self.notify_progress(force=True)
|
||||
|
||||
def count( self):
|
||||
|
|
Loading…
Reference in New Issue