Compare commits

..

5 Commits

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 import watching
from cista.fileio import FileServer 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 from cista.util.apphelpers import asend, websocket_wrapper
bp = Blueprint("api", url_prefix="/api") bp = Blueprint("api", url_prefix="/api")
@ -76,7 +76,8 @@ async def download(req, ws):
@bp.websocket("control") @bp.websocket("control")
@websocket_wrapper @websocket_wrapper
async def control(req, ws): async def control(req, ws):
cmd = msgspec.json.decode(await ws.recv(), type=ControlBase) while True:
cmd = msgspec.json.decode(await ws.recv(), type=ControlTypes)
await asyncio.to_thread(cmd) await asyncio.to_thread(cmd)
await asend(ws, StatusMsg(status="ack", req=cmd)) await asend(ws, StatusMsg(status="ack", req=cmd))

View File

@ -2,12 +2,13 @@ import mimetypes
from importlib.resources import files from importlib.resources import files
from urllib.parse import unquote from urllib.parse import unquote
import asyncio
import brotli
from sanic import Blueprint, Sanic, raw from sanic import Blueprint, Sanic, raw
from sanic.exceptions import Forbidden, NotFound from sanic.exceptions import Forbidden, NotFound
from cista import auth, config, session, watching from cista import auth, config, session, watching
from cista.api import bp from cista.api import bp
from cista.util import filename
from cista.util.apphelpers import handle_sanic_exception from cista.util.apphelpers import handle_sanic_exception
app = Sanic("cista", strict_slashes=True) app = Sanic("cista", strict_slashes=True)
@ -58,15 +59,54 @@ def http_fileserver(app, _):
app.blueprint(bp) 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) @app.get("/<path:path>", static=True)
async def wwwroot(req, path=""): async def wwwroot(req, path=""):
"""Frontend files only""" """Frontend files only"""
name = filename.sanitize(unquote(path)) if path else "index.html" name = unquote(path) or "index.html"
try: if name not in www:
index = files("cista").joinpath("wwwroot", name).read_bytes() raise NotFound(f"File not found: /{path}", extra={"name": name})
except OSError as e: data, br, mime = www[name]
raise NotFound( headers = {}
f"File not found: /{path}", extra={"name": name, "exception": repr(e)} # Brotli compressed?
) if br and "br" in req.headers.accept_encoding.split(", "):
mime = mimetypes.guess_type(name)[0] or "application/octet-stream" headers["content-encoding"] = "br"
return raw(index, content_type=mime) 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 ## File uploads and downloads

View File

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