Finalize EventBus design
This commit is contained in:
parent
b7ded32344
commit
a973ce8bcf
10 changed files with 105 additions and 73 deletions
|
|
@ -18,7 +18,7 @@ from .config import Config
|
||||||
from .DataStore import DataStore
|
from .DataStore import DataStore
|
||||||
from .Download import Download
|
from .Download import Download
|
||||||
from .Emitter import Emitter
|
from .Emitter import Emitter
|
||||||
from .EventsSubscriber import Events
|
from .Events import Events
|
||||||
from .ItemDTO import ItemDTO
|
from .ItemDTO import ItemDTO
|
||||||
from .Presets import Presets
|
from .Presets import Presets
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable
|
||||||
|
|
||||||
from .EventsSubscriber import Events
|
from .Events import Events
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
LOG = logging.getLogger("Emitter")
|
LOG = logging.getLogger("Emitter")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
|
|
@ -51,11 +53,50 @@ class Events:
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class Event:
|
class Event:
|
||||||
id: str
|
"""
|
||||||
data: dict
|
Event is a data transfer object that represents an event that was emitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str = field(default_factory=lambda: str(uuid.uuid4()), init=False)
|
||||||
|
"""The id of the event."""
|
||||||
|
|
||||||
|
created: str = field(default_factory=lambda: str(datetime.datetime.now(tz=datetime.timezone.UTC).isoformat()))
|
||||||
|
"""The time the event was created."""
|
||||||
|
|
||||||
|
event: str
|
||||||
|
"""The event that was emitted."""
|
||||||
|
|
||||||
|
data: any
|
||||||
|
"""The data that was passed to the event."""
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
"""
|
||||||
|
Serialize the event.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The serialized event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {"id": self.id, "created": self.created, "event": self.event, "data": self.data}
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Event(id={self.id}, created={self.created}, event={self.event}, data={self.data})"
|
||||||
|
|
||||||
|
def datatype(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the datatype of the data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The datatype of the data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return type(self.data).__name__
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Event(id={self.id}, created={self.created}, event={self.event})"
|
||||||
|
|
||||||
|
|
||||||
class EventsSubscriber(metaclass=Singleton):
|
class EventBus(metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
This class is used to subscribe to and emit events to the registered listeners.
|
This class is used to subscribe to and emit events to the registered listeners.
|
||||||
"""
|
"""
|
||||||
|
|
@ -67,10 +108,10 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
"""The listeners for the events."""
|
"""The listeners for the events."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
EventsSubscriber._instance = self
|
EventBus._instance = self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "EventsSubscriber":
|
def get_instance() -> "EventBus":
|
||||||
"""
|
"""
|
||||||
Get the instance of the EventsSubscriber.
|
Get the instance of the EventsSubscriber.
|
||||||
|
|
||||||
|
|
@ -78,18 +119,19 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
EventsSubscriber: The instance of the EventsSubscriber
|
EventsSubscriber: The instance of the EventsSubscriber
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not EventsSubscriber._instance:
|
if not EventBus._instance:
|
||||||
EventsSubscriber._instance = EventsSubscriber()
|
EventBus._instance = EventBus()
|
||||||
return EventsSubscriber._instance
|
|
||||||
|
|
||||||
def subscribe(self, event: str | list | tuple, id: str, callback: Awaitable) -> "EventsSubscriber":
|
return EventBus._instance
|
||||||
|
|
||||||
|
def subscribe(self, event: str | list | tuple, callback: Awaitable, name: str | None = None) -> "EventBus":
|
||||||
"""
|
"""
|
||||||
Subscribe to an event.
|
Subscribe to an event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to subscribe to.
|
event (str): The event to subscribe to.
|
||||||
id (str|None): The id of the subscriber, if None a random uuid will be generated.
|
name (str|None): The name of the subscriber, if None a random uuid will be generated.
|
||||||
callback (Awaitable): The function to call. Must be a coroutine.
|
callback(Event) (Awaitable): The function to call. Must be a coroutine.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
EventsSubscriber: The instance of the EventsSubscriber
|
EventsSubscriber: The instance of the EventsSubscriber
|
||||||
|
|
@ -98,21 +140,24 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
if isinstance(event, str):
|
if isinstance(event, str):
|
||||||
event = [event]
|
event = [event]
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
name = str(uuid.uuid4())
|
||||||
|
|
||||||
for e in event:
|
for e in event:
|
||||||
if e not in self._listeners:
|
if e not in self._listeners:
|
||||||
self._listeners[e] = {}
|
self._listeners[e] = {}
|
||||||
|
|
||||||
self._listeners[e][id] = callback
|
self._listeners[e][name] = callback
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def unsubscribe(self, event: str | list | tuple, id: str) -> "EventsSubscriber":
|
def unsubscribe(self, event: str | list | tuple, name: str) -> "EventBus":
|
||||||
"""
|
"""
|
||||||
Unsubscribe from an event.
|
Unsubscribe from an event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to unsubscribe from.
|
event (str): The event to unsubscribe from.
|
||||||
id (str): The id of the subscriber.
|
name (str): The name of the subscriber.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
EventsSubscriber: The instance of the EventsSubscriber
|
EventsSubscriber: The instance of the EventsSubscriber
|
||||||
|
|
@ -122,18 +167,18 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
event = [event]
|
event = [event]
|
||||||
|
|
||||||
for e in event:
|
for e in event:
|
||||||
if e in self._listeners and id in self._listeners[e]:
|
if e in self._listeners and name in self._listeners[e]:
|
||||||
del self._listeners[e][id]
|
del self._listeners[e][name]
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def emit_sync(self, event: str, *args, **kwargs):
|
def emit_sync(self, event: str, data: any, **kwargs) -> list:
|
||||||
"""
|
"""
|
||||||
Emit an event synchronously.
|
Emit an event synchronously.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to emit.
|
event (str): The event to emit.
|
||||||
*args: The arguments to pass to the event.
|
data (any): The data to pass to the event.
|
||||||
**kwargs: The keyword arguments to pass to the event.
|
**kwargs: The keyword arguments to pass to the event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -144,28 +189,26 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
if event not in self._listeners:
|
if event not in self._listeners:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
results = []
|
ev = Event(event=event, data=data)
|
||||||
for id, callback in self._listeners[event].items():
|
LOG.debug(f"Emitting event '{ev}'.")
|
||||||
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)))
|
results = []
|
||||||
|
for name, callback in self._listeners[event].items():
|
||||||
|
try:
|
||||||
|
results.append(asyncio.get_event_loop().run_until_complete(callback(ev, name, **kwargs)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error(f"Failed to emit event '{event}' to '{id}'. Error message '{e!s}'.")
|
LOG.error(f"Failed to emit event '{event}' to '{name}'. Error message '{e!s}'.")
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def emit(self, event: str, *args, **kwargs):
|
def emit(self, event: str, data: any, **kwargs) -> Awaitable:
|
||||||
"""
|
"""
|
||||||
Emit an event.
|
Emit an event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to emit.
|
event (str): The event to emit.
|
||||||
*args: The arguments to pass to the event.
|
data (any): The data to pass to the event.
|
||||||
**kwargs: The keyword arguments to pass to the event.
|
**kwargs: The keyword arguments to pass to the event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -175,19 +218,16 @@ class EventsSubscriber(metaclass=Singleton):
|
||||||
if event not in self._listeners:
|
if event not in self._listeners:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tasks = []
|
ev = Event(event=event, data=data)
|
||||||
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)))
|
LOG.debug(f"Emitting event '{ev}'.")
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
for name, callback in self._listeners[event].items():
|
||||||
|
try:
|
||||||
|
tasks.append(asyncio.create_task(callback(ev, name, **kwargs)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error(f"Failed to emit event '{event}' to '{id}'. Error message '{e!s}'.")
|
LOG.error(f"Failed to emit event '{event}' to '{name}'. Error message '{e!s}'.")
|
||||||
|
|
||||||
return asyncio.gather(*tasks)
|
return asyncio.gather(*tasks)
|
||||||
|
|
@ -26,7 +26,7 @@ from .config import Config
|
||||||
from .DownloadQueue import DownloadQueue
|
from .DownloadQueue import DownloadQueue
|
||||||
from .Emitter import Emitter
|
from .Emitter import Emitter
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .EventsSubscriber import Events
|
from .Events import Events
|
||||||
from .ffprobe import ffprobe
|
from .ffprobe import ffprobe
|
||||||
from .M3u8 import M3u8
|
from .M3u8 import M3u8
|
||||||
from .Notifications import Notification, NotificationEvents
|
from .Notifications import Notification, NotificationEvents
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from .config import Config
|
||||||
from .DownloadQueue import DownloadQueue
|
from .DownloadQueue import DownloadQueue
|
||||||
from .Emitter import Emitter
|
from .Emitter import Emitter
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .EventsSubscriber import Event, Events, EventsSubscriber
|
from .Events import EventBus, Events
|
||||||
from .Presets import Presets
|
from .Presets import Presets
|
||||||
from .Utils import arg_converter, is_downloaded
|
from .Utils import arg_converter, is_downloaded
|
||||||
|
|
||||||
|
|
@ -80,13 +80,9 @@ class HttpSocket(Common):
|
||||||
if hasattr(method, "_ws_event") and self.sio:
|
if hasattr(method, "_ws_event") and self.sio:
|
||||||
self.sio.on(method._ws_event)(method) # type: ignore
|
self.sio.on(method._ws_event)(method) # type: ignore
|
||||||
|
|
||||||
# self.sio.on("*", es.emit)
|
EventBus.get_instance().subscribe(
|
||||||
|
Events.ADD_URL, lambda data, _: self.add(**data.data), "socket_add_url"
|
||||||
async def handle_event(_: str, data: Event):
|
)
|
||||||
LOG.debug(f"Event received. '{data}'")
|
|
||||||
await self.add(**data.data)
|
|
||||||
|
|
||||||
EventsSubscriber.get_instance().subscribe(Events.ADD_URL, "socket_add_url", handle_event)
|
|
||||||
|
|
||||||
# register the shutdown event.
|
# register the shutdown event.
|
||||||
app.on_shutdown.append(self.on_shutdown)
|
app.on_shutdown.append(self.on_shutdown)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import httpx
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .EventsSubscriber import Events
|
from .Events import Events
|
||||||
from .ItemDTO import ItemDTO
|
from .ItemDTO import ItemDTO
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
from .Utils import ag, validate_uuid
|
from .Utils import ag, validate_uuid
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from aiohttp import web
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .Emitter import Emitter
|
from .Emitter import Emitter
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .EventsSubscriber import Event, Events, EventsSubscriber
|
from .Events import EventBus, Events
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
LOG = logging.getLogger("presets")
|
LOG = logging.getLogger("presets")
|
||||||
|
|
@ -81,13 +81,12 @@ class Presets(metaclass=Singleton):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handle_event(_, e: Event):
|
|
||||||
self.save(**e.data)
|
|
||||||
|
|
||||||
with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f:
|
with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f:
|
||||||
self._default_presets = [Preset(**preset) for preset in json.load(f)]
|
self._default_presets = [Preset(**preset) for preset in json.load(f)]
|
||||||
|
|
||||||
EventsSubscriber.get_instance().subscribe(Events.PRESETS_ADD, f"{__class__}.save", handle_event)
|
EventBus.get_instance().subscribe(
|
||||||
|
Events.PRESETS_ADD, lambda data, _: self.add(**data.data), f"{__class__}.save"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "Presets":
|
def get_instance() -> "Presets":
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
from aiocron import Cron
|
from aiocron import Cron
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from .EventsSubscriber import Event, Events, EventsSubscriber
|
from .Events import EventBus, Events
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
LOG = logging.getLogger("scheduler")
|
LOG = logging.getLogger("scheduler")
|
||||||
|
|
@ -26,10 +26,9 @@ class Scheduler(metaclass=Singleton):
|
||||||
|
|
||||||
self._loop = loop or asyncio.get_event_loop()
|
self._loop = loop or asyncio.get_event_loop()
|
||||||
|
|
||||||
def handle_event(_, e: Event):
|
EventBus.get_instance().subscribe(
|
||||||
self.add(**e.data)
|
Events.SCHEDULE_ADD, lambda data, _: self.add(**data.data), f"{__class__}.add"
|
||||||
|
)
|
||||||
EventsSubscriber.get_instance().subscribe(Events.SCHEDULE_ADD, f"{__class__}.add", handle_event)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "Scheduler":
|
def get_instance() -> "Scheduler":
|
||||||
|
|
@ -76,8 +75,9 @@ class Scheduler(metaclass=Singleton):
|
||||||
"""Return the job by id."""
|
"""Return the job by id."""
|
||||||
return self._jobs.get(id)
|
return self._jobs.get(id)
|
||||||
|
|
||||||
def add(self, timer: str, func: callable, args: tuple = (),
|
def add(
|
||||||
kwargs: dict | None = None, id: str | None = None) -> str:
|
self, timer: str, func: callable, args: tuple = (), kwargs: dict | None = None, id: str | None = None
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Add a job to the schedule.
|
Add a job to the schedule.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from aiohttp import web
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .Emitter import Emitter
|
from .Emitter import Emitter
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .EventsSubscriber import Event, Events, EventsSubscriber
|
from .Events import Event, EventBus, Events
|
||||||
from .Scheduler import Scheduler
|
from .Scheduler import Scheduler
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
|
|
@ -82,10 +82,7 @@ class Tasks(metaclass=Singleton):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handle_event(_, e: Event):
|
EventBus.get_instance().subscribe(Events.TASKS_ADD, lambda data, _: self.add(**data.data), f"{__class__}.save")
|
||||||
self.save(**e.data)
|
|
||||||
|
|
||||||
EventsSubscriber.get_instance().subscribe(Events.TASKS_ADD, f"{__class__}.save", handle_event)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "Tasks":
|
def get_instance() -> "Tasks":
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from aiohttp import web
|
||||||
from library.config import Config
|
from library.config import Config
|
||||||
from library.DownloadQueue import DownloadQueue
|
from library.DownloadQueue import DownloadQueue
|
||||||
from library.Emitter import Emitter
|
from library.Emitter import Emitter
|
||||||
from library.EventsSubscriber import Events, EventsSubscriber
|
from library.Events import EventBus, Events
|
||||||
from library.HttpAPI import HttpAPI
|
from library.HttpAPI import HttpAPI
|
||||||
from library.HttpSocket import HttpSocket
|
from library.HttpSocket import HttpSocket
|
||||||
from library.Notifications import Notification
|
from library.Notifications import Notification
|
||||||
|
|
@ -61,7 +61,7 @@ class Main:
|
||||||
self._socket = HttpSocket(queue=self._queue)
|
self._socket = HttpSocket(queue=self._queue)
|
||||||
|
|
||||||
Emitter.get_instance().add_emitter([Notification().emit], local=False).add_emitter(
|
Emitter.get_instance().add_emitter([Notification().emit], local=False).add_emitter(
|
||||||
[EventsSubscriber().emit], local=True
|
[EventBus().emit], local=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self._app.on_cleanup.append(_close_connection)
|
self._app.on_cleanup.append(_close_connection)
|
||||||
|
|
@ -94,7 +94,7 @@ class Main:
|
||||||
"""
|
"""
|
||||||
Start the application.
|
Start the application.
|
||||||
"""
|
"""
|
||||||
EventsSubscriber.get_instance().emit(Events.STARTUP, data={"app": self._app})
|
EventBus.get_instance().emit(Events.STARTUP, data={"app": self._app})
|
||||||
|
|
||||||
self._socket.attach(self._app)
|
self._socket.attach(self._app)
|
||||||
self._http.attach(self._app)
|
self._http.attach(self._app)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue