193 lines
5.4 KiB
Python
193 lines
5.4 KiB
Python
import asyncio
|
|
import logging
|
|
from collections.abc import Awaitable
|
|
from dataclasses import dataclass
|
|
|
|
from .Singleton import Singleton
|
|
|
|
LOG = logging.getLogger("EventsSubscriber")
|
|
|
|
|
|
class Events:
|
|
"""
|
|
The events that can be emitted.
|
|
"""
|
|
|
|
STARTUP = "startup"
|
|
SHUTDOWN = "shutdown"
|
|
|
|
ADDED = "added"
|
|
UPDATED = "updated"
|
|
COMPLETED = "completed"
|
|
CANCELLED = "cancelled"
|
|
CLEARED = "cleared"
|
|
ERROR = "error"
|
|
LOG_INFO = "log_info"
|
|
LOG_SUCCESS = "log_success"
|
|
|
|
INITIAL_DATA = "initial_data"
|
|
YTDLP_CONVERT = "ytdlp_convert"
|
|
ITEM_DELETE = "item_delete"
|
|
ITEM_CANCEL = "item_cancel"
|
|
STATUS = "status"
|
|
CLI_CLOSE = "cli_close"
|
|
CLI_OUTPUT = "cli_output"
|
|
UPDATE = "update"
|
|
TEST = "test"
|
|
ADD_URL = "add_url"
|
|
|
|
CLI_POST = "cli_post"
|
|
PAUSED = "paused"
|
|
|
|
TASKS_ADD = "task_add"
|
|
TASK_DISPATCHED = "task_dispatched"
|
|
TASK_FINISHED = "task_finished"
|
|
TASK_ERROR = "task_error"
|
|
|
|
PRESETS_ADD = "presets_add"
|
|
PRESETS_UPDATE = "presets_update"
|
|
SCHEDULE_ADD = "schedule_add"
|
|
|
|
|
|
@dataclass(kw_only=True)
|
|
class Event:
|
|
id: str
|
|
data: dict
|
|
|
|
|
|
class EventsSubscriber(metaclass=Singleton):
|
|
"""
|
|
This class is used to subscribe to and emit events to the registered listeners.
|
|
"""
|
|
|
|
_instance = None
|
|
"""the instance of the EventsSubscriber"""
|
|
|
|
_listeners: dict[str, list[str, Awaitable]] = {}
|
|
"""The listeners for the events."""
|
|
|
|
def __init__(self):
|
|
EventsSubscriber._instance = self
|
|
|
|
@staticmethod
|
|
def get_instance() -> "EventsSubscriber":
|
|
"""
|
|
Get the instance of the EventsSubscriber.
|
|
|
|
Returns:
|
|
EventsSubscriber: The instance of the EventsSubscriber
|
|
|
|
"""
|
|
if not EventsSubscriber._instance:
|
|
EventsSubscriber._instance = EventsSubscriber()
|
|
return EventsSubscriber._instance
|
|
|
|
def subscribe(self, event: str | list | tuple, id: str, callback: Awaitable) -> "EventsSubscriber":
|
|
"""
|
|
Subscribe to an event.
|
|
|
|
Args:
|
|
event (str): The event to subscribe to.
|
|
id (str|None): The id of the subscriber, if None a random uuid will be generated.
|
|
callback (Awaitable): The function to call. Must be a coroutine.
|
|
|
|
Returns:
|
|
EventsSubscriber: The instance of the EventsSubscriber
|
|
|
|
"""
|
|
if isinstance(event, str):
|
|
event = [event]
|
|
|
|
for e in event:
|
|
if e not in self._listeners:
|
|
self._listeners[e] = {}
|
|
|
|
self._listeners[e][id] = callback
|
|
|
|
return self
|
|
|
|
def unsubscribe(self, event: str | list | tuple, id: str) -> "EventsSubscriber":
|
|
"""
|
|
Unsubscribe from an event.
|
|
|
|
Args:
|
|
event (str): The event to unsubscribe from.
|
|
id (str): The id of the subscriber.
|
|
|
|
Returns:
|
|
EventsSubscriber: The instance of the EventsSubscriber
|
|
|
|
"""
|
|
if isinstance(event, str):
|
|
event = [event]
|
|
|
|
for e in event:
|
|
if e in self._listeners and id in self._listeners[e]:
|
|
del self._listeners[e][id]
|
|
|
|
return self
|
|
|
|
def emit_sync(self, event: str, *args, **kwargs):
|
|
"""
|
|
Emit an event synchronously.
|
|
|
|
Args:
|
|
event (str): The event to emit.
|
|
*args: The arguments to pass to the event.
|
|
**kwargs: The keyword arguments to pass to the event.
|
|
|
|
Returns:
|
|
list: The results are the return values of the coroutines. If the coroutine raises an exception,
|
|
the exception is caught and logged. If event does not exist, an empty list is returned.
|
|
|
|
"""
|
|
if event not in self._listeners:
|
|
return []
|
|
|
|
results = []
|
|
for id, callback in self._listeners[event].items():
|
|
try:
|
|
if "data" not in kwargs or not isinstance(kwargs["data"], Event):
|
|
data = Event(id=id, data={"args": args if args else [], **kwargs})
|
|
else:
|
|
data = kwargs["data"]
|
|
|
|
results.append(asyncio.get_event_loop().run_until_complete(callback(event, data)))
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
LOG.error(f"Failed to emit event '{event}' to '{id}'. Error message '{e!s}'.")
|
|
|
|
return results
|
|
|
|
def emit(self, event: str, *args, **kwargs):
|
|
"""
|
|
Emit an event.
|
|
|
|
Args:
|
|
event (str): The event to emit.
|
|
*args: The arguments to pass to the event.
|
|
**kwargs: The keyword arguments to pass to the event.
|
|
|
|
Returns:
|
|
Awaitable: The task that was created to run the event.
|
|
|
|
"""
|
|
if event not in self._listeners:
|
|
return None
|
|
|
|
tasks = []
|
|
for id, callback in self._listeners[event].items():
|
|
try:
|
|
if args and isinstance(args[0], Event):
|
|
data = args[0]
|
|
elif "data" in kwargs and isinstance(kwargs["data"], Event):
|
|
data = kwargs["data"]
|
|
else:
|
|
data = Event(id=id, data={"args": args if args else [], **kwargs})
|
|
|
|
tasks.append(asyncio.create_task(callback(event, data)))
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
LOG.error(f"Failed to emit event '{event}' to '{id}'. Error message '{e!s}'.")
|
|
|
|
return asyncio.gather(*tasks)
|