Exemple #1
0
    def __init__(self, path, spawn=None, thread=None, **kwargs):
        """
        :param path: path to the file to be edited
        :type path: str
        :param spawn: force running edtor in a new terminal
        :type spawn: bool
        :param thread: run asynchronously, don't block alot
        :type thread: bool
        """
        self.spawn = spawn
        if spawn is None:
            self.spawn = settings.get('editor_spawn')
        self.thread = thread
        if thread is None:
            self.thread = settings.get('editor_in_thread')

        editor_cmdstring = None
        if os.path.isfile('/usr/bin/editor'):
            editor_cmdstring = '/usr/bin/editor'
        editor_cmdstring = os.environ.get('EDITOR', editor_cmdstring)
        editor_cmdstring = settings.get('editor_cmd') or editor_cmdstring
        logging.debug('using editor_cmd: %s' % editor_cmdstring)

        self.cmdlist = None
        if '%s' in editor_cmdstring:
            cmdstring = editor_cmdstring.replace('%s',
                                                 helper.shell_quote(path))
            self.cmdlist = split_commandstring(cmdstring)
        else:
            self.cmdlist = split_commandstring(editor_cmdstring) + [path]

        logging.debug({'spawn: ': self.spawn, 'in_thread': self.thread})
        ExternalCommand.__init__(self, self.cmdlist,
                                 spawn=self.spawn, thread=self.thread,
                                 **kwargs)
Exemple #2
0
    def get_authors_string(self, own_addrs=None, replace_own=None):
        """
        returns a string of comma-separated authors
        Depending on settings, it will substitute "me" for author name if
        address is user's own.

        :param own_addrs: list of own email addresses to replace
        :type own_addrs: list of str
        :param replace_own: whether or not to actually do replacement
        :type replace_own: bool
        :rtype: str
        """
        if replace_own is None:
            replace_own = settings.get('thread_authors_replace_me')
        if replace_own:
            if own_addrs is None:
                own_addrs = settings.get_addresses()
            authorslist = []
            for aname, aaddress in self.get_authors():
                if aaddress in own_addrs:
                    aname = settings.get('thread_authors_me')
                if not aname:
                    aname = aaddress
                if aname not in authorslist:
                    authorslist.append(aname)
            return ', '.join(authorslist)
        else:
            return self._notmuch_authors_string
Exemple #3
0
    def __init__(self, path, spawn=None, thread=None, **kwargs):
        """
        :param path: path to the file to be edited
        :type path: str
        :param spawn: run command in a new terminal
        :type spawn: bool
        :param thread: run asynchronously, don't block alot
        :type thread: bool
        """
        self.path = path
        if spawn != None:
            self.spawn = spawn
        else:
            self.spawn = settings.get('editor_spawn')
        if thread != None:
            self.thread = thread
        else:
            self.thread = settings.get('editor_in_thread')

        self.editor_cmd = None
        if os.path.isfile('/usr/bin/editor'):
            self.editor_cmd = '/usr/bin/editor'
        self.editor_cmd = os.environ.get('EDITOR', self.editor_cmd)
        self.editor_cmd = settings.get('editor_cmd') or self.editor_cmd
        logging.debug('using editor_cmd: %s' % self.editor_cmd)

        ExternalCommand.__init__(self, self.editor_cmd, path=self.path,
                                 spawn=self.spawn, thread=self.thread,
                                 **kwargs)
Exemple #4
0
    def __init__(self, path, spawn=None, thread=None, **kwargs):
        """
        :param path: path to the file to be edited
        :type path: str
        :param spawn: force running edtor in a new terminal
        :type spawn: bool
        :param thread: run asynchronously, don't block alot
        :type thread: bool
        """
        self.spawn = spawn
        if spawn is None:
            self.spawn = settings.get('editor_spawn')
        self.thread = thread
        if thread is None:
            self.thread = settings.get('editor_in_thread')

        editor_cmdstring = None
        if os.path.isfile('/usr/bin/editor'):
            editor_cmdstring = '/usr/bin/editor'
        editor_cmdstring = os.environ.get('EDITOR', editor_cmdstring)
        editor_cmdstring = settings.get('editor_cmd') or editor_cmdstring
        logging.debug('using editor_cmd: %s' % editor_cmdstring)

        self.cmdlist = None
        if '%s' in editor_cmdstring:
            cmdstring = editor_cmdstring.replace('%s',
                                                 helper.shell_quote(path))
            self.cmdlist = split_commandstring(cmdstring)
        else:
            self.cmdlist = split_commandstring(editor_cmdstring) + [path]

        logging.debug({'spawn: ': self.spawn, 'in_thread': self.thread})
        ExternalCommand.__init__(self, self.cmdlist,
                                 spawn=self.spawn, thread=self.thread,
                                 **kwargs)
Exemple #5
0
    def get_authors_string(self, own_addrs=None, replace_own=None):
        """
        returns a string of comma-separated authors
        Depending on settings, it will substitute "me" for author name if
        address is user's own.

        :param own_addrs: list of own email addresses to replace
        :type own_addrs: list of str
        :param replace_own: whether or not to actually do replacement
        :type replace_own: bool
        :rtype: str
        """
        if replace_own == None:
            replace_own = settings.get("thread_authors_replace_me")
        if replace_own:
            if own_addrs == None:
                own_addrs = settings.get_addresses()
            authorslist = []
            for aname, aaddress in self.get_authors():
                if aaddress in own_addrs:
                    aname = settings.get("thread_authors_me")
                if not aname:
                    aname = aaddress
                if not aname in authorslist:
                    authorslist.append(aname)
            return ", ".join(authorslist)
        else:
            return self._notmuch_authors_string
Exemple #6
0
    def apply(self, ui):
        # look if this makes sense: do we have any accounts set up?
        my_accounts = settings.get_accounts()
        if not my_accounts:
            ui.notify('no accounts set', priority='error')
            return

        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        envelope = Envelope()

        if self.inline:  # inline mode
            # set body text
            name, address = self.message.get_author()
            timestamp = self.message.get_date()
            qf = settings.get_hook('forward_prefix')
            if qf:
                quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
            else:
                quote = 'Forwarded message from %s (%s):\n' % (name or address,
                                                               timestamp)
            mailcontent = quote
            quotehook = settings.get_hook('text_quote')
            if quotehook:
                mailcontent += quotehook(self.message.accumulate_body())
            else:
                quote_prefix = settings.get('quote_prefix')
                for line in self.message.accumulate_body().splitlines():
                    mailcontent += quote_prefix + line + '\n'

            envelope.body = mailcontent

        else:  # attach original mode
            # attach original msg
            mail.set_type('message/rfc822')
            mail['Content-Disposition'] = 'attachment'
            envelope.attach(Attachment(mail))

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        subject = 'Fwd: ' + subject
        forward_subject_hook = settings.get_hook('forward_subject')
        if forward_subject_hook:
            subject = forward_subject_hook(subject)
        else:
            fsp = settings.get('forward_subject_prefix')
            if not subject.startswith(('Fwd:', fsp)):
                subject = fsp + subject
        envelope.add('Subject', subject)

        # set From
        realname, address = recipient_to_from(mail, my_accounts)
        envelope.add('From', '%s <%s>' % (realname, address))

        # continue to compose
        ui.apply_command(
            ComposeCommand(envelope=envelope, spawn=self.force_spawn))
Exemple #7
0
    def apply(self, ui):
        # look if this makes sense: do we have any accounts set up?
        my_accounts = settings.get_accounts()
        if not my_accounts:
            ui.notify('no accounts set', priority='error')
            return

        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        envelope = Envelope()

        if self.inline:  # inline mode
            # set body text
            name, address = self.message.get_author()
            timestamp = self.message.get_date()
            qf = settings.get_hook('forward_prefix')
            if qf:
                quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
            else:
                quote = 'Forwarded message from %s (%s):\n' % (
                    name or address, timestamp)
            mailcontent = quote
            quotehook = settings.get_hook('text_quote')
            if quotehook:
                mailcontent += quotehook(self.message.accumulate_body())
            else:
                quote_prefix = settings.get('quote_prefix')
                for line in self.message.accumulate_body().splitlines():
                    mailcontent += quote_prefix + line + '\n'

            envelope.body = mailcontent

        else:  # attach original mode
            # attach original msg
            mail.set_type('message/rfc822')
            mail['Content-Disposition'] = 'attachment'
            envelope.attach(Attachment(mail))

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        subject = 'Fwd: ' + subject
        forward_subject_hook = settings.get_hook('forward_subject')
        if forward_subject_hook:
            subject = forward_subject_hook(subject)
        else:
            fsp = settings.get('forward_subject_prefix')
            if not subject.startswith(('Fwd:', fsp)):
                subject = fsp + subject
        envelope.add('Subject', subject)

        # set From
        realname, address = recipient_to_from(mail, my_accounts)
        envelope.add('From', '%s <%s>' % (realname, address))

        # continue to compose
        ui.apply_command(ComposeCommand(envelope=envelope,
                                        spawn=self.force_spawn))
