AudioControl/NetworkTransport.py
2022-11-17 20:22:52 +03:00

98 lines
3.5 KiB
Python

from typing import Callable
from loguru import logger
from dataclasses import asdict
import socket
import selectors
import Events
import json
from TransportABC import TransportABC
class NetworkTransport(TransportABC):
def __init__(self, rcv_callback: Callable[[Events.ClientToServerEvent], None]):
self._selector = selectors.DefaultSelector()
self.view_rcv_callback = rcv_callback
self._sock = socket.socket()
self._sock.bind(('localhost', 54683))
self._sock.listen(100)
self._sock.setblocking(False)
self._selector.register(self._sock, selectors.EVENT_READ, self._accept)
self._running = True
self._connections: list[socket.socket] = list()
def send(self, msg: Events.ServerToClientEvent):
"""This method gets called by `ServerSideView` when it wants to send a message to the client"""
# logger.debug(f'Sending {asdict(msg)}')
msg = json.dumps(asdict(msg)).encode() + b'\n' # TODO: Remove new line probably
self._send_to_all(msg)
def _send_to_all(self, msg: bytes):
for conn in self._connections:
conn.sendall(msg)
def _accept(self, sock: socket.socket, mask: int):
"""Callback which get called when accepting new connection"""
if not self._running:
logger.debug(f'Net: New connection during shutdown {sock.getpeername()}, not accepting')
return
conn, addr = sock.accept()
logger.debug(f'Net: Accepted {conn.getpeername()}')
conn.setblocking(False)
self._selector.register(conn, selectors.EVENT_READ, self._on_socket_receive)
self._connections.append(conn)
self.view_rcv_callback(Events.NewClient(-1))
def _close_conn(self, conn: socket.socket):
logger.debug(f'Net: Closing connection to {conn.getpeername()}')
self._selector.unregister(conn)
self._connections.remove(conn)
conn.close()
def _on_socket_receive(self, conn: socket.socket, mask: int):
try:
data = conn.recv(1000)
except ConnectionResetError:
logger.opt(exception=True).warning(f'Closing connection due to RST?')
self._close_conn(conn)
else:
if not data:
self._close_conn(conn)
return
for data_part in data.split(b'\n'):
if len(data_part) != 0:
self._handle_received_event(data_part, conn)
def _handle_received_event(self, data: bytes, conn: socket.socket):
try:
event_dict = json.loads(data)
event_name = event_dict['event']
event_cls = Events.lookup_event(event_name)
del event_dict['event']
logger.trace(f'Passing msg {event_dict} from client {conn.getpeername()}')
event = event_cls(**event_dict) # noqa
self.view_rcv_callback(event)
except Exception:
logger.opt(colors=False, exception=True).warning(f"Couldn't parse message from client: {data}")
def tick(self):
events = self._selector.select(timeout=0.1)
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
def shutdown(self):
logger.debug(f'Net: Shutting down')
self._running = False
while len(self._connections) > 0:
self._close_conn(self._connections[0])
logger.trace(f'Net: Shutdown completed, clients disconnected')