text: import base64
  import datetime
  import hashlib
  import io
  import os
  
  from loguru import logger
  from sqlalchemy import Column, DateTime, Integer, String, Unicode
  
  from flexget import db_schema, plugin
  from flexget.event import event
  from flexget.utils.sqlalchemy_utils import table_add_column, table_columns
  from flexget.utils.template import RenderError, get_template, render_from_entry
  
  logger = logger.bind(name='make_rss')
  Base = db_schema.versioned_base('make_rss', 0)
  
  rss2gen = True
  try:
      import PyRSS2Gen
  except ImportError:
      rss2gen = False
  
  
  @db_schema.upgrade('make_rss')
  def upgrade(ver, session):
      if ver is None:
          columns = table_columns('make_rss', session)
          if 'rsslink' not in columns:
              logger.info('Adding rsslink column to table make_rss.')
              table_add_column('make_rss', 'rsslink', String, session)
          ver = 0
      return ver
  
  
  class RSSEntry(Base):
      __tablename__ = 'make_rss'
  
      id = Column(Integer, primary_key=True)
      title = Column(Unicode)
      description = Column(Unicode)
      link = Column(String)
      rsslink = Column(String)
      file = Column(Unicode)
      published = Column(DateTime, default=datetime.datetime.utcnow)
      enc_length = Column(Integer)
      enc_type = Column(String)
  
  
  class OutputRSS:
      """
      Write RSS containing succeeded (downloaded) entries.
  
      Example::
  
        make_rss: ~/public_html/flexget.rss
  
      You may write into same file in multiple tasks.
  
      Example::
  
        my-task-A:
          make_rss: ~/public_html/series.rss
          .
          .
        my-task-B:
          make_rss: ~/public_html/series.rss
          .
          .
  
      With this example file series.rss would contain succeeded
      entries from both tasks.
  
      **Number of days / items**
  
      By default output contains items from last 7 days. You can specify
      different perioid, number of items or both. Value -1 means unlimited.
  
      Example::
  
        make_rss:
          file: ~/public_html/series.rss
          days: 2
          items: 10
  
      Generate RSS that will containing last two days and no more than 10 items.
  
      Example 2::
  
        make_rss:
          file: ~/public_html/series.rss
          days: -1
          items: 50
  
      Generate RSS that will contain last 50 items, regardless of dates.
  
      RSS feed properties:
  
      You can specify the URL, title, and description to include in tthe header
      of the RSS feed.
  
      Example::
  
        make_rss:
          file: ~/public_html/series.rss
          rsslink: http://my.server.net/series.rss
          rsstitle: The Flexget RSS Feed
          rssdesc: Episodes about Flexget.
  
      **RSS item title and link**
  
      You can specify the title and link for each item in the RSS feed.
  
      The item title can be any pattern that references fields in the input entry.
  
      The item link can be created from one of a list of fields in the input
      entry, in order of preference. The fields should be enumerated in a list.
      Note that the url field is always used as last possible fallback even
      without explicitly adding it into the list.
  
      Default field list for item URL: imdb_url, input_url, url
  
      Example::
  
        make_rss:
          file: ~/public_html/series.rss
          title: '{{title}} (from {{task}})'
          link:
            - imdb_url
  
      """
  
      schema = {
          'oneOf': [
              {'type': 'string'},  # TODO: path / file
              {
                  'type': 'object',
                  'properties': {
                      'file': {'type': 'string'},
                      'days': {'type': 'integer'},
                      'items': {'type': 'integer'},
                      'history': {'type': 'boolean'},
                      'timestamp': {'type': 'boolean'},
                      'rsslink': {'type': 'string'},
                      'rsstitle': {'type': 'string'},
                      'rssdesc': {'type': 'string'},
                      'encoding': {'type': 'string'},  # TODO: only valid choices
                      'title': {'type': 'string'},
                      'template': {'type': 'string'},
                      'link': {'type': 'array', 'items': {'type': 'string'}},
                  },
                  'required': ['file'],
                  'additionalProperties': False,
              },
          ]
      }
  
      def on_task_output(self, task, config):
          # makes this plugin count as output (stops warnings about missing outputs)
          pass
  
      def prepare_config(self, config):
          if not isinstance(config, dict):
              config = {'file': config}
          config.setdefault('days', 7)
          config.setdefault('items', -1)
          config.setdefault('history', True)
          config.setdefault('encoding', 'UTF-8')
          config.setdefault('timestamp', False)
          config.setdefault('link', ['imdb_url', 'input_url'])
          config.setdefault('title', '{{title}} (from {{task}})')
          config.setdefault('template', 'rss')
          # add url as last resort
          config['link'].append('url')
          return config
  
      def on_task_exit(self, task, config):
          """Store finished / downloaded entries at exit"""
          if not rss2gen:
              raise plugin.PluginWarning('plugin make_rss requires PyRSS2Gen library.')
          config = self.prepare_config(config)
  
          # when history is disabled, remove everything from backlog on every run (a bit hackish, rarely useful)
          if not config['history']:
              logger.debug('disabling history')
              for item in task.session.query(RSSEntry).filter(RSSEntry.file == config['file']).all():
                  task.session.delete(item)
  
          # save entries into db for RSS generation
          for entry in task.accepted:
              rss = RSSEntry()
              try:
                  rss.title = entry.render(config['title'])
              except RenderError as e:
                  logger.error(
                      'Error rendering jinja title for `{}` falling back to entry title: {}',
                      entry['title'],
                      e,
                  )
                  rss.title = entry['title']
              for field in config['link']:
                  if field in entry:
                      rss.link = entry[field]
                      break
  
              try:
                  template = get_template(config['template'], scope='task')
              except ValueError as e:
                  raise plugin.PluginError('Invalid template specified: %s' % e)
              try:
                  rss.description = render_from_entry(template, entry)
              except RenderError as e:
                  logger.error(
                      'Error while rendering entry {}, falling back to plain title: {}', entry, e
                  )
                  rss.description = entry['title'] + ' - (Render Error)'
              rss.file = config['file']
              if 'rss_pubdate' in entry:
                  rss.published = entry['rss_pubdate']
  
              rss.enc_length = entry['size'] if 'size' in entry else None
              rss.enc_type = entry['type'] if 'type' in entry else None
  
              # TODO: check if this exists and suggest disabling history if it does since it shouldn't happen normally ...
              logger.debug('Saving {} into rss database', entry['title'])
              task.session.add(rss)
  
          if not rss2gen:
              return
          # don't generate rss when learning
          if task.options.learn:
              return
  
          db_items = (
              task.session.query(RSSEntry)
              .filter(RSSEntry.file == config['file'])
              .order_by(RSSEntry.published.desc())
              .all()
          )
  
          # make items
          rss_items = []
          for db_item in db_items:
              add = True
              if config['items'] != -1:
                  if len(rss_items) > config['items']:
                      add = False
              if config['days'] != -1:
                  if (
                      datetime.datetime.today() - datetime.timedelta(days=config['days'])
                      > db_item.published
                  ):
                      add = False
              if add:
                  # add into generated feed
                  hasher = hashlib.sha1()
                  hasher.update(db_item.title.encode('utf8'))
                  hasher.update(db_item.description.encode('utf8'))
                  hasher.update(db_item.link.encode('utf8'))
                  guid = base64.urlsafe_b64encode(hasher.digest()).decode('ascii')
                  guid = PyRSS2Gen.Guid(guid, isPermaLink=False)
  
                  gen = {
                      'title': db_item.title,
                      'description': db_item.description,
                      'link': db_item.link,
                      'pubDate': db_item.published,
                      'guid': guid,
                  }
                  if db_item.enc_length is not None and db_item.enc_type is not None:
                      gen['enclosure'] = PyRSS2Gen.Enclosure(
                          db_item.link, db_item.enc_length, db_item.enc_type
                      )
                  logger.trace('Adding {} into rss {}', gen['title'], config['file'])
                  rss_items.append(PyRSS2Gen.RSSItem(**gen))
              else:
                  # no longer needed
                  task.session.delete(db_item)
  
          # make rss
          rss = PyRSS2Gen.RSS2(
              title=config.get('rsstitle', 'FlexGet'),
              link=config.get('rsslink', 'http://flexget.com'),
              description=config.get('rssdesc', 'FlexGet generated RSS feed'),
              lastBuildDate=datetime.datetime.utcnow() if config['timestamp'] else None,
              items=rss_items,
          )
  
          # don't run with --test
          if task.options.test:
              logger.info('Would write rss file with {} entries.', len(rss_items))
              return
  
          # write rss
          fn = os.path.expanduser(config['file'])
          with io.open(fn, 'wb') as file:
              try:
                  logger.verbose('Writing output rss to {}', fn)
                  rss.write_xml(file, encoding=config['encoding'])
              except LookupError:
                  logger.critical('Unknown encoding {}', config['encoding'])
                  return
              except IOError:
                  # TODO: plugins cannot raise PluginWarnings in terminate event ..
                  logger.critical('Unable to write {}', fn)
                  return
  
  
  @event('plugin.register')
  def register_plugin():
      plugin.register(OutputRSS, 'make_rss', api_ver=2)
  