Wed, 14 May 2008 15:34:25 +0200 <thp@perli.net>
Download start performance improvements; fix podcast list handling * data/gpodder.glade: Convert the gPodderAddPodcastDialog from a GtkWindow to a real GtkDialog to make Escape key work and set the URL entry box to activate the "Add" button when Enter is pressed in it * src/gpodder/gui.py: Fix podcast selection in updateComboBox, so the currect podcast is selected when removing/adding podcasts; allow to pass a URL which should be selected after the update; use DownloadStatusManager's new batch mode to speed up starting many downloads at once (very visible speed improvement); adding podcasts has also been improved a bit with the new code * src/gpodder/services.py: Support batch mode (i.e. only notify after all episodes have been added to th download list); this speeds up the UI when multiple episodes are downloaded at once git-svn-id: svn://svn.berlios.de/gpodder/trunk@715 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
parent
1721b454c8
commit
bb4f2f11c5
4 changed files with 162 additions and 77 deletions
16
ChangeLog
16
ChangeLog
|
@ -1,3 +1,19 @@
|
|||
Wed, 14 May 2008 15:34:25 +0200 <thp@perli.net>
|
||||
Download start performance improvements; fix podcast list handling
|
||||
|
||||
* data/gpodder.glade: Convert the gPodderAddPodcastDialog from a
|
||||
GtkWindow to a real GtkDialog to make Escape key work and set the URL
|
||||
entry box to activate the "Add" button when Enter is pressed in it
|
||||
* src/gpodder/gui.py: Fix podcast selection in updateComboBox, so the
|
||||
currect podcast is selected when removing/adding podcasts; allow to
|
||||
pass a URL which should be selected after the update; use
|
||||
DownloadStatusManager's new batch mode to speed up starting many
|
||||
downloads at once (very visible speed improvement); adding podcasts
|
||||
has also been improved a bit with the new code
|
||||
* src/gpodder/services.py: Support batch mode (i.e. only notify after
|
||||
all episodes have been added to th download list); this speeds up the
|
||||
UI when multiple episodes are downloaded at once
|
||||
|
||||
Wed, 14 May 2008 11:59:20 +0200 <thp@perli.net>
|
||||
Updated Russian translation by Vladimir Zemlyakov
|
||||
|
||||
|
|
|
@ -7125,8 +7125,7 @@ Filesystem-based MP3 player</property>
|
|||
</child>
|
||||
</widget>
|
||||
|
||||
<widget class="GtkWindow" id="gPodderAddPodcastDialog">
|
||||
<property name="border_width">10</property>
|
||||
<widget class="GtkDialog" id="gPodderAddPodcastDialog">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Add a new podcast</property>
|
||||
<property name="type">GTK_WINDOW_TOPLEVEL</property>
|
||||
|
@ -7142,15 +7141,60 @@ Filesystem-based MP3 player</property>
|
|||
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
|
||||
<property name="focus_on_map">True</property>
|
||||
<property name="urgency_hint">False</property>
|
||||
<property name="has_separator">False</property>
|
||||
|
||||
<child>
|
||||
<child internal-child="vbox">
|
||||
<widget class="GtkVBox" id="vbox44">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="spacing">0</property>
|
||||
|
||||
<child internal-child="action_area">
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox5">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkButton" id="btn_close">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_btn_close_clicked" last_modification_time="Thu, 24 Apr 2008 17:14:53 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkButton" id="btn_add">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">gtk-add</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_btn_add_clicked" last_modification_time="Thu, 24 Apr 2008 17:14:57 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox42">
|
||||
<property name="border_width">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">5</property>
|
||||
|
@ -7191,7 +7235,7 @@ Filesystem-based MP3 player</property>
|
|||
<property name="text" translatable="yes"></property>
|
||||
<property name="has_frame">True</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="activates_default">False</property>
|
||||
<property name="activates_default">True</property>
|
||||
<signal name="changed" handler="on_entry_url_changed" last_modification_time="Thu, 24 Apr 2008 17:15:56 GMT"/>
|
||||
</widget>
|
||||
<packing>
|
||||
|
@ -7207,47 +7251,6 @@ Filesystem-based MP3 player</property>
|
|||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox5">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<property name="spacing">5</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkButton" id="btn_close">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal name="clicked" handler="on_btn_close_clicked" last_modification_time="Thu, 24 Apr 2008 17:14:53 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkButton" id="btn_add">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">gtk-add</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal name="clicked" handler="on_btn_add_clicked" last_modification_time="Thu, 24 Apr 2008 17:14:57 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
|
|
|
@ -558,6 +558,7 @@ class gPodder(GladeWidget):
|
|||
error_str = _('Feedparser error: %s') % saxutils.escape(error_str.strip())
|
||||
error_str = '<span foreground="#ff0000">%s</span>\n' % error_str
|
||||
tooltip.set_markup( '<b>%s</b>\n<small><i>%s</i></small>\n%s%s\n\n<small>%s</small>' % (saxutils.escape(channel.title), saxutils.escape(channel.url), error_str, saxutils.escape(channel.description), diskspace_str))
|
||||
|
||||
return True
|
||||
|
||||
self.last_tooltip_channel = None
|
||||
|
@ -982,23 +983,32 @@ class gPodder(GladeWidget):
|
|||
def on_cbLimitDownloads_toggled(self, widget, *args):
|
||||
self.spinLimitDownloads.set_sensitive(self.cbLimitDownloads.get_active())
|
||||
|
||||
def updateComboBox( self):
|
||||
( model, iter ) = self.treeChannels.get_selection().get_selected()
|
||||
def updateComboBox(self, selected_url=None):
|
||||
(model, iter) = self.treeChannels.get_selection().get_selected()
|
||||
|
||||
if model and iter:
|
||||
selected = model.get_path( iter)
|
||||
else:
|
||||
selected = (0,)
|
||||
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.treeChannels.scroll_to_point( rect.x, rect.y)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration( False)
|
||||
self.treeChannels.scroll_to_point( rect.x, rect.y)
|
||||
util.idle_add(self.treeChannels.scroll_to_point, rect.x, rect.y)
|
||||
|
||||
try:
|
||||
self.treeChannels.get_selection().select_path( selected)
|
||||
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)
|
||||
if url == selected_url:
|
||||
selected_path = model.get_path(pos)
|
||||
break
|
||||
pos = model.iter_next(pos)
|
||||
|
||||
self.treeChannels.get_selection().select_path(selected_path)
|
||||
except:
|
||||
log( 'Cannot set selection on treeChannels', sender = self)
|
||||
self.on_treeChannels_cursor_changed( self.treeChannels)
|
||||
|
@ -1016,18 +1026,20 @@ class gPodder(GladeWidget):
|
|||
result = sel.data
|
||||
self.add_new_channel( result)
|
||||
|
||||
def add_new_channel( self, result = None, ask_download_new = True):
|
||||
def add_new_channel(self, result=None, ask_download_new=True):
|
||||
result = util.normalize_feed_url( result)
|
||||
|
||||
if result:
|
||||
for old_channel in self.channels:
|
||||
if old_channel.url == result:
|
||||
self.show_message( _('You have already subscribed to this podcast: %s') % ( saxutils.escape( old_channel.title), ), _('Already added'))
|
||||
log( 'Channel already exists: %s', result)
|
||||
# Select the existing channel in combo box
|
||||
for i in range( len( self.channels)):
|
||||
if self.channels[i] == old_channel:
|
||||
self.treeChannels.get_selection().select_path( (i,))
|
||||
self.on_treeChannels_cursor_changed(self.treeChannels)
|
||||
break
|
||||
self.show_message( _('You have already subscribed to this podcast: %s') % ( saxutils.escape( old_channel.title), ), _('Already added'))
|
||||
return
|
||||
log( 'Adding new channel: %s', result)
|
||||
try:
|
||||
|
@ -1039,8 +1051,8 @@ class gPodder(GladeWidget):
|
|||
if channel:
|
||||
self.channels.append( channel)
|
||||
save_channels( self.channels)
|
||||
# download changed channels
|
||||
self.update_feed_cache(force_update=False)
|
||||
# download changed channels and select the new episode in the UI afterwards
|
||||
self.update_feed_cache(force_update=False, select_url_afterwards=channel.url)
|
||||
|
||||
(username, password) = util.username_password_from_url( result)
|
||||
if username and self.show_confirmation( _('You have supplied <b>%s</b> as username and a password for this feed. Would you like to use the same authentication data for downloading episodes?') % ( saxutils.escape( username), ), _('Password authentication')):
|
||||
|
@ -1049,12 +1061,10 @@ class gPodder(GladeWidget):
|
|||
log('Saving authentication data for episode downloads..', sender = self)
|
||||
channel.save_settings()
|
||||
|
||||
# ask user to download some new episodes
|
||||
self.treeChannels.get_selection().select_path( (len( self.channels)-1,))
|
||||
self.active_channel = channel
|
||||
self.updateTreeView()
|
||||
if ask_download_new:
|
||||
self.new_episodes_show(channel.get_new_episodes())
|
||||
new_episodes = channel.get_new_episodes()
|
||||
if len(new_episodes):
|
||||
self.new_episodes_show(new_episodes)
|
||||
else:
|
||||
title = _('Error adding podcast')
|
||||
message = _('The podcast could not be added. Please check the spelling of the URL or try again later.')
|
||||
|
@ -1080,11 +1090,14 @@ class gPodder(GladeWidget):
|
|||
if count > 0:
|
||||
progressbar.set_fraction(float(position)/float(count))
|
||||
|
||||
def update_feed_cache_finish_callback(self, force_update=False, notify_no_new_episodes=False):
|
||||
def update_feed_cache_finish_callback(self, force_update=False, notify_no_new_episodes=False, select_url_afterwards=None):
|
||||
self.hboxUpdateFeeds.hide_all()
|
||||
self.btnUpdateFeeds.show_all()
|
||||
|
||||
self.updateComboBox()
|
||||
# If we want to select a specific podcast (via its URL)
|
||||
# after the update, we give it to updateComboBox here to
|
||||
# select exactly this podcast after updating the view
|
||||
self.updateComboBox(selected_url=select_url_afterwards)
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.set_status(None)
|
||||
|
@ -1130,13 +1143,13 @@ class gPodder(GladeWidget):
|
|||
self.pbFeedUpdate.set_text(_('Cancelling...'))
|
||||
self.feed_cache_update_cancelled = True
|
||||
|
||||
def update_feed_cache(self, force_update=True, notify_no_new_episodes=False):
|
||||
def update_feed_cache(self, force_update=True, notify_no_new_episodes=False, select_url_afterwards=None):
|
||||
if self.tray_icon:
|
||||
self.tray_icon.set_status(self.tray_icon.STATUS_UPDATING_FEED_CACHE)
|
||||
|
||||
# let's get down to business..
|
||||
callback_proc = lambda pos, count: util.idle_add(self.update_feed_cache_callback, self.pbFeedUpdate, pos, count, force_update)
|
||||
finish_proc = lambda: util.idle_add(self.update_feed_cache_finish_callback, force_update, notify_no_new_episodes)
|
||||
finish_proc = lambda: util.idle_add(self.update_feed_cache_finish_callback, force_update, notify_no_new_episodes, select_url_afterwards)
|
||||
|
||||
self.feed_cache_update_cancelled = False
|
||||
self.btnUpdateFeeds.hide_all()
|
||||
|
@ -1356,11 +1369,13 @@ class gPodder(GladeWidget):
|
|||
self.on_itemImportChannels_activate(self, widget)
|
||||
|
||||
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 os.path.exists( filename) and not services.download_status_manager.is_download_in_progress( episode.url):
|
||||
download.DownloadThread( episode.channel, episode, self.notification).start()
|
||||
services.download_status_manager.end_batch_mode()
|
||||
|
||||
def new_episodes_show(self, episodes):
|
||||
columns = (
|
||||
|
@ -1583,7 +1598,8 @@ class gPodder(GladeWidget):
|
|||
log('Warning: cannot delete %s', old_save_dir, sender=self)
|
||||
|
||||
save_channels(self.channels)
|
||||
self.update_feed_cache(force_update=False)
|
||||
# update feed cache and select the podcast with the new URL afterwards
|
||||
self.update_feed_cache(force_update=False, select_url_afterwards=new_url)
|
||||
|
||||
def on_itemRemoveChannel_activate(self, widget, *args):
|
||||
try:
|
||||
|
@ -1620,12 +1636,24 @@ class gPodder(GladeWidget):
|
|||
# only delete partial files if we do not have any downloads in progress
|
||||
delete_partial = not services.download_status_manager.has_items()
|
||||
gl.clean_up_downloads(delete_partial)
|
||||
|
||||
# get the URL of the podcast we want to select next
|
||||
position = self.channels.index(self.active_channel)
|
||||
if position == len(self.channels)-1:
|
||||
# this is the last podcast, so select the URL
|
||||
# of the item before this one (i.e. the "new last")
|
||||
select_url = self.channels[position-1].url
|
||||
else:
|
||||
# there is a podcast after the deleted one, so
|
||||
# we simply select the one that comes after it
|
||||
select_url = self.channels[position+1].url
|
||||
|
||||
# Remove the channel
|
||||
self.channels.remove(self.active_channel)
|
||||
save_channels(self.channels)
|
||||
if len(self.channels) > 0:
|
||||
self.treeChannels.get_selection().select_path((len(self.channels)-1,))
|
||||
self.active_channel = self.channels[len(self.channels)-1]
|
||||
self.update_feed_cache(force_update=False)
|
||||
|
||||
# Re-load the channels and select the desired new channel
|
||||
self.update_feed_cache(force_update=False, select_url_afterwards=select_url)
|
||||
except:
|
||||
log('There has been an error removing the channel.', traceback=True, sender=self)
|
||||
|
||||
|
@ -1777,6 +1805,7 @@ class gPodder(GladeWidget):
|
|||
if widget.get_name() == 'itemTransferSelected' or widget.get_name() == 'toolTransfer':
|
||||
transfer_files = True
|
||||
|
||||
services.download_status_manager.start_batch_mode()
|
||||
for apath in selection_tuple[1]:
|
||||
selection_iter = self.treeAvailable.get_model().get_iter( apath)
|
||||
url = self.treeAvailable.get_model().get_value( selection_iter, 0)
|
||||
|
@ -1785,6 +1814,7 @@ class gPodder(GladeWidget):
|
|||
episodes.append( self.active_channel.find_episode( url))
|
||||
else:
|
||||
self.download_podcast_by_url( url, show_message_dialog, widget_to_send)
|
||||
services.download_status_manager.end_batch_mode()
|
||||
|
||||
if transfer_files and len(episodes):
|
||||
self.on_sync_to_ipod_activate(None, episodes)
|
||||
|
@ -1835,8 +1865,10 @@ class gPodder(GladeWidget):
|
|||
message = _("Cancelling the download will stop the %d selected downloads and remove partially downloaded files.") % selection.count_selected_rows()
|
||||
|
||||
if self.show_confirmation( message, title):
|
||||
services.download_status_manager.start_batch_mode()
|
||||
for url in cancel_urls:
|
||||
services.download_status_manager.cancel_by_url( url)
|
||||
services.download_status_manager.end_batch_mode()
|
||||
|
||||
def on_btnCancelDownloadStatus_clicked(self, widget, *args):
|
||||
self.on_treeDownloads_row_activated( widget, None)
|
||||
|
|
|
@ -86,6 +86,11 @@ class DownloadStatusManager(ObservableService):
|
|||
self.tree_model = gtk.ListStore( *self.COLUMN_TYPES)
|
||||
self.tree_model_lock = threading.Lock()
|
||||
|
||||
# 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
|
||||
|
||||
# Used to notify all threads that they should
|
||||
# re-check if they can acquire the lock
|
||||
self.notification_event = threading.Event()
|
||||
|
@ -94,6 +99,29 @@ class DownloadStatusManager(ObservableService):
|
|||
signal_names = ['list-changed', 'progress-changed', 'progress-detail', 'download-complete']
|
||||
ObservableService.__init__(self, signal_names)
|
||||
|
||||
def start_batch_mode(self):
|
||||
"""
|
||||
This is called when we are going to add multiple
|
||||
episodes to our download list, and do not want to
|
||||
notify the GUI for every single episode.
|
||||
|
||||
After all episodes have been added, you MUST call
|
||||
the end_batch_mode() method to trigger a notification.
|
||||
"""
|
||||
self.batch_mode_enabled = True
|
||||
|
||||
def end_batch_mode(self):
|
||||
"""
|
||||
This is called after multiple episodes have been
|
||||
added when start_batch_mode() has been called before.
|
||||
|
||||
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
|
||||
|
||||
def notify_progress( self):
|
||||
now = ( self.count(), self.average_progress() )
|
||||
|
||||
|
@ -156,7 +184,10 @@ class DownloadStatusManager(ObservableService):
|
|||
def register_download_id( self, id, thread):
|
||||
self.tree_model_lock.acquire()
|
||||
self.status_list[id] = { 'iter': self.tree_model.append(), 'thread': thread, 'progress': 0.0, 'speed': _('Queued'), }
|
||||
self.notify( 'list-changed')
|
||||
if self.batch_mode_enabled:
|
||||
self.batch_mode_notify_flag = True
|
||||
else:
|
||||
self.notify('list-changed')
|
||||
self.tree_model_lock.release()
|
||||
|
||||
def remove_download_id( self, id):
|
||||
|
@ -173,7 +204,10 @@ class DownloadStatusManager(ObservableService):
|
|||
if not self.has_items():
|
||||
# Reset the counter now
|
||||
self.downloads_done_bytes = 0
|
||||
self.notify( 'list-changed')
|
||||
if self.batch_mode_enabled:
|
||||
self.batch_mode_notify_flag = True
|
||||
else:
|
||||
self.notify('list-changed')
|
||||
self.notify_progress()
|
||||
|
||||
def count( self):
|
||||
|
|
Loading…
Reference in a new issue