parent
5b3e91336f
commit
bd7cfce520
@ -0,0 +1,25 @@
|
||||
"""Local files browser."""
|
||||
|
||||
from bebop.browser.browser import Browser
|
||||
from bebop.page import Page
|
||||
|
||||
|
||||
def open_file(browser: Browser, filepath: str, encoding="utf-8", history=True):
|
||||
"""Open a file and render it.
|
||||
|
||||
This should be used only on Gemtext files or at least text files.
|
||||
Anything else will produce garbage and may crash the program. In the
|
||||
future this should be able to use a different parser according to a MIME
|
||||
type or something.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, "rt", encoding=encoding) as f:
|
||||
text = f.read()
|
||||
except (OSError, ValueError) as exc:
|
||||
browser.set_status_error(f"Failed to open file: {exc}")
|
||||
return
|
||||
browser.load_page(Page.from_gemtext(text))
|
||||
file_url = "file://" + filepath
|
||||
if history:
|
||||
browser.history.push(file_url)
|
||||
browser.current_url = file_url
|
@ -0,0 +1,126 @@
|
||||
"""Gemini-related features of the browser."""
|
||||
|
||||
from bebop.browser.browser import Browser
|
||||
from bebop.navigation import set_parameter
|
||||
from bebop.page import Page
|
||||
from bebop.protocol import Request, Response
|
||||
|
||||
|
||||
def open_gemini_url(browser: Browser, url, redirects=0, history=True,
|
||||
use_cache=True):
|
||||
"""Open a Gemini URL and set the formatted response as content.
|
||||
|
||||
After initiating the connection, TODO
|
||||
"""
|
||||
browser.set_status(f"Loading {url}")
|
||||
|
||||
if use_cache and url in browser.cache:
|
||||
browser.load_page(browser.cache[url])
|
||||
if browser.current_url and history:
|
||||
browser.history.push(browser.current_url)
|
||||
browser.current_url = url
|
||||
browser.set_status(url)
|
||||
return
|
||||
|
||||
req = Request(url, browser.stash)
|
||||
connected = req.connect()
|
||||
if not connected:
|
||||
if req.state == Request.STATE_ERROR_CERT:
|
||||
error = f"Certificate was missing or corrupt ({url})."
|
||||
elif req.state == Request.STATE_UNTRUSTED_CERT:
|
||||
error = f"Certificate has been changed ({url})."
|
||||
# TODO propose the user ways to handle this.
|
||||
elif req.state == Request.STATE_CONNECTION_FAILED:
|
||||
error_details = f": {req.error}" if req.error else "."
|
||||
error = f"Connection failed ({url})" + error_details
|
||||
else:
|
||||
error = f"Connection failed ({url})."
|
||||
browser.set_status_error(error)
|
||||
return
|
||||
|
||||
if req.state == Request.STATE_INVALID_CERT:
|
||||
# TODO propose abort / temp trust
|
||||
pass
|
||||
elif req.state == Request.STATE_UNKNOWN_CERT:
|
||||
# TODO propose abort / temp trust / perm trust
|
||||
pass
|
||||
else:
|
||||
pass # TODO
|
||||
|
||||
data = req.proceed()
|
||||
if not data:
|
||||
browser.set_status_error(f"Server did not respond in time ({url}).")
|
||||
return
|
||||
response = Response.parse(data)
|
||||
if not response:
|
||||
browser.set_status_error(f"Server response parsing failed ({url}).")
|
||||
return
|
||||
|
||||
if response.code == 20:
|
||||
handle_code = handle_response_content(browser, response)
|
||||
if handle_code == 0:
|
||||
if browser.current_url and history:
|
||||
browser.history.push(browser.current_url)
|
||||
browser.current_url = url
|
||||
browser.cache[url] = browser.page_pad.current_page
|
||||
browser.set_status(url)
|
||||
elif handle_code == 1:
|
||||
browser.set_status(f"Downloaded {url}.")
|
||||
elif response.generic_code == 30 and response.meta:
|
||||
browser.open_url(response.meta, base_url=url, redirects=redirects + 1)
|
||||
elif response.generic_code in (40, 50):
|
||||
error = f"Server error: {response.meta or Response.code.name}"
|
||||
browser.set_status_error(error)
|
||||
elif response.generic_code == 10:
|
||||
handle_input_request(browser, url, response.meta)
|
||||
else:
|
||||
error = f"Unhandled response code {response.code}"
|
||||
browser.set_status_error(error)
|
||||
|
||||
|
||||
def handle_response_content(browser: Browser, response: Response) -> int:
|
||||
"""Handle a response's content from a Gemini server.
|
||||
|
||||
According to the MIME type received or inferred, render or download the
|
||||
response's content.
|
||||
|
||||
Currently only text/gemini content is rendered.
|
||||
|
||||
Arguments:
|
||||
- response: a successful Response.
|
||||
|
||||
Returns:
|
||||
An error code: 0 means a page has been loaded, so any book-keeping such
|
||||
as history management can be applied; 1 means a content has been
|
||||
successfully retrieved but has not been displayed (e.g. non-text
|
||||
content) nor saved as a page; 2 means that the content could not be
|
||||
handled, either due to bogus MIME type or MIME parameters.
|
||||
"""
|
||||
mime_type = response.get_mime_type()
|
||||
if mime_type.main_type == "text":
|
||||
if mime_type.sub_type == "gemini":
|
||||
encoding = mime_type.charset
|
||||
try:
|
||||
text = response.content.decode(encoding, errors="replace")
|
||||
except LookupError:
|
||||
browser.set_status_error("Unknown encoding {encoding}.")
|
||||
return 2
|
||||
browser.load_page(Page.from_gemtext(text))
|
||||
return 0
|
||||
else:
|
||||
pass # TODO
|
||||
else:
|
||||
pass # TODO
|
||||
return 1
|
||||
|
||||
|
||||
def handle_input_request(browser: Browser, from_url: str, message: str =None):
|
||||
"""Focus command-line to pass input to the server."""
|
||||
if message:
|
||||
browser.set_status(f"Input needed: {message}")
|
||||
else:
|
||||
browser.set_status("Input needed:")
|
||||
user_input = browser.command_line.focus("?")
|
||||
if user_input:
|
||||
url = set_parameter(from_url, user_input)
|
||||
open_gemini_url(browser, url)
|
@ -0,0 +1,11 @@
|
||||
"""Ha! You thought there would be a Web browser in there?"""
|
||||
|
||||
import webbrowser
|
||||
|
||||
from bebop.browser.browser import Browser
|
||||
|
||||
|
||||
def open_web_url(browser: Browser, url):
|
||||
"""Open a Web URL. Currently relies in Python's webbrowser module."""
|
||||
browser.set_status(f"Opening {url}")
|
||||
webbrowser.open_new_tab(url)
|
Loading…
Reference in new issue