Compare commits

...

3 Commits

Author SHA1 Message Date
Eric Le Lay c967f10230 refactor filter extension for shorter code 2020-02-16 18:43:34 +01:00
Eric Le Lay c1570a0f7b remove remaining carriage return in nl translation file 2020-02-06 19:11:11 +01:00
auouymous 14a4c00082 extension to filter expisodes from being automatically downloaded 2020-02-03 18:13:34 -07:00
2 changed files with 278 additions and 4 deletions

View File

@ -1050,10 +1050,6 @@ msgid "Please wait while the podcasts are deleted"
msgstr "Even geduld; bezig met verwijderen van podcasts"
#: src/gpodder/gtkui/main.py:3193
#, fuzzy
#| msgid ""
#| "These podcasts and all their episodes will be PERMANENTLY DELETED.\r\n"
#| "Are you sure you want to continue?"
msgid ""
"These podcasts and all their episodes will be PERMANENTLY DELETED.\n"
"Are you sure you want to continue?"

View File

@ -0,0 +1,278 @@
# -*- coding: utf-8 -*-
# Disable automatic downloads based on episode title.
# Released under the same license terms as gPodder itself.
import re
from gi.repository import Gtk
import gpodder
_ = gpodder.gettext
__title__ = _('Filter Episodes')
__description__ = _('Disable automatic downloads based on episode title.')
__only_for__ = 'gtk'
__authors__ = 'Brand Huntsman <http://qzx.com/mail/>'
DefaultConfig = {
'filters': []
}
class BlockExceptFrame:
"""
Utility class to manage a Block or Except frame, with sub-widgets:
- Creation as well as internal UI change is handled;
- Changes to the other widget and to the model have to be handled outside.
It's less optimized than mapping each widget to a different signal handler,
but makes shorter code.
"""
def __init__(self, value, enable_re, enable_ic, on_change_cb):
self.on_change_cb = on_change_cb
self.frame = Gtk.Frame()
frame_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.frame.add(frame_vbox)
# checkbox and text entry
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
hbox.set_border_width(5)
frame_vbox.add(hbox)
self.checkbox = Gtk.CheckButton()
self.checkbox.set_active(value is not False)
hbox.pack_start(self.checkbox, False, False, 3)
self.entry = Gtk.Entry()
hbox.pack_start(self.entry, True, True, 5)
# lower hbox
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
hbox.set_border_width(5)
frame_vbox.add(hbox)
# regular expression checkbox
self.checkbox_re = Gtk.CheckButton(_('Regular Expression'))
hbox.pack_end(self.checkbox_re, False, False, 10)
# ignore case checkbox
self.checkbox_ic = Gtk.CheckButton(_('Ignore Case'))
hbox.pack_end(self.checkbox_ic, False, False, 10)
if value is False:
self.entry.set_sensitive(False)
self.entry.set_editable(False)
self.checkbox_re.set_sensitive(False)
self.checkbox_ic.set_sensitive(False)
else:
self.entry.set_text(value)
self.checkbox_re.set_active(enable_re)
self.checkbox_ic.set_active(enable_ic)
self.checkbox.connect('toggled', self.toggle_active)
self.entry.connect('changed', self.emit_change)
self.checkbox_re.connect('toggled', self.emit_change)
self.checkbox_ic.connect('toggled', self.emit_change)
def toggle_active(self, widget):
enabled = widget.get_active()
if enabled:
# enable text and RE/IC checkboxes
self.entry.set_sensitive(True)
self.entry.set_editable(True)
self.checkbox_re.set_sensitive(True)
self.checkbox_ic.set_sensitive(True)
else:
# clear and disable text and RE/IC checkboxes
self.entry.set_sensitive(False)
self.entry.set_text('')
self.entry.set_editable(False)
self.checkbox_re.set_active(False)
self.checkbox_re.set_sensitive(False)
self.checkbox_ic.set_active(False)
self.checkbox_ic.set_sensitive(False)
self.emit_change(widget)
def emit_change(self, widget):
del widget
if self.on_change_cb:
self.on_change_cb(active=self.checkbox.get_active(),
text=self.entry.get_text(),
regexp=self.checkbox_re.get_active(),
ignore_case=self.checkbox_ic.get_active())
class gPodderExtension:
def __init__(self, container):
self.core = container.manager.core # gpodder core
self.filters = container.config.filters # all filters
# the following are only valid when podcast channel settings dialog is open
# self.gpodder = gPodder
# self.ui_object = gPodderChannel
# self.channel = PodcastChannel
# self.url = current filter url
# self.f = current filter
# self.allow_widget = allow BlockExceptFrame
# self.block_widget = block BlockExceptFrame
def on_ui_object_available(self, name, ui_object):
if name == 'channel-gtk':
# to close channel settings dialog after re-filtering
self.ui_object = ui_object
elif name == 'gpodder-gtk':
# to update episode list after re-filtering
self.gpodder = ui_object
# add filter tab to podcast channel settings dialog
def on_channel_settings(self, channel):
return [(_('Filter'), self.show_channel_settings_tab)]
def show_channel_settings_tab(self, channel):
self.channel = channel
self.url = channel.url
self.f = self.find_filter(self.url)
allow = self.key('allow')
block = self.key('block')
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
box.set_border_width(10)
# allow widgets
self.allow_widget = BlockExceptFrame(value=allow,
enable_re=self.key('allow_re') is not False,
enable_ic=self.key('allow_ic') is not False,
on_change_cb=self.on_allow_changed)
self.allow_widget.frame.set_label(_('Allow'))
box.add(self.allow_widget.frame)
if self.f is None:
self.allow_widget.frame.set_sensitive(False)
# block widgets
self.block_widget = BlockExceptFrame(value=block,
enable_re=self.key('block_re') is not False,
enable_ic=self.key('block_ic') is not False,
on_change_cb=self.on_block_changed)
self.block_widget.frame.set_label(_('Block'))
box.add(self.block_widget.frame)
self.block_widget.checkbox.set_sensitive(allow is False)
# help
label = Gtk.Label(_(
'Clicking the block checkbox and leaving it empty will disable auto-download for all episodes in this channel.'
' The patterns match partial text in episode title, and an empty pattern matches any title.'
' The allow pattern unblocks blocked episodes (to block all then unblock some).'))
label.set_line_wrap(True)
box.add(label)
# re-filter
button = Gtk.Button(_('Filter episodes now (undoes any episodes you marked as old)'))
button.connect('clicked', self.refilter_podcast)
box.add(button)
box.show_all()
return box
# return filter for a given podcast channel url
def find_filter(self, url):
for f in self.filters:
if f['url'] == url:
return f
return None
# return value for a given key in current filter
def key(self, key):
if self.f is None:
return False
return self.f.get(key, False)
def on_allow_changed(self, active, text, regexp, ignore_case):
self.on_changed('allow', active, text, regexp, ignore_case)
self.block_widget.checkbox.set_sensitive(self.f is None or self.key('allow') is False)
def on_block_changed(self, active, text, regexp, ignore_case):
self.on_changed('block', active, text, regexp, ignore_case)
self.allow_widget.frame.set_sensitive(self.f is not None)
# update filter when toggling allow/block checkbox
def on_changed(self, field, enabled, text, regexp, ignore_case):
if enabled:
if self.f is None:
self.f = {'url': self.url}
self.filters.append(self.f)
self.f[field] = text
if regexp:
self.f[field + '_re'] = True
else:
self.f.pop(field + '_re', None)
if ignore_case:
self.f[field + '_ic'] = True
else:
self.f.pop(field + '_ic', None)
else:
if self.f is not None:
self.f.pop(field + '_ic', None)
self.f.pop(field + '_re', None)
self.f.pop(field, None)
if len(self.f.keys()) == 1:
self.filters.remove(self.f)
self.f = None
# save config
self.core.config.schedule_save()
# remove filter when podcast channel is removed
def on_podcast_delete(self, podcast):
f = self.find_filter(podcast.url)
if f is not None:
self.filters.remove(f)
# save config
self.core.config.schedule_save()
# mark new episodes as old to disable automatic download when they match a block filter
def on_podcast_updated(self, podcast):
self.filter_podcast(podcast, False)
# re-filter episodes after changing filters
def refilter_podcast(self, widget):
if self.filter_podcast(self.channel, True):
self.channel.db.commit()
self.gpodder.update_episode_list_model()
self.ui_object.main_window.destroy()
# compare filter pattern to episode title
def compare(self, title, pattern, regexp, ignore_case):
if regexp is not False:
return regexp.search(title)
elif ignore_case:
return (pattern.casefold() in title.casefold())
else:
return (pattern in title)
# filter episodes that aren't downloaded or deleted
def filter_podcast(self, podcast, mark_new):
f = self.find_filter(podcast.url)
if f is not None:
allow = f.get('allow', False)
block = f.get('block', False)
allow_ic = True if allow is not False and f.get('allow_ic', False) else False
block_ic = True if block is not False and f.get('block_ic', False) else False
allow_re = re.compile(allow, re.IGNORECASE if allow_ic else False) if allow is not False and f.get('allow_re', False) else False
block_re = re.compile(block, re.IGNORECASE if block_ic else False) if block is not False and f.get('block_re', False) else False
else:
allow = False
block = False
changes = False
for e in podcast.get_episodes(gpodder.STATE_NORMAL):
if allow is not False and self.compare(e.title, allow, allow_re, allow_ic):
# allow episode
if mark_new and not e.is_new:
e.mark_new()
changes = True
continue
if block is not False and self.compare(e.title, block, block_re, block_ic):
# block episode - mark as old to disable automatic download
if e.is_new:
e.mark_old()
changes = True
continue
if mark_new and not e.is_new:
e.mark_new()
changes = True
return changes