Exemple #8
0
def determine_sender(mail, action='reply'):
    """
    Inspect a given mail to reply/forward/bounce and find the most appropriate
    account to act from and construct a suitable From-Header to use.

    :param mail: the email to inspect
    :type mail: `email.message.Message`
    :param action: intended use case: one of "reply", "forward" or "bounce"
    :type action: str
    """
    assert action in ['reply', 'forward', 'bounce']
    realname = None
    address = None

    # get accounts
    my_accounts = settings.get_accounts()
    assert my_accounts, 'no accounts set!'

    # extract list of addresses to check for my address
    # X-Envelope-To and Envelope-To are used to store the recipient address
    # if not included in other fields
    candidate_addresses = getaddresses(mail.get_all('To', []) +
                                       mail.get_all('Cc', []) +
                                       mail.get_all('Delivered-To', []) +
                                       mail.get_all('X-Envelope-To', []) +
                                       mail.get_all('Envelope-To', []) +
                                       mail.get_all('From', []))

    logging.debug('candidate addresses: %s' % candidate_addresses)
    # pick the most important account that has an address in candidates
    # and use that accounts realname and the address found here
    for account in my_accounts:
        acc_addresses = account.get_addresses()
        for alias in acc_addresses:
            if realname is not None:
                break
            regex = re.compile(re.escape(alias), flags=re.IGNORECASE)
            for seen_name, seen_address in candidate_addresses:
                if regex.match(seen_address):
                    logging.debug("match!: '%s' '%s'" % (seen_address, alias))
                    if settings.get(action + '_force_realname'):
                        realname = account.realname
                    else:
                        realname = seen_name
                    if settings.get(action + '_force_address'):
                        address = account.address
                    else:
                        address = seen_address

    # revert to default account if nothing found
    if realname is None:
        account = my_accounts[0]
        realname = account.realname
        address = account.address
    logging.debug('using realname: "%s"' % realname)
    logging.debug('using address: %s' % address)

    from_value = address if realname == '' else '%s <%s>' % (realname, address)
    return from_value, account
Exemple #9
0
def determine_sender(mail, action='reply'):
    """
    Inspect a given mail to reply/forward/bounce and find the most appropriate
    account to act from and construct a suitable From-Header to use.

    :param mail: the email to inspect
    :type mail: `email.message.Message`
    :param action: intended use case: one of "reply", "forward" or "bounce"
    :type action: str
    """
    assert action in ['reply', 'forward', 'bounce']
    realname = None
    address = None

    # get accounts
    my_accounts = settings.get_accounts()
    assert my_accounts, 'no accounts set!'

    # extract list of recipients to check for my address
    rec_to = filter(lambda x: x, mail.get('To', '').split(','))
    rec_cc = filter(lambda x: x, mail.get('Cc', '').split(','))
    delivered_to = mail.get('Delivered-To', None)
    recipients = rec_to + rec_cc
    if delivered_to is not None:
        recipients.append(delivered_to)

    logging.debug('recipients: %s' % recipients)
    # pick the most important account that has an address in recipients
    # and use that accounts realname and the found recipient address
    for account in my_accounts:
        acc_addresses = account.get_addresses()
        for alias in acc_addresses:
            if realname is not None:
                break
            regex = re.compile(alias)
            for rec in recipients:
                seen_name, seen_address = parseaddr(rec)
                if regex.match(seen_address):
                    logging.debug("match!: '%s' '%s'" % (seen_address, alias))
                    if settings.get(action + '_force_realname'):
                        realname = account.realname
                    else:
                        realname = seen_name
                    if settings.get(action + '_force_address'):
                        address = account.address
                    else:
                        address = seen_address

    # revert to default account if nothing found
    if realname is None:
        account = my_accounts[0]
        realname = account.realname
        address = account.address
    logging.debug('using realname: "%s"' % realname)
    logging.debug('using address: %s' % address)

    from_value = address if realname == '' else '%s <%s>' % (realname, address)
    return from_value, account
Exemple #10
0
def determine_sender(mail, action='reply'):
    """
    Inspect a given mail to reply/forward/bounce and find the most appropriate
    account to act from and construct a suitable From-Header to use.

    :param mail: the email to inspect
    :type mail: `email.message.Message`
    :param action: intended use case: one of "reply", "forward" or "bounce"
    :type action: str
    """
    assert action in ['reply', 'forward', 'bounce']
    realname = None
    address = None

    # get accounts
    my_accounts = settings.get_accounts()
    assert my_accounts, 'no accounts set!'

    # extract list of addresses to check for my address
    # X-Envelope-To and Envelope-To are used to store the recipient address
    # if not included in other fields
    candidate_addresses = getaddresses(
        mail.get_all('To', []) + mail.get_all('Cc', []) +
        mail.get_all('Delivered-To', []) + mail.get_all('X-Envelope-To', []) +
        mail.get_all('Envelope-To', []) + mail.get_all('From', []))

    logging.debug('candidate addresses: %s' % candidate_addresses)
    # pick the most important account that has an address in candidates
    # and use that accounts realname and the address found here
    for account in my_accounts:
        acc_addresses = account.get_addresses()
        for alias in acc_addresses:
            if realname is not None:
                break
            regex = re.compile(re.escape(alias), flags=re.IGNORECASE)
            for seen_name, seen_address in candidate_addresses:
                if regex.match(seen_address):
                    logging.debug("match!: '%s' '%s'" % (seen_address, alias))
                    if settings.get(action + '_force_realname'):
                        realname = account.realname
                    else:
                        realname = seen_name
                    if settings.get(action + '_force_address'):
                        address = account.address
                    else:
                        address = seen_address

    # revert to default account if nothing found
    if realname is None:
        account = my_accounts[0]
        realname = account.realname
        address = account.address
    logging.debug('using realname: "%s"' % realname)
    logging.debug('using address: %s' % address)

    from_value = address if realname == '' else '%s <%s>' % (realname, address)
    return from_value, account
Exemple #11
0
def recipient_to_from(mail, my_accounts):
    """
    construct a suitable From-Header for forwards/replies to
    a given mail.

    :param mail: the email to inspect
    :type mail: `email.message.Message`
    :param my_accounts: list of accounts from which to chose from
    :type my_accounts: list of `alot.account.Account`
    """
    realname = None
    address = None

    # extract list of recipients to check for my address
    rec_to = filter(lambda x: x, mail.get('To', '').split(','))
    rec_cc = filter(lambda x: x, mail.get('Cc', '').split(','))
    delivered_to = mail.get('Delivered-To', None)
    recipients = rec_to + rec_cc
    if delivered_to is not None:
        recipients.append(delivered_to)

    logging.debug('recipients: %s' % recipients)
    # pick the most important account that has an address in recipients
    # and use that accounts realname and the found recipient address
    for acc in my_accounts:
        acc_addresses = acc.get_addresses()
        for alias_re in acc_addresses:
            if realname is not None:
                break
            regex = re.compile(alias_re)
            for rec in recipients:
                seen_name, seen_address = parseaddr(rec)
                if regex.match(seen_address):
                    logging.debug("match!: '%s' '%s'" %
                                  (seen_address, alias_re))
                    if settings.get('reply_force_realname'):
                        realname = acc.realname
                    else:
                        realname = seen_name
                    if settings.get('reply_force_address'):
                        address = acc.address
                    else:
                        address = seen_address

    # revert to default account if nothing found
    if realname is None:
        realname = my_accounts[0].realname
        address = my_accounts[0].address
    logging.debug('using realname: "%s"' % realname)
    logging.debug('using address: %s' % address)

    return address if realname == '' else '%s <%s>' % (realname, address)
Exemple #12
0
def recipient_to_from(mail, my_accounts):
    """
    construct a suitable From-Header for forwards/replies to
    a given mail.

    :param mail: the email to inspect
    :type mail: `email.message.Message`
    :param my_accounts: list of accounts from which to chose from
    :type my_accounts: list of `alot.account.Account`
    """
    realname = None
    address = None

    # extract list of recipients to check for my address
    rec_to = filter(lambda x: x, mail.get('To', '').split(','))
    rec_cc = filter(lambda x: x, mail.get('Cc', '').split(','))
    delivered_to = mail.get('Delivered-To', None)
    recipients = rec_to + rec_cc
    if delivered_to is not None:
        recipients.append(delivered_to)

    logging.debug('recipients: %s' % recipients)
    # pick the most important account that has an address in recipients
    # and use that accounts realname and the found recipient address
    for acc in my_accounts:
        acc_addresses = acc.get_addresses()
        for alias_re in acc_addresses:
            if realname is not None:
                break
            regex = re.compile(alias_re)
            for rec in recipients:
                seen_name, seen_address = parseaddr(rec)
                if regex.match(seen_address):
                    logging.debug("match!: '%s' '%s'" % (seen_address, alias_re))
                    if settings.get('reply_force_realname'):
                        realname = acc.realname
                    else:
                        realname = seen_name
                    if settings.get('reply_force_address'):
                        address = acc.address
                    else:
                        address = seen_address

    # revert to default account if nothing found
    if realname is None:
        realname = my_accounts[0].realname
        address = my_accounts[0].address
    logging.debug('using realname: "%s"' % realname)
    logging.debug('using address: %s' % address)

    return address if realname == '' else '%s <%s>' % (realname, address)
