ytptube/app/library/Emitter.py
2025-01-28 23:05:59 +03:00

132 lines
4.3 KiB
Python

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}'.")