mirror of
https://github.com/rio-labs/rio.git
synced 2026-02-09 07:09:00 -06:00
refactor icon fetching
This commit is contained in:
@@ -3824,7 +3824,7 @@ html.picking-component * {
|
||||
// Theses durations are also referenced in code!
|
||||
transition:
|
||||
opacity 0.2s ease-in-out,
|
||||
background-color 0.6s ease-in-out;
|
||||
background-color 0.5s ease-in-out;
|
||||
|
||||
& > * {
|
||||
transform: translateY(-2rem);
|
||||
|
||||
59
rio/app.py
59
rio/app.py
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
@@ -13,6 +14,7 @@ from pathlib import Path
|
||||
import fastapi
|
||||
import introspection
|
||||
import uvicorn
|
||||
from PIL import Image
|
||||
|
||||
import __main__
|
||||
import rio
|
||||
@@ -312,6 +314,63 @@ class App:
|
||||
else:
|
||||
self._ping_pong_interval = timedelta(seconds=ping_pong_interval)
|
||||
|
||||
# Initialized lazily, when the icon is first requested
|
||||
#
|
||||
# This starts out as `None`, then either becomes a `bytes` object if
|
||||
# it's successfully fetched, or an error message if fetching failed.
|
||||
self._icon_as_png_blob: bytes | str | None = None
|
||||
|
||||
async def fetch_icon_png_blob(self) -> bytes:
|
||||
"""
|
||||
Fetches the app's icon as a PNG blob.
|
||||
|
||||
The result is cached. It will be loaded the first time you call this
|
||||
method, and then returned immediately on subsequent calls. If fetching
|
||||
the icon fails, the exception is also cached, and no further fetching
|
||||
attempts will be made.
|
||||
|
||||
## Raises
|
||||
|
||||
`IOError`: If the icon could not be fetched.
|
||||
"""
|
||||
|
||||
# Already cached?
|
||||
if isinstance(self._icon_as_png_blob, bytes):
|
||||
return self._icon_as_png_blob
|
||||
|
||||
# Already failed?
|
||||
if isinstance(self._icon_as_png_blob, str):
|
||||
raise IOError(self._icon_as_png_blob)
|
||||
|
||||
# Nope, get it
|
||||
try:
|
||||
icon_blob, _ = await self._icon.try_fetch_as_blob()
|
||||
|
||||
input_buffer = io.BytesIO(icon_blob)
|
||||
output_buffer = io.BytesIO()
|
||||
|
||||
with Image.open(input_buffer) as image:
|
||||
image.save(output_buffer, format="png")
|
||||
|
||||
except Exception as err:
|
||||
if isinstance(self._icon, assets.PathAsset):
|
||||
message = f"Could not fetch the app's icon from {self._icon.path.resolve()}"
|
||||
elif isinstance(self._icon, assets.UrlAsset):
|
||||
message = (
|
||||
f"Could not fetch the app's icon from {self._icon.url}"
|
||||
)
|
||||
else:
|
||||
message = f"Could not fetch the app's icon from"
|
||||
|
||||
self._icon_as_png_blob = message
|
||||
raise IOError(message) from err
|
||||
|
||||
# Cache it
|
||||
self._icon_as_png_blob = output_buffer.getvalue()
|
||||
|
||||
# Done!
|
||||
return self._icon_as_png_blob
|
||||
|
||||
@functools.cached_property
|
||||
def _main_file_path(self) -> Path:
|
||||
if global_state.rio_run_app_module_path is not None:
|
||||
|
||||
@@ -4,12 +4,10 @@ import asyncio
|
||||
import contextlib
|
||||
import functools
|
||||
import html
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
import typing as t
|
||||
import warnings
|
||||
import weakref
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
@@ -18,7 +16,6 @@ from xml.etree import ElementTree as ET
|
||||
import crawlerdetect
|
||||
import fastapi
|
||||
import timer_dict
|
||||
from PIL import Image
|
||||
from uniserde import Jsonable, JsonDoc
|
||||
|
||||
import rio
|
||||
@@ -234,9 +231,6 @@ class FastapiServer(fastapi.FastAPI, AbstractAppServer):
|
||||
self._can_create_new_sessions = asyncio.Event()
|
||||
self._can_create_new_sessions.set()
|
||||
|
||||
# Initialized lazily, when the favicon is first requested.
|
||||
self._icon_as_png_blob: bytes | None = None
|
||||
|
||||
# The session tokens and Request object for all clients that have made a
|
||||
# HTTP request, but haven't yet established a websocket connection. Once
|
||||
# the websocket connection is created, these will be turned into
|
||||
@@ -617,43 +611,19 @@ Sitemap: {base_url / "/rio/sitemap"}
|
||||
"""
|
||||
Handler for serving the favicon via fastapi, if one is set.
|
||||
"""
|
||||
# If an icon is set, make sure a cached version exists
|
||||
if self._icon_as_png_blob is None and self.app._icon is not None:
|
||||
try:
|
||||
icon_blob, _ = await self.app._icon.try_fetch_as_blob()
|
||||
# Fetch the favicon. This method is already caching, so it's fine to
|
||||
# fetch every time.
|
||||
try:
|
||||
icon_png_blob = await self.app.fetch_icon_png_blob()
|
||||
except IOError as err:
|
||||
raise fastapi.HTTPException(
|
||||
status_code=fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Could not fetch the app's icon.",
|
||||
) from err
|
||||
|
||||
input_buffer = io.BytesIO(icon_blob)
|
||||
output_buffer = io.BytesIO()
|
||||
|
||||
with Image.open(input_buffer) as image:
|
||||
image.save(output_buffer, format="png")
|
||||
|
||||
except Exception as err:
|
||||
if isinstance(self.app._icon, assets.PathAsset):
|
||||
warnings.warn(
|
||||
f"Could not fetch the app's icon from {self.app._icon.path.resolve()}"
|
||||
)
|
||||
elif isinstance(self.app._icon, assets.UrlAsset):
|
||||
warnings.warn(
|
||||
f"Could not fetch the app's icon from {self.app._icon.url}"
|
||||
)
|
||||
else:
|
||||
warnings.warn(f"Could not fetch the app's icon from")
|
||||
|
||||
raise fastapi.HTTPException(
|
||||
status_code=fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Could not fetch the app's icon.",
|
||||
) from err
|
||||
|
||||
self._icon_as_png_blob = output_buffer.getvalue()
|
||||
|
||||
# No icon set or fetching failed
|
||||
if self._icon_as_png_blob is None:
|
||||
return fastapi.responses.Response(status_code=404)
|
||||
|
||||
# There is an icon, respond
|
||||
# Respond
|
||||
return fastapi.responses.Response(
|
||||
content=self._icon_as_png_blob,
|
||||
content=icon_png_blob,
|
||||
media_type="image/png",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user