Exemple #13
0
def text_quote(message):
    # avoid importing a big module by using a simple heuristic to guess the
    # right encoding
    def decode(s, encodings=('ascii', 'utf8', 'latin1')):
        for encoding in encodings:
            try:
                return s.decode(encoding)
            except UnicodeDecodeError:
                pass
        return s.decode('ascii', 'ignore')
    lines = message.splitlines()
    # delete empty lines at beginning and end (some email client insert these
    # outside of the pgp signed message...)
    if lines[0] == '' or lines[-1] == '':
        from itertools import dropwhile
        lines = list(dropwhile(lambda l: l == '', lines))
        lines = list(reversed(list(dropwhile(
            lambda l: l == '', reversed(lines)))))
    if len(lines) > 0 and lines[0] == '-----BEGIN PGP MESSAGE-----' \
            and lines[-1] == '-----END PGP MESSAGE-----':
        try:
            sigs, d = crypto.decrypt_verify(message.encode('utf-8'))
            message = decode(d)
        except errors.GPGProblem:
            pass
    elif len(lines) > 0 and lines[0] == '-----BEGIN PGP SIGNED MESSAGE-----' \
            and lines[-1] == '-----END PGP SIGNATURE-----':
        # gpgme does not seem to be able to extract the plain text part of
        # a signed message
        import gnupg
        gpg = gnupg.GPG()
        d = gpg.decrypt(message.encode('utf8'))
        message = d.data.decode('utf8')
    quote_prefix = settings.get('quote_prefix')
    return "\n".join([quote_prefix + line for line in message.splitlines()])
Exemple #14
0
    def apply(self, ui):
        if self.envelope is None:
            self.envelope = Envelope()
        if self.template is not None:
            #get location of tempsdir, containing msg templates
            tempdir = settings.get('template_dir')
            tempdir = os.path.expanduser(tempdir)
            if not tempdir:
                xdgdir = os.environ.get('XDG_CONFIG_HOME',
                                        os.path.expanduser('~/.config'))
                tempdir = os.path.join(xdgdir, 'alot', 'templates')

            path = os.path.expanduser(self.template)
            if not os.path.dirname(path):  # use tempsdir
                if not os.path.isdir(tempdir):
                    ui.notify('no templates directory: %s' % tempdir,
                              priority='error')
                    return
                path = os.path.join(tempdir, path)

            if not os.path.isfile(path):
                ui.notify('could not find template: %s' % path,
                          priority='error')
                return
            try:
                self.envelope.parse_template(open(path).read())
            except Exception, e:
                ui.notify(str(e), priority='error')
                return
Exemple #15
0
    def __init__(self, all=False, separately=False, raw=False, add_tags=False,
                 **kwargs):
        """
        :param all: print all, not only selected messages
        :type all: bool
        :param separately: call print command once per message
        :type separately: bool
        :param raw: pipe raw message string to print command
        :type raw: bool
        :param add_tags: add 'Tags' header to the message
        :type add_tags: bool
        """
        # get print command
        cmd = settings.get('print_cmd') or ''

        # set up notification strings
        if all:
            confirm_msg = 'print all messages in thread?'
            ok_msg = 'printed thread using %s' % cmd
        else:
            confirm_msg = 'print selected message?'
            ok_msg = 'printed message using %s' % cmd

        # no print cmd set
        noop_msg = 'no print command specified. Set "print_cmd" in the '\
                    'global section.'

        PipeCommand.__init__(self, [cmd], all=all, separately=separately,
                             background=True,
                             shell=False,
                             format='raw' if raw else 'decoded',
                             add_tags=add_tags,
                             noop_msg=noop_msg, confirm_msg=confirm_msg,
                             done_msg=ok_msg, **kwargs)
Exemple #16
0
    def apply(self, ui):
        try:
            ui.dbman.flush()
            if callable(self.callback):
                self.callback()
            logging.debug('flush complete')
            if ui.db_was_locked:
                if not self.silent:
                    ui.notify('changes flushed')
                ui.db_was_locked = False
            ui.update()

        except DatabaseLockedError:
            timeout = settings.get('flush_retry_timeout')

            def f(*args):
                self.apply(ui)
            ui.mainloop.set_alarm_in(timeout, f)
            if not ui.db_was_locked:
                if not self.silent:
                    ui.notify(
                        'index locked, will try again in %d secs' % timeout)
                ui.db_was_locked = True
            ui.update()
            return
Exemple #17
0
 def apply(self, ui):
     if settings.get("bug_on_exit"):
         if (yield ui.choice("realy quit?", select="yes", cancel="no", msg_position="left")) == "no":
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #18
0
    def apply(self, ui):
        if self.envelope == None:
            self.envelope = Envelope()
        if self.template is not None:
            #get location of tempsdir, containing msg templates
            tempdir = settings.get('template_dir')
            tempdir = os.path.expanduser(tempdir)
            if not tempdir:
                xdgdir = os.environ.get('XDG_CONFIG_HOME',
                                        os.path.expanduser('~/.config'))
                tempdir = os.path.join(xdgdir, 'alot', 'templates')

            path = os.path.expanduser(self.template)
            if not os.path.dirname(path):  # use tempsdir
                if not os.path.isdir(tempdir):
                    ui.notify('no templates directory: %s' % tempdir,
                              priority='error')
                    return
                path = os.path.join(tempdir, path)

            if not os.path.isfile(path):
                ui.notify('could not find template: %s' % path,
                          priority='error')
                return
            try:
                self.envelope.parse_template(open(path).read())
            except Exception, e:
                ui.notify(str(e), priority='error')
                return
Exemple #19
0
    def apply(self, ui):
        if self.envelope is None:
            self.envelope = Envelope()
        if self.template is not None:
            # get location of tempsdir, containing msg templates
            tempdir = settings.get("template_dir")
            tempdir = os.path.expanduser(tempdir)
            if not tempdir:
                xdgdir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
                tempdir = os.path.join(xdgdir, "alot", "templates")

            path = os.path.expanduser(self.template)
            if not os.path.dirname(path):  # use tempsdir
                if not os.path.isdir(tempdir):
                    ui.notify("no templates directory: %s" % tempdir, priority="error")
                    return
                path = os.path.join(tempdir, path)

            if not os.path.isfile(path):
                ui.notify("could not find template: %s" % path, priority="error")
                return
            try:
                self.envelope.parse_template(open(path).read())
            except Exception, e:
                ui.notify(str(e), priority="error")
                return
Exemple #20
0
    def refresh(self, thread=None):
        """refresh thread metadata from the index"""
        if not thread:
            thread = self._dbman._get_notmuch_thread(self._id)

        self._total_messages = thread.get_total_messages()
        self._notmuch_authors_string = thread.get_authors()

        subject_type = settings.get('thread_subject')
        if subject_type == 'notmuch':
            subject = thread.get_subject()
        elif subject_type == 'oldest':
            try:
                first_msg = list(thread.get_toplevel_messages())[0]
                subject = first_msg.get_header('subject')
            except IndexError:
                subject = ''
        self._subject = subject

        self._authors = None
        ts = thread.get_oldest_date()

        try:
            self._oldest_date = datetime.fromtimestamp(ts)
        except ValueError:  # year is out of range
            self._oldest_date = None
        try:
            timestamp = thread.get_newest_date()
            self._newest_date = datetime.fromtimestamp(timestamp)
        except ValueError:  # year is out of range
            self._newest_date = None

        self._tags = set([t for t in thread.get_tags()])
        self._messages = {}  # this maps messages to its children
        self._toplevel_messages = []
Exemple #21
0
    def refresh(self, thread=None):
        """refresh thread metadata from the index"""
        if not thread:
            thread = self._dbman._get_notmuch_thread(self._id)

        self._total_messages = thread.get_total_messages()
        self._notmuch_authors_string = thread.get_authors()

        subject_type = settings.get('thread_subject')
        if subject_type == 'notmuch':
            subject = thread.get_subject()
        elif subject_type == 'oldest':
            try:
                first_msg = list(thread.get_toplevel_messages())[0]
                subject = first_msg.get_header('subject')
            except IndexError:
                subject = ''
        self._subject = subject

        self._authors = None
        ts = thread.get_oldest_date()

        try:
            self._oldest_date = datetime.fromtimestamp(ts)
        except ValueError:  # year is out of range
            self._oldest_date = None
        try:
            timestamp = thread.get_newest_date()
            self._newest_date = datetime.fromtimestamp(timestamp)
        except ValueError:  # year is out of range
            self._newest_date = None

        self._tags = set([t for t in thread.get_tags()])
        self._messages = {}  # this maps messages to its children
        self._toplevel_messages = []
Exemple #22
0
    def __init__(self, all=False, separately=False, raw=False, add_tags=False,
                 **kwargs):
        """
        :param all: print all, not only selected messages
        :type all: bool
        :param separately: call print command once per message
        :type separately: bool
        :param raw: pipe raw message string to print command
        :type raw: bool
        :param add_tags: add 'Tags' header to the message
        :type add_tags: bool
        """
        # get print command
        cmd = settings.get('print_cmd') or ''

        # set up notification strings
        if all:
            confirm_msg = 'print all messages in thread?'
            ok_msg = 'printed thread using %s' % cmd
        else:
            confirm_msg = 'print selected message?'
            ok_msg = 'printed message using %s' % cmd

        # no print cmd set
        noop_msg = 'no print command specified. Set "print_cmd" in the '\
            'global section.'

        PipeCommand.__init__(self, [cmd], all=all, separately=separately,
                             background=True,
                             shell=False,
                             format='raw' if raw else 'decoded',
                             add_tags=add_tags,
                             noop_msg=noop_msg, confirm_msg=confirm_msg,
                             done_msg=ok_msg, **kwargs)
