Skip to content

Commit

Permalink
Merge pull request #1270 from dcbaker/use-asyncio
Browse files Browse the repository at this point in the history
Replace twisted with asyncio
  • Loading branch information
dcbaker committed Jul 26, 2018
2 parents 6ad6cbf + 006e2b7 commit 3c0097c
Show file tree
Hide file tree
Showing 28 changed files with 456 additions and 473 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ install:
- |
set -e
if [[ $JOB = docs ]]; then
pip install configobj twisted python-magic urwidtrees
pip install configobj python-magic urwidtrees
# Mock all "difficult" dependencies of alot in order to be able to import
# alot when rebuilding the documentation. Notmuch would have to be
# installed by hand in order to get a recent enough version on travis.
Expand Down
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
0.8:
* Port to python 3. Python 2.x no longer supported
* feature: Add a new 'namedqueries' buffer type for displaying named queries.
* feature: Replace twisted with asyncio

0.7:
* info: missing html mailcap entry now reported as mail body text
Expand Down
40 changes: 12 additions & 28 deletions alot/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,13 @@ def store_draft_mail(self, mail):
return self.store_mail(self.draft_box, mail)

@abc.abstractmethod
def send_mail(self, mail):
async def send_mail(self, mail):
"""
sends given mail
:param mail: the mail to send
:type mail: :class:`email.message.Message` or string
:returns: a `Deferred` that errs back with a class:`SendingMailFailed`,
containing a reason string if an error occurred.
:raises SendingMailFailed: if sending fails
"""
pass

Expand All @@ -338,35 +337,20 @@ def __init__(self, cmd, **kwargs):
super(SendmailAccount, self).__init__(**kwargs)
self.cmd = cmd

def send_mail(self, mail):
async def send_mail(self, mail):
"""Pipe the given mail to the configured sendmail command. Display a
short message on success or a notification on error.
:param mail: the mail to send out
:type mail: :class:`email.message.Message` or string
:returns: the deferred that calls the sendmail command
:rtype: `twisted.internet.defer.Deferred`
:raises: class:`SendingMailFailed` if sending failes
"""
cmdlist = split_commandstring(self.cmd)

def cb(out):
"""The callback used on success."""
logging.info('sent mail successfully')
logging.info(out)

def errb(failure):
"""The callback used on error."""
termobj = failure.value
errmsg = '%s failed with code %s:\n%s\nProcess stderr:\n%s' % \
(self.cmd, termobj.exitCode, str(failure.value),
failure.value.stderr)
logging.error(errmsg)
logging.error(failure.getTraceback())
raise SendingMailFailed(errmsg)

# make sure self.mail is a string
mail = str(mail)

d = call_cmd_async(cmdlist, stdin=mail)
d.addCallback(cb)
d.addErrback(errb)
return d
try:
# make sure self.mail is a string
out, *_ = await call_cmd_async(cmdlist, stdin=str(mail))
except Exception as e:
logging.error(str(e))
raise SendingMailFailed(str(e))
logging.info('sent mail successfully')
logging.info(out)
1 change: 1 addition & 0 deletions alot/buffers/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import urwid
from notmuch import NotmuchError

from .buffer import Buffer
from ..settings.const import settings
Expand Down
5 changes: 4 additions & 1 deletion alot/buffers/thread.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (C) 2011-2018 Patrick Totzke <patricktotzke@gmail.com>
# Copyright © 2018 Dylan Baker
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import asyncio
import urwid
import logging
from urwidtrees import ArrowTree, TreeBox, NestedTree
Expand Down Expand Up @@ -112,7 +114,8 @@ def clear():
self._auto_unread_writing = True
msg.remove_tags(['unread'], afterwards=clear)
fcmd = commands.globals.FlushCommand(silent=True)
self.ui.apply_command(fcmd)
asyncio.get_event_loop().create_task(
self.ui.apply_command(fcmd))
else:
logging.debug('Tbuffer: No, msg not unread')
else:
Expand Down
16 changes: 7 additions & 9 deletions alot/commands/bufferlist.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
# Copyright © 2018 Dylan Baker
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
from ..commands import Command, registerCommand
Expand All @@ -18,14 +19,11 @@ def apply(self, ui):
@registerCommand(MODE, 'close')
class BufferCloseCommand(Command):
"""close focussed buffer"""
def apply(self, ui):

async def apply(self, ui):
bufferlist = ui.current_buffer
selected = bufferlist.get_selected_buffer()
d = ui.apply_command(globals.BufferCloseCommand(buffer=selected))

