From 1d8c555c27483eb26d72c974a6294190c2bf480f Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Fri, 8 Jul 2016 23:51:42 -0700 Subject: [PATCH] Fighting with mailcap --- rtv/config.py | 3 +++ rtv/mime_handlers.py | 5 +++-- rtv/rtv.cfg | 3 +++ rtv/subreddit.py | 2 +- rtv/terminal.py | 51 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/rtv/config.py b/rtv/config.py index 9fb38b6..d93c849 100644 --- a/rtv/config.py +++ b/rtv/config.py @@ -59,6 +59,9 @@ def build_parser(): parser.add_argument( '--copy-config', dest='copy_config', action='store_const', const=True, help='Copy the default configuration to {HOME}/.config/rtv/rtv.cfg') + parser.add_argument( + '--enable-media', dest='enable_media', action='store_const', const=True, + help='Open external links using programs defined in the mailcap config') return parser diff --git a/rtv/mime_handlers.py b/rtv/mime_handlers.py index b5e2dc6..839457b 100644 --- a/rtv/mime_handlers.py +++ b/rtv/mime_handlers.py @@ -1,7 +1,7 @@ import re import mimetypes -from html.parser import HTMLParser +from six.moves.html_parser import HTMLParser import requests @@ -116,7 +116,8 @@ class ImgurHandler(BaseHandler): def get_mimetype(url): imgur_page = requests.get(url) try: - ImgurHTMLParser().feed(imgur_page.text) + # convert_charrefs will be true by default in python 3.5 + ImgurHTMLParser(convert_charrefs=True).feed(imgur_page.text) except HTMLParsed as data: # We found a link url = data.data diff --git a/rtv/rtv.cfg b/rtv/rtv.cfg index cb15877..2e88205 100644 --- a/rtv/rtv.cfg +++ b/rtv/rtv.cfg @@ -34,6 +34,9 @@ clear_auth = False ; Maximum number of opened links that will be saved in the history file. history_size = 200 +; Open external links using programs defined in the mailcap config. +enable_media = True + ################ # OAuth Settings ################ diff --git a/rtv/subreddit.py b/rtv/subreddit.py index b2c9e9d..cfd6d45 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -106,7 +106,7 @@ class SubredditPage(Page): self.open_submission(url=data['url_full']) self.config.history.add(data['url_full']) else: - self.term.open_browser(data['url_full']) + self.term.open_link(data['url_full']) self.config.history.add(data['url_full']) @SubredditController.register(Command('SUBREDDIT_POST')) diff --git a/rtv/terminal.py b/rtv/terminal.py index 94bf45c..e4ce527 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -7,6 +7,7 @@ import time import codecs import curses import logging +import mailcap import tempfile import webbrowser import subprocess @@ -19,8 +20,10 @@ import six from kitchen.text.display import textual_width_chop from . import exceptions +from . import mime_handlers from .objects import LoadScreen, Color + try: # Added in python 3.4+ from html import unescape @@ -49,6 +52,9 @@ class Terminal(object): self.loader = LoadScreen(self) self._display = None + # TODO: Load from custom location + self._mailcap_dict = mailcap.getcaps() + @property def up_arrow(self): symbol = '^' if self.config['ascii'] else '▲' @@ -304,6 +310,49 @@ class Terminal(object): return ch + def open_link(self, url): + + _logger.info('Opening link %s', url) + if not self.config['enable_media']: + return self.open_browser(url) + + command = None + for handler in mime_handlers.handlers: + if handler.pattern.match(url): + modified_url, content_type = handler.get_mimetype(url) + _logger.info('MIME type: %s', content_type) + _logger.info('Modified url: %s', modified_url) + if not content_type or content_type == 'text/html': + # Could not figure out the Content-Type + return self.open_browser(modified_url) + + # http://bugs.python.org/issue14977 + command, entry = mailcap.findmatch( + self._mailcap_dict, content_type, filename=modified_url) + if not entry: + _logger.info('Could not find a valid mailcap entry') + return self.open_browser(modified_url) + + break + + with self.loader('Opening page in a new window', delay=0): + args = [command] + _logger.info('Running command: %s', args) + # Non-blocking, run with a full shell to support pipes + p = subprocess.Popen( + args, shell=True, universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Wait a little while to make sure that the command doesn't exit + # with an error. This isn't perfect, but it should be good enough + # to catch invalid commands. + time.sleep(1.0) + code = p.poll() + if code is not None and code != 0: + stdout, stderr = p.communicate() + _logger.warning(stderr) + raise exceptions.BrowserError( + 'Program exited with status=%s' % code) + def open_browser(self, url): """ Open the given url using the default webbrowser. The preferred browser @@ -346,7 +395,7 @@ class Terminal(object): break # Success elif code is not None: raise exceptions.BrowserError( - 'Browser exited with status=%s' % code) + 'Program exited with status=%s' % code) time.sleep(0.01) else: raise exceptions.BrowserError(