Exemple #23
0
    def apply(self, ui):
        try:
            ui.dbman.flush()
            if callable(self.callback):
                self.callback()
            logging.debug('flush complete')
            if ui.db_was_locked:
                if not self.silent:
                    ui.notify('changes flushed')
                ui.db_was_locked = False
            ui.update()

        except DatabaseLockedError:
            timeout = settings.get('flush_retry_timeout')

            def f(*args):
                self.apply(ui)

            ui.mainloop.set_alarm_in(timeout, f)
            if not ui.db_was_locked:
                if not self.silent:
                    ui.notify('index locked, will try again in %d secs' %
                              timeout)
                ui.db_was_locked = True
            ui.update()
            return
Exemple #24
0
 def apply(self, ui):
     if settings.get('bug_on_exit'):
         if (yield ui.choice('realy quit?', select='yes', cancel='no',
                            msg_position='left')) == 'no':
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #25
0
def main():
    """The main entry point to alot.  It parses the command line and prepares
    for the user interface main loop to run."""
    options, command = parser()

    # logging
    root_logger = logging.getLogger()
    for log_handler in root_logger.handlers:
        root_logger.removeHandler(log_handler)
    root_logger = None
    numeric_loglevel = getattr(logging, options.debug_level.upper(), None)
    logformat = '%(levelname)s:%(module)s:%(message)s'
    logging.basicConfig(level=numeric_loglevel, filename=options.logfile,
                        filemode='w', format=logformat)

    # locate alot config files
    if options.config is None:
        alotconfig = os.path.join(
            os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
            'alot', 'config')
        if os.path.exists(alotconfig):
            settings.alot_rc_path = alotconfig
    else:
        settings.alot_rc_path = options.config

    settings.notmuch_rc_path = options.notmuch_config

    try:
        settings.read_config()
        settings.read_notmuch_config()
    except (ConfigError, OSError, IOError) as e:
        sys.exit(e)

    # store options given by config swiches to the settingsManager:
    if options.colour_mode:
        settings.set('colourmode', options.colour_mode)

    # get ourselves a database manager
    indexpath = settings.get_notmuch_setting('database', 'path')
    indexpath = options.mailindex_path or indexpath
    dbman = DBManager(path=indexpath, ro=options.read_only)

    # determine what to do
    if command is None:
        try:
            cmdstring = settings.get('initial_command')
        except CommandParseError as err:
            sys.exit(err)
    elif command.subcommand in _SUBCOMMANDS:
        cmdstring = ' '.join(options.command)

    # set up and start interface
    UI(dbman, cmdstring)

    # run the exit hook
    exit_hook = settings.get_hook('exit')
    if exit_hook is not None:
        exit_hook()
Exemple #26
0
def main():
    """The main entry point to alot.  It parses the command line and prepares
    for the user interface main loop to run."""
    options, command = parser()

    # logging
    root_logger = logging.getLogger()
    for log_handler in root_logger.handlers:
        root_logger.removeHandler(log_handler)
    root_logger = None
    numeric_loglevel = getattr(logging, options.debug_level.upper(), None)
    logformat = '%(levelname)s:%(module)s:%(message)s'
    logging.basicConfig(level=numeric_loglevel,
                        filename=options.logfile,
                        filemode='w',
                        format=logformat)

    # locate alot config files
    if options.config is None:
        alotconfig = os.path.join(
            os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
            'alot', 'config')
        if not os.path.exists(alotconfig):
            alotconfig = None
    else:
        alotconfig = options.config

    try:
        settings.read_config(alotconfig)
        settings.read_notmuch_config(options.notmuch_config)
    except (ConfigError, OSError, IOError) as e:
        sys.exit(e)

    # store options given by config swiches to the settingsManager:
    if options.colour_mode:
        settings.set('colourmode', options.colour_mode)

    # get ourselves a database manager
    indexpath = settings.get_notmuch_setting('database', 'path')
    indexpath = options.mailindex_path or indexpath
    dbman = DBManager(path=indexpath, ro=options.read_only)

    # determine what to do
    if command is None:
        try:
            cmdstring = settings.get('initial_command')
        except CommandParseError as err:
            sys.exit(err)
    elif command.subcommand in _SUBCOMMANDS:
        cmdstring = ' '.join(options.command)

    # set up and start interface
    UI(dbman, cmdstring)

    # run the exit hook
    exit_hook = settings.get_hook('exit')
    if exit_hook is not None:
        exit_hook()
Exemple #27
0
    def __init__(self,
                 message,
                 even=False,
                 folded=True,
                 raw=False,
                 all_headers=False,
                 depth=0,
                 bars_at=[]):
        """
        :param message: the message to display
        :type message: alot.db.Message
        :param even: use messagesummary_even theme for summary
        :type even: bool
        :param folded: fold message initially
        :type folded: bool
        :param raw: show message source initially
        :type raw: bool
        :param all_headers: show all headers initially
        :type all_headers: bool
        :param depth: number of characters to shift content to the right
        :type depth: int
        :param bars_at: defines for each column of the indentation whether to
                        use a vertical bar instead of a space.
        :type bars_at: list(bool)
        """
        self.message = message
        self.mail = self.message.get_email()

        self.depth = depth
        self.bars_at = bars_at
        self.even = even
        self.folded = folded
        self.show_raw = raw
        self.show_all_headers = all_headers

        # define subwidgets that will be created on demand
        self.sumline = None
        self.headerw = None
        self.attachmentw = None
        self.bodyw = None
        self.sourcew = None

        # set available and to be displayed headers
        self._all_headers = list(set(self.mail.keys()))
        displayed = settings.get('displayed_headers')
        self._filtered_headers = [
            k for k in displayed if k.lower() == 'tags' or k in self.mail
        ]
        self._displayed_headers = None

        bars = settings.get_theming_attribute('thread', 'arrow_bars')
        self.arrow_bars_att = bars
        heads = settings.get_theming_attribute('thread', 'arrow_heads')
        self.arrow_heads_att = heads
        logging.debug(self.arrow_heads_att)

        self.rebuild()  # this will build self.pile
        urwid.WidgetWrap.__init__(self, self.pile)
Exemple #28
0
    def __init__(self,
                 cmd,
                 stdin=None,
                 shell=False,
                 spawn=False,
                 refocus=True,
                 thread=False,
                 on_success=None,
                 **kwargs):
        """
        :param cmd: the command to call
        :type cmd: list or str
        :param stdin: input to pipe to the process
        :type stdin: file or str
        :param spawn: run command in a new terminal
        :type spawn: bool
        :param shell: let shell interpret command string
        :type shell: bool
        :param thread: run asynchronously, don't block alot
        :type thread: bool
        :param refocus: refocus calling buffer after cmd termination
        :type refocus: bool
        :param on_success: code to execute after command successfully exited
        :type on_success: callable
        """
        logging.debug({'spawn': spawn})
        # make sure cmd is a list of str
        if isinstance(cmd, unicode):
            # convert cmdstring to list: in case shell==True,
            # Popen passes only the first item in the list to $SHELL
            cmd = [cmd] if shell else split_commandstring(cmd)

        # determine complete command list to pass
        touchhook = settings.get_hook('touch_external_cmdlist')
        # filter cmd, shell and thread through hook if defined
        if touchhook is not None:
            logging.debug('calling hook: touch_external_cmdlist')
            res = touchhook(cmd, shell=shell, spawn=spawn, thread=thread)
            logging.debug('got: %s' % res)
            cmd, shell, self.in_thread = res
        # otherwise if spawn requested and X11 is running
        elif spawn:
            if 'DISPLAY' in os.environ:
                term_cmd = settings.get('terminal_cmd', '')
                logging.info('spawn in terminal: %s' % term_cmd)
                termcmdlist = split_commandstring(term_cmd)
                cmd = termcmdlist + cmd
            else:
                thread = False

        self.cmdlist = cmd
        self.stdin = stdin
        self.shell = shell
        self.refocus = refocus
        self.in_thread = thread
        self.on_success = on_success
        Command.__init__(self, **kwargs)
Exemple #29
0
 def apply(self, ui):
     msg = "index not fully synced. " if ui.db_was_locked else ""
     if settings.get("bug_on_exit") or ui.db_was_locked:
         msg += "really quit?"
         if (yield ui.choice(msg, select="yes", cancel="no", msg_position="left")) == "no":
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #30
0
 def __init__(self, tid, dbman):
     self.dbman = dbman
     self.thread = dbman.get_thread(tid)
     self.tag_widgets = []
     self.display_content = settings.get('display_content_in_threadline')
     self.structure = None
     self.rebuild()
     normal = self.structure['normal']
     focussed = self.structure['focus']
     urwid.AttrMap.__init__(self, self.columns, normal, focussed)
Exemple #31
0
 def __init__(self, tid, dbman):
     self.dbman = dbman
     self.thread = dbman.get_thread(tid)
     self.tag_widgets = []
     self.display_content = settings.get('display_content_in_threadline')
     self.structure = None
     self.rebuild()
     normal = self.structure['normal']
     focussed = self.structure['focus']
     urwid.AttrMap.__init__(self, self.columns, normal, focussed)