def cb(_):
if bufferlist is not selected:
bufferlist.rebuild()
ui.update()
d.addCallback(cb)
return d
await ui.apply_command(globals.BufferCloseCommand(buffer=selected))
if bufferlist is not selected:
bufferlist.rebuild()
ui.update()
6 changes: 4 additions & 2 deletions alot/commands/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
# Copyright © 2018 Dylan Baker
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file

Expand All @@ -10,7 +11,7 @@
class RetagPromptCommand(Command):

"""prompt to retag selected thread's or message's tags"""
def apply(self, ui):
async def apply(self, ui):
get_selected_item = getattr(ui.current_buffer, {
'search': 'get_selected_thread',
'thread': 'get_selected_message'}[ui.mode])
Expand All @@ -25,4 +26,5 @@ def apply(self, ui):
elif tag:
tags.append(tag)
initial_tagstring = ','.join(sorted(tags)) + ','
return ui.apply_command(PromptCommand('retag ' + initial_tagstring))
r = await ui.apply_command(PromptCommand('retag ' + initial_tagstring))
return r
101 changes: 45 additions & 56 deletions alot/commands/envelope.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
# Copyright © 2018 Dylan Baker
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import argparse
Expand All @@ -10,8 +11,7 @@
import re
import tempfile
import textwrap

from twisted.internet.defer import inlineCallbacks
import traceback

from . import Command, registerCommand
from . import globals
Expand Down Expand Up @@ -101,16 +101,16 @@ def __init__(self, key='', **kwargs):
Command.__init__(self, **kwargs)
self.key = key

def apply(self, ui):
async def apply(self, ui):
value = ui.current_buffer.envelope.get(self.key, '')
cmdstring = 'set %s %s' % (self.key, value)
ui.apply_command(globals.PromptCommand(cmdstring))
await ui.apply_command(globals.PromptCommand(cmdstring))


@registerCommand(MODE, 'save')
class SaveCommand(Command):
"""save draft"""
def apply(self, ui):
async def apply(self, ui):
envelope = ui.current_buffer.envelope

# determine account to use
Expand Down Expand Up @@ -140,15 +140,15 @@ def apply(self, ui):
logging.debug('adding new mail to index')
try:
ui.dbman.add_message(path, account.draft_tags + envelope.tags)
ui.apply_command(globals.FlushCommand())
ui.apply_command(commands.globals.BufferCloseCommand())
await ui.apply_command(globals.FlushCommand())
await ui.apply_command(commands.globals.BufferCloseCommand())
except DatabaseError as e:
logging.error(str(e))
ui.notify('could not index message:\n%s' % str(e),
priority='error',
block=True)
else:
ui.apply_command(commands.globals.BufferCloseCommand())
await ui.apply_command(commands.globals.BufferCloseCommand())


@registerCommand(MODE, 'send')
Expand All @@ -167,8 +167,7 @@ def __init__(self, mail=None, envelope=None, **kwargs):
self.envelope = envelope
self.envelope_buffer = None

@inlineCallbacks
def apply(self, ui):
async def apply(self, ui):
if self.mail is None:
if self.envelope is None:
# needed to close later
Expand All @@ -184,7 +183,7 @@ def apply(self, ui):
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',
if (await ui.choice(warning, cancel='no',
msg_position='left')) == 'no':
return

