Exemplo n.º 1
0
    def on_task_output(self, task, config):
        """
        Configuration::
            notify_xmpp:
                title: Message title, supports jinja templating, default {{task.name}}
                text: Message text, suports jinja templating, default {{title}}
        """
        if not config or not task.accepted:
            return

        title = config['title']
        try:
            title = render_from_task(title, task)
            log.debug('Setting message title to :%s', title)
        except RenderError as e:
            log.error('Error setting title message: %s' % e)
        items = []
        for entry in task.accepted:
            try:
                items.append(entry.render(config['text']))
            except RenderError as e:
                log.error('Error setting text message: %s' % e)
        text = '%s\n%s' % (title, '\n'.join(items))

        log.verbose('Send XMPP notification about: %s', ' - '.join(items))
        logging.getLogger('sleekxmpp').setLevel(logging.CRITICAL)

        recipients = config.get('recipients', [])
        if not isinstance(recipients, list):
            recipients = [recipients]

        xmpp = SendMsgBot(config['sender'], config['password'], recipients,
                          text)
        if xmpp.connect():
            xmpp.process(block=True)
Exemplo n.º 2
0
    def on_task_output(self, task, config):
        """
        Configuration::
            notify_xmpp:
                title: Message title, supports jinja templating, default {{task.name}}
                text: Message text, suports jinja templating, default {{title}}
        """
        if not config or not task.accepted:
            return
        
        title = config['title']
        try:
            title = render_from_task(title, task)
            log.debug('Setting message title to :%s', title)
        except RenderError as e:
            log.error('Error setting title message: %s' % e)
        items = []
        for entry in task.accepted:
            try:
                items.append(entry.render(config['text']))
            except RenderError as e:
                log.error('Error setting text message: %s' % e)
        text = '%s\n%s' % (title, '\n'.join(items))
        
        log.verbose('Send XMPP notification about: %s', ' - '.join(items))
        logging.getLogger('sleekxmpp').setLevel(logging.CRITICAL)

        recipients = config.get('recipients', [])
        if not isinstance(recipients, list):
            recipients = [recipients]

        xmpp = SendMsgBot(config['sender'], config['password'], recipients, text)
        if xmpp.connect():
            xmpp.process(block=True)
Exemplo n.º 3
0
    def on_task_exit(self, task, config):
        """Send email at exit."""

        config = prepare_config(config)

        if not config['active']:
            return

        # don't send mail when learning
        if task.manager.options.learn:
            return

        # generate email content
        if config.get('subject'):
            subject = config['subject']
        else:
            subject = '[FlexGet] {{task.name}}: '
            if task.aborted:
                subject += 'Aborted'
            elif task.failed:
                subject += '{{task.failed|length}} failed entries'
            else:
                subject += '{{task.accepted|length}} new entries downloaded'
        try:
            subject = render_from_task(subject, task)
        except RenderError, e:
            log.error('Error rendering email subject: %s' % e)
            return
Exemplo n.º 4
0
    def execute(self, task, phase_name, config):
        config = self.prepare_config(config)
        if not phase_name in config:
            log.debug('phase %s not configured' % phase_name)
            return

        name_map = {'for_entries': task.entries, 'for_accepted': task.accepted,
                    'for_rejected': task.rejected, 'for_failed': task.failed}

        allow_background = config.get('allow_background')
        for operation, entries in name_map.iteritems():
            if not operation in config[phase_name]:
                continue

            log.debug('running phase_name: %s operation: %s entries: %s' % (phase_name, operation, len(entries)))

            for entry in entries:
                cmd = config[phase_name][operation]
                entrydict = EscapingDict(entry) if config.get('auto_escape') else entry
                # Do string replacement from entry, but make sure quotes get escaped
                try:
                    cmd = render_from_entry(cmd, entrydict)
                except RenderError as e:
                    log.error('Could not set exec command for %s: %s' % (entry['title'], e))
                    # fail the entry if configured to do so
                    if config.get('fail_entries'):
                        entry.fail('Entry `%s` does not have required fields for string replacement.' % entry['title'])
                    continue

                log.debug('phase_name: %s operation: %s cmd: %s' % (phase_name, operation, cmd))
                if task.options.test:
                    log.info('Would execute: %s' % cmd)
                else:
                    # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,
                    # so logging continues to work.
                    try:
                        cmd.encode(config['encoding'])
                    except UnicodeEncodeError:
                        log.error('Unable to encode cmd `%s` to %s' % (cmd, config['encoding']))
                        if config.get('fail_entries'):
                            entry.fail('cmd `%s` could not be encoded to %s.' % (cmd, config['encoding']))
                        continue
                    # Run the command, fail entries with non-zero return code if configured to
                    if self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and config.get('fail_entries'):
                        entry.fail('exec return code was non-zero')

        # phase keyword in this
        if 'phase' in config[phase_name]:
            cmd = config[phase_name]['phase']
            try:
                cmd = render_from_task(cmd, task)
            except RenderError as e:
                log.error('Error rendering `%s`: %s' % (cmd, e))
            else:
                log.debug('phase cmd: %s' % cmd)
                if task.options.test:
                    log.info('Would execute: %s' % cmd)
                else:
                    self.execute_cmd(cmd, allow_background, config['encoding'])
