""" Background status checker — parallel server pings. """ import threading import time from concurrent.futures import ThreadPoolExecutor, as_completed from typing import TYPE_CHECKING if TYPE_CHECKING: from core.server_store import ServerStore from core.ssh_client import SSHClientWrapper from core.logger import log class StatusChecker: def __init__(self, store: "ServerStore"): self.store = store self._running = False self._thread: threading.Thread | None = None self._gui_callback = None def start(self): if self._running: return self._running = True self._thread = threading.Thread(target=self._loop, daemon=True) self._thread.start() def stop(self): self._running = False if self._thread: self._thread.join(timeout=3.0) def set_gui_callback(self, callback): self._gui_callback = callback def check_one(self, server: dict) -> bool: key_path = self.store.get_ssh_key_path() wrapper = SSHClientWrapper(server, key_path) return wrapper.check_connection() def check_all_now(self): threading.Thread(target=self._check_cycle, daemon=True).start() def _loop(self): while self._running: self._check_cycle() interval = self.store.get_check_interval() for _ in range(interval * 10): if not self._running: return time.sleep(0.1) def _check_cycle(self): servers = self.store.get_all() # Mark skipped servers as disabled for s in servers: if s.get("skip_check", False): self.store.set_status(s["alias"], "disabled") ssh_servers = [s for s in servers if s.get("type", "ssh") == "ssh" and not s.get("skip_check", False)] # Mark non-SSH (non-skipped) as unknown for s in servers: if s.get("type", "ssh") != "ssh" and not s.get("skip_check", False): self.store.set_status(s["alias"], "unknown") if not ssh_servers: return # Parallel checks — up to 10 concurrent max_workers = min(10, len(ssh_servers)) try: with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = { executor.submit(self.check_one, s): s["alias"] for s in ssh_servers } for future in as_completed(futures, timeout=30): if not self._running: return alias = futures[future] try: online = future.result(timeout=10) self.store.set_status(alias, "online" if online else "offline") except Exception: self.store.set_status(alias, "offline") except Exception as e: log.warning(f"Status check cycle error: {e}") if self._gui_callback: try: self._gui_callback() except Exception: pass