Expand All @@ -201,7 +200,7 @@ def apply(self, ui):
warning = textwrap.dedent("""\
Any BCC recipients will not be able to decrypt this
message. Do you want to send anyway?""").replace('\n', ' ')
if (yield ui.choice(warning, cancel='no',
if (await ui.choice(warning, cancel='no',
msg_position='left')) == 'no':
return

Expand Down Expand Up @@ -233,8 +232,25 @@ def apply(self, ui):
return
logging.debug("ACCOUNT: \"%s\"" % account.address)

# define callback
def afterwards(_):
# send out
clearme = ui.notify('sending..', timeout=-1)
if self.envelope is not None:
self.envelope.sending = True
try:
await account.send_mail(self.mail)
except SendingMailFailed as e:
if self.envelope is not None:
self.envelope.sending = False
ui.clear_notify([clearme])
logging.error(traceback.format_exc())
errmsg = 'failed to send: {}'.format(e)
ui.notify(errmsg, priority='error', block=True)
except StoreMailError as e:
ui.clear_notify([clearme])
logging.error(traceback.format_exc())
errmsg = 'could not store mail: {}'.format(e)
ui.notify(errmsg, priority='error', block=True)
else:
initial_tags = []
if self.envelope is not None:
self.envelope.sending = False
Expand All @@ -244,12 +260,13 @@ def afterwards(_):
ui.clear_notify([clearme])
if self.envelope_buffer is not None:
cmd = commands.globals.BufferCloseCommand(self.envelope_buffer)
ui.apply_command(cmd)
await ui.apply_command(cmd)
ui.notify('mail sent successfully')
if self.envelope.replied:
self.envelope.replied.add_tags(account.replied_tags)
if self.envelope.passed:
self.envelope.passed.add_tags(account.passed_tags)
if self.envelope is not None:
if self.envelope.replied:
self.envelope.replied.add_tags(account.replied_tags)
if self.envelope.passed:
self.envelope.passed.add_tags(account.passed_tags)

# store mail locally
# This can raise StoreMailError
Expand All @@ -259,32 +276,7 @@ def afterwards(_):
if path is not None:
logging.debug('adding new mail to index')
ui.dbman.add_message(path, account.sent_tags + initial_tags)
ui.apply_command(globals.FlushCommand())

# define errback
def send_errb(failure):
if self.envelope is not None:
self.envelope.sending = False
ui.clear_notify([clearme])
failure.trap(SendingMailFailed)
logging.error(failure.getTraceback())
errmsg = 'failed to send: %s' % failure.value
ui.notify(errmsg, priority='error', block=True)

def store_errb(failure):
failure.trap(StoreMailError)
logging.error(failure.getTraceback())
errmsg = 'could not store mail: %s' % failure.value
ui.notify(errmsg, priority='error', block=True)

# 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)
await ui.apply_command(globals.FlushCommand())


@registerCommand(MODE, 'edit', arguments=[
Expand All @@ -309,7 +301,7 @@ def __init__(self, envelope=None, spawn=None, refocus=True, **kwargs):
self.edit_only_body = False
Command.__init__(self, **kwargs)

def apply(self, ui):
async def apply(self, ui):
ebuffer = ui.current_buffer
if not self.envelope:
self.envelope = ui.current_buffer.envelope
Expand Down Expand Up @@ -395,7 +387,7 @@ def openEnvelopeFromTmpfile():
spawn=self.force_spawn,
thread=self.force_spawn,
refocus=self.refocus)
ui.apply_command(cmd)
await ui.apply_command(cmd)


@registerCommand(MODE, 'set', arguments=[
Expand All @@ -416,8 +408,7 @@ def __init__(self, key, value, append=False, **kwargs):
self.reset = not append
Command.__init__(self, **kwargs)

@inlineCallbacks
def apply(self, ui):
async def apply(self, ui):
envelope = ui.current_buffer.envelope
if self.reset:
if self.key in envelope:
Expand All @@ -428,7 +419,7 @@ def apply(self, ui):
# as the key of the person BCC'd will be available to other recievers,
# defeating the purpose of BCCing them
if self.key.lower() in ['to', 'from', 'cc'] and envelope.encrypt:
yield utils.update_keys(ui, envelope)
await utils.update_keys(ui, envelope)
ui.current_buffer.rebuild()


Expand All @@ -444,15 +435,14 @@ def __init__(self, key, **kwargs):
self.key = key
Command.__init__(self, **kwargs)

@inlineCallbacks
def apply(self, ui):
async def apply(self, ui):
del ui.current_buffer.envelope[self.key]
# FIXME: handle BCC as well
# Currently we don't handle bcc because it creates a side channel leak,
# as the key of the person BCC'd will be available to other recievers,
# defeating the purpose of BCCing them
if self.key.lower() in ['to', 'from', 'cc']:
yield utils.update_keys(ui, ui.current_buffer.envelope)
await utils.update_keys(ui, ui.current_buffer.envelope)
ui.current_buffer.rebuild()


Expand Down Expand Up @@ -577,8 +567,7 @@ def __init__(self, action=None, keyids=None, trusted=False, **kwargs):
self.trusted = trusted
Command.__init__(self, **kwargs)

@inlineCallbacks
def apply(self, ui):
async def apply(self, ui):
envelope = ui.current_buffer.envelope
if self.action == 'rmencrypt':
try:
Expand All @@ -603,7 +592,7 @@ def apply(self, ui):
tmp_key = crypto.get_key(keyid)
envelope.encrypt_keys[tmp_key.fpr] = tmp_key
else:
yield utils.update_keys(ui, envelope, signed_only=self.trusted)
await utils.update_keys(ui, envelope, signed_only=self.trusted)
envelope.encrypt = encrypt
if not envelope.encrypt:
# This is an extra conditional as it can even happen if encrypt is
Expand Down

0 comments on commit 3c0097c

Please sign in to comment.