Refactored and added documentation.
This commit is contained in:
@@ -39,4 +39,8 @@ class BrowserError(RTVError):
|
|||||||
|
|
||||||
|
|
||||||
class TemporaryFileError(RTVError):
|
class TemporaryFileError(RTVError):
|
||||||
"Indicates that an error has occurred and the file should not be deleted"
|
"Indicates that an error has occurred and the file should not be deleted"
|
||||||
|
|
||||||
|
|
||||||
|
class MailcapEntryNotFound(RTVError):
|
||||||
|
"A valid mailcap entry could not be coerced from the given url"
|
||||||
@@ -14,6 +14,10 @@ class HTMLParsed(Exception):
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
# TODO: open temp file, close after 60 seconds with thread.timer()
|
||||||
|
# TODO: switch to bs4 with "html.parser"
|
||||||
|
# TODO: Add media_readme.rst
|
||||||
|
# TODO: Add environment variables to config
|
||||||
|
|
||||||
class ImgurHTMLParser(HTMLParser):
|
class ImgurHTMLParser(HTMLParser):
|
||||||
"""
|
"""
|
||||||
|
|||||||
123
rtv/terminal.py
123
rtv/terminal.py
@@ -318,49 +318,62 @@ class Terminal(object):
|
|||||||
return ch
|
return ch
|
||||||
|
|
||||||
def open_link(self, url):
|
def open_link(self, url):
|
||||||
|
"""
|
||||||
|
Open a media link using the definitions from the user's mailcap file.
|
||||||
|
|
||||||
|
Most urls are parsed using their file extension, but special cases
|
||||||
|
exist for websites that are prevalent on reddit such as Imgur and
|
||||||
|
Gfycat. If there are no valid mailcap definitions, RTV will fall back
|
||||||
|
to using the default webbrowser.
|
||||||
|
|
||||||
|
RTV checks for certain mailcap fields to determine how to open a link:
|
||||||
|
- If ``copiousoutput`` is specified, the curses application will
|
||||||
|
be paused and stdout will be piped to the system pager.
|
||||||
|
- If `needsterminal`` is specified, the curses application will
|
||||||
|
yield terminal control to the subprocess until it has exited.
|
||||||
|
- Otherwise, we assume that the subprocess is meant to open a new
|
||||||
|
x-window, and we swallow all stdout output.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Stream youtube videos with VLC
|
||||||
|
Browse images and imgur albums with feh
|
||||||
|
Watch .webm videos through your terminal with mplayer
|
||||||
|
View images directly in your terminal with fbi or w3m
|
||||||
|
Play .mp3 files with sox player
|
||||||
|
Send HTML pages your pager using to html2text
|
||||||
|
...anything is possible!
|
||||||
|
"""
|
||||||
|
|
||||||
_logger.info('Opening link %s', url)
|
|
||||||
if not self.config['enable_media']:
|
if not self.config['enable_media']:
|
||||||
return self.open_browser(url)
|
return self.open_browser(url)
|
||||||
|
|
||||||
command, entry = None, None
|
try:
|
||||||
for parser in mime_handlers.parsers:
|
with self.loader('Checking link', catch_exception=False):
|
||||||
if parser.pattern.match(url):
|
command, entry = self.get_mailcap_entry(url)
|
||||||
modified_url, content_type = parser.get_mimetype(url)
|
except exceptions.MailcapEntryNotFound:
|
||||||
_logger.info('MIME type: %s', content_type)
|
return self.open_browser(url)
|
||||||
_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
|
_logger.info('Executing command: %s', command)
|
||||||
command, entry = mailcap.findmatch(
|
if 'copiousoutput' in entry:
|
||||||
self._mailcap_dict, content_type, filename=modified_url)
|
# TODO: open in pager
|
||||||
if not entry:
|
pass
|
||||||
_logger.info('Could not find a valid mailcap entry')
|
elif 'needsterminal' in entry:
|
||||||
return self.open_browser(modified_url)
|
# Blocking, pause rtv until the process returns
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
args = [command]
|
|
||||||
_logger.info('Running command: %s', args)
|
|
||||||
|
|
||||||
if 'needsterminal' in entry:
|
|
||||||
with self.suspend():
|
with self.suspend():
|
||||||
# Blocking, pause rtv until the process returns
|
p = subprocess.Popen(
|
||||||
p = subprocess.Popen(args, shell=True)
|
[command], stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True, shell=True)
|
||||||
code = p.wait()
|
code = p.wait()
|
||||||
if code != 0:
|
if code != 0:
|
||||||
stdout, stderr = p.communicate()
|
_, stderr = p.communicate()
|
||||||
# TODO: Need to get the error somehow, e.g. fbi without sudo
|
|
||||||
_logger.warning(stdout)
|
|
||||||
_logger.warning(stderr)
|
_logger.warning(stderr)
|
||||||
self.show_notification('Program exited with status=%s' % code)
|
self.show_notification(
|
||||||
|
'Program exited with status=%s\n%s' % (code, stderr))
|
||||||
else:
|
else:
|
||||||
with self.loader('Opening page in a new window', delay=0):
|
# Non-blocking, open a background process
|
||||||
# Non-blocking, run with a full shell to support pipes
|
with self.loader('Opening page', delay=0):
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
args, shell=True, universal_newlines=True,
|
[command], shell=True, universal_newlines=True,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
# Wait a little while to make sure that the command doesn't
|
# 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
|
# exit with an error. This isn't perfect, but it should be good
|
||||||
@@ -368,10 +381,50 @@ class Terminal(object):
|
|||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
code = p.poll()
|
code = p.poll()
|
||||||
if code is not None and code != 0:
|
if code is not None and code != 0:
|
||||||
stdout, stderr = p.communicate()
|
_, stderr = p.communicate()
|
||||||
_logger.warning(stderr)
|
|
||||||
raise exceptions.BrowserError(
|
raise exceptions.BrowserError(
|
||||||
'Program exited with status=%s' % code)
|
'Program exited with status=%s\n%s' % (code, stderr))
|
||||||
|
|
||||||
|
def get_mailcap_entry(self, url):
|
||||||
|
"""
|
||||||
|
Search through the mime handlers list and attempt to find the
|
||||||
|
appropriate command to open the provided url with.
|
||||||
|
|
||||||
|
Will raise a MailcapEntryNotFound exception if no valid command exists.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
url (text): URL that will be checked
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
command (text): The string of the command that should be executed
|
||||||
|
in a subprocess to open the resource.
|
||||||
|
entry (dict): The full mailcap entry for the corresponding command
|
||||||
|
"""
|
||||||
|
|
||||||
|
for parser in mime_handlers.parsers:
|
||||||
|
if parser.pattern.match(url):
|
||||||
|
# modified_url may be the same as the original url, but it
|
||||||
|
# could also be updated to point to a different page, or it
|
||||||
|
# could refer to the location of a temporary file with the
|
||||||
|
# page's downloaded content.
|
||||||
|
modified_url, content_type = parser.get_mimetype(url)
|
||||||
|
if not content_type:
|
||||||
|
_logger.info('Content type could not be determined')
|
||||||
|
raise exceptions.MailcapEntryNotFound()
|
||||||
|
elif content_type == 'text/html':
|
||||||
|
_logger.info('Content type text/html, deferring to browser')
|
||||||
|
raise exceptions.MailcapEntryNotFound()
|
||||||
|
|
||||||
|
command, entry = mailcap.findmatch(
|
||||||
|
self._mailcap_dict, content_type, filename=modified_url)
|
||||||
|
if not entry:
|
||||||
|
_logger.info('Could not find a valid mailcap entry')
|
||||||
|
raise exceptions.MailcapEntryNotFound()
|
||||||
|
|
||||||
|
return command, entry
|
||||||
|
|
||||||
|
# No parsers matched the url
|
||||||
|
raise exceptions.MailcapEntryNotFound()
|
||||||
|
|
||||||
def open_browser(self, url):
|
def open_browser(self, url):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user