Compare commits
3 Commits
debian/lat
...
701-auouym
Author | SHA1 | Date |
---|---|---|
Eric Le Lay | c967f10230 | |
Eric Le Lay | c1570a0f7b | |
auouymous | 14a4c00082 |
4
po/nl.po
4
po/nl.po
|
@ -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?"
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue