flexget.plugins.output.rss
Covered: 103 lines
Missed: 93 lines
Skipped 70 lines
Percent: 52 %
  1
import logging
  2
import datetime
  3
import os
  4
from sqlalchemy import Column, Integer, String, DateTime
  5
from flexget import schema
  6
from flexget.plugin import register_plugin, PluginWarning
  7
from flexget.utils.sqlalchemy_utils import table_columns, table_add_column
  9
log = logging.getLogger('make_rss')
 10
Base = schema.versioned_base('make_rss', 0)
 12
rss2gen = True
 13
try:
 14
    import PyRSS2Gen
 15
except:
 16
    rss2gen = False
 19
@schema.upgrade('make_rss')
 20
def upgrade(ver, session):
 21
    if ver is None:
 22
        columns = table_columns('make_rss', session)
 23
        if not 'rsslink' in columns:
 24
            log.info('Adding rsslink column to table make_rss.')
 25
            table_add_column('make_rss', 'rsslink', String, session)
 26
        ver = 0
 27
    return ver
 30
class RSSEntry(Base):
 32
    __tablename__ = 'make_rss'
 34
    id = Column(Integer, primary_key=True)
 35
    title = Column(String)
 36
    description = Column(String)
 37
    link = Column(String)
 38
    rsslink = Column(String)
 39
    file = Column(String)
 40
    published = Column(DateTime, default=datetime.datetime.utcnow())
 43
class OutputRSS(object):
 44
    """
 45
    Write RSS containing succeeded (downloaded) entries.
 47
    Example::
 49
      make_rss: ~/public_html/flexget.rss
 51
    You may write into same file in multiple feeds.
 53
    Example::
 55
      my-feed-A:
 56
        make_rss: ~/public_html/series.rss
 57
        .
 58
        .
 59
      my-feed-B:
 60
        make_rss: ~/public_html/series.rss
 61
        .
 62
        .
 64
    With this example file series.rss would contain succeeded
 65
    entries from both feeds.
 67
    **Number of days / items**
 69
    By default output contains items from last 7 days. You can specify
 70
    different perioid, number of items or both. Value -1 means unlimited.
 72
    Example::
 74
      make_rss:
 75
        file: ~/public_html/series.rss
 76
        days: 2
 77
        items: 10
 79
    Generate RSS that will containg last two days and no more than 10 items.
 81
    Example 2::
 83
      make_rss:
 84
        file: ~/public_html/series.rss
 85
        days: -1
 86
        items: 50
 88
    Generate RSS that will contain last 50 items, regardless of dates.
 90
    RSS location link:
 92
    You can specify the url location of the rss file.
 94
    Example::
 96
      make_rss:
 97
        file: ~/public_html/series.rss
 98
        rsslink: http://my.server.net/series.rss
100
    **RSS link**
102
    You can specify what field from entry is used as a link in generated rss feed.
104
    Example::
106
      make_rss:
107
        file: ~/public_html/series.rss
108
        link:
109
          - imdb_url
111
    List should contain a list of fields in order of preference.
112
    Note that the url field is always used as last possible fallback
113
    even without explicitly adding it into the list.
115
    Default list: imdb_url, input_url, url
116
    """
118
    def __init__(self):
119
        self.written = {}
121
    def validator(self):
122
        """Validate given configuration"""
123
        from flexget import validator
124
        root = validator.factory()
125
        root.accept('text') # TODO: path / file
126
        rss = root.accept('dict')
127
        rss.accept('text', key='file', required=True)
128
        rss.accept('integer', key='days')
129
        rss.accept('integer', key='items')
130
        rss.accept('boolean', key='history')
131
        rss.accept('text', key='rsslink')
132
        rss.accept('text', key='encoding') # TODO: only valid choices
133
        links = rss.accept('list', key='link')
134
        links.accept('text')
135
        return root
137
    def on_feed_output(self, feed):
139
        pass
141
    def get_config(self, feed):
142
        config = feed.config['make_rss']
143
        if not isinstance(config, dict):
144
            config = {'file': config}
145
        config.setdefault('days', 7)
146
        config.setdefault('items', -1)
147
        config.setdefault('history', True)
148
        config.setdefault('encoding', 'iso-8859-1')
149
        config.setdefault('link', ['imdb_url', 'input_url'])
151
        config['link'].append('url')
152
        return config
154
    def on_feed_exit(self, feed):
155
        """Store finished / downloaded entries at exit"""
156
        if not rss2gen:
157
            raise PluginWarning('plugin make_rss requires PyRSS2Gen library.')
158
        config = self.get_config(feed)
161
        if feed.manager.options.test:
162
            return
165
        if not config['history']:
166
            log.debug('disabling history')
167
            for item in feed.session.query(RSSEntry).filter(RSSEntry.file == config['file']).all():
168
                feed.session.delete(item)
171
        for entry in feed.accepted:
172
            rss = RSSEntry()
173
            rss.title = entry['title']
174
            for field in config['link']:
175
                if field in entry:
176
                    rss.link = entry[field]
177
                    break
180
            description = ''
181
            if 'imdb_score' in entry:
182
                description += 'Score: %s / 10 | ' % entry['imdb_score']
183
            if 'imdb_votes' in entry:
184
                description += 'Votes: %s | ' % entry['imdb_votes']
185
            if 'imdb_genres' in entry:
186
                description += 'Genres: %s | ' % ', '.join(entry['imdb_genres'])
187
            if 'imdb_plot_outline' in entry:
188
                description += 'Description: %s' % entry['imdb_plot_outline']
189
            else:
190
                description += entry.get('description', '')
192
            rss.description = description
193
            rss.file = config['file']
196
            log.debug('Saving %s into rss database' % entry['title'])
197
            feed.session.add(rss)
199
    def on_process_end(self, feed):
200
        """Write RSS file at application terminate."""
201
        if not rss2gen:
202
            return
204
        if feed.manager.options.learn:
205
            return
207
        config = self.get_config(feed)
208
        if config['file'] in self.written:
209
            log.debugall('skipping already written file %s' % config['file'])
210
            return
213
        from flexget.manager import Session
214
        session = Session()
216
        db_items = session.query(RSSEntry).filter(RSSEntry.file == config['file']).\
217
            order_by(RSSEntry.published.desc()).all()
220
        rss_items = []
221
        for db_item in db_items:
222
            add = True
223
            if config['items'] != -1:
224
                if len(rss_items) > config['items']:
225
                    add = False
226
            if config['days'] != -1:
227
                if datetime.datetime.today() - datetime.timedelta(days=config['days']) > db_item.published:
228
                    add = False
229
            if add:
231
                gen = {}
232
                gen['title'] = db_item.title
233
                gen['description'] = db_item.description
234
                gen['link'] = db_item.link
235
                gen['pubDate'] = db_item.published
236
                log.debugall('Adding %s into rss %s' % (gen['title'], config['file']))
237
                rss_items.append(PyRSS2Gen.RSSItem(**gen))
238
            else:
240
                session.delete(db_item)
242
        session.commit()
243
        session.close()
246
        rss = PyRSS2Gen.RSS2(title='FlexGet',
247
                             link=config.get('rsslink', 'http://flexget.com'),
248
                             description='FlexGet generated RSS feed',
249
                             lastBuildDate=datetime.datetime.utcnow(),
250
                             items=rss_items)
252
        fn = os.path.expanduser(config['file'])
253
        try:
254
            rss.write_xml(open(fn, 'w'), encoding=config['encoding'])
255
        except LookupError:
256
            log.critical('Unknown encoding %s' % config['encoding'])
257
            return
258
        except IOError:
260
            log.critical('Unable to write %s' % fn)
261
            return
262
        self.written[config['file']] = True
265
register_plugin(OutputRSS, 'make_rss')