Exemple #32
0
 def apply(self, ui):
     msg = 'index not fully synced. ' if ui.db_was_locked else ''
     if settings.get('bug_on_exit') or ui.db_was_locked:
         msg += 'really quit?'
         if (yield ui.choice(msg, select='yes', cancel='no',
                             msg_position='left')) == 'no':
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #33
0
 def apply(self, ui):
     if settings.get('bug_on_exit'):
         if (yield ui.choice('realy quit?',
                             select='yes',
                             cancel='no',
                             msg_position='left')) == 'no':
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #34
0
 def apply(self, ui):
     msg = 'index not fully synced. ' if ui.db_was_locked else ''
     if settings.get('bug_on_exit') or ui.db_was_locked:
         msg += 'really quit?'
         if (yield ui.choice(msg, select='yes', cancel='no',
                             msg_position='left')) == 'no':
             return
     for b in ui.buffers:
         b.cleanup()
     ui.exit()
Exemple #35
0
 def apply(self, ui):
     if self.buffer == None:
         self.buffer = ui.current_buffer
     if len(ui.buffers) == 1:
         if settings.get('quit_on_last_bclose'):
             logging.info('closing the last buffer, exiting')
             ui.apply_command(ExitCommand())
         else:
             logging.info('not closing last remaining buffer as '
                          'global.quit_on_last_bclose is set to False')
     else:
         ui.buffer_close(self.buffer)
Exemple #36
0
    def apply(self, ui):
        try:
            ui.dbman.flush()
        except DatabaseLockedError:
            timeout = settings.get('flush_retry_timeout')

            def f(*args):
                self.apply(ui)
            ui.mainloop.set_alarm_in(timeout, f)
            ui.notify('index locked, will try again in %d secs' % timeout)
            ui.update()
            return
Exemple #37
0
 def _get_headers(self):
     if self.display_all_headers is True:
         if self._all_headers_tree is None:
             self._all_headers_tree = self.construct_header_pile()
         ret = self._all_headers_tree
     else:
         if self._default_headers_tree is None:
             headers = settings.get('displayed_headers')
             self._default_headers_tree = self.construct_header_pile(
                 headers)
         ret = self._default_headers_tree
     return ret
Exemple #38
0
 def apply(self, ui):
     if self.buffer == None:
         self.buffer = ui.current_buffer
     if len(ui.buffers) == 1:
         if settings.get('quit_on_last_bclose'):
             logging.info('closing the last buffer, exiting')
             ui.apply_command(ExitCommand())
         else:
             logging.info('not closing last remaining buffer as '
                            'global.quit_on_last_bclose is set to False')
     else:
         ui.buffer_close(self.buffer)
Exemple #39
0
 def _get_headers(self):
     if self.display_all_headers is True:
         if self._all_headers_tree is None:
             self._all_headers_tree = self.construct_header_pile()
         ret = self._all_headers_tree
     else:
         if self._default_headers_tree is None:
             headers = settings.get('displayed_headers')
             self._default_headers_tree = self.construct_header_pile(
                 headers)
         ret = self._default_headers_tree
     return ret
Exemple #40
0
    def apply(self, ui):
        tags = settings.get('my_tag_list')
        if len(tags) == 0:
            tags = ui.dbman.get_all_tags()

        blists = ui.get_buffers_of_type(buffers.TagListBuffer)
        if blists:
            buf = blists[0]
            buf.tags = tags
            buf.rebuild()
            ui.buffer_focus(buf)
        else:
            ui.buffer_open(buffers.TagListBuffer(ui, tags, self.filtfun))
Exemple #41
0
    def apply(self, ui):
        tbuffer = ui.current_buffer
        logging.debug('matching lines %s...' % (self.query))
        if self.query is None:
            messagetrees = [tbuffer.get_selected_messagetree()]
        else:
            messagetrees = tbuffer.messagetrees()
            if self.query != '*':

                def matches(msgt):
                    msg = msgt.get_message()
                    return msg.matches(self.query)

                messagetrees = filter(matches, messagetrees)

        for mt in messagetrees:
            # determine new display values for this message
            if self.visible == 'toggle':
                visible = mt.is_collapsed(mt.root)
            else:
                visible = self.visible
            if self.raw == 'toggle':
                tbuffer.focus_selected_message()
            raw = not mt.display_source if self.raw == 'toggle' else self.raw
            all_headers = not mt.display_all_headers \
                if self.all_headers == 'toggle' else self.all_headers

            # collapse/expand depending on new 'visible' value
            if visible is False:
                mt.collapse(mt.root)

                # mark as read if needed
                if settings.get('auto_remove_unread'):
                    msg = mt.get_message()
                    if 'unread' in msg.get_tags():
                        msg.remove_tags(['unread'])
                        fcmd = FlushCommand(silent=True)
                        ui.apply_command(fcmd)
            elif visible is True:  # could be None
                mt.expand(mt.root)
            tbuffer.focus_selected_message()
            # set new values in messagetree obj
            if raw is not None:
                mt.display_source = raw
            if all_headers is not None:
                mt.display_all_headers = all_headers
            mt.debug()
            # let the messagetree reassemble itself
            mt.reassemble()
        # refresh the buffer (clears Tree caches etc)
        tbuffer.refresh()
Exemple #42
0
    def __init__(self, message, even=False, folded=True, raw=False,
                 all_headers=False, depth=0, bars_at=[]):
        """
        :param message: the message to display
        :type message: alot.db.Message
        :param even: use messagesummary_even theme for summary
        :type even: bool
        :param folded: fold message initially
        :type folded: bool
        :param raw: show message source initially
        :type raw: bool
        :param all_headers: show all headers initially
        :type all_headers: bool
        :param depth: number of characters to shift content to the right
        :type depth: int
        :param bars_at: defines for each column of the indentation whether to
                        use a vertical bar instead of a space.
        :type bars_at: list(bool)
        """
        self.message = message
        self.mail = self.message.get_email()

        self.depth = depth
        self.bars_at = bars_at
        self.even = even
        self.folded = folded
        self.show_raw = raw
        self.show_all_headers = all_headers

        # define subwidgets that will be created on demand
        self.sumline = None
        self.headerw = None
        self.attachmentw = None
        self.bodyw = None
        self.sourcew = None

        # set available and to be displayed headers
        self._all_headers = list(set(self.mail.keys()))
        displayed = settings.get('displayed_headers')
        self._filtered_headers = [k for k in displayed
                                  if k.lower() == 'tags' or k in self.mail]
        self._displayed_headers = None

        bars = settings.get_theming_attribute('thread', 'arrow_bars')
        self.arrow_bars_att = bars
        heads = settings.get_theming_attribute('thread', 'arrow_heads')
        self.arrow_heads_att = heads
        logging.debug(self.arrow_heads_att)

        self.rebuild()  # this will build self.pile
        urwid.WidgetWrap.__init__(self, self.pile)
Exemple #43
0
    def __init__(
        self, cmd, stdin=None, shell=False, spawn=False, refocus=True, thread=False, on_success=None, **kwargs
    ):
        """
        :param cmd: the command to call
        :type cmd: list or str
        :param stdin: input to pipe to the process
        :type stdin: file or str
        :param spawn: run command in a new terminal
        :type spawn: bool
        :param shell: let shell interpret command string
        :type shell: bool
        :param thread: run asynchronously, don't block alot
        :type thread: bool
        :param refocus: refocus calling buffer after cmd termination
        :type refocus: bool
        :param on_success: code to execute after command successfully exited
        :type on_success: callable
        """
        logging.debug({"spawn": spawn})
        # make sure cmd is a list of str
        if isinstance(cmd, unicode):
            # convert cmdstring to list: in case shell==True,
            # Popen passes only the first item in the list to $SHELL
            cmd = [cmd] if shell else split_commandstring(cmd)

        # determine complete command list to pass
        touchhook = settings.get_hook("touch_external_cmdlist")
        # filter cmd, shell and thread through hook if defined
        if touchhook is not None:
            logging.debug("calling hook: touch_external_cmdlist")
            res = touchhook(cmd, shell=shell, spawn=spawn, thread=thread)
            logging.debug("got: %s" % res)
            cmd, shell, self.in_thread = res
        # otherwise if spawn requested and X11 is running
        elif spawn:
            if "DISPLAY" in os.environ:
                term_cmd = settings.get("terminal_cmd", "")
                logging.info("spawn in terminal: %s" % term_cmd)
                termcmdlist = split_commandstring(term_cmd)
                cmd = termcmdlist + cmd
            else:
                thread = False

        self.cmdlist = cmd
        self.stdin = stdin
        self.shell = shell
        self.refocus = refocus
        self.in_thread = thread
        self.on_success = on_success
        Command.__init__(self, **kwargs)
Exemple #44
0
    def apply(self, ui):
        try:
            ui.dbman.flush()
        except DatabaseLockedError:
            timeout = settings.get('flush_retry_timeout')

            def f(*args):
                self.apply(ui)

            ui.mainloop.set_alarm_in(timeout, f)
            ui.notify('index locked, will try again in %d secs' % timeout)
            ui.update()
            return
        logging.debug('flush complete')
Exemple #45
0
    def apply(self, ui):
        try:
            ui.dbman.flush()
        except DatabaseLockedError:
            timeout = settings.get("flush_retry_timeout")

            def f(*args):
                self.apply(ui)

            ui.mainloop.set_alarm_in(timeout, f)
            ui.notify("index locked, will try again in %d secs" % timeout)
            ui.update()
            return
        logging.debug("flush complete")
Exemple #46
0
    def get_datestring(self):
        """
        returns reformated datestring for this messages.

        It uses the format spacified by `timestamp_format` in
        the general section of the config.
        """
        if self._datetime == None:
            return None
        formatstring = settings.get('timestamp_format')
        if formatstring == None:
            res = helper.pretty_datetime(self._datetime)
        else:
            res = self._datetime.strftime(formatstring)
        return res
