Compare commits

...

5 Commits

Author SHA1 Message Date
Leo Vasanko
ba36eaec1b Allow multiple commands on control socket without disconnecting. 2023-11-01 14:57:54 +00:00
Leo Vasanko
a435a30c88 Realtime updates of wwwroot files when --dev is used. 2023-11-01 14:53:57 +00:00
Leo Vasanko
742b05ed66 Faster wwwroot serving, uses RAM cache of brotli compressed data for all assets. 2023-11-01 14:40:08 +00:00
Leo Vasanko
a26dc42d88 Fix control message decoding. 2023-11-01 14:12:06 +00:00
Leo Vasanko
9002afbc7e Merged something 2023-11-01 14:03:17 +00:00
4 changed files with 59 additions and 14 deletions

View File

@ -6,7 +6,7 @@ from sanic import Blueprint
from cista import watching
from cista.fileio import FileServer
from cista.protocol import ControlBase, FileRange, StatusMsg
from cista.protocol import ControlTypes, FileRange, StatusMsg
from cista.util.apphelpers import asend, websocket_wrapper
bp = Blueprint("api", url_prefix="/api")
@ -76,9 +76,10 @@ async def download(req, ws):
@bp.websocket("control")
@websocket_wrapper
async def control(req, ws):
cmd = msgspec.json.decode(await ws.recv(), type=ControlBase)
await asyncio.to_thread(cmd)
await asend(ws, StatusMsg(status="ack", req=cmd))
while True:
cmd = msgspec.json.decode(await ws.recv(), type=ControlTypes)
await asyncio.to_thread(cmd)
await asend(ws, StatusMsg(status="ack", req=cmd))
@bp.websocket("watch")

View File

@ -2,12 +2,13 @@ import mimetypes
from importlib.resources import files
from urllib.parse import unquote
import asyncio
import brotli
from sanic import Blueprint, Sanic, raw
from sanic.exceptions import Forbidden, NotFound
from cista import auth, config, session, watching
from cista.api import bp
from cista.util import filename
from cista.util.apphelpers import handle_sanic_exception
app = Sanic("cista", strict_slashes=True)
@ -58,15 +59,54 @@ def http_fileserver(app, _):
app.blueprint(bp)
www = {}
@app.before_server_start
def load_wwwroot(app):
global www
wwwnew = {}
base = files("cista") / "wwwroot"
paths = ["."]
while paths:
path = paths.pop(0)
current = base / path
for p in current.iterdir():
if p.is_dir():
paths.append(current / p.parts[-1])
continue
name = p.relative_to(base).as_posix()
mime = mimetypes.guess_type(name)[0] or "application/octet-stream"
data = p.read_bytes()
# Use old data if not changed
if name in www and www[name][0] == data:
wwwnew[name] = www[name]
continue
# Precompress with Brotli
br = brotli.compress(data)
if len(br) >= len(data):
br = False
wwwnew[name] = data, br, mime
www = wwwnew
@app.add_task
async def refresh_wwwroot():
while app.debug:
await asyncio.sleep(0.5)
load_wwwroot(app)
@app.get("/<path:path>", static=True)
async def wwwroot(req, path=""):
"""Frontend files only"""
name = filename.sanitize(unquote(path)) if path else "index.html"
try:
index = files("cista").joinpath("wwwroot", name).read_bytes()
except OSError as e:
raise NotFound(
f"File not found: /{path}", extra={"name": name, "exception": repr(e)}
)
mime = mimetypes.guess_type(name)[0] or "application/octet-stream"
return raw(index, content_type=mime)
name = unquote(path) or "index.html"
if name not in www:
raise NotFound(f"File not found: /{path}", extra={"name": name})
data, br, mime = www[name]
headers = {}
# Brotli compressed?
if br and "br" in req.headers.accept_encoding.split(", "):
headers["content-encoding"] = "br"
data = br
return raw(data, content_type=mime, headers=headers)

View File

@ -78,6 +78,9 @@ class Cp(ControlBase):
)
ControlTypes = MkDir | Rename | Rm | Mv | Cp
## File uploads and downloads

View File

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