Compare commits
5 Commits
fa2da207a9
...
d3f68d3baf
| Author | SHA1 | Date | |
|---|---|---|---|
|
d3f68d3baf
|
|||
|
62acc4b181
|
|||
|
faecc38261
|
|||
|
3eb681e233
|
|||
|
bda10a6248
|
0
cache/.gitkeep
vendored
Normal file
0
cache/.gitkeep
vendored
Normal file
@@ -1,3 +1,5 @@
|
|||||||
from .backend_server import BackendServer
|
from .backend_server import BackendServer
|
||||||
|
from .client import Client
|
||||||
|
from .client_status import ClientStatus
|
||||||
|
|
||||||
__all__ = ["BackendServer"]
|
__all__ = ["BackendServer", "Client", "ClientStatus"]
|
||||||
|
|||||||
@@ -28,27 +28,6 @@ class BackendServer:
|
|||||||
)
|
)
|
||||||
self.logger.debug("Initializing Server...")
|
self.logger.debug("Initializing Server...")
|
||||||
|
|
||||||
self.known_clients: dict[str, dict[str, str | float]] = {}
|
|
||||||
try:
|
|
||||||
with open("cache/known_clients.yaml", "r") as f:
|
|
||||||
self.known_clients = (
|
|
||||||
yaml.safe_load(f).get("known_clients", {}) or {}
|
|
||||||
)
|
|
||||||
self.logger.debug(
|
|
||||||
f"Loaded known clients: {self.known_clients}"
|
|
||||||
)
|
|
||||||
self.logger.info(
|
|
||||||
f"Loaded {len(self.known_clients)} known clients"
|
|
||||||
)
|
|
||||||
except FileNotFoundError:
|
|
||||||
self.logger.warning(
|
|
||||||
"known_clients.yaml not found, creating empty known clients list"
|
|
||||||
)
|
|
||||||
with open("cache/known_clients.yaml", "w") as f:
|
|
||||||
yaml.safe_dump({"known_clients": {}}, f)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Error loading known clients: {e}")
|
|
||||||
|
|
||||||
self.selector = selectors.DefaultSelector()
|
self.selector = selectors.DefaultSelector()
|
||||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.server_socket.setsockopt(
|
self.server_socket.setsockopt(
|
||||||
@@ -63,16 +42,61 @@ class BackendServer:
|
|||||||
|
|
||||||
self.clients: dict[str, Client] = {}
|
self.clients: dict[str, Client] = {}
|
||||||
|
|
||||||
if self.known_clients:
|
self.known_clients: dict[str, dict[str, str | float]] = (
|
||||||
for client_id in self.known_clients:
|
self._load_known_clients()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.running: bool = False
|
||||||
|
|
||||||
|
def _load_known_clients(self) -> dict[str, dict[str, str | float]]:
|
||||||
|
"""Load the list of known clients from a YAML file and validate."""
|
||||||
|
known_clients: dict[str, dict[str, str | float]] = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open("cache/known_clients.yaml", "r") as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("YAML root must be a dict")
|
||||||
|
|
||||||
|
known_clients = data.get("known_clients", {}) or {}
|
||||||
|
|
||||||
|
if not isinstance(known_clients, dict):
|
||||||
|
raise ValueError("'known_clients' must be a dict")
|
||||||
|
|
||||||
|
for client_id, client_data in known_clients.items():
|
||||||
|
if not isinstance(client_data, dict):
|
||||||
|
raise ValueError(
|
||||||
|
f"Client {client_id} data must be a dict"
|
||||||
|
)
|
||||||
|
last_seen = client_data.get("last_seen", 0.0)
|
||||||
|
if not isinstance(last_seen, (float, int)):
|
||||||
|
raise ValueError(
|
||||||
|
f"Client {client_id} 'last_seen' must be a float or int"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.debug(f"Loaded known clients: {known_clients}")
|
||||||
|
self.logger.info(f"Loaded {len(known_clients)} known clients")
|
||||||
|
|
||||||
|
for client_id in known_clients:
|
||||||
client = Client(id=client_id, addr=None, socket=None)
|
client = Client(id=client_id, addr=None, socket=None)
|
||||||
client.status = ClientStatus.OFFLINE
|
client.status = ClientStatus.OFFLINE
|
||||||
client.last_seen = float(
|
client.last_seen = float(
|
||||||
self.known_clients[client_id].get("last_seen", 0.0)
|
known_clients[client_id].get("last_seen", 0.0)
|
||||||
)
|
)
|
||||||
self.clients[client_id] = client
|
self.clients[client_id] = client
|
||||||
|
|
||||||
self.running: bool = False
|
except FileNotFoundError:
|
||||||
|
self.logger.warning(
|
||||||
|
"known_clients.yaml not found, creating empty known clients list"
|
||||||
|
)
|
||||||
|
with open("cache/known_clients.yaml", "w") as f:
|
||||||
|
yaml.safe_dump({"known_clients": {}}, f)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error loading known clients: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return known_clients
|
||||||
|
|
||||||
def _save_known_clients(self) -> None:
|
def _save_known_clients(self) -> None:
|
||||||
"""Save the list of known clients to a YAML file."""
|
"""Save the list of known clients to a YAML file."""
|
||||||
|
|||||||
@@ -5,24 +5,19 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging as lg
|
import logging as lg
|
||||||
import socket
|
import socket
|
||||||
from enum import Enum
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from judas_server.backend.client_status import ClientStatus
|
||||||
class ClientStatus(str, Enum):
|
|
||||||
"""Enumeration of client connection statuses."""
|
|
||||||
|
|
||||||
ONLINE = "online"
|
|
||||||
PENDING = "pending"
|
|
||||||
OFFLINE = "offline"
|
|
||||||
STALE = "stale"
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""Represents a client."""
|
"""Represents a client."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, id: str | None, addr: tuple[str, int], socket: socket.socket
|
self,
|
||||||
|
id: str | None,
|
||||||
|
addr: tuple[str, int] | None,
|
||||||
|
socket: socket.socket | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the client.
|
"""Initialize the client.
|
||||||
|
|
||||||
@@ -41,13 +36,15 @@ class Client:
|
|||||||
self.last_seen: float = 0.0 # unix timestanp of last inbound message
|
self.last_seen: float = 0.0 # unix timestanp of last inbound message
|
||||||
self.status: ClientStatus = ClientStatus.PENDING
|
self.status: ClientStatus = ClientStatus.PENDING
|
||||||
|
|
||||||
self.socket: socket.socket = socket
|
self.socket: socket.socket | None = socket
|
||||||
self.addr: tuple[str, int] = addr
|
self.addr: tuple[str, int] | None = addr
|
||||||
self.inbound: bytes = b""
|
self.inbound: bytes = b""
|
||||||
self.outbound: bytes = b""
|
self.outbound: bytes = b""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Client({self.id} ({self.addr[0]}:{self.addr[1]}))"
|
if self.addr:
|
||||||
|
return f"Client({self.id} ({self.addr[0]}:{self.addr[1]}))"
|
||||||
|
return f"Client({self.id} (not connected))"
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Client({self.id}, {self.addr})"
|
return f"Client({self.id}, {self.addr})"
|
||||||
@@ -55,6 +52,11 @@ class Client:
|
|||||||
def disconnect(self) -> None:
|
def disconnect(self) -> None:
|
||||||
"""Disconnect the client and close the socket."""
|
"""Disconnect the client and close the socket."""
|
||||||
self.logger.debug(f"Disconnecting Client {self}...")
|
self.logger.debug(f"Disconnecting Client {self}...")
|
||||||
|
if self.socket is None:
|
||||||
|
self.logger.warning(
|
||||||
|
f"Client {self} not connected, nothing to disconnect."
|
||||||
|
)
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
11
src/judas_server/backend/client_status.py
Normal file
11
src/judas_server/backend/client_status.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ClientStatus(str, Enum):
|
||||||
|
"""Enumeration of client connection statuses."""
|
||||||
|
|
||||||
|
ONLINE = "online"
|
||||||
|
PENDING = "pending"
|
||||||
|
OFFLINE = "offline"
|
||||||
|
STALE = "stale"
|
||||||
Reference in New Issue
Block a user