Exemple #47
0
    def apply(self, ui):
        # get mail to bounce
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        # look if this makes sense: do we have any accounts set up?
        my_accounts = settings.get_accounts()
        if not my_accounts:
            ui.notify('no accounts set', priority='error')
            return

        # remove "Resent-*" headers if already present
        del mail['Resent-From']
        del mail['Resent-To']
        del mail['Resent-Cc']
        del mail['Resent-Date']
        del mail['Resent-Message-ID']

        # set Resent-From-header and sending account
        try:
            resent_from_header, account = determine_sender(mail, 'bounce')
        except AssertionError as e:
            ui.notify(e.message, priority='error')
            return
        mail['Resent-From'] = resent_from_header

        # set Reset-To
        allbooks = not settings.get('complete_matching_abook_only')
        logging.debug('allbooks: %s', allbooks)
        if account is not None:
            abooks = settings.get_addressbooks(order=[account],
                                               append_remaining=allbooks)
            logging.debug(abooks)
            completer = ContactsCompleter(abooks)
        else:
            completer = None
        to = yield ui.prompt('To', completer=completer)
        if to is None:
            raise CommandCanceled()

        mail['Resent-To'] = to.strip(' \t\n,')

        logging.debug("bouncing mail")
        logging.debug(mail.__class__)

        ui.apply_command(SendCommand(mail=mail))
Exemple #48
0
    def apply(self, ui):
        # get mail to bounce
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        # look if this makes sense: do we have any accounts set up?
        my_accounts = settings.get_accounts()
        if not my_accounts:
            ui.notify('no accounts set', priority='error')
            return

        # remove "Resent-*" headers if already present
        del mail['Resent-From']
        del mail['Resent-To']
        del mail['Resent-Cc']
        del mail['Resent-Date']
        del mail['Resent-Message-ID']

        # set Resent-From-header and sending account
        try:
            resent_from_header, account = determine_sender(mail, 'bounce')
        except AssertionError as e:
            ui.notify(e.message, priority='error')
            return
        mail['Resent-From'] = resent_from_header

        # set Reset-To
        allbooks = not settings.get('complete_matching_abook_only')
        logging.debug('allbooks: %s', allbooks)
        if account is not None:
            abooks = settings.get_addressbooks(order=[account],
                                               append_remaining=allbooks)
            logging.debug(abooks)
            completer = ContactsCompleter(abooks)
        else:
            completer = None
        to = yield ui.prompt('To', completer=completer)
        if to is None:
            raise CommandCanceled()

        mail['Resent-To'] = to.strip(' \t\n,')

        logging.debug("bouncing mail")
        logging.debug(mail.__class__)

        ui.apply_command(SendCommand(mail=mail))
Exemple #49
0
    def apply(self, ui):
        if settings.get('bug_on_exit'):
            msg = 'really quit?'
            if (yield ui.choice(msg, select='yes', cancel='no',
                                msg_position='left')) == 'no':
                return

        for b in ui.buffers:
            b.cleanup()
        ui.apply_command(FlushCommand(callback=ui.exit))

        if ui.db_was_locked:
            msg = 'Database locked. Exit without saving?'
            if (yield ui.choice(msg, select='yes', cancel='no',
                                msg_position='left')) == 'no':
                return
            ui.exit()
Exemple #50
0
    def apply(self, ui):
        if settings.get('bug_on_exit'):
            msg = 'really quit?'
            if (yield ui.choice(msg, select='yes', cancel='no',
                                msg_position='left')) == 'no':
                return

        for b in ui.buffers:
            b.cleanup()
        ui.apply_command(FlushCommand(callback=ui.exit))

        if ui.db_was_locked:
            msg = 'Database locked. Exit without saving?'
            if (yield ui.choice(msg, select='yes', cancel='no',
                                msg_position='left')) == 'no':
                return
            ui.exit()
Exemple #51
0
    def apply(self, ui):
        if self.buffer is None:
            self.buffer = ui.current_buffer

        if isinstance(self.buffer, buffers.EnvelopeBuffer) and not self.buffer.envelope.sent_time:
            if (
                not self.force
                and (yield ui.choice("close without sending?", select="yes", cancel="no", msg_position="left")) == "no"
            ):
                raise CommandCanceled()

        if len(ui.buffers) == 1:
            if settings.get("quit_on_last_bclose"):
                logging.info("closing the last buffer, exiting")
                ui.apply_command(ExitCommand())
            else:
                logging.info("not closing last remaining buffer as " "global.quit_on_last_bclose is set to False")
        else:
            ui.buffer_close(self.buffer, self.redraw)
Exemple #52
0
 def apply(self, ui):
     pcomplete = completion.PathCompleter()
     savedir = settings.get('attachment_prefix', '~')
     if self.all:
         msg = ui.current_buffer.get_selected_message()
         if not self.path:
             self.path = yield ui.prompt('save attachments to',
                                         text=os.path.join(savedir, ''),
                                         completer=pcomplete)
         if self.path:
             if os.path.isdir(os.path.expanduser(self.path)):
                 for a in msg.get_attachments():
                     dest = a.save(self.path)
                     name = a.get_filename()
                     if name:
                         ui.notify('saved %s as: %s' % (name, dest))
                     else:
                         ui.notify('saved attachment as: %s' % dest)
             else:
                 ui.notify('not a directory: %s' % self.path,
                           priority='error')
         else:
             ui.notify('canceled')
     else:  # save focussed attachment
         focus = ui.get_deep_focus()
         if isinstance(focus, widgets.AttachmentWidget):
             attachment = focus.get_attachment()
             filename = attachment.get_filename()
             if not self.path:
                 msg = 'save attachment (%s) to ' % filename
                 initialtext = os.path.join(savedir, filename)
                 self.path = yield ui.prompt(msg,
                                             completer=pcomplete,
                                             text=initialtext)
             if self.path:
                 try:
                     dest = attachment.save(self.path)
                     ui.notify('saved attachment as: %s' % dest)
                 except (IOError, OSError), e:
                     ui.notify(str(e), priority='error')
             else:
                 ui.notify('canceled')
Exemple #53
0
    def apply(self, ui):
        if self.buffer is None:
            self.buffer = ui.current_buffer

        if (isinstance(self.buffer, buffers.EnvelopeBuffer) and
                not self.buffer.envelope.sent_time):
            if (not self.force and (yield ui.choice('close without sending?',
                                                    select='yes', cancel='no',
                                                    msg_position='left')) ==
                    'no'):
                raise CommandCanceled()

        if len(ui.buffers) == 1:
            if settings.get('quit_on_last_bclose'):
                logging.info('closing the last buffer, exiting')
                ui.apply_command(ExitCommand())
            else:
                logging.info('not closing last remaining buffer as '
                             'global.quit_on_last_bclose is set to False')
        else:
            ui.buffer_close(self.buffer, self.redraw)
Exemple #54
0
    def __init__(self, message, even=True):
        """
        :param message: a message
        :type message: alot.db.Message
        :param even: even entry in a pile of messages? Used for theming.
        :type even: bool
        """
        self.message = message
        self.even = even
        if even:
            attr = settings.get_theming_attribute('thread', 'summary', 'even')
        else:
            attr = settings.get_theming_attribute('thread', 'summary', 'odd')
        focus_att = settings.get_theming_attribute('thread', 'summary',
                                                   'focus')
        cols = []

        sumstr = self.__str__()
        txt = urwid.Text(sumstr)
        cols.append(txt)

        if settings.get('msg_summary_hides_threadwide_tags'):
            thread_tags = message.get_thread().get_tags(intersection=True)
            outstanding_tags = set(message.get_tags()).difference(thread_tags)
            tag_widgets = [
                TagWidget(t, attr, focus_att) for t in outstanding_tags
            ]
        else:
            tag_widgets = [
                TagWidget(t, attr, focus_att) for t in message.get_tags()
            ]
        tag_widgets.sort(tag_cmp, lambda tag_widget: tag_widget.translated)
        for tag_widget in tag_widgets:
            if not tag_widget.hidden:
                cols.append(('fixed', tag_widget.width(), tag_widget))
        line = urwid.AttrMap(urwid.Columns(cols, dividechars=1), attr,
                             focus_att)
        urwid.WidgetWrap.__init__(self, line)
Exemple #55
0
        def openEnvelopeFromTmpfile():
            # This parses the input from the tempfile.
            # we do this ourselves here because we want to be able to
            # just type utf-8 encoded stuff into the tempfile and let alot
            # worry about encodings.

            # get input
            # tempfile will be removed on buffer cleanup
            f = open(self.envelope.tmpfile.name)
            enc = settings.get('editor_writes_encoding')
            template = string_decode(f.read(), enc)
            f.close()

            # call post-edit translate hook
            translate = settings.get_hook('post_edit_translate')
            if translate:
                template = translate(template, ui=ui, dbm=ui.dbman)
            self.envelope.parse_template(template,
                                         only_body=self.edit_only_body)
            if self.openNew:
                ui.buffer_open(buffers.EnvelopeBuffer(ui, self.envelope))
            else:
                ebuffer.envelope = self.envelope
                ebuffer.rebuild()
