import base64 import json import structlog class EventManager: """Manages and broadcasts events within the FreeDATA server. This class handles the broadcasting of various events, including PTT changes, scatter changes, buffer overflows, custom events, ARQ session updates, and freedata_server status changes, to multiple queues. It provides a centralized mechanism for distributing event information throughout the application. """ def __init__(self, ctx, queues): """Initializes the EventManager with a list of queues. Args: queues (list): A list of queues to which events will be broadcast. """ self.queues = queues self.ctx = ctx self.logger = structlog.get_logger('Event Manager') self.lastpttstate = False def broadcast(self, data): """Broadcasts an event to all registered queues. This method broadcasts the given event data to all queues registered with the EventManager. It clears a queue if its size exceeds 10 to prevent excessive queue buildup. Args: data: The event data to broadcast. """ if self.ctx.TESTMODE: self.ctx.TESTMODE_EVENTS.put(data) for q in self.queues: self.logger.debug(f"Event: ", ev=data) if q.qsize() > 10: q.queue.clear() q.put(data) def send_ptt_change(self, on:bool = False): """Sends a PTT change event. This method broadcasts a "ptt" event indicating whether the Push-to-Talk (PTT) is activated or deactivated. It avoids sending duplicate events by checking the last PTT state. Args: on (bool, optional): True if PTT is activated, False otherwise. Defaults to False. """ if (on == self.lastpttstate): return self.lastpttstate= on self.broadcast({"ptt": bool(on)}) def send_scatter_change(self, data): """Sends a scatter change event. This method broadcasts a "scatter" event containing the provided scatter data as a JSON string. Args: data: The scatter data to send. """ self.broadcast({"scatter": json.dumps(data)}) def send_buffer_overflow(self, data): """Sends a buffer overflow event. This method broadcasts a "buffer-overflow" event, indicating that a buffer overflow has occurred. The event data includes the provided data converted to a string. Args: data: The buffer overflow data to send. """ self.broadcast({"buffer-overflow": str(data)}) def send_custom_event(self, **event_data): """Sends a custom event. This method broadcasts a custom event with the provided keyword arguments as the event data. This allows for flexible creation and distribution of application-specific events. Args: **event_data: Keyword arguments representing the event data. """ self.broadcast(event_data) def send_arq_session_new(self, outbound: bool, session_id, dxcall, total_bytes, state): """Sends an event for a new ARQ session. This method broadcasts an event indicating the start of a new ARQ (Automatic Repeat reQuest) session. The event includes information about the session's direction (inbound or outbound), session ID, destination callsign, total bytes to be transferred, and initial state. Args: outbound (bool): True if the session is outbound (sending data), False if it's inbound (receiving data). session_id: The unique ID of the ARQ session. dxcall (str): The callsign of the remote station. total_bytes (int): The total number of bytes to be transferred. state (str): The initial state of the ARQ session. """ direction = 'outbound' if outbound else 'inbound' event = { "type": "arq", f"arq-transfer-{direction}": { 'session_id': session_id, 'dxcall': dxcall, 'total_bytes': total_bytes, 'state': state, } } self.broadcast(event) def send_arq_session_progress(self, outbound: bool, session_id, dxcall, received_bytes, total_bytes, state, speed_level, statistics=None): """Sends an ARQ session progress update event. This method broadcasts an event indicating the progress of an ARQ session. The event includes the session ID, destination callsign, received and total bytes, current state, speed level, and any relevant statistics. Args: outbound (bool): True if the session is outbound, False otherwise. session_id: The ID of the ARQ session. dxcall (str): The callsign of the remote station. received_bytes (int): The number of bytes received so far. total_bytes (int): The total number of bytes to be transferred. state (str): The current state of the ARQ session. speed_level: The current speed level of the ARQ session. statistics (dict, optional): A dictionary containing session statistics. Defaults to None. """ if statistics is None: statistics = {} direction = 'outbound' if outbound else 'inbound' event = { "type": "arq", f"arq-transfer-{direction}": { 'session_id': session_id, 'dxcall': dxcall, 'received_bytes': received_bytes, 'total_bytes': total_bytes, 'state': state, 'speed_level': speed_level, 'statistics': statistics, } } self.broadcast(event) def send_arq_session_finished(self, outbound: bool, session_id, dxcall, success: bool, state: bool, data=False, statistics=None): """Sends an ARQ session finished event. This method broadcasts an event indicating the completion of an ARQ session. The event includes information about the session's direction, ID, destination callsign, success status, final state, data (if any), and statistics. It base64-encodes any data included in the event. Args: outbound (bool): True if the session was outbound, False otherwise. session_id: The ID of the ARQ session. dxcall (str): The callsign of the remote station. success (bool): True if the session completed successfully, False otherwise. state (str): The final state of the ARQ session. data (any, optional): The data transferred during the session. Defaults to False. statistics (dict, optional): A dictionary of session statistics. Defaults to None. """ if statistics is None: statistics = {} if data: if isinstance(data, dict): data = json.dumps(data).encode('utf-8') # Base64 encode the bytes-like object data = base64.b64encode(data).decode("UTF-8") direction = 'outbound' if outbound else 'inbound' event = { "type" : "arq", f"arq-transfer-{direction}": { 'session_id': session_id, 'dxcall': dxcall, 'statistics': statistics, 'success': bool(success), 'state': state, 'data': data } } self.broadcast(event) def modem_started(self): """Sends a freedata_server started event. This method broadcasts an event indicating that the FreeDATA freedata_server has started successfully. """ event = {"freedata_server": "started"} self.broadcast(event) def modem_restarted(self): """Sends a freedata_server restarted event. This method broadcasts an event indicating that the FreeDATA freedata_server has been restarted. """ event = {"freedata_server": "restarted"} self.broadcast(event) def modem_stopped(self): """Sends a freedata_server stopped event. This method broadcasts an event indicating that the FreeDATA freedata_server has stopped. """ event = {"freedata_server": "stopped"} self.broadcast(event) def modem_failed(self): """Sends a freedata_server failed event. This method broadcasts an event indicating that the FreeDATA freedata_server has failed to start or has encountered an error. """ event = {"freedata_server": "failed"} self.broadcast(event) def freedata_message_db_change(self, message_id=None): """Sends a FreeDATA message database change event. This method broadcasts an event indicating that the FreeDATA message database has been changed. The event includes the ID of the message that triggered the change, if available. Args: message_id (any, optional): The ID of the changed message. Defaults to None. """ self.broadcast({"message-db": "changed", "message_id": message_id}) def freedata_logging(self, type, status, message): """Broadcasts a FreeDATA logging event. This method broadcasts an event related to FreeDATA logging, indicating the type of logging endpoint and its status. It is used to inform other parts of the application about logging activities. Args: type (str): The type of logging endpoint (e.g., "file", "websocket"). status (any): The status of the logging operation. message (str): The message to be displayed """ self.broadcast({"type": "message-logging", "endpoint": type, "status": status, "message": message})