diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2012-11-07 09:23:53 +0000 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2012-11-07 09:23:53 +0000 |
commit | db6e528d1062f40c1b1ae8270a5f724256649ea0 (patch) | |
tree | f094a1c0f9d2560aa1fbe01821145b1f2f61bc37 /alot/commands | |
parent | fc002480829f900ded44c63132c555e48d238820 (diff) | |
parent | 7e00dacae94cd26a0892b4e4feda80d259189ee9 (diff) |
Merge remote-tracking branch 'origin/0.3.3-feature-bounce-524'
Diffstat (limited to 'alot/commands')
-rw-r--r-- | alot/commands/envelope.py | 121 | ||||
-rw-r--r-- | alot/commands/thread.py | 134 |
2 files changed, 177 insertions, 78 deletions
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py index 330fca75..6354f2e0 100644 --- a/alot/commands/envelope.py +++ b/alot/commands/envelope.py @@ -114,33 +114,66 @@ class SaveCommand(Command): @registerCommand(MODE, 'send') class SendCommand(Command): """send mail""" + def __init__(self, mail=None, envelope=None, **kwargs): + """ + :param mail: email to send + :type email: email.message.Message + :param envelope: envelope to use to construct the outgoing mail. This + will be ignored in case the mail parameter is set. + :type envelope: alot.db.envelope.envelope + """ + Command.__init__(self, **kwargs) + self.mail = mail + self.envelope = envelope + self.envelope_buffer = None + @inlineCallbacks def apply(self, ui): - currentbuffer = ui.current_buffer # needed to close later - envelope = currentbuffer.envelope - - # This is to warn the user before re-sending - # an already sent message in case the envelope buffer - # was not closed because it was the last remaining buffer. - if envelope.sent_time: - warning = 'A modified version of ' * envelope.modified_since_sent - warning += 'this message has been sent at %s.' % envelope.sent_time - warning += ' Do you want to resend?' - if (yield ui.choice(warning, cancel='no', - msg_position='left')) == 'no': + if self.mail is None: + if self.envelope is None: + # needed to close later + self.envelope_buffer = ui.current_buffer + self.envelope = self.envelope_buffer.envelope + + # This is to warn the user before re-sending + # an already sent message in case the envelope buffer + # was not closed because it was the last remaining buffer. + if self.envelope.sent_time: + mod = self.envelope.modified_since_sent + when = self.envelope.sent_time + warning = 'A modified version of ' * mod + warning += 'this message has been sent at %s.' % when + warning += ' Do you want to resend?' + if (yield ui.choice(warning, cancel='no', + msg_position='left')) == 'no': + return + + # don't do anything if another SendCommand is in the middle of + # sending the message and we were triggered accidentally + if self.envelope.sending: + msg = 'sending this message already!' + logging.debug(msg) return - # don't do anything if another SendCommand is in the middle of sending - # the message and we were triggered accidentally - if envelope.sending: - msg = 'sending this message already!' - logging.debug(msg) - return + clearme = ui.notify(u'constructing mail (GPG, attachments)\u2026', + timeout=-1) + + try: + self.mail = self.envelope.construct_mail() + self.mail['Date'] = email.Utils.formatdate(localtime=True) + self.mail = crypto.email_as_string(self.mail) + except GPGProblem, e: + ui.clear_notify([clearme]) + ui.notify(e.message, priority='error') + return - frm = envelope.get('From') - sname, saddr = email.Utils.parseaddr(frm) + ui.clear_notify([clearme]) # determine account to use for sending + msg = self.mail + if not isinstance(msg, email.message.Message): + msg = email.message_from_string(self.mail) + sname, saddr = email.Utils.parseaddr(msg.get('From', '')) account = settings.get_account_by_address(saddr) if account is None: if not settings.get_accounts(): @@ -149,43 +182,39 @@ class SendCommand(Command): else: account = settings.get_accounts()[0] - clearme = ui.notify(u'constructing mail (GPG, attachments)\u2026', - timeout=-1) - - try: - mail = envelope.construct_mail() - mail['Date'] = email.Utils.formatdate(localtime=True) - mail = crypto.email_as_string(mail) - except GPGProblem, e: - ui.clear_notify([clearme]) - ui.notify(e.message, priority='error') - return - - ui.clear_notify([clearme]) - - # send - clearme = ui.notify('sending..', timeout=-1) + # make sure self.mail is a string + logging.debug(self.mail.__class__) + if isinstance(self.mail, email.message.Message): + self.mail = str(self.mail) + # define callback def afterwards(returnvalue): - envelope.sending = False + initial_tags = [] + if self.envelope is not None: + self.envelope.sending = False + self.envelope.sent_time = datetime.datetime.now() + initial_tags = self.envelope.tags logging.debug('mail sent successfully') ui.clear_notify([clearme]) - envelope.sent_time = datetime.datetime.now() - ui.apply_command(commands.globals.BufferCloseCommand()) + if self.envelope_buffer is not None: + cmd = commands.globals.BufferCloseCommand(self.envelope_buffer) + ui.apply_command(cmd) ui.notify('mail sent successfully') # store mail locally # This can raise StoreMailError - path = account.store_sent_mail(mail) + path = account.store_sent_mail(self.mail) # add mail to index if maildir path available if path is not None: logging.debug('adding new mail to index') - ui.dbman.add_message(path, account.sent_tags + envelope.tags) + ui.dbman.add_message(path, account.sent_tags + initial_tags) ui.apply_command(globals.FlushCommand()) + # define errback def send_errb(failure): - envelope.sending = False + if self.envelope is not None: + self.envelope.sending = False ui.clear_notify([clearme]) failure.trap(SendingMailFailed) logging.error(failure.getTraceback()) @@ -198,12 +227,14 @@ class SendCommand(Command): errmsg = 'could not store mail: %s' % failure.value ui.notify(errmsg, priority='error', block=True) - envelope.sending = True - d = account.send_mail(mail) + # send out + clearme = ui.notify('sending..', timeout=-1) + if self.envelope is not None: + self.envelope.sending = True + d = account.send_mail(self.mail) d.addCallback(afterwards) d.addErrback(send_errb) d.addErrback(store_errb) - logging.debug('added errbacks,callbacks') @registerCommand(MODE, 'edit', arguments=[ diff --git a/alot/commands/thread.py b/alot/commands/thread.py index deed04b2..97275abd 100644 --- a/alot/commands/thread.py +++ b/alot/commands/thread.py @@ -15,6 +15,7 @@ from alot.commands import Command, registerCommand from alot.commands.globals import ExternalCommand from alot.commands.globals import FlushCommand from alot.commands.globals import ComposeCommand +from alot.commands.envelope import SendCommand from alot import completion from alot.db.utils import decode_header from alot.db.utils import encode_header @@ -27,6 +28,7 @@ from alot.settings import settings from alot.helper import parse_mailcap_nametemplate from alot.helper import split_commandstring from alot.utils.booleanaction import BooleanAction +from alot.completion import ContactsCompleter from alot.widgets.globals import AttachmentWidget from alot.widgets.thread import MessageSummaryWidget @@ -34,19 +36,24 @@ from alot.widgets.thread import MessageSummaryWidget MODE = 'thread' -def recipient_to_from(mail, my_accounts): +def determine_sender(mail, action='reply'): """ - construct a suitable From-Header for forwards/replies to - a given mail. + 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 my_accounts: list of accounts from which to chose from - :type my_accounts: list of `alot.account.Account` + :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(',')) @@ -58,33 +65,35 @@ def recipient_to_from(mail, my_accounts): 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: + 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_re) + 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_re)) - if settings.get('reply_force_realname'): - realname = acc.realname + logging.debug("match!: '%s' '%s'" % (seen_address, alias)) + if settings.get(action + '_force_realname'): + realname = account.realname else: realname = seen_name - if settings.get('reply_force_address'): - address = acc.address + if settings.get(action + '_force_address'): + address = account.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 + account = my_accounts[0] + realname = account.realname + address = account.address logging.debug('using realname: "%s"' % realname) logging.debug('using address: %s' % address) - return address if realname == '' else '%s <%s>' % (realname, address) + from_value = address if realname == '' else '%s <%s>' % (realname, address) + return from_value, account @registerCommand(MODE, 'reply', arguments=[ @@ -108,12 +117,6 @@ class ReplyCommand(Command): Command.__init__(self, **kwargs) 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() @@ -149,8 +152,13 @@ class ReplyCommand(Command): subject = rsp + subject envelope.add('Subject', subject) - # set From - envelope.add('From', recipient_to_from(mail, my_accounts)) + # set From-header and sending account + try: + from_header, account = determine_sender(mail, 'reply') + except AssertionError as e: + ui.notify(e.message, priority='error') + return + envelope.add('From', from_header) # set To sender = mail['Reply-To'] or mail['From'] @@ -219,12 +227,6 @@ class ForwardCommand(Command): Command.__init__(self, **kwargs) 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() @@ -271,14 +273,80 @@ class ForwardCommand(Command): subject = fsp + subject envelope.add('Subject', subject) - # set From - envelope.add('From', recipient_to_from(mail, my_accounts)) + # set From-header and sending account + try: + from_header, account = determine_sender(mail, 'reply') + except AssertionError as e: + ui.notify(e.message, priority='error') + return + envelope.add('From', from_header) # continue to compose ui.apply_command(ComposeCommand(envelope=envelope, spawn=self.force_spawn)) +@registerCommand(MODE, 'bounce') +class BounceMailCommand(Command): + """directly re-send selected message""" + def __init__(self, message=None, **kwargs): + """ + :param message: message to bounce (defaults to selected message) + :type message: `alot.db.message.Message` + """ + self.message = message + Command.__init__(self, **kwargs) + + @inlineCallbacks + 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: + ui.notify('canceled') + return + mail['Resent-To'] = to.strip(' \t\n,') + + logging.debug("bouncing mail") + logging.debug(mail.__class__) + + ui.apply_command(SendCommand(mail=mail)) + + @registerCommand(MODE, 'editnew', arguments=[ (['--spawn'], {'action': BooleanAction, 'default':None, 'help':'open editor in new window'})]) |