Exemplo n.º 5
0
    def on_task_exit(self, task, config):
        """Send email at exit."""

        config = prepare_config(config)

        if not config['active']:
            return

        # don't send mail when learning
        if task.options.learn:
            return

        # generate email content
        if config.get('subject'):
            subject = config['subject']
        else:
            subject = '[FlexGet] {{task.name}}: '
            if task.aborted:
                subject += 'Aborted'
            elif task.failed:
                subject += '{{task.failed|length}} failed entries'
            else:
                subject += '{{task.accepted|length}} new entries downloaded'
        try:
            subject = render_from_task(subject, task)
        except RenderError as e:
            log.error('Error rendering email subject: %s' % e)
            return
        try:
            content = render_from_task(
                get_template(config['template'], 'email'), task)
        except RenderError as e:
            log.error('Error rendering email body: %s' % e)
            return

        if not content.strip():
            log.verbose(
                'No content generated from template, not sending email.')
            return

        if config.get('global'):
            # Email plugin was configured at root, save the email output
            log.debug('Saving email content for task %s' % task.name)
            task_content[task.name] = content
        else:
            send_email(subject, content, config)
Exemplo n.º 6
0
    def on_task_exit(self, task, config):
        """Send email at exit."""

        config = prepare_config(config)

        if not config['active']:
            return

        # don't send mail when learning
        if task.manager.options.learn:
            return

        # generate email content
        if config.get('subject'):
            subject = config['subject']
        else:
            subject = '[FlexGet] {{task.name}}: '
            if task.aborted:
                subject += 'Aborted'
            elif task.failed:
                subject += '{{task.failed|length}} failed entries'
            else:
                subject += '{{task.accepted|length}} new entries downloaded'
        try:
            subject = render_from_task(subject, task)
        except RenderError as e:
            log.error('Error rendering email subject: %s' % e)
            return
        try:
            content = render_from_task(get_template(config['template'], 'email'), task)
        except RenderError as e:
            log.error('Error rendering email body: %s' % e)
            return

        if not content.strip():
            log.verbose('No content generated from template, not sending email.')
            return

        if config.get('global'):
            # Email plugin was configured at root, save the email output
            log.debug('Saving email content for task %s' % task.name)
            task_content[task.name] = content
        else:
            send_email(subject, content, config)
Exemplo n.º 7
0
    def on_task_output(self, task, config):
        config = prepare_config(config)
        if not config["active"]:
            return

        # don't send mail when learning
        if task.options.learn:
            return

        # generate email content
        if config.get("subject"):
            subject = config["subject"]
        else:
            subject = "[FlexGet] {{task.name}}: "
            if task.aborted:
                subject += "Aborted"
            elif task.failed:
                subject += "{{task.failed|length}} failed entries"
            else:
                subject += "{{task.accepted|length}} new entries downloaded"
        try:
            subject = render_from_task(subject, task)
        except RenderError as e:
            log.error("Error rendering email subject: %s" % e)
            return
        try:
            content = render_from_task(get_template(config["template"], "email"), task)
        except RenderError as e:
            log.error("Error rendering email body: %s" % e)
            return

        if not content.strip():
            log.verbose("No content generated from template, not sending email.")
            return

        if config.get("global"):
            # Email plugin was configured at root, save the email output
            log.debug("Saving email content for task %s" % task.name)
            task_content[task.name] = content
        else:
            send_email(subject, content, config)
