summaryrefslogtreecommitdiff
path: root/alot/commands/thread.py
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-01-26 10:26:27 +0100
committerAnton Khirnov <anton@khirnov.net>2021-01-26 10:29:24 +0100
commit5cb88d559ae462dad80d346d44f12dcc9eb3ec10 (patch)
treed6971568c4f026be6ccdf6d93042c5d0c2770acc /alot/commands/thread.py
parent6a22c9f8026f255c5e6eed5916168552138581e2 (diff)
Rewrite mailcap handling.
Add a class that encapsulates the handler and is responsible for managing the temporary file, if one is needed. Use this class for both rendering inline content and displaying attachments externally. External attachments are now wrapped in an asyncio task that is added to a pool of tasks managed by ui.
Diffstat (limited to 'alot/commands/thread.py')
-rw-r--r--alot/commands/thread.py89
1 files changed, 40 insertions, 49 deletions
diff --git a/alot/commands/thread.py b/alot/commands/thread.py
index 84b1c611..ff70289b 100644
--- a/alot/commands/thread.py
+++ b/alot/commands/thread.py
@@ -3,6 +3,7 @@
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import argparse
+import asyncio
import logging
import mailcap
import os
@@ -14,10 +15,11 @@ from email.utils import getaddresses, parseaddr
from email.message import Message
import urwid
+from urwid.util import detected_encoding
+
from io import BytesIO
from . import Command, registerCommand
-from .globals import ExternalCommand
from .globals import ComposeCommand
from .globals import MoveCommand
from .globals import CommandCanceled
@@ -30,9 +32,9 @@ from ..db.attachment import Attachment
from ..db.errors import DatabaseROError
from ..settings.const import settings
from ..helper import formataddr
-from ..helper import parse_mailcap_nametemplate
from ..helper import split_commandstring
from ..utils import argparse as cargparse
+from ..utils.mailcap import MailcapHandler
MODE = 'thread'
@@ -908,60 +910,49 @@ class OpenAttachmentCommand(Command):
async def apply(self, ui):
logging.info('open attachment')
+ data = self.attachment.get_data()
mimetype = self.attachment.get_content_type()
+ part = self.attachment.get_mime_representation()
+ fname = self.attachment.get_filename()
- # returns pair of preliminary command string and entry dict containing
- # more info. We only use the dict and construct the command ourselves
- _, entry = settings.mailcap_find_match(mimetype)
- if entry:
- afterwards = None # callback, will rm tempfile if used
- handler_stdin = None
- tempfile_name = None
- handler_raw_commandstring = entry['view']
- # read parameter
- part = self.attachment.get_mime_representation()
- parms = tuple('='.join(p) for p in part.get_params())
-
- # 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:
- nametemplate = entry.get('nametemplate', '%s')
- prefix, suffix = parse_mailcap_nametemplate(nametemplate)
-
- fn_hook = settings.get_hook('sanitize_attachment_filename')
- if fn_hook:
- # get filename
- filename = self.attachment.get_filename()
- prefix, suffix = fn_hook(filename, prefix, suffix)
-
- with tempfile.NamedTemporaryFile(delete=False, prefix=prefix,
- suffix=suffix) as tmpfile:
- tempfile_name = tmpfile.name
- self.attachment.write(tmpfile)
-
- def afterwards():
- os.unlink(tempfile_name)
- else:
- handler_stdin = BytesIO()
- self.attachment.write(handler_stdin)
+ h = MailcapHandler(data, mimetype, part.get_params(), fname, 'view')
+ if not h:
+ ui.notify('No handler for: %s' % mimetype)
+ return
- # create handler command list
- handler_cmd = mailcap.subst(handler_raw_commandstring, mimetype,
- filename=tempfile_name, plist=parms)
+ # TODO: page copiousoutput
+ # TODO: hook for processing the command
+ if h.needs_terminal:
+ with ui.paused(), h:
+ logging.debug('Displaying part %s on terminal: %s',
+ mimetype, h.cmd)
+
+ try:
+ result = subprocess.run(h.cmd, shell = True, check = True,
+ input = h.stdin, stderr = subprocess.PIPE)
+ except subprocess.CalledProcessError as e:
+ logging.error('Calling mailcap handler "%s" failed with code %d: %s',
+ h.cmd, e.returncode,
+ e.stderr.decode(detected_encoding, errors = 'backslashreplace'))
+ return
- handler_cmdlist = split_commandstring(handler_cmd)
+ # does not need terminal - launch asynchronously
+ async def view_attachment_task(h):
+ with h:
+ logging.debug('Displaying part %s asynchronously: %s',
+ mimetype, h.cmd)
- # 'needsterminal' makes handler overtake the terminal
- # XXX: could this be repalced with "'needsterminal' not in entry"?
- overtakes = entry.get('needsterminal') is None
+ stdin = subprocess.PIPE if h.stdin else subprocess.DEVNULL
+ stdout = subprocess.DEVNULL
+ stderr = subprocess.DEVNULL
+ child = await asyncio.create_subprocess_shell(h.cmd, stdin, stdout, stderr)
+ await child.communicate(h.stdin)
- await ui.apply_command(ExternalCommand(handler_cmdlist,
- stdin=handler_stdin,
- on_success=afterwards,
- thread=overtakes))
- else:
- ui.notify('unknown mime type')
+ if child.returncode != 0:
+ logging.error('Calling mailcap handler "%s" failed with code %d:',
+ h.cmd, e.returncode)
+ ui.run_task(view_attachment_task(h))
@registerCommand(
MODE, 'move', help='move focus in current buffer',