54 lines
2.0 KiB
Python
54 lines
2.0 KiB
Python
"""Run the web UI's FastAPI app on a background thread.
|
|
|
|
The radar app already owns the hardware and the Qt event loop, so the web server
|
|
lives in a daemon thread inside that process (uvicorn brings its own asyncio loop
|
|
for the thread). Control requests hop back to the Qt main thread via the bridge's
|
|
queued signals — the web thread never touches Qt directly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import threading
|
|
|
|
import uvicorn
|
|
|
|
from python_app.webui.app import create_app
|
|
from python_app.webui.controller import WebController
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WebUiServer:
|
|
"""Owns a uvicorn server bound to a controller, run on a daemon thread."""
|
|
|
|
def __init__(self, controller: WebController, *, host: str = "0.0.0.0", port: int = 8080) -> None:
|
|
"""Build the server for ``controller`` (not started until :meth:`start`)."""
|
|
config = uvicorn.Config(create_app(controller), host=host, port=port, log_level="warning")
|
|
self._server = uvicorn.Server(config)
|
|
self._thread = threading.Thread(target=self._serve, name="radar-webui", daemon=True)
|
|
|
|
def start(self) -> None:
|
|
"""Start serving on the background thread."""
|
|
self._thread.start()
|
|
|
|
def is_alive(self) -> bool:
|
|
"""Return whether the server thread is still running."""
|
|
return self._thread.is_alive()
|
|
|
|
def _serve(self) -> None:
|
|
"""Run uvicorn, surfacing a startup/runtime failure instead of dying silently.
|
|
|
|
The bind happens on this thread after ``start()`` has already returned, so a
|
|
failure (e.g. the port is taken) would otherwise be invisible.
|
|
"""
|
|
try:
|
|
self._server.run()
|
|
except Exception: # noqa: BLE001 - log, never crash the host process
|
|
logger.exception("Web UI server thread exited with an error")
|
|
|
|
def stop(self) -> None:
|
|
"""Ask uvicorn to exit and wait briefly for the thread to unwind."""
|
|
self._server.should_exit = True
|
|
self._thread.join(timeout=5.0)
|