2
from datetime import datetime, timedelta
3
from sqlalchemy import Column, Integer, String, DateTime, PickleType
4
from flexget.manager import Base, Session
5
from flexget.feed import Entry
6
from flexget.plugin import register_plugin, priority, PluginWarning
8
log = logging.getLogger('backlog')
11
class BacklogEntry(Base):
13
__tablename__ = 'backlog'
15
id = Column(Integer, primary_key=True)
17
title = Column(String)
18
expire = Column(DateTime)
19
entry = Column(PickleType(mutable=False))
22
return '<BacklogEntry(title=%s)>' % (self.title)
25
class InputBacklog(object):
27
Keeps feed history for given amount of time.
33
Rarely useful for end users, mainly used by other plugins.
37
from flexget import validator
38
root = validator.factory('regexp_match', message='Must be in format <number> <hours|minutes|days|weeks>')
39
root.accept('\d+ (minute|hour|day|week)s?')
42
def get_amount(self, value):
44
# If no time is given, default to 0 (entry will only be injected on next execution)
46
amount, unit = value.split(' ')
47
# Make sure unit name is plural.
48
if not unit.endswith('s'):
50
log.debug('amount: %s unit: %s' % (repr(amount), repr(unit)))
51
params = {unit: int(amount)}
53
return timedelta(**params)
55
raise PluginWarning('Invalid time format \'%s\'' % value, log)
58
def on_feed_input(self, feed, config):
59
# Get a list of entries to inject
60
injections = self.get_injections(feed)
61
# Take a snapshot of the entries' states after the input event in case we have to store them to backlog
62
for entry in feed.entries:
63
entry.take_snapshot('after_input')
65
# If backlog is manually enabled for this feed, learn the entries.
66
self.learn_backlog(feed, config)
67
# Return the entries from backlog that are not already in the feed
70
def on_feed_abort(self, feed, config):
71
"""Remember all entries until next execution when feed gets aborted."""
72
log.debug('Remembering all entries to backlog because of feed abort.')
73
self.learn_backlog(feed)
75
def add_backlog(self, feed, entry, amount=''):
76
"""Add single entry to feed backlog
78
If :amount: is not specified, entry will only be injected on next execution."""
79
snapshot = entry.snapshots.get('after_input')
81
log.warning('No input snapshot available for `%s`, using current state' % entry['title'])
82
snapshot = dict(entry)
84
expire_time = datetime.now() + self.get_amount(amount)
85
backlog_entry = session.query(BacklogEntry).filter(BacklogEntry.title == entry['title']).\
86
filter(BacklogEntry.feed == feed.name).first()
88
# If there is already a backlog entry for this, update the expiry time if necessary.
89
if backlog_entry.expire < expire_time:
90
log.debug('Updating expiry time for %s' % entry['title'])
91
backlog_entry.expire = expire_time
93
log.debug('Saving %s' % entry['title'])
94
backlog_entry = BacklogEntry()
95
backlog_entry.title = entry['title']
96
backlog_entry.entry = snapshot
97
backlog_entry.feed = feed.name
98
backlog_entry.expire = expire_time
99
session.add(backlog_entry)
102
def learn_backlog(self, feed, amount=''):
103
"""Learn current entries into backlog. All feed inputs must have been executed."""
104
for entry in feed.entries:
105
self.add_backlog(feed, entry, amount)
107
def get_injections(self, feed):
108
"""Insert missing entries from backlog."""
110
feed_backlog = feed.session.query(BacklogEntry).filter(BacklogEntry.feed == feed.name)
111
for backlog_entry in feed_backlog.all():
112
entry = Entry(backlog_entry.entry)
114
# this is already in the feed
115
if feed.find_entry(title=entry['title'], url=entry['url']):
117
log.debug('Restoring %s' % entry['title'])
118
entries.append(entry)
120
feed.verbose_progress('Added %s entries from backlog' % len(entries), log)
123
for backlog_entry in feed_backlog.filter(datetime.now() > BacklogEntry.expire).all():
124
log.debug('Purging %s' % backlog_entry.title)
125
feed.session.delete(backlog_entry)
129
register_plugin(InputBacklog, 'backlog', builtin=True, api_ver=2)