mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
"""链式和广播事件的独立调度算法。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
from app.runtime.event.binding import EventBindingResolver
|
||||
from app.runtime.event.registry import EventRegistry
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import EventType
|
||||
|
||||
|
||||
class EventDispatcher:
|
||||
"""基于订阅快照执行链式或广播事件,不拥有注册和生命周期状态。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
registry: EventRegistry,
|
||||
binding_resolver: EventBindingResolver,
|
||||
executor: Callable[[], Any],
|
||||
event_loop: Callable[[], Any],
|
||||
event_factory: Callable[..., Any],
|
||||
error_handler: Callable[..., None],
|
||||
) -> None:
|
||||
"""注入注册表、绑定器、执行器和错误策略回调。"""
|
||||
self._registry = registry
|
||||
self._binding_resolver = binding_resolver
|
||||
self._executor = executor
|
||||
self._event_loop = event_loop
|
||||
self._event_factory = event_factory
|
||||
self._error_handler = error_handler
|
||||
|
||||
def dispatch_chain(self, event: Any) -> bool:
|
||||
"""同步按优先级顺序执行链式事件快照。"""
|
||||
handlers = self._registry.chain_snapshot(event.event_type)
|
||||
enabled = tuple(
|
||||
(handler_id, priority, handler)
|
||||
for handler_id, (priority, handler) in handlers
|
||||
if self._registry.is_handler_enabled(handler)
|
||||
)
|
||||
if not enabled:
|
||||
logger.debug("No enabled handlers found for chain event: %s", event)
|
||||
return False
|
||||
self._log_lifecycle(event, "Started")
|
||||
for _handler_id, priority, handler in enabled:
|
||||
started_at = time.time()
|
||||
self.invoke_sync(handler, event)
|
||||
logger.debug(
|
||||
"%s (Priority: %s), completed in %.3fs for event: %s",
|
||||
EventRegistry.handler_identifier(handler),
|
||||
priority,
|
||||
time.time() - started_at,
|
||||
event,
|
||||
)
|
||||
self._log_lifecycle(event, "Completed")
|
||||
return True
|
||||
|
||||
async def async_dispatch_chain(self, event: Any) -> bool:
|
||||
"""异步按优先级顺序执行链式事件快照。"""
|
||||
handlers = self._registry.chain_snapshot(event.event_type)
|
||||
enabled = tuple(
|
||||
(handler_id, priority, handler)
|
||||
for handler_id, (priority, handler) in handlers
|
||||
if self._registry.is_handler_enabled(handler)
|
||||
)
|
||||
if not enabled:
|
||||
logger.debug("No enabled handlers found for chain event: %s", event)
|
||||
return False
|
||||
self._log_lifecycle(event, "Started")
|
||||
for _handler_id, priority, handler in enabled:
|
||||
started_at = time.time()
|
||||
await self.invoke_async(handler, event)
|
||||
logger.debug(
|
||||
"%s (Priority: %s), completed in %.3fs for event: %s",
|
||||
EventRegistry.handler_identifier(handler),
|
||||
priority,
|
||||
time.time() - started_at,
|
||||
event,
|
||||
)
|
||||
self._log_lifecycle(event, "Completed")
|
||||
return True
|
||||
|
||||
def dispatch_broadcast(self, event: Any) -> None:
|
||||
"""按订阅快照把广播事件投递到线程池或主事件循环。"""
|
||||
handlers = self._registry.broadcast_snapshot(event.event_type)
|
||||
if not handlers:
|
||||
logger.debug("No handlers found for broadcast event: %s", event)
|
||||
return
|
||||
target_plugin_id = None
|
||||
if event.event_type == EventType.MessageAction and isinstance(
|
||||
event.event_data,
|
||||
dict,
|
||||
):
|
||||
target_plugin_id = event.event_data.get("__mp_target_plugin_id")
|
||||
for handler_id, handler in handlers:
|
||||
if target_plugin_id and not self.should_dispatch_to_target_plugin(
|
||||
handler,
|
||||
handler_id,
|
||||
str(target_plugin_id),
|
||||
):
|
||||
continue
|
||||
if isinstance(event.event_data, dict):
|
||||
event_data = event.event_data.copy()
|
||||
event_data.pop("__mp_target_plugin_id", None)
|
||||
else:
|
||||
event_data = event.event_data
|
||||
isolated = self._event_factory(
|
||||
event_type=event.event_type,
|
||||
event_data=event_data,
|
||||
priority=event.priority,
|
||||
)
|
||||
if inspect.iscoroutinefunction(handler):
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.safe_invoke_async(handler, isolated),
|
||||
self._event_loop(),
|
||||
)
|
||||
else:
|
||||
self._executor().submit(
|
||||
self.safe_invoke_sync,
|
||||
handler,
|
||||
isolated,
|
||||
)
|
||||
|
||||
def safe_invoke_sync(self, handler: Callable, event: Any) -> None:
|
||||
"""仅在处理器启用时执行同步调用。"""
|
||||
if self._registry.is_handler_enabled(handler):
|
||||
self.invoke_sync(handler, event)
|
||||
|
||||
async def safe_invoke_async(self, handler: Callable, event: Any) -> None:
|
||||
"""仅在处理器启用时执行异步调用。"""
|
||||
if self._registry.is_handler_enabled(handler):
|
||||
await self.invoke_async(handler, event)
|
||||
|
||||
def invoke_sync(self, handler: Callable, event: Any) -> None:
|
||||
"""解析实例绑定并同步调用处理器。"""
|
||||
resolved = self._binding_resolver.resolve(handler)
|
||||
if not resolved:
|
||||
return
|
||||
method, binding, class_name, method_name = resolved
|
||||
try:
|
||||
method(event)
|
||||
except Exception as err:
|
||||
self._error_handler(
|
||||
event=event,
|
||||
module_name=binding.owner_name,
|
||||
class_name=class_name,
|
||||
method_name=method_name,
|
||||
e=err,
|
||||
)
|
||||
|
||||
async def invoke_async(self, handler: Callable, event: Any) -> None:
|
||||
"""解析实例绑定,并按处理器类型选择协程、线程池或同步调用。"""
|
||||
resolved = self._binding_resolver.resolve(handler)
|
||||
if not resolved:
|
||||
return
|
||||
method, binding, class_name, method_name = resolved
|
||||
try:
|
||||
if inspect.iscoroutinefunction(method):
|
||||
await method(event)
|
||||
elif binding.run_sync_in_threadpool or not class_name:
|
||||
await run_in_threadpool(method, event)
|
||||
else:
|
||||
method(event)
|
||||
except Exception as err:
|
||||
self._error_handler(
|
||||
event=event,
|
||||
module_name=binding.owner_name,
|
||||
class_name=class_name,
|
||||
method_name=method_name,
|
||||
e=err,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def should_dispatch_to_target_plugin(
|
||||
handler: Callable,
|
||||
handler_identifier: str,
|
||||
target_plugin_id: str,
|
||||
) -> bool:
|
||||
"""只把定向输入事件投递给标识和声明均匹配的目标插件。"""
|
||||
class_name, method_name = EventBindingResolver.parse_handler_names(handler)
|
||||
if class_name != target_plugin_id:
|
||||
return False
|
||||
parts = (handler_identifier or "").split(".")
|
||||
return len(parts) >= 2 and parts[-2:] == [class_name, method_name]
|
||||
|
||||
@staticmethod
|
||||
def _log_lifecycle(event: Any, stage: str) -> None:
|
||||
"""记录事件调度的开始和完成阶段。"""
|
||||
logger.debug("%s - %s", stage, event)
|
||||
Reference in New Issue
Block a user