Frontend created and rewritten a few times, with some backend fixes #1

Merged
leo merged 110 commits from plaintable into main 2023-11-08 20:38:40 +00:00
2 changed files with 59 additions and 13 deletions
Showing only changes of commit bdc0bbd44f - Show all commits

View File

@ -2,15 +2,21 @@ import mimetypes
from importlib.resources import files
from urllib.parse import unquote
import sanic.helpers
import asyncio
import brotli
from sanic import Blueprint, Sanic, raw
from blake3 import blake3
from sanic import Blueprint, Sanic, raw, empty
from sanic.exceptions import Forbidden, NotFound
from wsgiref.handlers import format_date_time
from cista import auth, config, session, watching
from cista.api import bp
from cista.util.apphelpers import handle_sanic_exception
# Workaround until Sanic PR #2824 is merged
sanic.helpers._ENTITY_HEADERS = frozenset()
app = Sanic("cista", strict_slashes=True)
app.blueprint(auth.bp)
app.blueprint(bp)
@ -63,8 +69,12 @@ www = {}
@app.before_server_start
def load_wwwroot(app):
async def load_wwwroot(*_ignored):
global www
www = await asyncio.get_event_loop().run_in_executor(None, _load_wwwroot, www)
def _load_wwwroot(www):
wwwnew = {}
base = files("cista") / "wwwroot"
paths = ["."]
@ -77,36 +87,71 @@ def load_wwwroot(app):
continue
name = p.relative_to(base).as_posix()
mime = mimetypes.guess_type(name)[0] or "application/octet-stream"
mtime = p.stat().st_mtime
data = p.read_bytes()
etag = blake3(data).hexdigest(length=8)
if name == "index.html":
name = ""
# Use old data if not changed
if name in www and www[name][0] == data:
if name in www and www[name][2]["etag"] == etag:
wwwnew[name] = www[name]
continue
cached = name.startswith("assets/")
headers = {
"etag": etag,
"last-modified": format_date_time(mtime),
"cache-control": "max-age=31536000, immutable"
if cached
else "no-cache",
"content-type": mime,
}
# Precompress with Brotli
br = brotli.compress(data)
if len(br) >= len(data):
br = False
wwwnew[name] = data, br, mime
www = wwwnew
wwwnew[name] = data, br, headers
return wwwnew
@app.add_task
async def refresh_wwwroot():
while app.debug:
while True:
try:
wwwold = www
await load_wwwroot()
changes = ""
for name in sorted(www):
attr = www[name]
if wwwold.get(name) == attr:
continue
headers = attr[2]
changes += f"{headers['last-modified']} {headers['etag']} /{name}\n"
for name in sorted(set(wwwold) - set(www)):
changes += f"Deleted /{name}\n"
if changes:
print(f"Updated wwwroot:\n{changes}", end="", flush=True)
except Exception as e:
print("Error loading wwwroot", e)
if not app.debug:
return
await asyncio.sleep(0.5)
load_wwwroot(app)
@app.get("/<path:path>", static=True)
@app.route("/<path:path>", methods=["GET", "HEAD"])
async def wwwroot(req, path=""):
"""Frontend files only"""
name = unquote(path) or "index.html"
name = unquote(path)
if name not in www:
raise NotFound(f"File not found: /{path}", extra={"name": name})
data, br, mime = www[name]
headers = {}
data, br, headers = www[name]
if req.headers.if_none_match == headers["etag"]:
# The client has it cached, respond 304 Not Modified
return empty(304, headers=headers)
# Brotli compressed?
if br and "br" in req.headers.accept_encoding.split(", "):
headers["content-encoding"] = "br"
headers = {
**headers,
"content-encoding": "br",
}
data = br
return raw(data, content_type=mime, headers=headers)
return raw(data, headers=headers)

View File

@ -15,6 +15,7 @@ classifiers = [
]
dependencies = [
"argon2-cffi",
"blake3",
"brotli",
"docopt",
"inotify",