summaryrefslogtreecommitdiff
path: root/alot/commands
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2012-11-07 09:23:53 +0000
committerPatrick Totzke <patricktotzke@gmail.com>2012-11-07 09:23:53 +0000
commitdb6e528d1062f40c1b1ae8270a5f724256649ea0 (patch)
treef094a1c0f9d2560aa1fbe01821145b1f2f61bc37 /alot/commands
parentfc002480829f900ded44c63132c555e48d238820 (diff)
parent7e00dacae94cd26a0892b4e4feda80d259189ee9 (diff)
Merge remote-tracking branch 'origin/0.3.3-feature-bounce-524'
Diffstat (limited to 'alot/commands')
-rw-r--r--alot/commands/envelope.py121
-rw-r--r--alot/commands/thread.py134
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'})])