Watcher cleanup. Restart server on config changes and if frontend is modified.

This commit is contained in:
Leo Vasanko 2023-10-24 00:57:50 +03:00 committed by Leo Vasanko
parent 5d3f419508
commit 4a13f642b2
3 changed files with 26 additions and 11 deletions

View File

@ -2,11 +2,11 @@ import asyncio
import typing import typing
import msgspec import msgspec
from sanic import Blueprint, SanicException 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, ErrorMsg, FileRange, StatusMsg from cista.protocol import ControlBase, 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")

View File

@ -1,6 +1,6 @@
import os import os
import re import re
from pathlib import Path from pathlib import Path, PurePath
from sanic import Sanic from sanic import Sanic
@ -13,14 +13,23 @@ def run(dev=False):
url, opts = parse_listen(config.config.listen) url, opts = parse_listen(config.config.listen)
# Silence Sanic's warning about running in production rather than debug # Silence Sanic's warning about running in production rather than debug
os.environ["SANIC_IGNORE_PRODUCTION_WARNING"] = "1" os.environ["SANIC_IGNORE_PRODUCTION_WARNING"] = "1"
confdir = config.conffile.parent
wwwroot = PurePath(__file__).parent / "wwwroot"
if opts.get("ssl"): if opts.get("ssl"):
# Run plain HTTP redirect/acme server on port 80 # Run plain HTTP redirect/acme server on port 80
server80.app.prepare(port=80, motd=False) server80.app.prepare(port=80, motd=False)
domain = opts["host"] domain = opts["host"]
opts["ssl"] = str(config.conffile.parent / domain) # type: ignore check_cert(confdir / domain, domain)
app.prepare(**opts, motd=False, dev=dev, auto_reload=dev, access_log=True) # type: ignore opts["ssl"] = str(confdir / domain) # type: ignore
app.prepare(**opts, motd=False, dev=dev, auto_reload=dev, reload_dir={confdir, wwwroot}, access_log=True) # type: ignore
Sanic.serve() Sanic.serve()
def check_cert(certdir, domain):
if (certdir / "privkey.pem").exist() and (certdir / "fullchain.pem").exists():
return
# TODO: Use certbot to fetch a cert
raise ValueError(f"TLS certificate files privkey.pem and fullchain.pem needed in {certdir}")
def parse_listen(listen): def parse_listen(listen):
if listen.startswith("/"): if listen.startswith("/"):
unix = Path(listen).resolve() unix = Path(listen).resolve()

View File

@ -13,7 +13,7 @@ from cista.protocol import DirEntry, FileEntry, UpdateEntry
pubsub = {} pubsub = {}
tree = {"": None} tree = {"": None}
tree_lock = threading.Lock() tree_lock = threading.Lock()
rootpath = None rootpath: Path = None # type: ignore
quit = False quit = False
modified_flags = "IN_CREATE", "IN_DELETE", "IN_DELETE_SELF", "IN_MODIFY", "IN_MOVE_SELF", "IN_MOVED_FROM", "IN_MOVED_TO" modified_flags = "IN_CREATE", "IN_DELETE", "IN_DELETE_SELF", "IN_MODIFY", "IN_MOVE_SELF", "IN_MOVED_FROM", "IN_MOVED_TO"
disk_usage = None disk_usage = None
@ -22,6 +22,7 @@ def watcher_thread(loop):
global disk_usage global disk_usage
while True: while True:
rootpath = config.config.path
i = inotify.adapters.InotifyTree(rootpath.as_posix()) i = inotify.adapters.InotifyTree(rootpath.as_posix())
old = format_tree() if tree[""] else None old = format_tree() if tree[""] else None
with tree_lock: with tree_lock:
@ -36,18 +37,25 @@ def watcher_thread(loop):
for event in i.event_gen(): for event in i.event_gen():
if quit: return if quit: return
# Disk usage update
du = shutil.disk_usage(rootpath) du = shutil.disk_usage(rootpath)
if du != disk_usage: if du != disk_usage:
disk_usage = du disk_usage = du
asyncio.run_coroutine_threadsafe(broadcast(format_du()), loop) asyncio.run_coroutine_threadsafe(broadcast(format_du()), loop)
break
# Do a full refresh?
if time.monotonic() > refreshdl: break if time.monotonic() > refreshdl: break
if event is None: continue if event is None: continue
_, flags, path, filename = event _, flags, path, filename = event
if not any(f in modified_flags for f in flags): if not any(f in modified_flags for f in flags):
continue continue
# Update modified path
path = PurePosixPath(path) / filename path = PurePosixPath(path) / filename
#print(path, flags) try:
update(path.relative_to(rootpath), loop) update(path.relative_to(rootpath), loop)
except Exception as e:
print("Watching error", e)
break
i = None # Free the inotify object i = None # Free the inotify object
def format_du(): def format_du():
@ -84,7 +92,7 @@ def walk(path: Path) -> DirEntry | FileEntry | None:
print("OS error walking path", path, e) print("OS error walking path", path, e)
return None return None
def update(relpath: Path, loop): def update(relpath: PurePosixPath, loop):
"""Called by inotify updates, check the filesystem and broadcast any changes.""" """Called by inotify updates, check the filesystem and broadcast any changes."""
new = walk(rootpath / relpath) new = walk(rootpath / relpath)
with tree_lock: with tree_lock:
@ -149,9 +157,7 @@ async def broadcast(msg):
await queue.put_nowait(msg) await queue.put_nowait(msg)
async def start(app, loop): async def start(app, loop):
global rootpath
config.load_config() config.load_config()
rootpath = config.config.path
app.ctx.watcher = threading.Thread(target=watcher_thread, args=[loop]) app.ctx.watcher = threading.Thread(target=watcher_thread, args=[loop])
app.ctx.watcher.start() app.ctx.watcher.start()