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" msgstr "Even geduld; bezig met verwijderen van podcasts"
#: src/gpodder/gtkui/main.py:3193 #: 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 "" msgid ""
"These podcasts and all their episodes will be PERMANENTLY DELETED.\n" "These podcasts and all their episodes will be PERMANENTLY DELETED.\n"
"Are you sure you want to continue?" "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