2010-11-29 12:51:51 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# gPodder - A media aggregator and podcast client
|
2018-01-28 19:39:53 +01:00
|
|
|
# Copyright (c) 2005-2018 The gPodder Team
|
2010-11-29 12:51:51 +01:00
|
|
|
#
|
|
|
|
# gPodder is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# gPodder is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# gpodder.query - Episode Query Language (EQL) implementation (2010-11-29)
|
|
|
|
#
|
|
|
|
|
|
|
|
import gpodder
|
|
|
|
|
|
|
|
import re
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
class Matcher(object):
|
|
|
|
"""Match implementation for EQL
|
|
|
|
|
|
|
|
This class implements the low-level matching of
|
|
|
|
EQL statements against episode objects.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, episode):
|
|
|
|
self._episode = episode
|
|
|
|
|
|
|
|
def match(self, term):
|
|
|
|
try:
|
|
|
|
return bool(eval(term, {'__builtins__': None}, self))
|
2016-11-21 23:13:46 +01:00
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
2010-11-29 12:51:51 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def __getitem__(self, k):
|
|
|
|
episode = self._episode
|
|
|
|
|
|
|
|
# Adjectives (for direct usage)
|
|
|
|
if k == 'new':
|
2011-02-26 16:32:34 +01:00
|
|
|
return (episode.state == gpodder.STATE_NORMAL and episode.is_new)
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k in ('downloaded', 'dl'):
|
|
|
|
return episode.was_downloaded(and_exists=True)
|
|
|
|
elif k in ('deleted', 'rm'):
|
|
|
|
return episode.state == gpodder.STATE_DELETED
|
|
|
|
elif k == 'played':
|
2011-02-26 16:32:34 +01:00
|
|
|
return not episode.is_new
|
2012-01-19 11:56:09 +01:00
|
|
|
elif k == 'downloading':
|
|
|
|
return episode.downloading
|
2010-12-20 00:18:22 +01:00
|
|
|
elif k == 'archive':
|
2011-02-26 16:48:48 +01:00
|
|
|
return episode.archive
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k in ('finished', 'fin'):
|
|
|
|
return episode.is_finished()
|
|
|
|
elif k in ('video', 'audio'):
|
|
|
|
return episode.file_type() == k
|
|
|
|
elif k == 'torrent':
|
2010-12-20 14:35:46 +01:00
|
|
|
return episode.url.endswith('.torrent') or 'torrent' in episode.mime_type
|
2010-11-29 12:51:51 +01:00
|
|
|
|
|
|
|
# Nouns (for comparisons)
|
|
|
|
if k in ('megabytes', 'mb'):
|
2017-02-14 15:50:07 +01:00
|
|
|
return episode.file_size / (1024*1024)
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k == 'title':
|
|
|
|
return episode.title
|
2011-04-04 19:05:13 +02:00
|
|
|
elif k == 'description':
|
|
|
|
return episode.description
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k == 'since':
|
2010-12-20 14:35:46 +01:00
|
|
|
return (datetime.datetime.now() - datetime.datetime.fromtimestamp(episode.published)).days
|
2012-02-05 13:30:48 +01:00
|
|
|
elif k == 'age':
|
|
|
|
return episode.age_in_days()
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k in ('minutes', 'min'):
|
2017-02-14 15:50:07 +01:00
|
|
|
return episode.total_time / 60
|
2010-11-29 12:51:51 +01:00
|
|
|
elif k in ('remaining', 'rem'):
|
2017-02-14 15:50:07 +01:00
|
|
|
return episode.total_time - episode.current_position / 60
|
2010-11-29 12:51:51 +01:00
|
|
|
|
|
|
|
raise KeyError(k)
|
|
|
|
|
|
|
|
|
|
|
|
class EQL(object):
|
|
|
|
"""A Query in EQL
|
|
|
|
|
|
|
|
Objects of this class represent a query on episodes
|
|
|
|
using EQL. Example usage:
|
|
|
|
|
|
|
|
>>> q = EQL('downloaded and megabytes > 10')
|
2018-02-06 16:05:10 +01:00
|
|
|
>>> # q.filter(channel.get_all_episodes())
|
2010-11-29 12:51:51 +01:00
|
|
|
|
2018-02-06 16:05:10 +01:00
|
|
|
>>> # EQL('new and video').match(episode)
|
2010-11-29 12:51:51 +01:00
|
|
|
|
|
|
|
Regular expression queries are also supported:
|
|
|
|
|
|
|
|
>>> q = EQL('/^The.*/')
|
|
|
|
|
|
|
|
>>> q = EQL('/community/i')
|
|
|
|
|
|
|
|
Normal string matches are also supported:
|
|
|
|
|
|
|
|
>>> q = EQL('"S04"')
|
|
|
|
|
|
|
|
>>> q = EQL("'linux'")
|
|
|
|
|
|
|
|
Normal EQL queries cannot be mixed with RegEx
|
|
|
|
or string matching yet, so this does NOT work:
|
|
|
|
|
2018-02-06 16:05:10 +01:00
|
|
|
>>> # EQL('downloaded and /The.*/i')
|
2010-11-29 12:51:51 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, query):
|
|
|
|
self._query = query
|
|
|
|
self._flags = 0
|
|
|
|
self._regex = False
|
|
|
|
self._string = False
|
|
|
|
|
|
|
|
# Regular expression based query
|
|
|
|
match = re.match(r'^/(.*)/(i?)$', query)
|
|
|
|
if match is not None:
|
|
|
|
self._regex = True
|
|
|
|
self._query, flags = match.groups()
|
|
|
|
if flags == 'i':
|
|
|
|
self._flags |= re.I
|
|
|
|
|
|
|
|
# String based query
|
|
|
|
match = re.match("^([\"'])(.*)(\\1)$", query)
|
|
|
|
if match is not None:
|
|
|
|
self._string = True
|
|
|
|
a, query, b = match.groups()
|
|
|
|
self._query = query.lower()
|
|
|
|
|
2011-02-01 20:01:35 +01:00
|
|
|
# For everything else, compile the expression
|
|
|
|
if not self._regex and not self._string:
|
|
|
|
try:
|
|
|
|
self._query = compile(query, '<eql-string>', 'eval')
|
2016-11-21 23:13:46 +01:00
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
2011-02-01 20:01:35 +01:00
|
|
|
self._query = None
|
|
|
|
|
|
|
|
|
2010-11-29 12:51:51 +01:00
|
|
|
def match(self, episode):
|
2011-02-01 20:01:35 +01:00
|
|
|
if self._query is None:
|
|
|
|
return False
|
|
|
|
|
2010-11-29 12:51:51 +01:00
|
|
|
if self._regex:
|
|
|
|
return re.search(self._query, episode.title, self._flags) is not None
|
|
|
|
elif self._string:
|
2014-10-23 09:59:13 +02:00
|
|
|
return self._query in episode.title.lower() or self._query in episode.description.lower()
|
2010-11-29 12:51:51 +01:00
|
|
|
|
|
|
|
return Matcher(episode).match(self._query)
|
|
|
|
|
|
|
|
def filter(self, episodes):
|
2016-11-21 23:13:46 +01:00
|
|
|
return list(filter(self.match, episodes))
|
2010-11-29 12:51:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
def UserEQL(query):
|
|
|
|
"""EQL wrapper for user input
|
|
|
|
|
|
|
|
Automatically adds missing quotes around a
|
|
|
|
non-EQL string for user-based input. In this
|
|
|
|
case, EQL queries need to be enclosed in ().
|
|
|
|
"""
|
|
|
|
|
2011-02-11 16:27:57 +01:00
|
|
|
if query is None:
|
|
|
|
return None
|
|
|
|
|
2010-11-29 12:51:51 +01:00
|
|
|
if query == '' or (query and query[0] not in "(/'\""):
|
|
|
|
return EQL("'%s'" % query)
|
|
|
|
else:
|
|
|
|
return EQL(query)
|
|
|
|
|
|
|
|
|