Exemple #56
0
    def construct_mail(self):
        """
        compiles the information contained in this envelope into a
        :class:`email.Message`.
        """
        # Build body text part. To properly sign/encrypt messages later on, we
        # convert the text to its canonical format (as per RFC 2015).
        canonical_format = self.body.encode('utf-8')
        canonical_format = canonical_format.replace('\\t', ' ' * 4)
        textpart = MIMEText(canonical_format, 'plain', 'utf-8')

        # wrap it in a multipart container if necessary
        if self.attachments:
            inner_msg = MIMEMultipart()
            inner_msg.attach(textpart)
            # add attachments
            for a in self.attachments:
                inner_msg.attach(a.get_mime_representation())
        else:
            inner_msg = textpart

        if self.sign:
            plaintext = helper.email_as_string(inner_msg)
            logging.debug('signing plaintext: ' + plaintext)

            try:
                signatures, signature_str = crypto.detached_signature_for(
                    plaintext, self.sign_key)
                if len(signatures) != 1:
                    raise GPGProblem("Could not sign message (GPGME "
                                     "did not return a signature)",
                                     code=GPGCode.KEY_CANNOT_SIGN)
            except gpgme.GpgmeError as e:
                if e.code == gpgme.ERR_BAD_PASSPHRASE:
                    # If GPG_AGENT_INFO is unset or empty, the user just does
                    # not have gpg-agent running (properly).
                    if os.environ.get('GPG_AGENT_INFO', '').strip() == '':
                        msg = "Got invalid passphrase and GPG_AGENT_INFO\
                                not set. Please set up gpg-agent."
                        raise GPGProblem(msg, code=GPGCode.BAD_PASSPHRASE)
                    else:
                        raise GPGProblem("Bad passphrase. Is gpg-agent "
                                         "running?",
                                         code=GPGCode.BAD_PASSPHRASE)
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_SIGN)

            micalg = crypto.RFC3156_micalg_from_algo(signatures[0].hash_algo)
            unencrypted_msg = MIMEMultipart('signed', micalg=micalg,
                                            protocol='application/pgp-signature')

            # wrap signature in MIMEcontainter
            stype = 'pgp-signature; name="signature.asc"'
            signature_mime = MIMEApplication(_data=signature_str,
                                             _subtype=stype,
                                             _encoder=encode_7or8bit)
            signature_mime['Content-Description'] = 'signature'
            signature_mime.set_charset('us-ascii')

            # add signed message and signature to outer message
            unencrypted_msg.attach(inner_msg)
            unencrypted_msg.attach(signature_mime)
            unencrypted_msg['Content-Disposition'] = 'inline'
        else:
            unencrypted_msg = inner_msg

        if self.encrypt:
            plaintext = helper.email_as_string(unencrypted_msg)
            logging.debug('encrypting plaintext: ' + plaintext)

            try:
                encrypted_str = crypto.encrypt(plaintext,
                                               self.encrypt_keys.values())
            except gpgme.GpgmeError as e:
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_ENCRYPT)

            outer_msg = MIMEMultipart('encrypted',
                                      protocol='application/pgp-encrypted')

            version_str = 'Version: 1'
            encryption_mime = MIMEApplication(_data=version_str,
                                              _subtype='pgp-encrypted',
                                              _encoder=encode_7or8bit)
            encryption_mime.set_charset('us-ascii')

            encrypted_mime = MIMEApplication(_data=encrypted_str,
                                             _subtype='octet-stream',
                                             _encoder=encode_7or8bit)
            encrypted_mime.set_charset('us-ascii')
            outer_msg.attach(encryption_mime)
            outer_msg.attach(encrypted_mime)

        else:
            outer_msg = unencrypted_msg

        headers = self.headers.copy()
        # add Message-ID
        if 'Message-ID' not in headers:
            headers['Message-ID'] = [email.Utils.make_msgid()]

        if 'User-Agent' in headers:
            uastring_format = headers['User-Agent'][0]
        else:
            uastring_format = settings.get('user_agent').strip()
        uastring = uastring_format.format(version=__version__)
        if uastring:
            headers['User-Agent'] = [uastring]

        # copy headers from envelope to mail
        for k, vlist in headers.items():
            for v in vlist:
                outer_msg[k] = encode_header(k, v)

        return outer_msg
Exemple #57
0
class ComposeCommand(Command):
    """compose a new email"""
    def __init__(self,
                 envelope=None,
                 headers={},
                 template=None,
                 sender=u'',
                 subject=u'',
                 to=[],
                 cc=[],
                 bcc=[],
                 attach=None,
                 omit_signature=False,
                 spawn=None,
                 **kwargs):
        """
        :param envelope: use existing envelope
        :type envelope: :class:`~alot.db.envelope.Envelope`
        :param headers: forced header values
        :type header: doct (str->str)
        :param template: name of template to parse into the envelope after
                         creation. This should be the name of a file in your
                         template_dir
        :type template: str
        :param sender: From-header value
        :type sender: str
        :param subject: Subject-header value
        :type subject: str
        :param to: To-header value
        :type to: str
        :param cc: Cc-header value
        :type cc: str
        :param bcc: Bcc-header value
        :type bcc: str
        :param attach: Path to files to be attached (globable)
        :type attach: str
        :param omit_signature: do not attach/append signature
        :type omit_signature: bool
        :param spawn: force spawning of editor in a new terminal
        :type spawn: bool
        """

        Command.__init__(self, **kwargs)

        self.envelope = envelope
        self.template = template
        self.headers = headers
        self.sender = sender
        self.subject = subject
        self.to = to
        self.cc = cc
        self.bcc = bcc
        self.attach = attach
        self.omit_signature = omit_signature
        self.force_spawn = spawn

    @inlineCallbacks
    def apply(self, ui):
        if self.envelope is None:
            self.envelope = Envelope()
        if self.template is not None:
            #get location of tempsdir, containing msg templates
            tempdir = settings.get('template_dir')
            tempdir = os.path.expanduser(tempdir)
            if not tempdir:
                xdgdir = os.environ.get('XDG_CONFIG_HOME',
                                        os.path.expanduser('~/.config'))
                tempdir = os.path.join(xdgdir, 'alot', 'templates')

            path = os.path.expanduser(self.template)
            if not os.path.dirname(path):  # use tempsdir
                if not os.path.isdir(tempdir):
                    ui.notify('no templates directory: %s' % tempdir,
                              priority='error')
                    return
                path = os.path.join(tempdir, path)

            if not os.path.isfile(path):
                ui.notify('could not find template: %s' % path,
                          priority='error')
                return
            try:
                self.envelope.parse_template(open(path).read())
            except Exception, e:
                ui.notify(str(e), priority='error')
                return

        # set forced headers
        for key, value in self.headers.items():
            self.envelope.add(key, value)

        # set forced headers for separate parameters
        if self.sender:
            self.envelope.add('From', self.sender)
        if self.subject:
            self.envelope.add('Subject', self.subject)
        if self.to:
            self.envelope.add('To', ','.join(self.to))
        if self.cc:
            self.envelope.add('Cc', ','.join(self.cc))
        if self.bcc:
            self.envelope.add('Bcc', ','.join(self.bcc))

        # get missing From header
        if not 'From' in self.envelope.headers:
            accounts = settings.get_accounts()
            if len(accounts) == 1:
                a = accounts[0]
                fromstring = "%s <%s>" % (a.realname, a.address)
                self.envelope.add('From', fromstring)
            else:
                cmpl = AccountCompleter()
                fromaddress = yield ui.prompt('From', completer=cmpl, tab=1)
                if fromaddress is None:
                    ui.notify('canceled')
                    return
                self.envelope.add('From', fromaddress)

        # add signature
        if not self.omit_signature:
            name, addr = email.Utils.parseaddr(self.envelope['From'])
            account = settings.get_account_by_address(addr)
            if account is not None:
                if account.signature:
                    logging.debug('has signature')
                    sig = os.path.expanduser(account.signature)
                    if os.path.isfile(sig):
                        logging.debug('is file')
                        if account.signature_as_attachment:
                            name = account.signature_filename or None
                            self.envelope.attach(sig, filename=name)
                            logging.debug('attached')
                        else:
                            sigcontent = open(sig).read()
                            enc = helper.guess_encoding(sigcontent)
                            mimetype = helper.guess_mimetype(sigcontent)
                            if mimetype.startswith('text'):
                                sigcontent = helper.string_decode(
                                    sigcontent, enc)
                                self.envelope.body += '\n' + sigcontent
                    else:
                        ui.notify('could not locate signature: %s' % sig,
                                  priority='error')
                        if (yield ui.choice('send without signature?', 'yes',
                                            'no')) == 'no':
                            return

        # Figure out whether we should GPG sign messages by default
        # and look up key if so
        sender = self.envelope.get('From')
        name, addr = email.Utils.parseaddr(sender)
        account = settings.get_account_by_address(addr)
        if account:
            self.envelope.sign = account.sign_by_default
            self.envelope.sign_key = account.gpg_key

        # get missing To header
        if 'To' not in self.envelope.headers:
            allbooks = not settings.get('complete_matching_abook_only')
            logging.debug(allbooks)
            if account is not None:
                abooks = settings.get_addressbooks(order=[account],
                                                   append_remaining=allbooks)
                logging.debug(abooks)
                completer = ContactsCompleter(abooks)
            else:
                completer = None
            to = yield ui.prompt('To', completer=completer)
            if to is None:
                ui.notify('canceled')
                return
            self.envelope.add('To', to.strip(' \t\n,'))

        if settings.get('ask_subject') and \
                not 'Subject' in self.envelope.headers:
            subject = yield ui.prompt('Subject')
            logging.debug('SUBJECT: "%s"' % subject)
            if subject is None:
                ui.notify('canceled')
                return
            self.envelope.add('Subject', subject)

        if settings.get('compose_ask_tags'):
            comp = TagsCompleter(ui.dbman)
            tagsstring = yield ui.prompt('Tags', completer=comp)
            tags = filter(lambda x: x, tagsstring.split(','))
            if tags is None:
                ui.notify('canceled')
                return
            self.envelope.tags = tags

        if self.attach:
            for gpath in self.attach:
                for a in glob.glob(gpath):
                    self.envelope.attach(a)
                    logging.debug('attaching: ' + a)

        cmd = commands.envelope.EditCommand(envelope=self.envelope,
                                            spawn=self.force_spawn,
                                            refocus=False)
        ui.apply_command(cmd)