Exemplo n.º 8
0
    def render(self, template):
        """
        Renders a template string based on fields in the entry.

        :param template: A template string or FlexGetTemplate that uses jinja2 or python string replacement format.
        :return: The result of the rendering.
        :rtype: string
        :raises RenderError: If there is a problem.
        """
        if not isinstance(template, (str, FlexGetTemplate)):
            raise ValueError(
                'Trying to render non string template or unrecognized template format, got %s' % repr(template))
        log.trace('rendering: %s', template)
        return render_from_task(template, self)
Exemplo n.º 9
0
    def render(self, template):
        """
        Renders a template string based on fields in the entry.

        :param template: A template string or FlexGetTemplate that uses jinja2 or python string replacement format.
        :return: The result of the rendering.
        :rtype: string
        :raises RenderError: If there is a problem.
        """
        if not isinstance(template, (str, FlexGetTemplate)):
            raise ValueError(
                'Trying to render non string template or unrecognized template format, got %s' % repr(template))
        log.trace('rendering: %s', template)
        return render_from_task(template, self)
Exemplo n.º 10
0
    def on_task_output(self, task, config):
        # Use the default template if none is specified
        if not config.get('template'):
            config['template'] = 'default.template'

        filename = os.path.expanduser(config['template'])
        output = os.path.expanduser(config['file'])
        # Output to config directory if absolute path has not been specified
        if not os.path.isabs(output):
            output = os.path.join(task.manager.config_base, output)

        # create the template
        template = render_from_task(get_template(filename, PLUGIN_NAME), task)

        log.verbose('Writing output html to %s' % output)
        with open(output, 'w') as f:
            f.write(template.encode('utf-8'))
Exemplo n.º 11
0
    def on_task_output(self, task, config):
        # Use the default template if none is specified
        if not config.get('template'):
            config['template'] = 'default.template'

        filename = os.path.expanduser(config['template'])
        output = os.path.expanduser(config['file'])
        # Output to config directory if absolute path has not been specified
        if not os.path.isabs(output):
            output = os.path.join(task.manager.config_base, output)

        # create the template
        template = render_from_task(get_template(filename, PLUGIN_NAME), task)

        log.verbose('Writing output html to %s' % output)
        with open(output, 'w') as f:
            f.write(template.encode('utf-8'))
Exemplo n.º 12
0
    def on_task_output(self, task, config):
        # Use the default template if none is specified
        if not config.get("template"):
            config["template"] = "default.template"

        filename = os.path.expanduser(config["template"])
        output = os.path.expanduser(config["file"])
        # Output to config directory if absolute path has not been specified
        if not os.path.isabs(output):
            output = os.path.join(task.manager.config_base, output)

        # create the template
        template = render_from_task(get_template(filename, PLUGIN_NAME), task)

        log.verbose("Writing output html to %s" % output)
        f = open(output, "w")
        f.write(template.encode("utf-8"))
        f.close()
Exemplo n.º 13
0
    def on_task_output(self, task, config):
        # Use the default template if none is specified
        if not config.get('template'):
            config['template'] = 'default.template'

        filename = os.path.expanduser(config['template'])
        output = os.path.expanduser(config['file'])
        # Output to config directory if absolute path has not been specified
        if not os.path.isabs(output):
            output = os.path.join(task.manager.config_base, output)

        # create the template
        try:
            template = render_from_task(get_template(filename, PLUGIN_NAME), task)
            log.verbose('Writing output html to %s' % output)
            with open(output, 'w') as f:
                f.write(template.encode('utf-8'))
        except RenderError as e:
            log.error('Error while rendering task %s, Error: %s' % (task, e))
            raise plugin.PluginError('There was an error rendering the specified template')
Exemplo n.º 14
0
    def on_task_output(self, task, config):
        # Use the default template if none is specified
        if not config.get('template'):
            config['template'] = 'default.template'

        filename = os.path.expanduser(config['template'])
        output = os.path.expanduser(config['file'])
        # Output to config directory if absolute path has not been specified
        if not os.path.isabs(output):
            output = os.path.join(task.manager.config_base, output)

        # create the template
        try:
            template = render_from_task(get_template(filename, PLUGIN_NAME), task)
            log.verbose('Writing output html to %s' % output)
            with open(output, 'w') as f:
                f.write(template.encode('utf-8'))
        except RenderError as e:
            log.error('Error while rendering task %s, Error: %s' % (task, e))
            raise plugin.PluginError('There was an error rendering the specified template')
