Watcher cleanup. Restart server on config changes and if frontend is modified.
This commit is contained in:
parent
5d3f419508
commit
4a13f642b2
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user