Exemple #58
0
    def apply(self, ui):
        # look if this makes sense: do we have any accounts set up?
        my_accounts = settings.get_accounts()
        if not my_accounts:
            ui.notify('no accounts set', priority='error')
            return

        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        # set body text
        name, address = self.message.get_author()
        timestamp = self.message.get_date()
        qf = settings.get_hook('reply_prefix')
        if qf:
            quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
        else:
            quotestring = 'Quoting %s (%s)\n' % (name or address, timestamp)
        mailcontent = quotestring
        quotehook = settings.get_hook('text_quote')
        if quotehook:
            mailcontent += quotehook(self.message.accumulate_body())
        else:
            quote_prefix = settings.get('quote_prefix')
            for line in self.message.accumulate_body().splitlines():
                mailcontent += quote_prefix + line + '\n'

        envelope = Envelope(bodytext=mailcontent)

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        reply_subject_hook = settings.get_hook('reply_subject')
        if reply_subject_hook:
            subject = reply_subject_hook(subject)
        else:
            rsp = settings.get('reply_subject_prefix')
            if not subject.startswith(('Re:', rsp)):
                subject = rsp + subject
        envelope.add('Subject', subject)

        # set From
        realname, address = recipient_to_from(mail, my_accounts)
        envelope.add('From', '%s <%s>' % (realname, address))

        # set To
        sender = mail['Reply-To'] or mail['From']
        recipients = [sender]
        my_addresses = settings.get_addresses()
        if self.groupreply:
            if sender != mail['From']:
                recipients.append(mail['From'])
            cleared = self.clear_my_address(my_addresses, mail.get('To', ''))
            recipients.append(cleared)

            # copy cc for group-replies
            if 'Cc' in mail:
                cc = self.clear_my_address(my_addresses, mail['Cc'])
                envelope.add('Cc', decode_header(cc))

        to = ', '.join(recipients)
        logging.debug('reply to: %s' % to)
        envelope.add('To', decode_header(to))

        # set In-Reply-To header
        envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id())

        # set References header
        old_references = mail.get('References', '')
        if old_references:
            old_references = old_references.split()
            references = old_references[-8:]
            if len(old_references) > 8:
                references = old_references[:1] + references
            references.append('<%s>' % self.message.get_message_id())
            envelope.add('References', ' '.join(references))
        else:
            envelope.add('References', '<%s>' % self.message.get_message_id())

        # continue to compose
        ui.apply_command(
            ComposeCommand(envelope=envelope, spawn=self.force_spawn))
Exemple #59
0
    def apply(self, ui):
        ebuffer = ui.current_buffer
        if not self.envelope:
            self.envelope = ui.current_buffer.envelope

        #determine editable headers
        edit_headers = set(settings.get('edit_headers_whitelist'))
        if '*' in edit_headers:
            edit_headers = set(self.envelope.headers.keys())
        blacklist = set(settings.get('edit_headers_blacklist'))
        if '*' in blacklist:
            blacklist = set(self.envelope.headers.keys())
        edit_headers = edit_headers - blacklist
        logging.info('editable headers: %s' % edit_headers)

        def openEnvelopeFromTmpfile():
            # This parses the input from the tempfile.
            # we do this ourselves here because we want to be able to
            # just type utf-8 encoded stuff into the tempfile and let alot
            # worry about encodings.

            # get input
            # tempfile will be removed on buffer cleanup
            f = open(self.envelope.tmpfile.name)
            enc = settings.get('editor_writes_encoding')
            template = string_decode(f.read(), enc)
            f.close()

            # call post-edit translate hook
            translate = settings.get_hook('post_edit_translate')
            if translate:
                template = translate(template, ui=ui, dbm=ui.dbman)
            self.envelope.parse_template(template,
                                         only_body=self.edit_only_body)
            if self.openNew:
                ui.buffer_open(buffers.EnvelopeBuffer(ui, self.envelope))
            else:
                ebuffer.envelope = self.envelope
                ebuffer.rebuild()

        # decode header
        headertext = u''
        for key in edit_headers:
            vlist = self.envelope.get_all(key)
            if not vlist:
                # ensure editable headers are present in template
                vlist = ['']
            else:
                # remove to be edited lines from envelope
                del self.envelope[key]

            for value in vlist:
                # newlines (with surrounding spaces) by spaces in values
                value = value.strip()
                value = re.sub('[ \t\r\f\v]*\n[ \t\r\f\v]*', ' ', value)
                headertext += '%s: %s\n' % (key, value)

        # determine editable content
        bodytext = self.envelope.body
        if headertext:
            content = '%s\n%s' % (headertext, bodytext)
            self.edit_only_body = False
        else:
            content = bodytext
            self.edit_only_body = True

        # call pre-edit translate hook
        translate = settings.get_hook('pre_edit_translate')
        if translate:
            content = translate(content, ui=ui, dbm=ui.dbman)

        #write stuff to tempfile
        old_tmpfile = None
        if self.envelope.tmpfile:
            old_tmpfile = self.envelope.tmpfile
        self.envelope.tmpfile = tempfile.NamedTemporaryFile(delete=False,
                                                            prefix='alot.')
        self.envelope.tmpfile.write(content.encode('utf-8'))
        self.envelope.tmpfile.flush()
        self.envelope.tmpfile.close()
        if old_tmpfile:
            os.unlink(old_tmpfile.name)
        cmd = globals.EditCommand(self.envelope.tmpfile.name,
                                  on_success=openEnvelopeFromTmpfile,
                                  spawn=self.force_spawn,
                                  thread=self.force_spawn,
                                  refocus=self.refocus)
        ui.apply_command(cmd)
Exemple #60
0
def extract_body(mail, types=None):
    """
    returns a body text string for given mail.
    If types is `None`, `text/*` is used:
    The exact preferred type is specified by the prefer_plaintext config option
    which defaults to text/html.

    :param mail: the mail to use
    :type mail: :class:`email.Message`
    :param types: mime content types to use for body string
    :type types: list of str
    """

    preferred = 'text/plain' if settings.get(
        'prefer_plaintext') else 'text/html'
    has_preferred = False

    # see if the mail has our preferred type
    if types is None:
        has_preferred = list(
            typed_subpart_iterator(mail, *preferred.split('/')))

    body_parts = []
    for part in mail.walk():
        ctype = part.get_content_type()

        if types is not None:
            if ctype not in types:
                continue
        cd = part.get('Content-Disposition', '')
        if cd.startswith('attachment'):
            continue
        # if the mail has our preferred type, we only keep this type
        # note that if types != None, has_preferred always stays False
        if has_preferred and ctype != preferred:
            continue

        enc = part.get_content_charset() or 'ascii'
        raw_payload = part.get_payload(decode=True)
        if ctype == 'text/plain':
            raw_payload = string_decode(raw_payload, enc)
            body_parts.append(string_sanitize(raw_payload))
        else:
            # get mime handler
            key = 'copiousoutput'
            handler, entry = settings.mailcap_find_match(ctype, key=key)
            tempfile_name = None
            stdin = None

            if entry:
                handler_raw_commandstring = entry['view']
                # in case the mailcap defined command contains no '%s',
                # we pipe the files content to the handling command via stdin
                if '%s' in handler_raw_commandstring:
                    # open tempfile, respect mailcaps nametemplate
                    nametemplate = entry.get('nametemplate', '%s')
                    prefix, suffix = parse_mailcap_nametemplate(nametemplate)
                    tmpfile = tempfile.NamedTemporaryFile(delete=False,
                                                          prefix=prefix,
                                                          suffix=suffix)
                    # write payload to tmpfile
                    tmpfile.write(raw_payload)
                    tmpfile.close()
                    tempfile_name = tmpfile.name
                else:
                    stdin = raw_payload

                # read parameter, create handler command
                parms = tuple(map('='.join, part.get_params()))

                # create and call external command
                cmd = mailcap.subst(entry['view'],
                                    ctype,
                                    filename=tempfile_name,
                                    plist=parms)
                logging.debug('command: %s' % cmd)
                logging.debug('parms: %s' % str(parms))
                cmdlist = split_commandstring(cmd)
                # call handler
                rendered_payload, errmsg, retval = helper.call_cmd(cmdlist,
                                                                   stdin=stdin)

                # remove tempfile
                if tempfile_name:
                    os.unlink(tempfile_name)

                if rendered_payload:  # handler had output
                    body_parts.append(string_sanitize(rendered_payload))
    return u'\n\n'.join(body_parts)