diff options
author | Anton Khirnov <anton@khirnov.net> | 2021-01-26 10:26:27 +0100 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2021-01-26 10:29:24 +0100 |
commit | 5cb88d559ae462dad80d346d44f12dcc9eb3ec10 (patch) | |
tree | d6971568c4f026be6ccdf6d93042c5d0c2770acc /alot/commands/thread.py | |
parent | 6a22c9f8026f255c5e6eed5916168552138581e2 (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.py | 89 |
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', |