Exemplo n.º 15
0
    def on_task_output(self, task, config):
        """
        Configuration::
            notify_osd:
                title_template: Notification title, supports jinja templating, default {{task.name}}
                item_template: Notification body, suports jinja templating, default {{title}}
                timeout: Set how long the Notification is displayed, this is an integer default = 4 seconds, Default: 4
        """
        from gi.repository import Notify

        if not config or not task.accepted:
            return

        config = self.prepare_config(config)
        body_items = []
        for entry in task.accepted:
            try:
                body_items.append(entry.render(config['item_template']))
            except RenderError as e:
                log.error('Error setting body message: %s' % e)
        log.verbose("Send Notify-OSD notification about: %s",
                    " - ".join(body_items))

        title = config['title_template']
        try:
            title = render_from_task(title, task)
            log.debug('Setting bubble title to :%s', title)
        except RenderError as e:
            log.error('Error setting title Notify-osd message: %s' % e)

        if not Notify.init("Flexget"):
            log.error('Unable to init libnotify.')
            return

        n = Notify.Notification.new(title, '\n'.join(body_items), None)
        timeout = (config['timeout'] * 1000)
        n.set_timeout(timeout)

        if not n.show():
            log.error('Unable to send notification for %s', title)
            return
Exemplo n.º 16
0
    def on_task_output(self, task, config):
        """
        Configuration::
            notify_osd:
                title_template: Notification title, supports jinja templating, default {{task.name}}
                item_template: Notification body, suports jinja templating, default {{title}}
                timeout: Set how long the Notification is displayed, this is an integer default = 4 seconds, Default: 4
        """
        from gi.repository import Notify

        if not config or not task.accepted:
            return

        config = self.prepare_config(config)
        body_items = []
        for entry in task.accepted:
            try:
                body_items.append(entry.render(config['item_template']))
            except RenderError as e:
                log.error('Error setting body message: %s' % e)
        log.verbose("Send Notify-OSD notification about: %s",
                    " - ".join(body_items))

        title = config['title_template']
        try:
            title = render_from_task(title, task)
            log.debug('Setting bubble title to :%s', title)
        except RenderError as e:
            log.error('Error setting title Notify-osd message: %s' % e)

        if not Notify.init("Flexget"):
            log.error('Unable to init libnotify.')
            return

        n = Notify.Notification.new(title, '\n'.join(body_items), None)
        timeout = (config['timeout'] * 1000)
        n.set_timeout(timeout)

        if not n.show():
            log.error('Unable to send notification for %s', title)
            return
Exemplo n.º 17
0
 def on_task_output(self, task, config):
     if not task.accepted:
         log.debug('nothing accepted, aborting')
         return
     rooms = [s.encode('utf8').lower() for s in config.get('feeds', [])]
     if task.options.test:
         log.info('Test posting to feed(s): ' + ','.join(rooms))
     else:
         from flexget.plugins.local.friendfeed2 import FriendFeed, fetch_installed_app_access_token
         consumer_token = {'key': config['app_key'], 'secret': config['app_secret']}
         access_token = fetch_installed_app_access_token(consumer_token, config['username'], config['password'])
         ff = FriendFeed(oauth_consumer_token=consumer_token, oauth_access_token=access_token)
     if config['mode'] == 'posts':
         for entry in task.accepted:
             try:
                 fftext = entry.render(config['text'])
                 fflink = entry.render(config['link']) if 'link' in config else None
                 ffcomm = entry.render(config['comment']) if 'comment' in config else None
                 ffpict = entry.render(config['image']) if 'image' in config else None
             except RenderError as e:
                 log.error('Error rendering data: %s' % e)
             if task.options.test:
                 log.info('Test run for entry ' + entry['title'])
                 log.info('- Text would be: ' + fftext)
                 if fflink:
                     log.info('- Link would be: ' + fflink)
                 if ffpict:
                     log.info('- Image would be: ' + ffpict)
                 if ffcomm:
                     log.info('- Comment would be: ' + ffcomm)
             else:
                 try:
                     res = ff.post_entry(fftext, link=fflink, comment=ffcomm, 
                                         to=','.join(rooms), image_url=ffpict)
                     log.info('Published id: %s' % res['id'])
                 except Exception as err:
                     log.info('post_entry() failed with %s' % str(err))
     else:
         if not config.get('comment'):
             raise plugin.PluginError('"comment" option is required when "mode"=="comments".')
         try:
             fftext = render_from_task(config['text'], task)
             fflink = render_from_task(config['link'], task) if 'link' in config else None
             ffpict = render_from_task(config['image'], task) if 'image' in config else None
         except RenderError as e:
             log.error('Error rendering data: %s' % e)
         if task.options.test:
             log.info('Test run for task.')
             log.info('- Text would be: ' + fftext)
             if fflink:
                 log.info('- Link would be: ' + fflink)
             if ffpict:
                 log.info('- Image would be: ' + ffpict)
         else:
             res = ff.post_entry(fftext, link=fflink, to=','.join(rooms), image_url=ffpict)
             log.info('Published id: %s' % res['id'])
         for entry in task.accepted:
             try:
                 ffcomm = entry.render(config['comment'])
             except RenderError as e:
                 log.error('Error rendering data: %s' % e)
             if task.options.test:
                 log.info('- Comment would be: ' + ffcomm)
             else:
                 try:
                     time.sleep(1)
                     rcm = ff.post_comment(res['id'], ffcomm)
                     log.verbose('Published comment id: %s' % rcm['id'])
                 except Exception as err:
                     log.info('post_comment() failed with %s' % str(err))
