import asyncio import logging from collections.abc import Awaitable from .EventsSubscriber import Events from .Singleton import Singleton LOG = logging.getLogger("Emitter") class Emitter(metaclass=Singleton): """ This class is used to emit events to the registered emitters. """ emitters: list[(str, Awaitable)] = [] """The emitters for the events.""" _instance = None def __init__(self): Emitter._instance = self @staticmethod def get_instance(): """ Get the instance of the Emitter. Returns: Emitter: The instance of the Emitter """ if not Emitter._instance: Emitter._instance = Emitter() return Emitter._instance def add_emitter(self, emitter: list[Awaitable] | Awaitable, local: bool = False) -> "Emitter": """ Add an emitter to the list of emitters. Args: emitter (Awaitable|list[Awaitable]): The emitter function. The function must return a coroutine or None. local (bool): Mark the emitter as target for local events. Returns: Emitter: The instance of the Emitter """ if not isinstance(emitter, list): emitter = [emitter] for e in emitter: self.emitters.append((local, e)) return self async def added(self, dl: dict, local: bool = False, **kwargs): await self.emit(Events.ADDED, data=dl, local=local, **kwargs) async def updated(self, dl: dict, local: bool = False, **kwargs): await self.emit(Events.UPDATED, data=dl, local=local, **kwargs) async def completed(self, dl: dict, local: bool = False, **kwargs): await self.emit(Events.COMPLETED, data=dl, local=local, **kwargs) async def cancelled(self, dl: dict, local: bool = False, **kwargs): await self.emit(Events.CANCELLED, data=dl, local=local, **kwargs) async def cleared(self, dl: dict | None = None, local: bool = False, **kwargs): await self.emit(Events.CLEARED, data=dl, local=local, **kwargs) async def error(self, message: str, data: dict|None = None, local: bool = False, **kwargs): msg = {"type": "error", "message": message, "data": {}} if data: msg.update({"data": data}) await self.emit(Events.ERROR, data=msg, local=local, **kwargs) async def info(self, message: str, data: dict|None = None, local: bool = False, **kwargs): msg = {"type": "info", "message": message, "data": {}} if data: msg.update({"data": data}) await self.emit(Events.LOG_INFO, data=msg, local=local, **kwargs) async def success(self, message: str, data: dict|None = None, local: bool = False, **kwargs): msg = {"type": "success", "message": message, "data": {}} if data: msg.update({"data": data}) await self.emit(Events.LOG_SUCCESS, data=msg, local=local, **kwargs) async def emit(self, event: str, data, local: bool = False, **kwargs): """ Emit an event. Args: event (str): The event to emit. data (dict): The data to send with the event. local (bool): If the event should be sent to the local emitters only. kwargs: Additional arguments to pass to the emitters. Returns: None """ tasks = [] for emitter in self.emitters: try: isLocal, callback = emitter if local and not isLocal: continue _ret = callback(event, data, **kwargs) if _ret: if isinstance(_ret, list): tasks.extend(_ret) else: tasks.append(_ret) except Exception as e: LOG.error(f"Emitter '{callback}' failed with error '{e}'.") if len(tasks) < 1: return try: await asyncio.wait_for(asyncio.gather(*tasks), timeout=60) except asyncio.CancelledError: LOG.error(f"Cancelled sending event '{event}'.") except TimeoutError: LOG.error(f"Timed out sending event '{event}'.") except Exception as e: LOG.exception(e) LOG.error(f"Failed to send event '{event}'. '{e!s}'.")