Files
MoviePilot/app/runtime/event/registry.py
T

201 lines
7.9 KiB
Python

"""事件订阅、禁用状态和调度快照注册表。"""
from __future__ import annotations
import inspect
from collections.abc import Callable
from typing import Any
from app.runtime.log import logger
from app.schemas.types import ChainEventType, EventType
class EventRegistry:
"""集中管理事件处理器注册、启停和不可变调度快照。"""
def __init__(
self,
*,
lock: Any,
broadcast_subscribers: Callable[[], dict],
chain_subscribers: Callable[[], dict],
disabled_handlers: Callable[[], set],
disabled_classes: Callable[[], set],
) -> None:
"""绑定由兼容门面持有的存储,便于热重载和旧测试替换快照。"""
self._lock = lock
self._broadcast_subscribers = broadcast_subscribers
self._chain_subscribers = chain_subscribers
self._disabled_handlers = disabled_handlers
self._disabled_classes = disabled_classes
@staticmethod
def handler_identifier(target: Callable | type) -> str:
"""返回包含模块和限定名的稳定处理器标识。"""
module = inspect.getmodule(target)
module_name = module.__name__ if module else "unknown_module"
return f"{module_name}.{target.__qualname__}"
@classmethod
def handler_class_identifier(cls, handler: Callable) -> str | None:
"""返回可调用对象所属类的稳定标识;自由函数返回空值。"""
if inspect.ismethod(handler) and hasattr(handler, "__self__"):
return cls.handler_identifier(handler.__self__.__class__)
if not inspect.isfunction(handler) and hasattr(handler, "__call__"):
return cls.handler_identifier(handler.__class__)
qualname_parts = handler.__qualname__.split(".")
if len(qualname_parts) <= 1:
return None
module = inspect.getmodule(handler)
module_name = module.__name__ if module else "unknown_module"
return f"{module_name}.{'.'.join(qualname_parts[:-1])}"
def is_handler_enabled(self, handler: Callable) -> bool:
"""判断处理器及其所属类是否均处于启用状态。"""
handler_id = self.handler_identifier(handler)
class_id = self.handler_class_identifier(handler)
return not (
handler_id in self._disabled_handlers()
or (
class_id is not None
and class_id in self._disabled_classes()
)
)
def check(self, event_type: EventType | ChainEventType) -> bool:
"""检查指定事件是否存在启用的处理器。"""
if isinstance(event_type, ChainEventType):
handlers = self._chain_subscribers().get(event_type, {})
return any(
self.is_handler_enabled(handler)
for _, handler in handlers.values()
)
handlers = self._broadcast_subscribers().get(event_type, {})
return any(self.is_handler_enabled(handler) for handler in handlers.values())
def add(
self,
event_type: EventType | ChainEventType,
handler: Callable,
priority: int,
) -> None:
"""注册处理器,并为链式事件按优先级维护稳定顺序。"""
with self._lock:
handler_id = self.handler_identifier(handler)
if isinstance(event_type, ChainEventType):
subscribers = self._chain_subscribers()
handlers = subscribers.setdefault(event_type, {})
existed = handler_id in handlers
handlers.pop(handler_id, None)
if not existed:
logger.debug(
"Subscribed to chain event: %s, Priority: %s - %s",
event_type.value,
priority,
handler_id,
)
handlers[handler_id] = (priority, handler)
subscribers[event_type] = dict(
sorted(handlers.items(), key=lambda item: item[1][0])
)
return
subscribers = self._broadcast_subscribers()
handlers = subscribers.setdefault(event_type, {})
existed = handler_id in handlers
handlers.pop(handler_id, None)
if not existed:
logger.debug(
"Subscribed to broadcast event: %s - %s",
event_type.value,
handler_id,
)
handlers[handler_id] = handler
def remove(
self,
event_type: EventType | ChainEventType,
handler: Callable,
) -> None:
"""从指定事件中移除处理器。"""
with self._lock:
handler_id = self.handler_identifier(handler)
if isinstance(event_type, ChainEventType):
self._chain_subscribers().get(event_type, {}).pop(
handler_id,
None,
)
logger.debug(
"Unsubscribed from chain event: %s - %s",
event_type.value,
handler_id,
)
return
self._broadcast_subscribers().get(event_type, {}).pop(
handler_id,
None,
)
logger.debug(
"Unsubscribed from broadcast event: %s - %s",
event_type.value,
handler_id,
)
def disable(self, target: Callable | type) -> None:
"""禁用单个处理器或整个处理器类。"""
identifier = self.handler_identifier(target)
if isinstance(target, type):
self._disabled_classes().add(identifier)
logger.debug("Disabled event handler class - %s", identifier)
else:
self._disabled_handlers().add(identifier)
logger.debug("Disabled event handler - %s", identifier)
def enable(self, target: Callable | type) -> None:
"""重新启用单个处理器或整个处理器类。"""
identifier = self.handler_identifier(target)
if isinstance(target, type):
self._disabled_classes().discard(identifier)
logger.debug("Enabled event handler class - %s", identifier)
else:
self._disabled_handlers().discard(identifier)
logger.debug("Enabled event handler - %s", identifier)
def chain_snapshot(self, event_type: ChainEventType) -> tuple:
"""返回当前链式订阅快照,运行期变更从下一次事件生效。"""
with self._lock:
return tuple(self._chain_subscribers().get(event_type, {}).items())
def broadcast_snapshot(self, event_type: EventType) -> tuple:
"""返回当前广播订阅快照,运行期变更从下一次事件生效。"""
with self._lock:
return tuple(
self._broadcast_subscribers().get(event_type, {}).items()
)
def visualize(self) -> list[dict]:
"""导出所有订阅处理器的事件、优先级和启停状态。"""
result = []
combined = {
**self._broadcast_subscribers(),
**self._chain_subscribers(),
}
for event_type, subscribers in combined.items():
for handler_id, handler_data in subscribers.items():
if isinstance(handler_data, tuple) and len(handler_data) == 2:
priority, handler = handler_data
else:
priority, handler = None, handler_data
item = {
"event_type": event_type.value,
"handler_identifier": handler_id,
"status": (
"enabled"
if self.is_handler_enabled(handler)
else "disabled"
),
}
if priority is not None:
item["priority"] = priority
result.append(item)
return result