diff --git a/telnetserver/telnetserver.py b/telnetserver/telnetserver.py index 7991951..993d248 100644 --- a/telnetserver/telnetserver.py +++ b/telnetserver/telnetserver.py @@ -21,21 +21,16 @@ class TelnetServer: self._running = False self._clients = set() self._loop = None + self._shutdown_event = asyncio.Event() def start(self, port=7373): """Starts the telnet server""" self._port = port - # Start telnet server on the async loop - def run_loop(): - self._loop = asyncio.new_event_loop() - asyncio.set_event_loop(self._loop) - self._loop.run_until_complete(self._start_internal()) - self._loop.run_forever() - - # Start the network thread as a daemon so it exits cleanly when the main script stops - t = threading.Thread(target=run_loop, daemon=True) + # Start the telnet server. asyncio.run() needs a coroutine, and threading.Thread needs a plain callable, so + # hand Thread the bridge between the two directly rather than writing a one-line wrapper method for it. + t = threading.Thread(target=asyncio.run, args=(self._start_internal(),), name="TelnetServer", daemon=True) t.start() logger.debug("Telnet server background thread spawned") @@ -44,8 +39,16 @@ class TelnetServer: self._running = True async def _start_internal(self): + """Start method (async). Sets up the telnet server and waits for shutdown.""" + + self._loop = asyncio.get_running_loop() + + server = await asyncio.start_server(self._handle_client, "0.0.0.0", self._port) logger.info(f"Telnet server listening on port {self._port}") - await asyncio.start_server(self._handle_client, "0.0.0.0", self._port) + async with server: + await self._shutdown_event.wait() + + await self._stop_internal() async def _handle_client(self, reader, writer): """Handles a new client connection""" @@ -73,25 +76,12 @@ class TelnetServer: if not data: break - text = data.decode("ascii", errors="ignore") - - for char in text: - if char in ("\r", "\n"): - # User pressed Enter, evaluate the command - command = input_buffer.strip().lower() - input_buffer = "" - - if command == "exit": - writer.write(b"Goodbye!\r\n") - await writer.drain() - # Exit the while read loop, this will disconnect the client. - return - - elif char in ("\b", "\x7f"): - # Handle backspaces - input_buffer = input_buffer[:-1] - else: - input_buffer += char + input_buffer, command = self._consume_input(input_buffer, data) + if command == "exit": + writer.write(b"Goodbye!\r\n") + await writer.drain() + # Exit the while read loop, this will disconnect the client. + return except asyncio.CancelledError: pass @@ -106,9 +96,10 @@ class TelnetServer: def stop(self): """Stops the telnet server""" + self._running = False if self._loop and self._loop.is_running(): logger.debug("Stopping telnet server...") - self._loop.call_soon_threadsafe(self._loop.stop) + self._loop.call_soon_threadsafe(self._shutdown_event.set) async def _stop_internal(self): """Stops the telnet server""" @@ -146,6 +137,25 @@ class TelnetServer: for writer in disconnected_clients: self._clients.discard(writer) + @staticmethod + def _consume_input(input_buffer: str, data: bytes) -> tuple[str, str | None]: + """Handle any input the user gives us, keeping a rolling buffer that we keep passing back through and + adding to. Once we get a command, return that as well, so the caller can deal with it.""" + + command = None + for char in data.decode("ascii", errors="ignore"): + if char in ("\r", "\n"): + stripped = input_buffer.strip().lower() + if stripped: + command = stripped + input_buffer = "" + elif char in ("\b", "\x7f"): + # Handle backspaces + input_buffer = input_buffer[:-1] + else: + input_buffer += char + return input_buffer, command + @staticmethod def _format_dxspider_spot(spot: Spot) -> str: """Formats a spot into the format DXspider uses: diff --git a/templates/add_spot.html b/templates/add_spot.html index 58e4806..d0bd641 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -77,7 +77,7 @@ - + diff --git a/templates/alerts.html b/templates/alerts.html index 66bfaad..1aef369 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -83,7 +83,7 @@ - + diff --git a/templates/bands.html b/templates/bands.html index f611cc2..9ed712e 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -76,8 +76,8 @@ - - + + diff --git a/templates/base.html b/templates/base.html index fb136d5..fe101b5 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% extends "skeleton.html" %} {% block head_extra %} - + @@ -16,10 +16,10 @@ window.fetchEventSource = fetchEventSource; - - - - + + + + {% end %} {% block body %}
diff --git a/templates/conditions.html b/templates/conditions.html index f43b2fb..8d92ea7 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -284,7 +284,7 @@
- + diff --git a/templates/map.html b/templates/map.html index c7235e7..262bcde 100644 --- a/templates/map.html +++ b/templates/map.html @@ -113,8 +113,8 @@ const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}"; - - + + diff --git a/templates/spots.html b/templates/spots.html index 6a6b662..4dc1adb 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -125,8 +125,8 @@ - - + + diff --git a/templates/status.html b/templates/status.html index 8233b6f..9913e73 100644 --- a/templates/status.html +++ b/templates/status.html @@ -86,7 +86,7 @@ - +