Exemplo n.º 18
0
    def execute(self, task, phase_name, config):
        config = self.prepare_config(config)
        if phase_name not in config:
            log.debug("phase %s not configured" % phase_name)
            return

        name_map = {
            "for_entries": task.entries,
            "for_accepted": task.accepted,
            "for_rejected": task.rejected,
            "for_failed": task.failed,
        }

        allow_background = config.get("allow_background")
        for operation, entries in name_map.items():
            if operation not in config[phase_name]:
                continue

            log.debug("running phase_name: %s operation: %s entries: %s" % (phase_name, operation, len(entries)))

            for entry in entries:
                for cmd in config[phase_name][operation]:
                    entrydict = EscapingEntry(entry) if config.get("auto_escape") else entry
                    # Do string replacement from entry, but make sure quotes get escaped
                    try:
                        cmd = render_from_entry(cmd, entrydict)
                    except RenderError as e:
                        log.error("Could not set exec command for %s: %s" % (entry["title"], e))
                        # fail the entry if configured to do so
                        if config.get("fail_entries"):
                            entry.fail(
                                "Entry `%s` does not have required fields for string replacement." % entry["title"]
                            )
                        continue

                    log.debug("phase_name: %s operation: %s cmd: %s" % (phase_name, operation, cmd))
                    if task.options.test:
                        log.info("Would execute: %s" % cmd)
                    else:
                        # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,
                        # so logging continues to work.
                        try:
                            cmd.encode(config["encoding"])
                        except UnicodeEncodeError:
                            log.error("Unable to encode cmd `%s` to %s" % (cmd, config["encoding"]))
                            if config.get("fail_entries"):
                                entry.fail("cmd `%s` could not be encoded to %s." % (cmd, config["encoding"]))
                            continue
                        # Run the command, fail entries with non-zero return code if configured to
                        if self.execute_cmd(cmd, allow_background, config["encoding"]) != 0 and config.get(
                            "fail_entries"
                        ):
                            entry.fail("exec return code was non-zero")

        # phase keyword in this
        if "phase" in config[phase_name]:
            for cmd in config[phase_name]["phase"]:
                try:
                    cmd = render_from_task(cmd, task)
                except RenderError as e:
                    log.error("Error rendering `%s`: %s" % (cmd, e))
                else:
                    log.debug("phase cmd: %s" % cmd)
                    if task.options.test:
                        log.info("Would execute: %s" % cmd)
                    else:
                        self.execute_cmd(cmd, allow_background, config["encoding"])
