refactor icon fetching

This commit is contained in:
Jakob Pinterits
2024-11-14 20:46:17 +01:00
parent 7ef6ac3d04
commit 5ed6e7aa0c
3 changed files with 71 additions and 42 deletions

View File

@@ -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);

View File

@@ -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:

View File

@@ -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",
)