protocol: download unknown mime types

exec
dece 3 years ago
parent b2fdabea71
commit cf48818c24

@ -10,15 +10,17 @@ TODO DONE
encodings
bookmarks
view/edit sources
downloads
open last download
non shit command-line
home page
downloads
media files
view history
identity management
configuration
--------------------------------------------------------------------------------
BACKLOG
download to disk, not in memory
margins / centering
pre blocks folding
buffers (tabs)

@ -1,6 +1,9 @@
"""Gemini-related features of the browser."""
from pathlib import Path
from bebop.browser.browser import Browser
from bebop.fs import get_downloads_path
from bebop.navigation import set_parameter
from bebop.page import Page
from bebop.protocol import Request, Response
@ -55,17 +58,10 @@ def open_gemini_url(browser: Browser, url, redirects=0, history=True,
if not response:
browser.set_status_error(f"Server response parsing failed ({url}).")
return
response.url = url
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}.")
handle_response_content(browser, url, response, history)
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):
@ -78,44 +74,72 @@ def open_gemini_url(browser: Browser, url, redirects=0, history=True,
browser.set_status_error(error)
def handle_response_content(browser: Browser, response: Response) -> int:
"""Handle a response's content from a Gemini server.
def handle_response_content(browser: Browser, url: str, response: Response,
history: bool):
"""Handle a successful response content from a Gemini server.
According to the MIME type received or inferred, render or download the
response's content.
According to the MIME type received or inferred, the response is either
rendered by the browser, or saved to disk. If an error occurs, the browser
displays it.
Currently only text content is rendered. For Gemini, the encoding specified
in the response is used, if available on the Python distribution. For other
text formats, only UTF-8 is attempted.
Only text content is rendered. For Gemini, the encoding specified in the
response is used, if available on the Python distribution. For other text
formats, only UTF-8 is attempted.
Arguments:
- browser: Browser instance that made the initial request.
- url: original URL.
- 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.
- history: whether to modify history on a page load.
"""
mime_type = response.get_mime_type()
page = None
error = None
filepath = None
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
error = f"Unknown encoding {encoding}."
else:
page = Page.from_gemtext(text)
else:
text = response.content.decode("utf-8", errors="replace")
browser.load_page(Page.from_text(text))
return 0
page = Page.from_text(text)
else:
filepath = get_download_path(url)
if page:
browser.load_page(page)
if browser.current_url and history:
browser.history.push(browser.current_url)
browser.current_url = url
browser.cache[url] = page
browser.set_status(url)
elif filepath:
try:
with open(filepath, "wb") as download_file:
download_file.write(response.content)
except OSError as exc:
browser.set_status_error(f"Failed to save {url} ({exc})")
else:
browser.set_status(f"Downloaded {url} ({mime_type.short}).")
elif error:
browser.set_status_error(error)
def get_download_path(url: str) -> Path:
"""Try to find the best download file path possible from this URL."""
download_dir = get_downloads_path()
url_parts = url.rsplit("/", maxsplit=1)
if url_parts:
filename = url_parts[-1]
else:
pass # TODO
return 1
filename = url.split("://")[1] if "://" in url else url
filename = filename.replace("/", "_")
return download_dir / filename
def handle_input_request(browser: Browser, from_url: str, message: str =None):

@ -4,6 +4,7 @@ A lot of logic comes from `appdirs`:
https://github.com/ActiveState/appdirs/blob/master/appdirs.py
"""
from functools import lru_cache
from os import getenv
from os.path import expanduser
from pathlib import Path
@ -12,7 +13,28 @@ from pathlib import Path
APP_NAME = "bebop"
@lru_cache(None)
def get_user_data_path() -> Path:
"""Return the user data directory path."""
path = Path(getenv("XDG_DATA_HOME", expanduser("~/.local/share")))
return path / APP_NAME
@lru_cache(None)
def get_downloads_path() -> Path:
"""Return the user downloads directory path."""
xdg_config_path = Path(getenv("XDG_CONFIG_HOME", expanduser("~/.config")))
download_path = ""
try:
with open(xdg_config_path / "user-dirs.dirs", "rt") as user_dirs_file:
for line in user_dirs_file:
if line.startswith("XDG_DOWNLOAD_DIR="):
download_path = line.rstrip().split("=", maxsplit=1)[1]
download_path = download_path.strip('"')
download_path = download_path.replace("$HOME", expanduser("~"))
break
except OSError:
pass
if download_path:
return Path(download_path)
return Path.home()

@ -13,6 +13,10 @@ class MimeType:
sub_type: str
parameters: dict
@property
def short(self):
return f"{self.main_type or '*'}/{self.sub_type or '*'}"
@property
def charset(self):
return self.parameters.get("charset", DEFAULT_CHARSET)

@ -182,7 +182,16 @@ class StatusCode(IntEnum):
@dataclass
class Response:
"""A Gemini response."""
"""A Gemini response.
Response objects can be created only by parsing a Gemini response using the
static `parse` method, so you're guaranteed to have a valid object.
Attributes:
- code: the status code returned by the server.
- meta: optional meta content.
- content: bytes as returned by the server, only in successful requests.
"""
code: StatusCode
meta: str = ""

@ -18,7 +18,7 @@ def render_lines(metalines, window, max_width):
- window: window that will be resized as filled with rendered lines.
- max_width: line length limit for the pad.
Return:
Returns:
The tuple of integers (error, height, width), error being a non-zero value
if an error occured during rendering, and height and width being the new
dimensions of the resized window.
@ -53,7 +53,7 @@ def render_line(metaline, window, max_width):
window.addstr(url_text, attributes)
def get_base_line_attributes(line_type):
def get_base_line_attributes(line_type) -> int:
"""Return the base attributes for this line type.
Other attributes may be freely used later for this line type but this is

Loading…
Cancel
Save