Exemplo n.º 19
0
    def execute(self, task, phase_name, config):
        config = self.prepare_config(config)
        if phase_name not in config:
            log.debug('phase %s not configured' % phase_name)
            return

        name_map = {
            'for_entries': task.entries,
            'for_accepted': task.accepted,
            'for_rejected': task.rejected,
            'for_failed': task.failed
        }

        allow_background = config.get('allow_background')
        for operation, entries in name_map.items():
            if operation not in config[phase_name]:
                continue

            log.debug('running phase_name: %s operation: %s entries: %s' %
                      (phase_name, operation, len(entries)))

            for entry in entries:
                for cmd in config[phase_name][operation]:
                    entrydict = EscapingEntry(entry) if config.get(
                        'auto_escape') else entry
                    # Do string replacement from entry, but make sure quotes get escaped
                    try:
                        cmd = render_from_entry(cmd, entrydict)
                    except RenderError as e:
                        log.error('Could not set exec command for %s: %s' %
                                  (entry['title'], e))
                        # fail the entry if configured to do so
                        if config.get('fail_entries'):
                            entry.fail(
                                'Entry `%s` does not have required fields for string replacement.'
                                % entry['title'])
                        continue

                    log.debug('phase_name: %s operation: %s cmd: %s' %
                              (phase_name, operation, cmd))
                    if task.options.test:
                        log.info('Would execute: %s' % cmd)
                    else:
                        # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,
                        # so logging continues to work.
                        try:
                            cmd.encode(config['encoding'])
                        except UnicodeEncodeError:
                            log.error('Unable to encode cmd `%s` to %s' %
                                      (cmd, config['encoding']))
                            if config.get('fail_entries'):
                                entry.fail(
                                    'cmd `%s` could not be encoded to %s.' %
                                    (cmd, config['encoding']))
                            continue
                        # Run the command, fail entries with non-zero return code if configured to
                        if (self.execute_cmd(cmd, allow_background,
                                             config['encoding']) != 0
                                and config.get('fail_entries')):
                            entry.fail('exec return code was non-zero')

        # phase keyword in this
        if 'phase' in config[phase_name]:
            for cmd in config[phase_name]['phase']:
                try:
                    cmd = render_from_task(cmd, task)
                except RenderError as e:
                    log.error('Error rendering `%s`: %s' % (cmd, e))
                else:
                    log.debug('phase cmd: %s' % cmd)
                    if task.options.test:
                        log.info('Would execute: %s' % cmd)
                    else:
                        self.execute_cmd(cmd, allow_background,
                                         config['encoding'])
Exemplo n.º 20
0
class OutputEmail(object):

    """
    Send an e-mail with the list of all succeeded (downloaded) entries.

    Configuration options

    ===============  ===================================================================
    Option           Description
    ===============  ===================================================================
    from             The email address from which the email will be sent (required)
    to               The email address of the recipient (required)
    smtp_host        The host of the smtp server
    smtp_port        The port of the smtp server
    smtp_username    The username to use to connect to the smtp server
    smtp_password    The password to use to connect to the smtp server
    smtp_tls         Should we use TLS to connect to the smtp server
    smtp_ssl         Should we use SSL to connect to the smtp server
                     Due to a bug in python, this only works in python 2.6.3 and up
    active           Is this plugin active or not
    ===============  ===================================================================

    Config basic example::

      email:
        from: [email protected]
        to: [email protected]
        smtp_host: smtp.host.com

    Config example with smtp login::

      email:
        from: [email protected]
        to: [email protected]
        smtp_host: smtp.host.com
        smtp_port: 25
        smtp_login: true
        smtp_username: my_smtp_login
        smtp_password: my_smtp_password
        smtp_tls: true

    Config multi-task example::

      global:
        email:
          from: [email protected]
          to: [email protected]
          smtp_host: smtp.host.com

      tasks:
        task1:
          rss: http://xxx
        task2:
          rss: http://yyy
          email:
            active: False
        task3:
          rss: http://zzz
          email:
            to: [email protected]

    GMAIL example::

      from: [email protected]
      to: [email protected]
      smtp_host: smtp.gmail.com
      smtp_port: 587
      smtp_login: true
      smtp_username: gmailUser
      smtp_password: gmailPassword
      smtp_tls: true

    Default values for the config elements::

      email:
        active: True
        smtp_host: localhost
        smtp_port: 25
        smtp_login: False
        smtp_username:
        smtp_password:
        smtp_tls: False
        smtp_ssl: False
    """

    def validator(self):
        v = options_validator()
        v.accept('boolean', key='global')
        return v

    def on_task_output(self, task, config):
        """Count the email as an output"""

    def on_task_exit(self, task, config):
        """Send email at exit."""

        config = prepare_config(config)

        if not config['active']:
            return

        # don't send mail when learning
        if task.manager.options.learn:
            return

        # generate email content
        if config.get('subject'):
            subject = config['subject']
        else:
            subject = '[FlexGet] {{task.name}}: '
            if task.aborted:
                subject += 'Aborted'
            elif task.failed:
                subject += '{{task.failed|length}} failed entries'
            else:
                subject += '{{task.accepted|length}} new entries downloaded'
        try:
            subject = render_from_task(subject, task)
        except RenderError, e:
            log.error('Error rendering email subject: %s' % e)
            return
        try:
            content = render_from_task(get_template(config['template'], 'email'), task)
        except RenderError, e:
            log.error('Error rendering email body: %s' % e)
            return