Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Download strategy: Only keep latest (bug 188)
Add per-podcast option to only keep the latest episode of a
channel (default strategy is still the current setting).
  • Loading branch information
thp committed Oct 13, 2012
1 parent e3f5360 commit 236ee1f
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 61 deletions.
61 changes: 46 additions & 15 deletions share/gpodder/ui/gtk/gpodderchannel.ui
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 2.24 -->
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkDialog" id="gPodderChannel">
<property name="can_focus">False</property>
Expand Down Expand Up @@ -58,7 +58,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">10</property>
<property name="n_rows">5</property>
<property name="n_rows">6</property>
<property name="n_columns">4</property>
<property name="column_spacing">5</property>
<property name="row_spacing">10</property>
Expand All @@ -75,7 +75,7 @@
<property name="right_attach">4</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -90,7 +90,7 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -105,7 +105,7 @@
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -126,8 +126,8 @@
</object>
<packing>
<property name="right_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
Expand Down Expand Up @@ -202,6 +202,37 @@
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_strategy">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Strategy:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combo_strategy">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="tab">
Expand Down Expand Up @@ -251,7 +282,7 @@
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -265,7 +296,7 @@
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -281,7 +312,7 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -299,7 +330,7 @@
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
</object>
Expand Down Expand Up @@ -351,7 +382,7 @@
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -366,7 +397,7 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -380,7 +411,7 @@
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand All @@ -397,7 +428,7 @@
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
Expand Down
40 changes: 40 additions & 0 deletions src/gpodder/common.py
Expand Up @@ -93,3 +93,43 @@ def find_partial_downloads(channels, start_progress_callback, progress_callback,
else:
clean_up_downloads(True)

def get_expired_episodes(channels, config):
for channel in channels:
for index, episode in enumerate(channel.get_downloaded_episodes()):
# Never consider archived episodes as old
if episode.archive:
continue

# Download strategy "Only keep latest"
if (channel.download_strategy == channel.STRATEGY_LATEST and
index > 0):
logger.info('Removing episode (only keep latest strategy): %s',
episode.title)
yield episode
continue

# Only expire episodes if the age in days is positive
if config.episode_old_age < 1:
continue

# Never consider fresh episodes as old
if episode.age_in_days() < config.episode_old_age:
continue

# Do not delete played episodes (except if configured)
if not episode.is_new:
if not config.auto_remove_played_episodes:
continue

# Do not delete unfinished episodes (except if configured)
if not episode.is_finished():
if not config.auto_remove_unfinished_episodes:
continue

# Do not delete unplayed episodes (except if configured)
if episode.is_new:
if not config.auto_remove_unplayed_episodes:
continue

yield episode

16 changes: 16 additions & 0 deletions src/gpodder/gtkui/desktop/channel.py
Expand Up @@ -50,6 +50,19 @@ def new(self):
self.combo_section.add_attribute(cell_renderer, 'text', 0)
self.combo_section.set_active(active_index)

self.strategy_list = gtk.ListStore(str, int)
active_index = 0
for index, (checked, strategy_id, strategy) in \
enumerate(self.channel.get_download_strategies()):
self.strategy_list.append([strategy, strategy_id])
if checked:
active_index = index
self.combo_strategy.set_model(self.strategy_list)
cell_renderer = gtk.CellRendererText()
self.combo_strategy.pack_start(cell_renderer)
self.combo_strategy.add_attribute(cell_renderer, 'text', 0)
self.combo_strategy.set_active(active_index)

self.LabelDownloadTo.set_text( self.channel.save_dir)
self.LabelWebsite.set_text( self.channel.link)

Expand Down Expand Up @@ -187,6 +200,9 @@ def on_btnOK_clicked(self, widget, *args):
else:
section_changed = False

new_strategy = self.strategy_list[self.combo_strategy.get_active()][1]
self.channel.set_download_strategy(new_strategy)

self.channel.save()

self.gPodderChannel.destroy()
Expand Down
36 changes: 1 addition & 35 deletions src/gpodder/gtkui/main.py
Expand Up @@ -237,7 +237,7 @@ def new(self):
self.restart_auto_update_timer()

# Find expired (old) episodes and delete them
old_episodes = list(self.get_expired_episodes())
old_episodes = list(common.get_expired_episodes(self.channels, self.config))
if len(old_episodes) > 0:
self.delete_episode_list(old_episodes, confirm=False)
updated_urls = set(e.channel.url for e in old_episodes)
Expand Down Expand Up @@ -2509,40 +2509,6 @@ def close_gpodder(self):
if macapp is None:
sys.exit(0)

def get_expired_episodes(self):
# XXX: Move out of gtkui and into a generic module (gpodder.model)?

# Only expire episodes if the age in days is positive
if self.config.episode_old_age < 1:
return

for channel in self.channels:
for episode in channel.get_downloaded_episodes():
# Never consider archived episodes as old
if episode.archive:
continue

# Never consider fresh episodes as old
if episode.age_in_days() < self.config.episode_old_age:
continue

# Do not delete played episodes (except if configured)
if not episode.is_new:
if not self.config.auto_remove_played_episodes:
continue

# Do not delete unfinished episodes (except if configured)
if not episode.is_finished():
if not self.config.auto_remove_unfinished_episodes:
continue

# Do not delete unplayed episodes (except if configured)
if episode.is_new:
if not self.config.auto_remove_unplayed_episodes:
continue

yield episode

def delete_episode_list(self, episodes, confirm=True, skip_locked=True):
if not episodes:
return False
Expand Down
52 changes: 43 additions & 9 deletions src/gpodder/model.py
Expand Up @@ -791,6 +791,15 @@ class PodcastChannel(PodcastModelObject):

UNICODE_TRANSLATE = {ord(u'ö'): u'o', ord(u'ä'): u'a', ord(u'ü'): u'u'}

# Enumerations for download strategy
STRATEGY_DEFAULT, STRATEGY_LATEST = range(2)

# Description and ordering of strategies
STRATEGIES = [
(STRATEGY_DEFAULT, _('Default')),
(STRATEGY_LATEST, _('Only keep latest')),
]

MAX_FOLDERNAME_LENGTH = 60
SECONDS_PER_WEEK = 7*24*60*60
EpisodeClass = PodcastEpisode
Expand Down Expand Up @@ -821,6 +830,7 @@ def __init__(self, model):

self.section = _('Other')
self._common_prefix = None
self.download_strategy = PodcastChannel.STRATEGY_DEFAULT

@property
def model(self):
Expand All @@ -830,6 +840,21 @@ def model(self):
def db(self):
return self.parent.db

def get_download_strategies(self):
for value, caption in PodcastChannel.STRATEGIES:
yield self.download_strategy == value, value, caption

def set_download_strategy(self, download_strategy):
if download_strategy == self.download_strategy:
return

caption = dict(self.STRATEGIES).get(download_strategy)
if caption is not None:
logger.debug('Strategy for %s changed to %s', self.title, caption)
self.download_strategy = download_strategy
else:
logger.warn('Cannot set strategy to %d', download_strategy)

def check_download_folder(self):
"""Check the download folder for externally-downloaded files
Expand Down Expand Up @@ -1037,21 +1062,21 @@ def _consume_updated_feed(self, feed, max_episodes=0):
# Load all episodes to update them properly.
existing = self.get_all_episodes()

# We can limit the maximum number of entries that gPodder will parse
if max_episodes > 0 and len(feed.entries) > max_episodes:
try:
# We have to sort the entries in descending chronological order,
# because if the feed lists items in ascending order and has >
# max_episodes old episodes, new episodes will not be shown.
# See also: gPodder Bug 1186
try:
entries = sorted(feed.entries, key=feedcore.get_pubdate,
reverse=True)[:max_episodes]
except Exception, e:
logger.warn('Could not sort episodes: %s', e, exc_info=True)
entries = feed.entries[:max_episodes]
else:
entries = sorted(feed.entries, key=feedcore.get_pubdate,
reverse=True)
except Exception, e:
logger.warn('Could not sort episodes: %s', e, exc_info=True)
entries = feed.entries

# We can limit the maximum number of entries that gPodder will parse
if max_episodes > 0 and len(entries) > max_episodes:
entries = entries[:max_episodes]

# Title + PubDate hashes for existing episodes
existing_dupes = dict((e.duplicate_id(), e) for e in existing)

Expand All @@ -1064,6 +1089,9 @@ def _consume_updated_feed(self, feed, max_episodes=0):
# Keep track of episode GUIDs currently seen in the feed
seen_guids = set()

# Number of new episodes found
new_episodes = 0

# Search all entries for new episodes
for entry in entries:
try:
Expand Down Expand Up @@ -1104,6 +1132,12 @@ def _consume_updated_feed(self, feed, max_episodes=0):
existing_episode.save()
continue

new_episodes += 1
# Only allow a certain number of new episodes per update
if (self.download_strategy == PodcastChannel.STRATEGY_LATEST and
new_episodes > 1):
episode.is_new = False

# Workaround for bug 340: If the episode has been
# published earlier than one week before the most
# recent existing episode, do not mark it as new.
Expand Down

0 comments on commit 236ee1f

Please sign in to comment.